RadicalBeard.Evaluate 0.7.2

dotnet add package RadicalBeard.Evaluate --version 0.7.2
                    
NuGet\Install-Package RadicalBeard.Evaluate -Version 0.7.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RadicalBeard.Evaluate" Version="0.7.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RadicalBeard.Evaluate" Version="0.7.2" />
                    
Directory.Packages.props
<PackageReference Include="RadicalBeard.Evaluate" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RadicalBeard.Evaluate --version 0.7.2
                    
#r "nuget: RadicalBeard.Evaluate, 0.7.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RadicalBeard.Evaluate@0.7.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RadicalBeard.Evaluate&version=0.7.2
                    
Install as a Cake Addin
#tool nuget:?package=RadicalBeard.Evaluate&version=0.7.2
                    
Install as a Cake Tool

<p align="center"><img src="logo.png" alt="Evaluate" width="180"></p>

Evaluate

A data-driven, moddable game framework built as an extension that runs inside Godot (Godot is the host; Evaluate has no main). Engine-side code is .NET/C# (net10.0, Godot 4.6-mono). Gameplay is authored in "evt" scripts whose YAML frontmatter declares a capability-scoped signature; a custom loader sandboxes each script to exactly what it declares. Scripts are auto-discovered and hot-reloaded by default.

Architecture

  • Frontmatter → signature. The leading --- block is split off and parsed as real YAML (YamlDotNet) into config:, apis:, register:, returns:, assets:, scenes:; the Lua body is handed to the VM verbatim (line numbers preserved). The signature is the C#↔Lua boundary contract — returns declares typed, access-scoped members (no Lua type system). (src/Evaluate/Frontmatter.cs)
  • Per-script sandbox. Each body runs via load(body, name, "t", env) on a shared LuaState, where env holds only the declared config.*, declared apis, the always-available std and ambient godot, plus a few safe primitives — including the metatable builtins (setmetatable/getmetatable/ rawget/rawset/rawequal/rawlen) so scripts can define classes the idiomatic Lua way. Undeclared globals (os, io, …) are absent, and pcall is intentionally withheld (errors surface rather than being swallowed). (src/Evaluate/Loader.cs)
  • Custom require narrows the returned module to its returns contract (get/set property → get_/set_ accessors; plain method; read-only hides the setter; missing accessor errors).
  • Data lives in the engine. Game objects are Godot nodes — declared in scene files or created via godot.<Type>.new() / world — so there is no separate entity system. The Lua handle is a thin proxy whose metatable routes reads/writes into the engine. (src/Evaluate/GodotBinder.cs)
  • Auto-discovery. The runtime recursively scans res://scripts; any system .evt with a register: block is wired to the Godot lifecycle. A hook may be registered once per scene (see Scenes & layers) — so the same on_update can be registered in menu, level1, and globally, each by a different script.
  • Hot reload (default). A FileSystemWatcher watches scripts, scene files, configs, and frontmatter-declared assets:; changes reload on the main thread. A changed system or node script re-runs its body and refreshes its hook closures while live nodes persist; a changed *.scene rebuilds the active scene. (src/Evaluate/EvaluateRuntime.cs)
  • Scenes & layers. Gameplay is split into a persistent global layer and a swappable scene layer, so one program holds many scenes, each with its own registered functions:
    • global.scene (reserved manifest) declares nodes that never clear (e.g. the player) plus start_scene. Loaded once into a persistent Global root.
    • *.scene files (TOML content) declare a node tree as keyed, nested tables — [nodes.Player] then [nodes.Player.Camera] makes Camera a child of Player. A node's name is the table key; reserved keys type/script; a sub-table is a child node and a scalar/array is a property (position = [x, y, z]). A sub-table tagged with _type is a property, not a child — it names an inline resource (mesh = { _type = "BoxMesh", size = [1,1,1] }) or a builtin struct (custom_aabb = { _type = "Aabb", position = [1,2,3], size = [4,5,6] }), which is how composite structs like Transform3D/Rect2/Basis/Projection are written. A property key may be quoted to carry a / (Control theme overrides: "theme_override_colors/font_color" = [..]). An optional top-level description = "…" holds scene docs as data. Parsed with Tomlyn; instantiated under a per-scene container that is freed wholesale on switch. The editor addon can round-trip all of this back to the .scene (see below).
    • *.node.evt is one node's behavior, attached via the scene file; its hooks run with self bound to that node (no spawning in the script).
    • *.evt systems are conductors: no scenes:global (always run); scenes: [a, b] ⇒ active only while a/b is current. The scene API does routing — scene.change(name) (applied at the next frame boundary, never mid-hook), scene.current(), scene.find(path) (unique by node path, e.g. "Level/Enemy"), scene.add(node).
  • Editor preview + write-back (optional addon). A .scene file is TOML, which the Godot editor doesn't render natively. The dev/addons/evaluate_scene addon registers an EditorImportPlugin that converts .scene → a PackedScene (via the same SceneFile/SceneBuilder the runtime uses), so the editor shows and renders the node tree. It also adds an "Evaluate" dock (and Tools-menu entries) to open a .scene as an editable native scene, edit it with the normal editor — move nodes, create and place new nodes, set properties, attach a node script via a __evt_script metadata entry — and Save to .scene, which serializes the edited tree back to the TOML file via SceneWriter (the inverse of SceneFile/SceneBuilder). Editor-only (#if TOOLS) and needs no running game (a running game's hot-reload picks the saved file up for free). The addon is not part of the NuGet package (the runtime library carries no editor types); instead it ships as a drop-in zip on each GitHub release (evaluate_scene-<version>.zip): extract it into your project to get res://addons/evaluate_scene/, then enable the plugin in Project Settings (it needs the RadicalBeard.Evaluate package for the build/serialize logic). A C# editor addon can't be a bare DLL — its .cs must live under res://addons/ and compile with the game — so the zip carries the source; the stable Evaluate.Editor namespace means it drops in verbatim.
  • Lifecycle hooks. register: wires Godot's Node lifecycle. System hooks: on_start (global, once), on_enter/on_exit (scene-scoped, per activation), on_update(dt), on_physics_update(dt), on_input(event). Node hooks (*.node.evt, with self): on_ready, on_update, on_physics_update, on_input, on_exit.
  • std.* standard library. Real C#-backed types via the [LuaObject] source generator — std.vec3, std.vec2, std.color, std.vector, std.linked_list (src/Evaluate/Std.cs).
  • Persistence (save). SQLite-backed runtime/player data in Godot's per-project user:// directory (the platform-native, per-game path Steam Cloud syncs from) — save.set/get/delete (src/Evaluate/Persistence.cs).
  • Godot binding (ambient, default). godot.<Type> resolves any Godot type on first use (src/Evaluate/GodotBinder.cs):
    • instancesgodot.Node3D.new() (Activator); member access routes through the engine ClassDB (GodotObject.Call/Get/Set), not reflection. Instances are ILuaUserData, so they pass back into the engine (world:add_child(node));
    • propertiesnode.position = std.vec3(…) / node.position;
    • signalsnode:connect("timeout", fn) (0–6 args, dispatched on the main thread) and node:emit_signal(...);
    • enums/constantsgodot.Key.Space, godot.Timer.TimerProcessCallback.Idle;
    • marshalling — exhaustive: primitives, strings, NodePath, objects, Array/Dictionary; rich C#-backed Vector2/3 & Color (↔ std.*); and every other struct (Vector4, Vector2I/3I, Quaternion, Rect2, Plane, Aabb, Basis, Transform2D/3D) + packed arrays as named-field/list tables. Reads are tables; writes are target-type-aware — assigning a table to a struct property builds the exact struct (so node.transform = t round-trips);
    • statics — reflection fallback, or a pre-baked zero-reflection binding.
  • Pre-bake (source generator). generator/ is a Roslyn IIncrementalGenerator: [BindGodot(typeof(Godot.OS))] emits OsBinding.Create() with direct, reflection-free calls (53 bound OS methods), filtered to Lua-convertible signatures. The binder prefers pre-baked bindings.

