Portable public API specification
Since: 0.1.0
This document defines the normative language-neutral public API shared by the Swift, Kotlin, Go, Rust, C++, C#, and Unity implementations. It specifies concepts and observable behavior, not the spelling imposed by one language.
The C# implementation is the current reference implementation. A port may use idiomatic constructors, optionals, result types, collection views, naming, and callback forms, but it must preserve the responsibilities, state transitions, ordering, errors, and data described here. The public C# API snapshot is a regression tool for the C# binding and is not the portable API definition.
The optional standalone presentation API is specified in Syntax highlighting. Its C# and Swift packages have no Runtime, Compiler, Bridge or Conformance dependency and are not part of VM execution.
Scope and dependency direction
The portable product modules and independent verification tooling are separated as follows:
GameEventScript Runtime values, messages, program, binary codec, validation, host, VM, literal parsing
GameEventScript Compiler source builder, lexer, parser, validation, optimizer, bytecode generation -> depends only on GameEventScript Runtime
Independent Conformance runner (Python, repository tooling) Markdown parser, normalized test model, assertions, reports, received output -> communicates with internal implementation adapters over JSON -> C# and Swift adapters depend on their Runtime and Compiler; no public Conformance libraryRuntime must load and execute precompiled Programs without a source compiler. Shared value semantics, text/number conversions, runtime literal parsing, Program validation, and declarative external-type definitions remain in Runtime. There is no additional shared Core module. Compiler uses these Runtime contracts; Runtime never references Compiler or Conformance. Compiler never references Conformance or language-specific adapters. Both portable modules remain synchronous and independent of filesystem, network, threads, and test frameworks.
Conformance is repository tooling, not a published NuGet library or root SwiftPM product. The Python runner owns assertions; the internal C# and Swift adapters expose only the process protocol in PythonRunner.md. Neither implementation publishes a Conformance library.
The following documents own detailed behavior and are incorporated by reference:
- Language owns source-language syntax and evaluation.
- Host runtime owns dispatch, random boundaries, execution, and the host state machine.
- Program model owns immutable program data and permitted representation.
- Bytecode and Binary format own numeric IDs,
instructions, segments, validation, and
.gesbencoding. - Diagnostics owns stable diagnostic phases and codes.
- Text, numbers, and determinism own value-level portable semantics.
- Conformance Markdown and the Conformance runner own normalized test fields and execution details.
C# reflection attributes, delegates, locks, threads, tasks, System.Type, and
dictionary conveniences belong to CSharpBridge. They are public C# adapters,
not portable Runtime or Compiler concepts. Other ports may provide equivalent
conveniences, but conformance code and portable application code must not
require them.
Embedding API baseline and compatibility
The reviewed application API baseline consists of Runtime, the optional Compiler and the native Bridge in each implementation. SyntaxHighlighter is a separate optional product with its own contract. CLI and Conformance are not application library products. Public implementation details must not be inferred from private types, compiler SPI or the shape of generated code.
From 1.0 onward, compatible releases preserve the supported public source API: existing calls and implementations of public callback interfaces/protocols must remain valid under the declared platform/toolchain requirements. Signatures, argument labels/order, specified defaults, result interpretation, ownership, lifetime and error contracts are part of that surface. An addition is compatible only if existing conforming consumers continue to work; for example, requiring an extra callback implementation is not automatically compatible merely because it adds a member. Deprecation may guide migration but does not permit removal within a compatible major line.
Native naming, value/reference representation and error transport may differ
between languages as specified below; each published language binding retains
its own source API compatibility. There is no requirement for identical symbol
names across languages. Native object layout and compiler-generated ABI are not
the portable binary interchange contract; .gesb compatibility is owned by
Binary format.
The existing compiler and host builder workflows are the selected baseline; no additional manager/facade or combined load-and-run operation is required. Programs remain reusable data. Host-specific bindings, execution and lifecycle handles remain separate from compilation. Existing native adapters provide callbacks and external type bindings without bringing Compiler into Runtime or Bridge consumers.
The approved C# and Swift snapshots record each implementation’s declared surface and are checked against freshly built APIs. XML documentation and authored Swift symbol documentation are checked alongside them. Snapshots detect signature drift; the normative contracts and shared Conformance govern behavior. Keeping a snapshot unchanged does not justify changing ownership, defaults or execution semantics. The review evidence and intentional language differences are summarized in the API review.
This baseline does not turn a 0.x release into a stable 1.0 release. The version policy governs any subsequent pre-1.0 correction; required bytecode changes must be rechecked for impact on the public Program representation and codec APIs before 1.0.
Normative conventions
Names used by this specification
Type and operation names below are conceptual PascalCase names. They correspond closely to the C# reference API only to make cross-checking practical. Swift, Kotlin, Go, Rust, and C++ may use their normal naming and callable conventions. Removing a concept, changing its ownership, or changing its result is not an idiomatic mapping and is therefore not permitted.
List<T> means an ordered immutable view unless the operation is explicitly a
builder input. Bytes means an ordered immutable byte sequence. Optional<T>
means explicit absence, not a sentinel native object. Fixed-width integer names
refer to their exact bit width.
Nullability and invalid arguments
- Required object, string, callback, and sequence inputs must be present.
- Optional inputs are identified explicitly. An absent optional sequence has the same meaning as an empty sequence only where this document says so.
- Public output collections are never null. They are empty when no items exist.
- Collection elements that are not explicitly optional must not be null.
- Invalid caller arguments fail synchronously before observable state changes. Each language uses its idiomatic argument/precondition error.
- Script, decode, link, and runtime failures use structured GES diagnostics or format errors. Platform exception text and exception class names are never a portable comparison value.
- Portable identifiers use the grammar and normalization rules in Text. Comparisons are ordinal and culture independent.
Copy and reference semantics
- Immutable data objects copy mutable input sequences before returning from construction. Their public sequences are read-only views.
- Value records may be native value types or immutable reference objects. Their equality and hash behavior must follow portable content, never object identity.
Program, messages, signatures, definitions, diagnostics, and normalized conformance models may be shared for concurrent read access.Builder,Host,Context, lifecycle handles, random generator, extension calls, and runner callbacks are identity-bearing reference objects.- Returning a read-only view must not expose storage that a caller can mutate.
Synchronization and reentrancy
- Core operations are synchronous, threadless, and internally unsynchronized.
- Immutable values and programs permit concurrent reads. A builder, host, context, random generator, or active call object does not permit concurrent use.
- A host is serial but not thread-affine. A later call may run on another thread after an embedding-provided happens-before handoff.
- Host pumping is not reentrant: a callback must not call ExecuteFrame or RunToCompletion on the active Host. Synchronous Receive, Load, Subscribe, Detach, and Unsubscribe calls from a native callback are permitted and obey the enqueue-time snapshot rules; they must return before the callback returns.
- Observer and publish-sink callbacks run synchronously on the pumping caller.
- Portable library operations remain synchronous. Conformance process orchestration belongs to repository tooling, outside the application library contract.
Allocation contract
The allocation contract applies to the warmed runtime hot path, not compilation,
Markdown/YAML parsing, .gesb decoding, message construction, debug dumping, or
report creation.
After host warmup, queue dispatch, handler selection, execution-result creation, publish-result creation, VM stepping/resume, and random scope operations must not allocate heap objects by themselves. Value-style execution and publish results must therefore remain representable without heap allocation. User callbacks, extension implementations, emitted messages, and values may allocate.
The dispatch guarantee applies equally to exact-signature and message-name handlers, with no observer or with observer callbacks that allocate nothing. Obtaining the dispatch signature for VM entry or observer callbacks is part of the runtime’s own dispatch work and must not allocate after warmup.
Compiler API
CompilerBuilder
CompilerBuilder is a mutable, single-caller configuration and source accumulator.
It owns snapshots of all inputs needed after an operation returns.
| Operation | Contract |
|---|---|
Create() |
Returns an empty builder with debug information set to All, program version 0, and no external-type catalog. |
AddScript(text, sourceName?) |
Adds one logical source in call order. Text is required and must be valid Unicode. The optional source name is diagnostic/debug identity, not a path to open; absence uses the implementation’s stable unknown-source name. The builder retains its own source snapshot. |
WithDebugInfo(options) |
Replaces the debug emission mask. None, DebugSymbols, SourceMap, and SourceArchive are independent flags; All is their union. |
WithProgramVersion(version) |
Sets the exact unsigned 64-bit application program version; zero means development/unspecified. |
WithExternalTypeCatalog(catalog) |
Sets the immutable declarative catalog used for compile-time resolution. The catalog is required when configured and contains no runtime callbacks. |
Compile(options) |
Parses all added sources as one compilation, validates, optimizes, materializes an immutable Program, and validates that Program before returning it. Sources retain their AddScript order and source IDs. |
CompileOptions is an immutable or snapshot-at-call value containing
DebugInfo and ProgramVersion. Operation-local options override matching
builder defaults. Ports may choose either builder setters or one options object,
provided there is one unambiguous resolved configuration. Compilation never
modifies an earlier Program.
An empty builder produces the valid empty Program named EmptyModule.
Malformed source fails with one or more structured diagnostics.
The fixed source nesting limits in Language
apply to every compilation. Exceeding them produces the Parse diagnostic
parse.sourceNestingExceeded (DiagnosticCodes.ParseSourceNestingExceeded in
C#), including source identity and location. CompileOptions cannot disable
these limits.
CompileException is the C# transport carrying an ordered,
non-empty diagnostic list. A port may return a result union instead. Diagnostics
remain the portable failure value.
The C# compiler entry point is
GameEventScriptBuilder.Create().AddScript(text, sourceName).Compile(options).
Host construction independently uses GameEventScriptHost.CreateBuilder().
Program and bytecode data API
Program
Program is the immutable, reusable, language-neutral parsed representation of
one .gesb artifact. Only Compiler and ProgramReader create Programs publicly.
There is no free-form public Program constructor.
The Program exposes:
| Property | Contract |
|---|---|
FormatVersion |
.gesb format version represented by the model. |
ModuleName, ProgramVersion |
Stable program identity and unsigned application version from ProgramMetadata. |
RequiredRegisterCount, RequiredCallStackDepth |
Maxima across executable handlers, verified by the validator. |
Metadata, StringConstants, UInt16IndexLists, Bindings, Code |
Exactly one required immutable runtime segment of each kind. |
DebugSymbols, SourceMap, SourceArchive, BuildMetadata |
Independent optional known segments. Absence is represented explicitly. |
OpaqueSections |
Immutable retained unknown optional sections in original relative order. |
All segment entry order, duplicate string constants, instruction bits, source bytes, and opaque payload bytes are meaningful immutable data. Resolving a string or index list returns a decoded value/view without mutating Program. Out-of-range access fails as an argument/index error for direct low-level API use; Reader, Writer, Validator, and Host must report structured format/link failures before such access can occur for untrusted input.
Required segment views
The portable segment model contains these data records and operations:
ProgramMetadataSegment: module name, required register count, required call depth, and program version.StringConstantSegment: immutable UTF-8 storage and slice records;Resolve(index)strictly decodes one string andResolveSize(index)returns its byte length.UInt16IndexListSegment: immutableu16storage and slice records;Resolve(index)returns one immutable indexed list view.BindingSegment: ordered bindings containing kind, ID, name index, ordered argument-name indices, required/excluded tag indices, optional entry address, and per-handler resource requirements.CodeSegment: ordered 16-byte logical instructions;Count/Lengthand indexed access do not expose mutable program storage.
ReadOnlyArray<T>, string slices, and UInt16IndexList are representation
helpers for immutable ordered access. A port may use its native immutable list
or slice type instead. Count and Length are aliases where both exist in C#;
they must report the same value.
An index into a resolved UInt16IndexList is zero-based relative to that list.
Only 0 <= index < Length is valid, irrespective of storage before or after the
slice. An empty list has no valid index. Access outside these relative bounds
fails as an argument/index error before applying the storage offset.
Instruction and numeric identifier types
BytecodeInstruction exposes opcode, unit/flag byte, three u16 words, and a
u64 payload with the semantic aliases specified by Bytecode.
The aliases read and write the same logical words/bits. C# struct layout and
native endianness do not define the binary format.
The following enum-like types expose the fixed numeric IDs from the bytecode and binary specifications and must reject undefined values at validation boundaries:
BytecodeOpCode,BytecodeTypeKind,BytecodeInstructionUnit,InstructionFlag,BytecodePatternKind, andBytecodeSeriesKind;BinaryBindKind,SectionType,SectionFlags, andDebugSymbolKind.
BytecodeOpCode.ParseLiteral exposes the portable parse operation at opcode
0xD9, with operand and limit semantics owned by Bytecode.
BytecodeInstructionUnits supplies pure conversions between type names, units,
and suffixes. Parse operations return absence for an unknown valid string;
predicate operations never use locale; formatting returns canonical GES text.
BinaryFormat exposes the fixed V1 magic bytes, format version, file-header
size, and section-header size as named constants; their numeric values are owned
by BinaryFormat.md.
Optional program data
DebugSymbolsSegmentexposes orderedDebugSymbolrecords. Each record has kind, physical register ID, name, and half-open code range.SourceMapSegmentexposes orderedSourceMapSourceandSourceMapEntryrecords. Hashes and line offsets are immutable bytes/integers; source and code ranges are half-open and use the units defined by the binary specification.SourceArchiveSegmentexposes orderedSourceArchiveEntryrecords.ResolveText()strictly decodes retained UTF-8 content.BuildMetadataSegmentexposes required non-empty compiler ID and compiler version when the optional segment exists. Runtime decisions must ignore them.OpaqueSectionexposes type, flags, version, original ordinal, and immutable raw payload. Only unknown optional sections may be represented this way.
The public constructors of individual segment records are data helpers. They defensively copy their inputs but cannot assemble a Program and do not bypass Program validation.
Program codec, validation, and dump API
ProgramReader
ProgramReader.Read(bytes, options) -> Program consumes caller-provided bytes
only. It performs overflow-safe bounded structural decoding followed by complete
semantic validation. It retains no caller buffer. Success returns a fully valid
immutable Program; failure returns/throws ProgramFormatError with a stable
ProgramFormatErrorCode and optional byte offset, section type, and entry index.
ProgramReadOptions contains a required ProgramReadLimits snapshot and one
retention mode:
PreserveAll: parse all supported known sections and retain unknown optional sections as opaque data;PreserveKnown: parse supported known sections and discard unknown optional sections;RuntimeOnly: materialize required runtime sections only.
ProgramReadLimits exposes maxima for file bytes, section count, source archive
bytes, retained opaque bytes, instructions, bindings, strings, and index lists.
All values are validated before allocation. The shared Default object is
read-only by convention; a mutable language mapping must snapshot settings at
the start of Read.
ProgramWriter
| Operation | Contract |
|---|---|
GetEncodedSize(program) |
Fully validates Program and returns the exact canonical byte count with checked arithmetic. |
Write(program, destination) |
Fully validates before modifying destination, requires sufficient caller storage, writes canonical Little Endian .gesb, and returns bytes written. |
ToArray(program) |
Allocates exactly the required output byte array and delegates to canonical Write semantics. |
The writer reconstructs every known section from structured data. Only opaque payloads are copied raw. It performs no file or stream I/O.
ProgramValidator and ProgramDumper
ProgramValidator.Validate(program) is deterministic, side-effect free, and
performs the same complete semantic checks used by Reader, Writer, and Host.Load.
It does not link executable callbacks. Failure uses ProgramFormatError.
ProgramDumper.Dump(program, includeInstructionAddresses = false) returns
canonical .gesa text for a valid Program. It uses embedded debug/source
segments when available, performs no file I/O, and never changes Program. The
dumper is not a trust boundary and does not replace explicit Program validation.
Dump allocation is outside the runtime hot path.
Message and value API
MessageSignature
MessageSignature is immutable content identified by normalized message name
plus ordered parameter labels. Tags and values are not part of a signature.
| Operation | Contract |
|---|---|
Create(name, parameters) |
Normalizes and copies the name and ordered labels. An absent parameter sequence means empty. Repeated labels remain part of the signature; constructing actual MessageArguments later rejects duplicate named labels while repeated _ labels remain permitted. |
NormalizeMessageName(name) |
Trims portable ASCII edge whitespace and validates the message-name grammar; absent input yields empty only for this normalization helper. |
NormalizeParameterName(name) |
Trims portable ASCII edge whitespace; absent/empty becomes _; otherwise validates a portable identifier. |
CreateSignatureId(name, parameters) |
Returns canonical Name(label,...) from normalized content. |
Matches(message) |
True exactly when normalized name and complete signature ID are equal ordinally. Message tags and values are ignored. |
CreateMessage(values) |
Binds values in parameter order. Returns absence when arity does not match or the signature is Empty. |
WithArguments(values...) |
Same binding, but invalid arity is a synchronous argument error. |
Signature equality and hashing use canonical signature content. Empty is a
read-only sentinel and cannot create a message.
Public parameter views cannot change the signature or any message created from
it. Message factories snapshot caller-owned value collections; later changes to
those collections cannot change an existing message.
Message and arguments
MessageArgument is an immutable name/value pair. MessageArguments is an
immutable ordered collection exposing Count, indexed argument access, indexed
name/value access, and lookup by normalized named label. Lookup returns absence
when not found. It never converts the collection to unordered semantics.
Message.Create(name, arguments?, tags?) validates and copies the logical
inputs. Absent arguments/tags mean empty. WithTags(tags) returns a new Message
whose normalized tag set is the stable merge of existing and supplied tags; it does not
mutate the original. HasTag(tag) uses normalized ordinal matching.
Public tag and argument-label views cannot modify a message, its signature, or
any other message sharing its immutable data. This also applies after a message
has been enqueued: its delivery tags and argument labels remain unchanged.
Message equality compares signature, argument values in order, and normalized tags in order. Hashing must preserve the equal-values-have-equal-hash invariant. Human formatting is diagnostic only and must not be parsed as transport.
Value
Value is the compact tagged GES runtime value. A language may map it to a
struct or another compact representation. Public factory operations create:
- Nothing and Boolean;
- signed-64 Integer, Binary64 Float, Percentage, and numeric values with a unit;
- Text and Tag;
- Vector and Point;
- IntegerRange and FloatRange;
- Dice from ordered roll values;
- immutable List and Map;
- Handler signature and Message;
- immutable Record by type name and ordered fields;
- External values through the external-type boundary.
Factory operations snapshot mutable arrays/maps before returning. The portable ordered map/record model must not derive order from platform dictionary iteration. C# dictionary factories belong to CSharpBridge and delegate to the portable ordered factories; their canonical ordering is governed by Determinism.
ValueKind, ValueUnit, HasValue, IsNothing, IsNumeric, HasUnit,
Length, CustomTypeName, coordinate/range/message/handler views, and typed
read operations expose the active value. A typed As... operation on an
incompatible kind follows the public value conversion contract and must never
expose uninitialized native storage. AsList and AsMap return immutable
views; AsDice returns an independent ordered copy in the C# mapping.
Numeric register storage is a numeric projection only. Boolean stores 0/1 and Dice
stores its exact Int64 roll sum (zero for empty Dice). Nonnumeric values have no
numeric projection; their numeric storage is cleared. Collection counts and cached
Unicode scalar/range lengths belong to the immutable payload. GesValue.Length
returns zero for scalar values and saturates collection lengths at Int32.MaxValue.
Value equality is the strict structural equality in Determinism,
including numeric rules in Numbers. Hashing must be
consistent with this equality. ToString is nonnormative diagnostic formatting.
ValueSlice, ValueArguments, and ValueMap are read-only runtime views.
They expose Length, indexed values, kind/unit checks, typed reads, and map key
lookup. Indexes are zero-based at the API boundary. Invalid indexes fail as
argument/index errors; VM opcodes must instead evaluate according to Language
semantics. A borrowed read-only value reference must not outlive the callback or
operation that supplied its backing storage.
IntegerRange and FloatRange are immutable triples (from, to, step); they do
not enumerate by themselves. Range termination and overflow behavior belong to
the deterministic language semantics.
Host construction and lifecycle API
HostBuilder
HostBuilder is mutable and single-caller. Every Build() creates an independent
Host and private random generator. Reusing a builder must not cause hosts to
share queues, random state, runtime budgets, dispatch state, or VM state.
Immutable configuration and configured registry, sink, or observer callback
identities may be shared; the embedding then owns any synchronization and
reentrancy requirements of those callbacks.
| Operation | Contract |
|---|---|
WithRuntimeLimits(limits) |
Replaces the required immutable limits configuration. A language with mutable option objects must snapshot all scalar values at Build. |
WithRandomSeed(seed) |
Selects the complete signed-64 deterministic seed and clears a prior entropy or sequence configuration. |
WithRandomEntropy(entropy) |
Folds a non-empty entropy byte span into the deterministic seed with the portable mixing function and clears a prior direct seed or sequence configuration. |
WithRandomSequence(values, fallbackSeed?) |
Copies a finite Binary64 start sequence, optionally selects a deterministic fallback seed, and clears a prior direct seed. Exhaustion continues with the private fallback generator. |
WithRegistry(registry) |
Selects the extension registry. Absence is represented by an empty registry, not null. |
WithExternalTypeRegistry(registry) |
Selects the runtime external-constructor registry. |
WithPublishSink(sink) |
Selects the single outbound sink. Not calling it means no sink. |
WithProfiler(profiler) |
Selects optional synchronous opcode instrumentation; omission disables it. See opcode instrumentation. |
WithRuntimeObserver(observer) |
Selects the single observer. Not calling it means no observer. |
WithClock(clock) |
Selects the borrowed monotonic clock; omission uses the system clock. See delayed-send scheduling. |
Build() |
Validates configuration and returns a new loading, native-capable Host with no loaded Program or subscription. |
RuntimeLimits
RuntimeLimits is a configuration snapshot with the following signed integer
settings: processed events per RunToCompletion call, queued logical
messages, execution steps per handler, register values, loop iterations, call
depth, random scope depth, range items, generated collection items, dice count,
and dice sides. The exact defaults are part of the public versioned API.
| Setting | Default |
|---|---|
MaxProcessedEventsPerRun |
64 |
MaxQueuedMessagesPerRun |
0 (unlimited) |
MaxExecutionSteps |
100,000 |
MaxRegisterValues |
512 |
MaxLoopIterations |
100,000 |
MaxCallDepth |
64 |
MaxRandomScopeDepth |
16 |
MaxRangeItems |
10,000 |
MaxGeneratedCollectionItems |
10,000 |
MaxDiceCount |
1,000 |
MaxDiceSides |
1,000,000 |
A nonpositive value disables the processed-event, queue, execution-step, loop,
range, generated-item, dice-count, or dice-side limit. A nonpositive register
limit selects the portable default capacity of 512. Call depth is clamped to the
encoded u16 range for linking; zero permits only root handlers. Random scope
depth alone must be in 0...65535 and is validated at Build. Host.Load checks
static register/call requirements before registration. Dynamic limit exhaustion
is a structured runtime-limit result/observation, not a thrown script exception.
The counting rules and boundary behavior for loops and incrementally generated
collections are defined in Host runtime.
Host
Host owns one serial execution domain. The complete state machine and snapshot rules are in Host runtime.
| Operation | Contract |
|---|---|
CreateBuilder() |
Returns a new HostBuilder. |
Load(program, priority = 0) |
Validates, links and atomically registers one instance with an initialization snapshot when present. During initial loading it joins the Start group; on a ready host it joins the ordinary FIFO. |
Start() |
Synchronously initializes the initial group without processing ordinary messages and returns StartResult. Repeated calls return the original result. |
IsReady |
True after successful initial Start; later instance failures and queued messages do not reset it. |
Subscribe(signature, handler, requiredTags?, excludedTags?, priority = 0) |
Adds one exact-signature native subscription and returns a Subscription. Inputs are copied/retained as immutable registration data. |
SubscribeMessageName(name, handler, requiredTags?, excludedTags?, priority = 0) |
Adds a native subscription matching every signature of that normalized message name. |
Receive(message) |
Captures current matching subscriptions and attempts to enqueue the logical message locally. Returns whether accepted. Before Start succeeds it returns false. It never pumps and never publishes outbound. |
ExecuteFrame(opcodeBudget) |
Synchronously pumps on the caller until the scheduler budget pauses script execution, the Host becomes idle, or a runtime limit/error ends the call. Budget must be positive and the host must be ready. |
RunToCompletion() |
Synchronously pumps until idle, only future messages remain, or a runtime limit/error terminates this pump call. The host must be ready. It creates no worker thread. |
IsIdle |
True only when no active message/handler and no queued logical message exists. |
IdleDurationMicroseconds |
Optional Int64 idle duration on a ready Host; see idle observation. |
PendingMessageCount |
Number of queued logical messages according to HostRuntime; it never counts handler invocations. |
Equal priority uses stable registration order. A logical message completes all
captured handlers before the next logical message. Load/Subscribe/Detach/
Unsubscribe changes affect snapshots captured afterward only.
Load rejects insufficient initialization-queue capacity with
link.initializationQueueFull. The full atomicity and retry contract is defined
in Host runtime.
Instance is a Host-owned lifecycle handle for one linked Program. Program
is the original immutable object. Detach() returns true exactly once when it
transitions attached to detached; later calls return false. IsAttached reports
that transition. Detach never cancels handlers already captured by an enqueued
message and never attaches to another Host.
Subscription is the equivalent native-handler lifecycle handle.
Unsubscribe() returns true exactly once; later calls return false.
IsSubscribed reports that transition. Both handle types retain host identity
and a stable non-reused host-local registration ID, not a closure.
StartResult is an immutable value containing State (Ready, RuntimeError or
RuntimeLimitReached) and optional structured Diagnostic. Ready does not imply
an empty message queue. Instance.StartResult is absent while initialization is
pending and retains its completed outcome after detachment or init failure.
Init failure cancels captured recipients as specified by HostRuntime; ordinary
Detach retains its existing captured-delivery semantics.
StartResult includes ExecutedOpcodes, ProcessedMessages, EmittedMessages, and PublishedMessages for initialization work, using the same counter meanings as ExecutionResult. A host result covers its initial group; an instance result covers that instance. Repeated Start returns the original counters.
Context, messaging, and execution results
Context
Exactly one Context exists per Host. The Host passes the same identity to native
handlers and extensions. Context exposes Random, RuntimeLimits, the extension
registry, IsIdle, and PendingMessageCount as borrowed Host views.
Emit(message) attempts local enqueue and returns acceptance. Convenience
overloads construct a message from name and ordered arguments. Publish(message)
first attempts the identical local enqueue, then invokes the configured outbound
sink once unless a random fault gate forbids delivery or initialization defers it. It returns PublishResult.
Context has no independent queue, VM, or scheduler. It must not be used concurrently or retained for asynchronous calls after its callback. Emit and Publish do not recursively dispatch the new message.
PublishSink and PublishResult
PublishResult.OutboundDeferred distinguishes initialization publication waiting
for commitment from a sink call already attempted. Its complete ordering and
failure semantics are owned by HostRuntime.
PublishSink.Publish(message) -> bool is a synchronous, preferably nonblocking
handoff. true means accepted by the next layer, not remotely delivered.
PublishResult is a value with:
LocalAccepted: local queue accepted the message;OutboundAttempted: a configured sink was invoked;OutboundAccepted: that invocation returned true;OutboundDeferred: outbound delivery is waiting for successful initialization;AnyAccepted: derivedLocalAccepted || OutboundAccepted || OutboundDeferred.
No sink produces attempted=false/accepted=false. Sink rejection is not an error. A sink exception becomes a runtime diagnostic and outbound rejection; it never rolls back successful local enqueue and does not escape the portable Host API.
NativeMessageHandler and RuntimeObserver
NativeMessageHandler.Handle(message, context) is one atomic trusted callback.
It may Emit, Publish, and use Context.Random synchronously. An implementation
failure becomes runtime.nativeHandlerFailure; it aborts that handler, restores
runtime boundaries, and does not corrupt Host state.
RuntimeObserver receives synchronous callbacks in exact dispatch order:
MessageEmitted(message, accepted)after each Emit attempt;MessagePublished(message, result)after local/outbound Publish processing;DispatchStarted(message, dispatchSignatureId)immediately before a handler;DispatchCompleted(message, dispatchSignatureId)when the handler boundary closes, including cleanup after a handler failure or limit;RuntimeLimitReached(name, detail, limit)for a limit stop;RuntimeError(diagnostic)for a runtime diagnostic.
An observer is observational only and has no return value. Observer callbacks must not throw. Throwing is an embedding-contract violation, is not converted to a GES diagnostic, and lies outside Host state/recovery guarantees. This differs deliberately from publish-sink exceptions, which the Host contains as specified above.
Opcode instrumentation
Since: Unreleased
WithProfiler / withProfiler selects an optional synchronous
IGameEventScriptProfiler / GameEventScriptProfiler factory. Swift also accepts
profiler in its Host initializer. The Host calls
CreateProgramProfiler(program) / createProgramProfiler(program) once per linked
load instance, before execution. The result implements
IGameEventScriptProgramProfiler / GameEventScriptProgramProfiler and remains
host-specific; no instrumentation state enters the immutable Program. Repeated
loads, including identical Programs or module names, receive separate collectors.
The factory can retain collectors for reporting after detach.
The VM announces sequential loop phases through PhaseStarting(phase) /
phaseStarting(phase): StateCheck, BudgetCheck, SliceCheck, Fetch, and Advance
(Swift lower camel case). These respectively check processing state, budget
exhaustion, the reserved slice count, fetch/increment the instruction pointer,
and advance the completed-opcode count. Checks short-circuit in that order;
a terminal loop attempt may therefore have no instruction. Execute begins through
InstructionStarting, not a separate PhaseStarting callback. It includes opcode
dispatch, synchronous callbacks and any fault handling before FinishSlice.
The VM calls InstructionStarting(address) / instructionStarting(address) once
before executing each fetched opcode, with its zero-based address before jumps or
calls change the instruction pointer. This counts instruction starts, including
an opcode that later fails; it need not equal ExecutionResult’s completed-opcode
counter after a fault. FinishSlice() / finishSlice() closes the last interval
on completion, pause, limit or error. It can occur without an instruction and must
clear pending timing. A resumed slice starts a fresh interval. Initializations use
the same instrumentation; native message handlers do not.
Callbacks are observational, synchronous and serialized with the owning Host; they must not throw, mutate execution, or reenter any Host. A shared factory across Hosts requires embedding-provided serialization. Violations are outside execution and recovery guarantees. Timing, storage and report generation belong to the embedding, not to the portable runtime. No clock access, source mapping or profiling allocation occurs when instrumentation is omitted. The C# loop uses six optional callback checks per completed instruction, checks on the terminal loop attempt, and a finish check per slice. Swift selects a specialized instrumented or uninstrumented loop once per slice, sharing the opcode implementation.
A phase-based timer ends the previous phase at callback entry, updates its preallocated counters, and starts the next interval after bookkeeping. It flushes the last phase at FinishSlice. Pre-execution phases are attributed to the following instruction; terminal checks without an instruction remain in phase totals only. This excludes bookkeeping and time outside slices, but includes callback/clock overhead, loop transitions, and synchronous work performed by an instruction. Call instructions measure the call mechanism; callee opcodes receive their own exclusive measurements. These are instrumented wall-time observations, not portable timing guarantees or uninstrumented opcode costs.
ExecutionResult
Since: 0.2.0 — Waiting result
ExecutionResult is an allocation-free value containing state, opcodes executed,
logical messages processed, messages emitted, messages published, and an optional
first diagnostic. Counters cover only the current pump call.
The states are:
Paused: a script handler remains resumable because ExecuteFrame exhausted its scheduler opcode budget;Completed: the pump reached idle without a stopping limit/error;Waiting: no runnable work remains but delayed messages are pending; the caller may wait forNextMessageDelayand pump again;RuntimeLimitReached: a configured safety limit stopped the pump;RuntimeError: at least one runtime diagnostic occurred during the pump; the Host still completes the remaining captured dispatch work unless another stopping limit is reached.
The Host remains reusable after a limit or runtime error as specified by HostRuntime. Native callbacks are atomic and do not consume scheduler opcodes.
Random generator API
RandomGenerator is mutable identity-owned state. Create() chooses a
nondeterministic seed; FromSeed(Int64) uses the exact portable seed;
FromSequence(values) copies a finite-length Binary64 sequence and continues
with a private generator after exhaustion. The 32-bit seed overload is an exact widening
convenience.
FromEntropy(bytes) folds a non-empty byte span into a deterministic seed with
the portable, endian-neutral mixing function, so identical bytes reproduce a run
across ports; it equals FromSeed(SeedFromEntropy(bytes)). SeedFromEntropy(bytes)
exposes that fold so one derived seed can configure several generators or hosts.
Acquiring default entropy for Create() and the seedless host builder is
platform-specific and outside the deterministic contract. The exact mixing
function and known-answer vectors are in Determinism.
NextInclusiveInteger(first, second) samples uniformly over the inclusive
signed-64 range after ordering the bounds. NextFloat(first, second) uses a
[0,1) source and Binary64 scaling; final rounding may produce the upper bound.
Equal and NaN bounds consume no stream value. Exact transitions and known-answer
vectors are in Determinism.
Push(seed) starts a child scope from that seed. Push() clones the current
stream into a child scope. Pop() restores the parent stream. The Boolean result
reports whether the requested transition was accepted. Host-owned generators
add private runtime boundary and fault-gate behavior from HostRuntime. Runtime
markers are deliberately not public API; only Host can fence callbacks.
A RandomGenerator is not thread-safe. A Host never accepts a live generator from outside, preventing cross-Host random-state interference.
Extension API
ExtensionRegistry and references
ExtensionReference is immutable normalized (extensionName, functionName, orderedArgumentLabels) data with a canonical SignatureId. It copies labels;
neither caller-owned input nor a returned collection view may change them.
Collection adapters must not expose mutable backing storage, and label
enumeration must remain consistent with SignatureId for the reference lifetime.
ExtensionRegistry.Resolve(reference) -> Optional<ExtensionFunction> is called
during Host.Load, not for each invocation. Absence causes a stable link failure.
The registry must resolve equal references consistently during one Load and must
not depend on culture or reflection in portable Core.
ExtensionFunction.Invoke(call) is a synchronous trusted callback. Host/VM
establishes a random boundary before invoking it and restores that boundary
after return, including imbalance and limit handling.
ExtensionCall
ExtensionCall is a reusable call-scoped mutable adapter. During Invoke it exposes
borrowed ordered Arguments, Host Context, Random, and RuntimeLimits.
Exactly one result may be selected through SetNothing, SetValue,
SetBoolean, SetInteger, SetFloat, SetPercentage, SetText, or SetTag;
the last setter call is the effective result. Omitting a result produces Nothing.
The callback must not retain the call, arguments, Context, or borrowed values.
Setters are invalid outside an active invocation. Implementations should reuse
the adapter and must not allocate it per hot-path invocation after warmup.
Invoke may throw ExtensionFaultException or an unanticipated exception; see
“Diagnostics and failure transport” for the resulting contract.
External-type API
Declarative catalog
ExternalTypeDefinition is immutable normalized data containing a type name,
ordered unique fields, and ordered unique constructor signatures.
ExternalTypeFieldDefinition and ExternalTypeParameterDefinition each contain
a normalized name and exactly one portable type description: builtin kind plus
unit, or custom type name. ExternalTypeConstructorDefinition contains type name,
ordered unique parameters, and derived SignatureId.
A Vector or Point declaration without a unit constraint preserves the supplied spatial value’s unit. An explicit non-none unit overrides that unit without rescaling components. This applies equally to constructor arguments, direct field reads, and fields materialized by conversion to Map. A missing spatial unit constraint must never silently strip an existing unit.
Construction copies sequences and rejects null entries, duplicate fields, duplicate constructor signatures, duplicate parameter labels, constructors for another type, and constructor parameters that are not declared fields. The resulting field, constructor, and parameter views must not expose mutable backing storage through collection adapters or auxiliary collection APIs.
ExternalTypeCatalog.Types is immutable declaration order.
Resolve(typeName) normalizes and returns a definition or absence. Catalog
construction rejects duplicate type names. The catalog contains no constructor,
field accessor, delegate, reflection object, or host instance.
Its public enumeration and name lookup must continue to describe the same
definitions after construction; accessing a collection view cannot alter either.
Runtime bindings
ExternalTypeConstructorReference is immutable normalized type name plus ordered
argument labels and canonical SignatureId. It copies labels and exposes only
immutable views, so caller mutations and collection adapters cannot change the
reference or make its labels disagree with SignatureId. CreateSignatureId is
a pure canonical formatter.
ExternalTypeRegistry.Resolve(reference) -> Optional<ExternalTypeConstructor>
runs during Host.Load. Absence or a returned constructor whose Definition
does not exactly match the import causes a stable link failure.
ExternalTypeConstructor.Invoke(call) is synchronous. The call exposes borrowed
ordered Arguments. SetExternalValue(value) accepts only the expected declared
type; mismatch becomes a runtime diagnostic and Nothing. SetNothing() rejects
construction without throwing a script exception. Omitting a result also yields
Nothing. The call object and arguments must not be retained.
ExternalValue.Definition identifies the declarative type. GetField(name)
returns a Value or absence. It must be synchronous and deterministic for an
immutable logical external value during one handler. Platform object identity,
reflection, and lifetime management are adapter responsibilities.
Diagnostics and failure transport
Diagnostic is immutable structured data containing phase, stable ASCII code,
human message, optional symbol and symbol kind, optional SourceLocation,
optional program/handler identity, and optional technical details.
SourceLocation contains optional source ID, source/module names, and one-based
Unicode-scalar line/column bounds. Missing context is explicit absence.
Diagnostic phases are Parse, Validate, Compile, Decode, Link, and Runtime.
SymbolKind classifies unknown, type, predicate, function, handler, message,
variable, and global definition. DiagnosticCodes exposes stable named constants;
Decode(formatErrorCode) maps a .gesb format failure to its stable decode code.
CompileException, DynamicLinkException, ProgramFormatException, and the
abstract fatal-runtime base are C# failure transports carrying diagnostics. A
language may use thrown errors or result unions. Callers must be able to inspect
the same structured portable fields without parsing text. Runtime diagnostics
normally flow through Observer and ExecutionResult rather than escaping callbacks;
the fatal-runtime base is for trusted native integration and does not define a
script-visible exception mechanism.
ExtensionFaultException extends the fatal-runtime base. An extension function,
external-type constructor, or ExternalValue.GetField implementation
constructs and throws it to deliberately report a defined failure with a
caller-chosen code, message, and optional symbol; the code must not use the
reserved runtime. prefix owned by DiagnosticCodes. The C# constructor rejects
null codes or messages, and empty or whitespace-only codes. An optional inner cause
may be supplied; it is never part of the diagnostic’s normative phase, code, or
message, only of its non-normative technical details. Any other exception from
the same three boundaries is unanticipated and is reported instead with the
matching runtime.extensionCallFailed, runtime.externalConstructorFailed, or
runtime.externalFieldAccessFailed code. Both outcomes end only the current
handler with a diagnosable runtime error; the host remains reusable, and neither
outcome evaluates to Nothing.
Native handlers may also use this failure transport; their unexpected exceptions
use runtime.nativeHandlerFailure, as defined in Diagnostics.
C# Reflection adapters forward the original exception thrown by an attributed
constructor, factory method, or property getter. Wrappers introduced by the
Bridge’s own Reflection invocation must not change whether the callback reports
a deliberate or unexpected fault. This guarantee does not unwrap exceptions
originating inside application code: if a callback uses its own Reflection call
and lets a TargetInvocationException escape, that wrapper is the unexpected
exception reported at the callback boundary, even if its inner cause is an
ExtensionFaultException. The cause remains available in technical details.
Conformance parser API
The independent Python runner owns Markdown parsing and normalized test inputs. See MarkdownFormat.md.
Conformance runner API
The runner owns assertions and communicates with implementation adapters through the V1 process protocol. There is no public C# Conformance library API.
Conformance writer API
Reports and Received Markdown are produced by the independent runner. Their contracts remain in Runner.md.
Language-binding requirements
C# and Unity
GameEventScript.Runtime supplies Runtime. The optional
GameEventScript.Compiler package supplies GameEventScriptBuilder,
GameEventScriptCompileOptions, and GameEventScriptCompileException in the
GameEventScript.Api namespace. Applications compile through
GameEventScriptBuilder.Create(), construct hosts through
GameEventScriptHost.CreateBuilder(), and load precompiled Programs through
the Reader.
Package and assembly identities use GameEventScript.Runtime,
GameEventScript.Compiler, GameEventScript.CSharpBridge, and
GameEventScript.SyntaxHighlighter. Their shared C# namespace root is
GameEventScript; the module name does not add another namespace layer to
the public GameEventScript.Api contracts. Public type names remain unchanged.
GameEventScript.CSharpBridge depends only on Runtime and must not pull in
Compiler or Conformance, directly or transitively. It supplies:
GameEventScriptCSharpValue.GesMap(entries)andGameEventScriptCSharpValue.GesRecord(typeName, fields)snapshot dictionary entries and delegate to the portable ordered value factories. Null or empty dictionaries mean no entries; dictionary enumeration order does not determine the resulting canonical key order;- tuple/dictionary message and Context conveniences;
- delegate native-handler adapters;
- reflection/attribute extension and external-type registries;
- an optional lock-based automatic Host runner with the ownership-transfer and scheduling rules in HostRuntime.
Source-file reading belongs to the CLI or embedding. The compiler accepts
AddScript(text, sourceName); CSharpBridge has no compiler-builder extensions.
These adapters must delegate to the portable semantics. Unity consumes the C# DLL and may choose main-thread/manual pumping instead of the automatic runner.
Swift
GesValue, GameEventScriptMessage and GameEventScriptMessageSignature
retain immutable public value semantics. Read-only properties may use shared
immutable backing storage; copying a value and replacing a VM register never
mutates another copy. Native register layout is an implementation detail and
does not define binary or JSON encoding.
Swift message arguments retain ordered signature labels and values in separate
immutable arrays. Their Iterator yields GameEventScriptMessageArgument pairs
without creating an intermediate pair array. Returned arrays retain Swift
copy-on-write semantics; caller mutations cannot change a message.
The Swift mapping separates the GameEventScriptRuntime,
GameEventScriptCompiler, and GameEventScriptConformance SwiftPM packages.
Compiler depends only on Runtime; Conformance depends on both. Runtime has no
dependency on Compiler, a test framework, or Conformance.
GameEventScriptBuilder.addScript(_:sourceName:) accepts source text and assigns
an independent source ID to each addition, even when diagnostic names repeat.
The builder supports withDebugInfo, withProgramVersion, and a declarative
withExternalTypeCatalog. compile(options:) returns a reusable immutable
Program; repeated compilation does not mutate earlier results. A supplied
GameEventScriptCompileOptions overrides the builder’s options for that call.
GameEventScriptDebugInfoOptions independently selects symbols, source maps,
and source archives; .all is the default and .none omits all three. Unknown
option bits are invalid public arguments. Source diagnostics are thrown as
GameEventScriptCompileError with ordered GameEventScriptDiagnostic values.
Compiler internals and the compiler-only Runtime SPI are not public embedding APIs.
Runtime exposes GesValue, GesUnit, immutable range descriptors, messages,
signatures, Program data and codecs, GameEventScriptHost, Context, lifecycle
handles, random generators, diagnostics, and the extension/external-type
protocols. GameEventScriptHost.createBuilder() provides the same mutable
configuration workflow as C#, including seed, entropy, sequence, limits, observer,
sink and registries. Every build() creates an independent Host. The direct
initializer remains available. GameEventScriptBuilder.create() creates a
compiler builder. load, start, isReady, subscribe, receive, executeFrame,
and runToCompletion retain the host contracts above. The mutable VM is host-private.
Program segments use let properties and native Array values with copy-on-write
storage; callers cannot mutate a Program through a returned array. Int64,
UInt64, UInt16, and Double retain their specified transport widths. Instructions
expose numeric words and payload bits, independently of Swift enum memory layout.
GameEventScriptProgramReader.read, GameEventScriptProgramWriter.bytes,
encodedSize, write(_:into:), GameEventScriptProgramValidator.validate, and
GameEventScriptProgramDumper.dump are synchronous and accept/return memory.
Reader options retain the three portable retention modes and bounded read limits.
Invalid public construction uses structured throws errors. Signature message
creation returns an optional for mismatched arity. Decode failures use
GameEventScriptProgramFormatError; linking uses GameEventScriptDynamicLinkError;
execution results carry GameEventScriptDiagnostic. Explicit callback faults use
GameEventScriptExtensionFault; unexpected errors receive the owning runtime
boundary’s stable classification. Call objects and borrowed argument views must
not outlive their synchronous callback.
Strict value equality and hashing preserve kind, unit, and Unicode-scalar
identity. materializedMap() reads immutable Map/Record contents or lazily
materializes declared external fields, propagating field failures. It does not
perform a source-language cast. The random generator has seed, sequence, and
entropy initializers; each Host constructs its own generator.
Swift Conformance uses the independent Python runner and an internal executable
adapter. The adapter returns raw compiler/runtime observations and optional
profile measurements, without parsing Markdown or evaluating expectations.
ges-conformance-probe provides codec exchange and binary-fuzz entry points.
Neither executable is a public library API. Runtime-only verification supplies
hash-bound C# Programs to the adapter and reports their compiler identity separately.
Native Swift adapters
The optional GameEventScriptSwiftBridge library depends on Runtime and an internal
compile-time macro target. Only that macro target depends on SwiftSyntax; the
generated bindings have no SwiftSyntax runtime dependency.
Compiler and Runtime must not acquire a Bridge dependency. Its native tests may
use a separate compiler consumer, but the Bridge product has no Compiler or
Conformance dependency. These adapters do not add portable Core capabilities.
Closure-based native handlers, publish sinks and extension functions delegate to the Runtime protocols. They preserve ordered signatures, tags, lifecycle, synchronous argument borrowing and the existing callback error classifications. Extension registries reject duplicate signatures and optionally resolve missing signatures through an explicit fallback registry. Dictionary message binding requires a known signature with exclusively named parameters and exactly one value for each normalized label. Ordered tuple inputs support positional labels.
GameEventScriptSwiftValueConvertible defines explicit native conversion.
Built-in conformances cover Boolean, Text, signed and unsigned fixed-width Swift
integers, Float/Double, Optional, Array, String-keyed Dictionary and GesValue.
Native decoding checks kind and units, rejects fractional or out-of-range integer
conversion and floating-point precision loss. These conversions do not invoke
source-language casts. Native integer encoding and decoding are bounded by both
Int64 and the requested Swift integer type. Floating-point encoding uses the portable value factory’s
NaN, infinity, zero and integral-storage normalization. Nothing represents nil;
GesValue conversion is identity. Collection conversions recurse and preserve
canonical GES ordering. Dictionary decoding rejects distinct scalar keys that
Swift String equality would merge; encoding preserves only keys present in the
input dictionary. Native failures use GameEventScriptSwiftConversionError.
GameEventScriptSwiftType<Root> exposes only its declared KeyPath/getter fields
and constructor closures. Constructor parameter labels refer to declared fields
and inherit their type definitions. Call arguments follow the constructor’s
declared parameter order. A type-erased binding supplies a validated compiler
catalog and a separate executable constructor registry. Programs contain only
the declarative transport data. Wrapped class roots preserve native identity;
struct roots follow Swift value semantics. Unwrapping requires the exact binding
descriptor that created the external value. Runtime field coercion and map
materialization retain the external-value contract above.
Since: 0.2.0 — Swift binding macros and unit conversion helpers
@GesType generates a throwing static createGesType() factory on a nongeneric
struct or final class. Each invocation creates a distinct descriptor; callers
retain and reuse it for registry construction, wrapping and unwrapping. The
optional type name defaults to the Swift name. @GesField exposes only the
annotated stored or computed instance property, using its name unless overridden.
Fields require explicit Swift type annotations. Standard numeric types infer
Number, Bool infers Boolean, String infers Text, Array infers List and Dictionary
infers Map; Optional delegates to its wrapped type. Aliases, custom convertible
types and GesValue require explicit typeName. Swift compiler type checking
still requires the existing strict value-conversion conformances.
@GesField(unit:) may instead declare a numeric quantity in meters, seconds or
degrees. It cannot be combined with typeName or a nonnumeric field. Encoding
attaches the explicit unit to a native numeric magnitude; constructor decoding
checks the exact unit before stripping it and invoking strict native conversion.
Nothing retains existing Optional/NaN semantics. No magnitude scaling, truncation
or implicit reinterpretation of an already unit-bearing value occurs. These
operations are also exposed by GameEventScriptSwiftValue.encode(_:unit:) and
decode(_:as:unit:); .none delegates to the ordinary strict conversion.
@GesConstruct exposes a synchronous, nonfailable initializer or static factory
returning the annotated type. Throwing callbacks preserve normal classification.
Each parameter maps first by external label to an exposed field, otherwise by
internal name to an annotated Swift property. Declaration order defines the
callback’s argument order. Defaults do not add overloads; unmarked constructors
remain unavailable to scripts. Async, variadic, inout, ownership-qualified and
generic constructors are rejected. Conditional #if field/constructor declarations
require manual bindings. Annotation misuse is a Swift compile error;
portable descriptor name/signature validation still occurs when the throwing
factory executes. Manual descriptors remain available for foreign/generic types
and throwing getters. Macro expansion adds no GES syntax or portable behavior.
GameEventScriptSwiftHostRunner is an optional synchronized embedding adapter.
It takes exclusive ownership through Swift 6 sending parameters and serializes
Host access and lifecycle operations with one recursive lock. Transferred Host,
callback and external-value state must not be accessed outside that ownership
domain. Its explicit start() runs the initial group synchronously. Accepted
receives and later loads schedule work on a shared serial background dispatcher,
as in the C# bridge. An already ready transferred Host schedules preexisting work;
a loading Host waits for Start. Immutable Programs remain reusable without exclusive
ownership transfer. Recursive receives only enqueue; recursive pumping fails.
runToCompletion() can drain a ready Host synchronously under the same gate.
Automatic pumps continue scheduling while ready queued work remains.
lastResult is a synchronized snapshot of the most recent completed pump.
isReady reports the startup barrier; a registration’s startResult reports its
instance initialization. C# exposes the same outcome through runner.GetStartResult(instance).
Runner-owned handles use stable registration IDs, retain their runner and detach
idempotently under its lock. Closing rejects further work, detaches owned
registrations and releases the Host; an already executing pump may finish.
Blocking on another caller while inside a callback is outside this contract.
Portable language mappings
Ports should prefer native immutable collection views, nullable/optional result types, and their standard error transport. They must retain:
- ordered message arguments and stable signature IDs;
- exact fixed-width integer/Binary64 values and
.gesbIDs; - one serial, non-thread-affine Host execution domain;
- synchronous callbacks and explicit outer synchronization;
- idempotent lifecycle handles and enqueue-time snapshots;
- separate declarative external catalogs and executable runtime registries;
- fileless byte/text codec and Conformance operations;
- identical diagnostics, result states, ordering, and canonical writer output.
An actor, coroutine, executor, or mutex wrapper is an embedding adapter. It must not become hidden scheduling inside portable Host operations.
Completeness map
The following map assigns every exported C# reference type to exactly one conceptual public family and owning section. Every public constructor, method, property, field, and event inherits the assignment of its declaring type. The approved C# API snapshot fixes the concrete reference-binding spellings and members; reviewing a snapshot change therefore also requires reviewing the corresponding row below. Numeric enum members and individual normalized Conformance fields are owned by their linked format specifications rather than duplicated as a second source of truth here.
| Public family | Owning section |
|---|---|
| Builder, CompileOptions, CompileException | Compiler API |
| Program, all segment/entry/view types, instruction and ID types | Program and bytecode data API |
| BinaryFormat constants, Reader, read options/limits/retention, Writer, Validator, format error, Dumper | Program codec, validation, and dump API |
| Message, MessageArgument(s), MessageSignature | Message and value API |
| Value, ValueSlice, ValueArguments, ValueMap, integer/float range | Message and value API |
| HostBuilder, RuntimeLimits, Host, StartResult/State, Instance, Subscription | Host construction and lifecycle API |
| Context, NativeMessageHandler, PublishSink/Result, Observer, ExecutionResult/State | Context, messaging, and execution results |
| RandomGenerator | Random generator API |
| ExtensionReference, ExtensionRegistry/Function, ExtensionCall | Extension API |
| External definitions/catalog, constructor reference/registry/call, ExternalValue | External-type API |
| Diagnostic, codes/phase/symbol/location and failure transports | Diagnostics and failure transport |
| CSharpBridge reflection, delegate, dictionary, and automatic-runner adapters | Language-binding requirements; non-portable |
The portable public API is limited to the families above. VM execution state, compiler trees, filesystem services, network clients, task schedulers, and reflection objects remain private or embedding-specific.
Monotonic scheduling API
Since: 0.2.0
C# IGameEventScriptClock.ElapsedMicroseconds and Swift
GameEventScriptClock.elapsedMicroseconds provide a borrowed monotonic Int64
microsecond source. Host builders accept WithClock / withClock; absent an
injected source, each host uses an independent origin on the system monotonic
clock. A shared injected clock is read serially by its embedding.
Host NextMessageDelay / nextMessageDelay returns optional remaining Int64
microseconds without pumping. Waiting / waiting extends execution results
when only delayed work remains. The authoritative timing and lifecycle rules
are in HostRuntime. C# and Swift automatic runners arm wake-ups outside Runtime,
reschedule when new work arrives and cancel wake-ups when closed/disposed.
Explicit product JSON codec
Since: 0.2.0
C# GameEventScriptMessageJson.Serialize/Deserialize and Swift
GameEventScriptMessageJson.serialize/deserialize encode/decode messages;
SerializeValue/DeserializeValue and serializeValue/deserializeValue handle
standalone values. These Runtime-only APIs are synchronous, fileless and Host
independent. They implement MessageFormat, including ordered
arguments, numeric normalization, limits, data-only reconstruction and explicit
external-to-Record export. Encoding may propagate external getter failures.
Local message publication never implicitly invokes these APIs.
ConstructData (0xDE) and SplitText (0xDF) extend the public opcode registry.
Integral Binary64 range factory inputs normalize to the exact integer range model;
see Language. API names that accept Binary64 describe their
input representation, not a promise of unnormalized internal storage.
Idle duration API
Since: 0.3.0
C# Host and CSharpHostRunner expose long? IdleDurationMicroseconds; Swift Host
and SwiftHostRunner expose Int64? idleDurationMicroseconds. These are read-only
observations using the configured monotonic clock. Their lifecycle, reset and
absence rules are owned by Host Runtime.
Runner reads are serialized through the ownership gate; a disposed/closed runner
returns no value. Reading does not schedule work or produce notifications.
Compound collection instruction identifiers
Since: Unreleased — Cartesian opcode
The public bytecode opcode enumeration assigns Cartesian the stable value
0xE0. Its operands and reserved fields are owned by Bytecode.
Iterator modes and component output reuse existing instruction words and the
UInt16 list pool; no mutable iterator is added to the portable Program API.
GameEventScriptInstructionFlag.Numeric aliases bit 0x40 for Move, Add, Subtract,
Multiply, Divide, Power, IntegerDivide, Modulo, Remainder, Min, Max, Negate, Abs and Clamp only. The same bit retains WithTags semantics on result-bearing sends;
interpret flags by opcode.