C# implementation
The C# implementation is the reference implementation for the shared, language-neutral contracts. Runtime, Compiler and C# adapters are separate libraries; the CLI and internal Conformance adapter are executables.
| Project / package | Responsibility | Dependencies |
|---|---|---|
GameEventScript.Runtime |
Immutable values and Programs, .gesb codecs/validation, GESA dumping, Host, VM, random streams, extensions and external types |
.NET Standard library |
GameEventScript.Compiler |
Source lexer/parser, validation, lowering, optimization, register allocation, immutable Programs and debug sections | Runtime |
GameEventScript.CSharpBridge |
Delegate adapters, Reflection/attribute bindings, CLR value conversion and optional automatic host pumping | Runtime |
GameEventScript.SyntaxHighlighter |
GES/GESA UTF-16 spans, TextMate scopes, incremental states and ANSI rendering | .NET Standard library |
GameEventScript.Conformance |
Internal observation adapter for the independent Python runner | Runtime and Compiler |
GameEventScript.Tool / dotnet ges |
Compile/check/run/dump, interactive console, filesystem and terminal adapters | Runtime, Compiler, CSharpBridge, SyntaxHighlighter and PrettyPrompt |
All four libraries target .NET Standard 2.1 and use C# 12. Runtime,
and Compiler are synchronous, fileless and independent of a test
framework. CLR-specific adapters belong to CSharpBridge. Compiler and
CSharpBridge each depend only on Runtime, so a product executing precompiled
.gesb files can ship Runtime with optional C# adapters without shipping the
compiler or test infrastructure.
The public entry points use GameEventScript.Api; values use
GameEventScript.Runtime.Values. The compiler’s builder also lives in
GameEventScript.Api, even though its assembly is separate. C# adapters use GameEventScript.CSharpBridge. The internal Conformance adapter
is not a public library API.
Module layout
Each module owns its production project in src and its native test project in
tests. The repository solution groups them by module, so a Runtime or Compiler
change and its implementation tests are visible together. Build or test one
module directly by passing its .csproj to dotnet build or dotnet test.
implementation/csharp/ GameEventScript.Runtime/ src/ + tests/ GameEventScript.Compiler/ src/ + tests/ + worker/ GameEventScript.CSharpBridge/ src/ + tests/ GameEventScript.Tool/ src/ + tests/ GameEventScript.Conformance/ adapter/ + fixtures/ + fixture-exporter/ verification/ Repository.Tests, consumers, package tool, test supportThe internal C# Conformance adapter depends on Runtime and Compiler. The shared
Markdown corpus and portable fixtures stay in repository-level conformance/.
verification/Tests.props supplies common MSTest settings and linked helpers;
it contains no test cases. The six test projects each discover their own tests
exactly once. Some native Runtime and Bridge tests use Compiler to build inputs;
these are test-only dependencies and do not change the product boundaries.
Build and verify
Solution and toolchain
Open GameEventScript.sln from the repository root
in an IDE supporting the repository’s .NET SDK. The solution includes the
libraries, CLI, native tests and supporting verification projects.
Development uses the exact .NET SDK 10.0.201 pinned in
global.json. The tests and CLI target .NET 8 and also
need its runtime installed. The SDK version does not change the libraries’
.NET Standard 2.1 target.
Run these commands from the repository root. The scripts also resolve the checkout correctly when invoked by absolute path from another directory:
| Command | Purpose |
|---|---|
./scripts/clean.sh [--dry-run] [--artifacts-only] |
Remove repository build outputs; preview with --dry-run (scope) |
./scripts/build-csharp.sh |
Build the complete solution in Release |
./scripts/test-csharp.sh |
Run shared Conformance and native non-performance tests in the default Debug configuration |
./scripts/format-csharp.sh |
Verify solution formatting without modifying files |
./scripts/test-csharp-performance.sh |
Run Release allocation and elapsed-time gates; timing measurements require the matching calibrated profile |
./scripts/install-csharp-tool.sh |
Build the solution and install/update the global dotnet ges CLI |
./scripts/uninstall-csharp-tool.sh |
Remove the C# CLI installation; succeeds when already absent |
./scripts/pack-csharp.sh 0.1.0-rc1 |
Build canonical NuGet and symbol packages for the four public libraries |
./scripts/release-csharp-dry-run.sh 0.1.0-rc1 |
Pack, stage DLL sets and verify artifact consumption without publishing |
./scripts/verify-csharp-reproducibility.sh 0.1.0-rc1 |
Build packages independently twice and verify byte-identical results |
Normal library build and test intermediates use project-local bin/obj
directories. Packages, staged DLL sets, CLI build outputs and generated reports
stay under ignored artifacts. Handwritten C# follows
CodeStyle.md and the root .editorconfig.
Public XML documentation and the approved API surface are checked by the build
and native tests.
Conformance and native tests
For Release verification including the independent allocation gates:
dotnet test GameEventScript.sln \ --configuration Release --filter "TestCategory!=Performance|TestCategory=Allocation"Portable behavior is authored in the shared Markdown corpus.
The independent Python runner evaluates observations from the internal C# adapter.
Run sh scripts/test-csharp-conformance.sh for correctness and managed allocations.
Each product module owns a separate native test project.
Native tests cover C# adapters, implementation details, public API boundaries,
CLI/process integration and measurements that require the CLR.
Performance is opt-in and always measured; no reference values are echoed.
Managed allocation checks use the csharp-dotnet-release-managed profile.
Elapsed-time benchmarks require their calibrated hardware/toolchain profile.
See the test guide for measurement scope.
Reports are written below artifacts/conformance/python. Received Markdown is
explicitly generated with the Python runner’s received command; it never
replaces approved references automatically. The shared
CapabilityMatrix records
corpus identity and acceptance status across implementations.
Use the CLI
Install or update from the checkout:
./scripts/install-csharp-tool.shdotnet ges --helpdotnet ges check game.gesdotnet ges compile handlers.ges definitions.ges -o artifacts/game.gesbdotnet ges dump artifacts/game.gesbdotnet ges run artifacts/game.gesb --args 12 Hello 34dotnet ges run --interactive --colorThe package is GameEventScript.Tool; the installed executable is dotnet-ges,
invoked as dotnet ges. The global tool directory (normally
$HOME/.dotnet/tools) must be on PATH. The native Swift CLI is named ges, so
both implementations can be installed together.
Both installation and removal support --tool-path DIRECTORY for an isolated
installation. Run the installer again to update it, or
./scripts/uninstall-csharp-tool.sh to remove the global installation. For a
custom directory, use the same --tool-path when uninstalling.
By default, run drains initialization and sends Main(args) with a List of
Text values. --interactive selects the event console instead. Full command
syntax, scenarios, console handlers, multiline input and inspection commands
are documented in the CLI guide.
Compile source text
For a local embedding, add a project reference to the compiler. Replace the path with the checkout’s location:
<ItemGroup> <ProjectReference Include="/path/to/GameEventScript/implementation/csharp/GameEventScript.Compiler/src/GameEventScript.Compiler.csproj" /></ItemGroup>Runtime is brought in transitively. The builder accepts source text and optional diagnostic source names; file access remains with the embedding:
using GameEventScript.Api;
var program = GameEventScriptBuilder.Create() .AddScript(""" module example function double(_ value as :Number) be value + value on Start(value) { emit Done(value: double(value)) } """, "example.ges") .Compile();byte[] bytes = GameEventScriptProgramWriter.ToArray(program);// The embedding can save bytes, or load program directly into a Host.Repeated AddScript calls compile sources jointly. Compile() can be repeated;
previously returned Programs remain immutable. Debug symbols, source maps and
source archives are included by default. Use
.WithDebugInfo(GameEventScriptDebugInfoOptions.None) for compact output,
.WithProgramVersion(...) to set the portable version, and
.WithExternalTypeCatalog(...) for declarative external types. Runtime bindings
remain separate. GameEventScriptCompileException.Diagnostics exposes stable
diagnostic codes and structured context, including source locations.
Consume the Runtime
A product that receives precompiled Programs needs only the Runtime reference:
<ItemGroup> <ProjectReference Include="/path/to/GameEventScript/implementation/csharp/GameEventScript.Runtime/src/GameEventScript.Runtime.csproj" /></ItemGroup>The following example loads bytes supplied by the embedding, subscribes to
Done(value) and sends Start(value) to the program above. The native callback
prints Done(value: 84):
using System;using GameEventScript.Api;using GameEventScript.Runtime.Values;
static (GameEventScriptStartResult Start, GameEventScriptExecutionResult? Execution) Execute(byte[] bytes){ var program = GameEventScriptProgramReader.Read(bytes); var host = GameEventScriptHost.CreateBuilder().WithRandomSeed(123).Build(); var output = GameEventScriptMessageSignature.Create("Done", ["value"]); var subscription = host.Subscribe(output, new Output()); var instance = host.Load(program); try { var startup = host.Start(); if (!host.IsReady) return (startup, null);
host.Receive(GameEventScriptMessage.Create("Start", [ new GameEventScriptMessageArgument("value", GesValue.GesInteger(42)) ])); return (startup, host.RunToCompletion()); } finally { instance.Detach(); subscription.Unsubscribe(); }}
sealed class Output : IGameEventScriptNativeMessageHandler{ public void Handle(GameEventScriptMessage message, GameEventScriptContext context) { // Console I/O belongs to this embedding callback, not Runtime. Console.WriteLine(message); }}Call Execute(bytes) and inspect the startup and execution results. The initial
flow is Build, Load all Programs, register native handlers, then Start. Start
initializes the group without draining its emitted messages. A later Load with initialization returns
an instance with a pending StartResult; ordinary pumping completes that init.
An init failure removes that instance and its pending deliveries while the host
remains ready. Host runtime defines the full startup and output rules.
ExecuteFrame(opcodeBudget) supports bounded stepping and resumption. Retain
the Host and instance between frames when embedding it in a game loop. Dropping
an instance or subscription handle does not detach it; use Detach() or
Unsubscribe() explicitly. A Host is serial and requires one caller at a time.
The immutable Program can be reused in multiple independent Hosts.
Add GameEventScript.CSharpBridge when you need delegate handlers, CLR-backed
external types, attribute-based extensions or GameEventScriptCSharpHostRunner
for synchronization and automatic pumping. It does not add a Compiler dependency.
The public API specification documents the portable
contracts and C# mapping; Host runtime owns lifecycle,
ordering, execution limits and diagnostic behavior.
Distribution
Runtime, Compiler, CSharpBridge and SyntaxHighlighter are packaged independently with a shared
release version. Conformance stays internal. NuGet CLI tools (since 0.3.0) are
packaged separately as GameEventScript.Tool and GameEventScript.Tool.Aot;
see CLI installation.
Local packages are written to
artifacts/csharp/packages/<version>; the release dry run also stages versioned DLL sets
below artifacts/csharp/dll/<version> for Runtime, Compiler and Unity adapters.
XML documentation and portable symbols accompany the
assemblies. CLI packaging is handled separately by its installer.
Use the C# distribution guide for local NuGet consumption, direct DLL references, package validation, reproducibility and the gated publication workflow. The dry run never publishes. The Unity DLL set is checked with an external .NET consumer; validation in a real Unity project remains deferred.
The documentation index links the owning language-neutral
specifications. Deferred work is tracked only in BACKLOG.md.
Explicit message JSON exchange
The Runtime provides GameEventScriptMessageJson for optional product transport.
Use Serialize/Deserialize in C#, or serialize/deserialize in Swift, for
messages; the corresponding SerializeValue/serializeValue methods encode a
standalone value. Decoding reconstructs immutable data and never runs Record or
native constructors. External objects export as typed Record snapshots. Local
Publish does not serialize; configure encoding explicitly at your I/O boundary.
The message format defines the envelope and errors.