Base VM

Lua-CSharp (LuaCSharp 0.5.5, Lua 5.2). Source-generator interop ([LuaObject]) → AOT-clean, low-allocation. Its API is async-only; we bridge to sync for Godot's _Process (safe because script calls don't actually await). Dialect note: scripts use x = x + …, not the planned custom dialect's +=.

Run

godot --headless --path dev                       # demo: global layer, scene switch (menu -> level1), node script
godot --headless --path dev -- --test             # enforcement suite (18 tests)
godot --headless --path dev -- --quit-after 8     # demo, then quit after 8 frames

Layout

README.md  CHANGELOG.md  Directory.Build.props  Evaluate.slnx  global.json  LICENSE.md
src/Evaluate/            Evaluate.csproj  EvaluateRuntime.cs  Loader.cs  Std.cs
                         Frontmatter.cs  Toml.cs  SceneFile.cs  SceneBuilder.cs
                         GodotBinder.cs  Persistence.cs  BindGodotAttribute.cs  Prebaked.cs  EvaluateTests.cs
src/Evaluate.Generator/  Evaluate.Generator.csproj  BindGodotGenerator.cs   (Roslyn source generator)
dev/        the demo / enforcement-test harness game (consumes the lib by project reference):
  project.godot  main.tscn  EvaluateHost.cs  Dev.csproj
  scripts/  global.scene  menu.scene  level1.scene
            player.node.evt  showcase.evt  menu.evt  level1.evt  game.toml  player.toml
  addons/evaluate_scene/  editor import plugin (.scene -> PackedScene preview)
  tests/    enforcement suite (.evt): forbidden_global / undeclared_api / missing_accessor /
            system_a / system_b / readonly_module / metatable_oop / scene_a / scene_b /
            scene_a_dup / global_update / self_node, + global.scene
artifacts/  packed .nupkgs (gitignored)
  • Godot coverage is complete: instance + static members (any type), all Variant structs + packed arrays, enums/constants, and signals — all two-way. Static methods with struct/packed signatures bind via the reflection fallback (layered over the pre-baked primitives). Instance access is reflection-free (engine ClassDB Call/Get/Set). Remaining (perf only): extend the source-gen pre-bake to struct/packed static signatures so they too avoid reflection.
  • Frontmatter LSP (deferred): editor diagnostics for the signature block — e.g. red-underline a non-existent godot.* API or missing config file. The Lua body itself rides the standard Lua LSP.
  • AOT export & mod packaging/distribution: not yet built.
  • Out of scope by decision: a Lua type system (signature contract is enough) and sandbox resource limits / DoS protection (modding is free).
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.7.2 51 6/24/2026
0.7.1 42 6/24/2026
0.7.0 54 6/24/2026
0.6.0 91 6/23/2026
0.5.2 95 6/22/2026
0.5.1 96 6/22/2026
0.5.0 95 6/22/2026
0.4.2 95 6/19/2026
0.4.1 97 6/19/2026
0.4.0 94 6/19/2026
0.2.0 94 6/18/2026
0.1.0 99 6/17/2026