Conformance Markdown format specification
This document is the normative authoring-format specification for portable Game Event Script conformance suites. The key words must, must not, required, should, and may are to be interpreted as normative requirements.
The format is intended to be implemented independently in C#, Swift, Kotlin, Go, Rust, C++, and other ports. It is deliberately a small structural Markdown scanner plus a strict YAML subset. It is not CommonMark plus full YAML.
Conformance Markdown is test data. It is not the product message/wire format.
Encoding and logical lines
- A document is strict UTF-8. An optional UTF-8 BOM is recognized only at byte offset zero and is not part of the document content. The same byte sequence elsewhere represents the ordinary Unicode scalar U+FEFF and is retained, including inside a GES source payload.
LF,CRLF, andCRare recognized as logical line endings. A parser interprets all three asLF; other Unicode line separators are ordinary text.- Source ranges are offsets and lengths into the original UTF-8 byte sequence, excluding an optional BOM. This permits a received writer to preserve all unrelated bytes exactly.
- Line and column diagnostics are one-based Unicode-scalar positions. A tab is one scalar for column counting.
- Structural markers are recognized only at column zero and only outside a fenced block. Their ASCII spelling and case are exact.
- A document containing invalid UTF-8, an unclosed recognized fence, or a NUL byte is invalid.
Structural Markdown profile
Only the following Markdown structures carry V1 semantics:
- the YAML frontmatter at the beginning of the file;
- headings whose line starts exactly with
## Test:; - the reserved heading
## Fixturesbefore the first test; - the exact heading
### Stepsinside a test; - fenced blocks with one of the recognized info strings below;
- the pipe table immediately following
### Steps.
All other headings, paragraphs, lists, block quotes, callouts, and code blocks
are prose. They are retained by the source document but ignored by the parser.
An unrecognized fenced block whose info string starts with ges inside a test
is an error, because it is likely a misspelled semantic block.
Canonical human-readable layout
Normative suites in the checked-in corpus must use a single H1 display title immediately followed by this caution callout:
> [!CAUTION]> **Executable Conformance Test Markdown**>> - This file controls executable conformance tests; it is not free-form documentation.> - Keep its structural elements in the form required by the Conformance Test Markdown syntax.> - Validate every edit with a conforming parser and runner.A short suite-level explanation follows the callout. Every test must be
preceded by a --- thematic break, followed by its ## Test: heading and a
short prose explanation of the behavior under test. Semantic blocks use these
H3 display headings:
| Heading | Content that follows |
|---|---|
### Case description |
the gesBlock: case YAML fence |
### Source code under test |
one or more ges source fences |
### Steps |
the executable Steps table |
### Expectation |
the gesBlock: expect YAML fence |
### Expected Game Event Script Assembler |
the gesa snapshot fence |
Except for the already semantic ### Steps heading, these presentation
elements remain nonsemantic in V1. A conforming parser must derive behavior from
the semantic fences and Steps table rather than from prose or presentation
headings.
Frontmatter
After the optional BOM, the first logical line must be exactly ---. The next
line that is exactly --- closes the frontmatter. ... is not a closing
marker. The content between the markers is one YAML-subset mapping. No content
may precede the opening marker.
Test boundaries
A line of the form
## Test: Display titlestarts a test. The title is trimmed of surrounding ASCII space and tab and must not be empty. It is a display value only. It may change without changing test identity.
The test continues up to, but does not include, the next ## Test: heading or
the end of the document. Other H2 headings do not end the test. A suite must
contain at least one test.
Fixtures
## Fixtures may occur at most once and only before the first test. Everything
from that heading to the first test is nonnormative documentation, including
tables and recognized-looking code examples. A V1 parser must not materialize,
resolve, substitute, or execute these values.
A future matrix feature must use a new explicitly versioned construct. No later
format version may silently reinterpret a V1 ## Fixtures section as executable
data.
Fenced blocks
A semantic fence inside a test starts with exactly three backticks at column zero, followed by one of these exact info strings, and ends with exactly three backticks at column zero:
| Info string | Meaning | Cardinality |
|---|---|---|
yaml with gesBlock: case |
Case metadata and execution configuration | exactly one per test |
ges |
One GES source input | kind-dependent |
yaml with gesBlock: expect |
Structured expectations | zero or one, kind-dependent |
gesa |
Expected Game Event Script Assembler dump | exactly one for bytecodeSnapshot; optional for a valid programBinary |
Opening or closing fences may not have trailing whitespace. Semantic fences may
not be indented or nested. The payload is the sequence of logical content lines
joined by LF; the structural line ending immediately before the closing fence
is not part of the payload. An author can represent a terminal payload newline
by leaving an additional empty content line before the closing fence.
GES source and GESA payloads are passed on with logical LF line endings. Every
exact yaml fence inside a test is semantic, uses the YAML subset below, and
must contain exactly one root discriminator named gesBlock. Its value is
exactly case or expect. Additional fence text such as yaml ges-case is not
part of V1 and is rejected as an unknown semantic fence. YAML fences outside a
test, including those under ## Fixtures, remain ordinary documentation.
Portable YAML subset
The frontmatter, semantic yaml blocks, and YAML flow values all use the same
restricted YAML 1.2-inspired profile.
Supported forms
- one root mapping;
- block mappings and block sequences using exactly two spaces per indentation level;
- flow mappings
{ key: value }and flow sequences[value, value]on one logical line; - plain, single-quoted, and double-quoted scalar values;
- comments beginning with
#outside quotes when#is the first non-space character or is preceded by whitespace; - lowercase
null,true, andfalse; - signed base-10 integers with grammar
-?(0|[1-9][0-9]*); - finite decimal values with JSON number grammar. A decimal token is retained losslessly until its schema field converts it to Int64, UInt64, or Binary64.
Block mapping keys must be a plain token matching
[A-Za-z][A-Za-z0-9_.-]* or a quoted string. Keys are case-sensitive. Duplicate
keys are invalid after quoted-string decoding. Tabs are invalid in indentation.
Trailing whitespace has no meaning.
Double-quoted strings support the JSON escapes \", \\, \/, \b, \f,
\n, \r, \t, and \uXXXX; a valid surrogate pair represents one Unicode
scalar and an unpaired surrogate is invalid. A single quote inside a
single-quoted string is written as ''. Plain strings must be quoted when they
would otherwise be resolved as null, boolean, or number. Values beginning with
#, &, *, !, %, @, or a backtick must be quoted.
Explicitly unsupported forms
Anchors, aliases, merge keys, tags, directives, block scalars (| and >),
complex keys, explicit ? keys, multiple YAML documents, implicit dates,
sexagesimal numbers, non-decimal integers, .nan, .inf, and implementation-
specific scalar resolution are invalid. A parser must report them; it must not
silently accept a larger host-library YAML dialect.
Schema mappings are closed. An unknown property is an error unless this specification explicitly declares it open. Property names are always matched case-sensitively.
The mappings keyed by declared IDs or canonical names are intentionally open:
step expectations by Step ID, performance profiles by Profile ID, metrics by
Metric ID, and opcode counts/minimumCounts by canonical opcode name. Their
keys and values are still fully validated by the enclosing schema.
IDs, suite frontmatter, and inheritance
An ID component matches:
[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*suiteId and each case-local id use this grammar. The full case ID is
suiteId + "/" + case.id. Suite IDs must be unique in a corpus and case IDs
must be unique in a suite. IDs are never derived from a path, heading, title, or
testframework method.
The frontmatter root supports these fields:
| Field | Type | Required | Meaning |
|---|---|---|---|
formatVersion |
integer | yes | must be 1 |
suiteId |
string | yes | stable suite identity |
title |
string | no | display title; otherwise suiteId |
kind |
test-kind string | no | default test kind |
level |
atomic or scenario |
no | default level |
categories |
ID sequence | no | default runner/framework categories |
tags |
string sequence | no | default tags |
requires |
capability requirement | no | default capabilities |
compile |
compile options | no | default compile options |
runtimeLimits |
runtime-limit mapping | no | default runtime limits |
comparison |
comparison options | no | default value comparison |
The same defaultable fields may occur in the gesBlock: case mapping. Resolution
follows these rules:
- a test scalar replaces the suite scalar;
compile,runtimeLimits, andcomparisonare overlaid by field, with test fields replacing fields of the same name;- suite categories/tags followed by test categories/tags are concatenated and then de-duplicated by exact ordinal spelling while retaining first occurrence;
- core and optional capability lists are combined and de-duplicated in the same way;
- all other case fields are never inherited.
level must resolve to a value; it is never inferred from a directory. kind
must also resolve to a supported value.
A capability requirement has this shape:
requires: core: [compiler, host, vm, external-types] optional: []Core capabilities required by a test kind are added implicitly and cannot be downgraded to optional. Capability behavior is defined by Runner.
Case metadata
Every test contains exactly one yaml root mapping whose first-class
discriminator is gesBlock: case. It supports:
| Field | Type | Required | Meaning |
|---|---|---|---|
gesBlock |
case |
yes | identifies this YAML block as case metadata |
id |
ID | yes | stable local identity |
kind |
test-kind string | inherited | kind override |
level |
level string | inherited | level override |
categories |
ID sequence | no | additional categories |
tags |
string sequence | no | additional tags |
requires |
capability requirement | no | additional requirements |
compile |
mapping | no | compile options |
runtimeLimits |
mapping | no | host runtime-limit overrides |
comparison |
mapping | no | value comparison override |
sources |
source descriptor sequence | conditional | names and program grouping |
random |
mapping | no | deterministic random configuration |
publishSink |
accept, absent, reject, or throw |
no | configured publish-sink behavior; default accept |
externalTypeRegistry |
environment, absent, or mismatch |
no | runtime registry used for External-Type link tests; default environment |
hostCount |
positive integer | no | run the same compiled Programs and expectations independently in this many Hosts; default 1 |
deferredPrograms |
ID sequence | no | source-program groups loaded only by a native host action |
nativeHandlers |
sequence | no | declarative portable native handlers |
stepActions |
mapping | no | ordered host actions performed immediately before a named step’s Receive |
messageApi |
mapping | messageApi only |
signature and message input |
valueApi |
mapping | valueApi only |
portable value construction, comparison and copy input |
externalTypeApi |
mapping | externalTypeApi only |
portable external-type catalog input |
binaryFixture |
mapping | programBinary only |
immutable .gesb fixture manifest entry |
performance |
mapping | performance only |
workload configuration |
Compile options are:
compile: debugInfo: [debugSymbols, sourceMap, sourceArchive] binaryRoundTrip: falsedebugInfo is a sequence containing each listed value at most once. An empty
sequence means no debug segments. Its default is all three values.
binaryRoundTrip defaults to false; when true, the compiled program is
written as canonical .gesb, read again, and only then used by the test.
Enabling it implicitly requires the core capability program-binary.
runtimeLimits uses the exact portable names maxProcessedEventsPerRun,
maxQueuedMessagesPerRun, maxExecutionSteps, maxRegisterValues,
maxLoopIterations, maxCallDepth, maxRandomScopeDepth, maxRangeItems,
maxGeneratedCollectionItems, maxDiceCount, and maxDiceSides. Values are
positive integers. Omitted fields use Core defaults.
comparison is:
comparison: binary64: mode: exactor:
comparison: binary64: mode: ulp maxUlps: 4096Exact comparison is the default. ULP mode is allowed only where platform math or another documented operation requires it. Kind, unit, integer, text, boolean, tag, ordering, and structure remain exact in both modes.
random contains exactly one of seed (signed Int64), sequence (a
non-empty sequence of canonical Binary64 strings), or entropy (a non-empty
string of uppercase hexadecimal byte pairs without separators or a prefix).
For example, random: { entropy: "0001FF" } supplies the three ordered bytes
00, 01, and FF to the host builder’s entropy configuration. The runner
must exercise that configuration path instead of replacing the bytes with a
precomputed seed. Seed derivation follows
Entropy-to-seed mixing.
Invalid entropy encodings and combined random configurations are rejected with
conformance.schema.invalidValue.
Tests that execute a random operation must supply deterministic random configuration. V1 test kinds do not support expectations based on nondeterministic behavior; fixed entropy input never acquires platform entropy.
Sources and programs
The order of ges fences is compiler input order. If sources is absent,
exactly one ges fence is required and receives source name
<suiteId>.<caseId>.ges and program ID main.
If sources is present, it must contain exactly one descriptor per ges fence:
sources: - name: rules.ges program: rules - name: helpers.ges program: rules - name: opponent.ges program: opponentname is a non-empty portable source name, not a path to open. program is an
ID and defaults to main. Sources with the same program ID are added to one
compiler builder in descriptor order. Programs are loaded into the host in the
order their ID first occurs. Interleaving descriptors for different programs is
valid but should be avoided for readability.
No parser or runner opens a source path. For programBinary, source fences are
provenance: they record the logical compiler input named by the manifest but are
not compiled or substituted by the runner. External binary bytes use only the
resource-resolver contract defined below.
Binary fixture manifest entries
Each programBinary test contains exactly one binaryFixture mapping:
binaryFixture: id: gesb-v1-valid-runtime resourceId: gesb-v1.valid-runtime relativePath: GesbV1/valid-runtime.gesb sha256: 594808EB171F039AF2182A22360C39183CC4960412EF3D76B97E3632603EECF8 compilerId: steph.ges.compiler.csharp compilerVersion: 0.1.0 programVersion: 42 compareCompiledRuntime: true derivation: optional human-readable transformation descriptionid and resourceId are stable portable IDs. resourceId is the only value
given to the injected resolver. relativePath is packaging metadata using
forward slashes; it must be relative, contain no empty, . or .. component,
and is never opened by the parser or runner. sha256 is exactly 64 uppercase
hexadecimal digits and identifies the complete resource bytes. Compiler ID and
version, resolved compile options, source fences, and the required
programVersion record reproducible provenance. compareCompiledRuntime
defaults to false; when true it requires source and compiler, compiles exactly
one source Program group with the recorded ProgramVersion, and compares only
the canonical Required Runtime sections. BuildMetadata and optional debug/source
sections therefore may differ between language compilers. derivation is used
only when helpful to explain a deliberately altered fixture and is
non-executable.
Declarative native handlers
The V1 shape preserves portable host-dispatch cases and supports a closed set of host-lifecycle actions:
nativeHandlers: - id: notify-handler message: Notify parameters: [playerId, count] messageName: false priority: 0 initiallySubscribed: true throw: false actions: - loadProgram: deferred-rules - subscribeHandler: late-handler emit: - name: ExternalSeen forwardArguments: truemessage is required. messageName: true selects name-only matching and then
parameters must be empty; its default is false. id defaults to the stable metadata-order name
native-0001, native-0002, and so on. parameters defaults to an empty
ordered sequence, priority to zero, initiallySubscribed to true, throw to
false, and actions/emit to empty. An emit entry requires name and exactly
one of forwardArguments: true or an ordered args sequence.
An optional faultCode string selects a deliberate native-handler runtime fault
with that caller-chosen diagnostic code, for example faultCode: test.nativeFault.
It is absent by default, must not be empty or whitespace-only, must not start
with runtime., and cannot accompany throw: true. Both failure modes abort
before actions or emits: throw: true raises an unanticipated platform exception,
while faultCode reports a deliberate diagnostic using the platform’s failure
transport (GameEventScriptExtensionFaultException in C#). The Host supplies
the active handler context as defined in Diagnostics.
With faultCode, an optional faultContext mapping accepts only programName
and handlerName, each a string or null. An omitted or null field is absent in
the diagnostic supplied by the native fixture; the Host then fills available
context. Supplied strings must be preserved. faultContext without faultCode
is invalid. This is portable diagnostic input, not an exception-class selector.
Each action mapping contains exactly one of loadProgram, detachProgram,
subscribeHandler, or unsubscribeHandler, plus an optional boolean
expectResult or diagnostic expectError. Its operation value is an existing program
or native-handler ID of the appropriate kind. Only a program listed in
deferredPrograms may be a loadProgram target. Actions execute in metadata
order before emits. expectResult compares the portable boolean operation
result. Repeated detach/unsubscribe operations return false after the first
state change; load/subscribe ensure the target is active and return true.
Only loadProgram accepts expectError, which is mutually exclusive with
expectResult. It uses the structured diagnostic fields described below,
requires phase: link and a code, and may constrain context such as
programName:
stepActions: rejected: - loadProgram: deferred-rules expectError: phase: link code: link.initializationQueueFull programName: rulesThe runner requires the load to fail with that diagnostic, handles the expected failure, and continues subsequent actions and steps. Successful loading or a different diagnostic fails the assertion. The diagnostic is an action result, not a RuntimeError observer callback. The same rules apply to actions inside a native handler. The set remains closed and declarative; arbitrary native code is not test data.
A scriptApi case with at least one native handler may have no GES source and
thereby defines a native-only Host. hostCount is bounded by the parser’s
MaxHostsPerTest; every Host loads the same immutable compiled Program objects,
but owns independent Context, queue, random generator and VM state. Expectations
are compared independently for every Host. This option is intended for Program
reuse and isolation tests, not performance measurement.
publishSink configures the fixed test sink. absent installs no sink;
accept records the call and returns true; reject records the call and returns
false; throw records the call and throws a platform exception which the Host
must convert to the portable runtime.publishSinkFailure diagnostic. Outbound
messages record calls handed to the sink, regardless of acceptance.
externalTypeRegistry leaves the runner environment’s registry unchanged for
environment, installs no runtime registry for absent, and installs a fixed
constructor with a deliberately different definition for mismatch. The
compiler still uses the environment’s portable catalog in all three modes.
stepActions is a mapping from an existing Step ID to the same ordered,
closed host-action sequence used by native handlers. These actions execute
immediately before that step’s Receive. This makes enqueue/load ordering and
changes while a VM is paused expressible without embedding test callbacks.
Ordered steps
Runtime and performance tests express chronological input in exactly one table
under an exact ### Steps heading. After optional blank lines, its header and
delimiter are exactly:
| step | receive | pump | budget || --- | --- | --- | --- |Each following nonblank pipe-table row has four cells:
stepis an ID unique within the case;receiveis the exact incoming message name;pumpiscompletion,frames,enqueue, orframe;budgetis empty forcompletionandenqueue, and a positive UInt32 forframesandframe.
Cells are trimmed of surrounding ASCII space and tab. Backslash escapes, inline Markdown, multiline cells, and additional columns are not supported. A table ends at the first blank or non-table line. At least one row is required whenever the test kind requires steps.
For completion, the runner calls the synchronous run-to-completion operation
once. For frames, it repeatedly executes frames of the specified budget until
the host is idle or reports a runtime limit. The expectation records whether at
least one frame returned paused. enqueue performs no pump after Receive.
frame performs exactly one ExecuteFrame(budget) call and records whether
that result was paused.
The corresponding yaml block with gesBlock: expect keys its detailed input
and outputs by step ID:
gesBlock: expectsteps: add: input: tags: [] args: - name: value value: { type: ":Number.int64", value: "7" } accepted: true local: - name: Done args: - name: result value: { type: ":Number.int64", value: "12" } outbound: [] paused: false runtimeLimits: include: [] exclude: [] diagnostics: [] trace: - event: publish message: { name: Remote } result: { localAccepted: true, outboundAttempted: true, outboundAccepted: false, anyAccepted: true }input.tags and input.args default to empty. accepted defaults to true;
local, outbound, runtime-limit lists, and diagnostics default to empty.
paused is optional and is not compared when absent. A step expectation key
must name a table row, and every table row may have at most one expectation
entry. Message lists and argument lists are order-sensitive. local observes
both Emit and the local half of Publish; outbound observes messages handed to
the configured publish sink in call order.
Each runtimeLimits.include or .exclude entry may constrain name,
detailContains, and limit; at least one field is required. The explicit
wildcard { any: true } may be used alone, most commonly in exclude, to match
every runtime-limit observation. Each diagnostics
entry uses the portable diagnostic expectation shape from the error kinds
below, except that phase and code remain required. Entries and observations
are compared in order; exclusions must not occur anywhere in the observation
sequence.
trace is optional. When absent, observer callback order is not compared. When
present, including as [], it is an exact ordered expectation over all observer
callbacks in that channel interval. Supported event shapes are:
trace: - event: emit message: { name: Local } accepted: true - event: publish message: { name: Remote } result: localAccepted: true outboundAttempted: true outboundAccepted: false anyAccepted: true - event: dispatchStarted message: { name: Start } signatureId: "Start()" - event: dispatchCompleted message: { name: Start } signatureId: "Start()" - event: runtimeLimit runtimeLimit: { name: MaxLoopIterations, limit: 10 } - event: diagnostic diagnostic: { phase: runtime, code: runtime.publishSinkFailure }Emit requires the complete accepted flag. Publish trace assertions require the four final-delivery fields shown above; outboundAccepted implies
outboundAttempted, and anyAccepted must equal localAccepted OR outboundAccepted. Dispatch events require the exact dispatched message and
signature ID. Runtime-limit and diagnostic constraints use the same shapes as
their standalone expectation lists. Message/value comparison uses the case’s
ordinary Binary64 comparison mode.
An optional top-level initialization expectation has local, outbound,
runtimeLimits, diagnostics, and optional exact trace with the same meanings.
It describes startup after all initial programs and native handlers are installed
and before the first step. Absent channel, runtime-limit and diagnostic expectations
are empty; absent trace and lifecycle fields make no corresponding assertion.
Host startup observations
For runtime cases, initialization.pump is completion by default: the runner
calls Start and, on success, drains ordinary messages. pump: start compares
immediately after Start without draining. A failed Start is never pumped; later
step inputs are rejected and no script code is executed.
Initialization and step expectations may contain hostReady: true|false and
programStarts, a mapping from loaded program IDs to pending, ready,
runtimeError or runtimeLimitReached. Missing handles fail the assertion.
These assertions inspect the portable Host/Instance API. Init emit observations
remain attempted emits, including discarded attempts; assert downstream handler
output to prove delivery. Deferred publications are observed upon commitment.
Initialization and step observations may also assert idleDurationMicroseconds:
a quoted canonical nonnegative Int64 string, or "none" when no idle phase exists.
Omission makes no assertion. The virtual clock starts at zero and existing
advanceMicroseconds actions advance it without pumping, enabling exact elapsed
time assertions without wall-clock sleeps.
Portable message and value shape
A message is:
name: Donetags: [combat]args: - name: result value: { type: ":Number.int64", value: "12" }name is required; tags and args default to empty. Arguments are always an
ordered sequence. A mapping keyed by argument name is invalid.
Values use the existing portable conformance shape:
type |
Additional fields |
|---|---|
:Nothing |
none |
:Text, :Tag |
value string |
:Boolean |
value boolean |
:Number.int64 |
canonical signed-Int64 value string |
:Number.binary64 |
canonical Binary64 value string |
:Quantity.int64 |
canonical signed-Int64 value string and required unit |
:Quantity.binary64 |
canonical Binary64 value string and required unit |
:Percentage |
canonical Binary64 ratio value string |
:Vector, :Point |
Binary64 strings x, y, z; optional unit |
:List |
ordered items value sequence |
:Map |
entries, an ordered sequence of { key, value } |
:Dice |
ordered Int32 rolls |
:Range.int64, :Range.binary64 |
numeric strings from, to, step; the variant fixes all three storage kinds |
:Message |
nested message |
| any declared custom type | ordered entries sequence |
The .int64 and .binary64 suffixes are exact conformance transport variants,
not GES source type names. Source uses :Number, :Quantity(unit), and :Range.
There is no separate rangeKind field.
Numeric strings and special values follow Number semantics. Message names, argument names, tags, units, and custom types follow the portable Core contracts. Map/record semantic comparison follows Determinism. Duplicate construction keys use last-entry-wins semantics before scalar-ordinal key sorting.
Expectation fields by test kind
Supported V1 kinds are scriptApi, compileError, loadError, messageApi,
valueApi, externalTypeApi, compileMetadata, bytecode, performance, and
bytecodeSnapshot, and programBinary. A kind’s
required core capabilities are additive to explicit requires.core.
The presence of nativeHandlers implicitly requires native-handlers.
compile.binaryRoundTrip: true implicitly requires program-binary. Source
that uses an external type must explicitly require external-types; the runner
does not infer capabilities by parsing GES source.
scriptApi
The source-backed form requires compiler, host, vm, observer, and
publish-sink; it requires at least one source and either a Steps table or an
explicit initialization expectation. It uses the common step expectation
shape above.
The native-only form with no source instead requires only host, observer
and the implicitly added native-handlers capability. It does not require a
compiler, VM or publish sink because the closed V1 native actions can only emit
locally. It still requires Steps or an explicit initialization expectation.
compileError and loadError
compileError requires compiler; loadError requires compiler, host, and
vm. Each requires source and a yaml block with gesBlock: expect containing:
gesBlock: expecterror: phase: validate code: validate.missingCallable symbol: missing symbolKind: function sourceName: sample.ges line: 3 column: 7 endLine: 3 endColumn: 14 programName: Sample handlerName: Startphase and code are required. Other fields are optional exact constraints.
For programName and handlerName, an explicit YAML null requires that the
observed context field be absent; omitting the key leaves that field unconstrained.
Human-readable messages and technical details cannot be expectations. The
diagnostic contract is Diagnostics.
messageApi
Requires message-api and no GES source. The gesBlock: case mapping contains:
gesBlock: casemessageApi: signature: name: Start parameters: [a, b] message: name: Start args: [] compareSignature: { name: Start, parameters: [a, b] } compareMessage: { name: Start, args: [] } compareConformanceMessage: { name: Start, args: [] } compareHandler: { name: Start, parameters: [a, b] } createArguments: []For the single negative shape test args may instead be a mapping. The mapping
is retained as ordered source data in the normalized model, but it is never
accepted as message arguments. This form is valid only when the expectation is
exactly error: invalidArgumentsShape; every constructible message continues
to require an ordered argument sequence.
Its expectation contains at least one of:
message: name: Start signatureId: "Start(a,b)" messageSignatureId: "Start()" matches: false argumentCount: 0 signatureEquals: true signatureHashEquals: true messageEquals: true messageHashEquals: true conformanceEquals: true handlerEquals: true handlerHashEquals: true createdMessageSignatureId: "Start(a,b)" error: duplicateArgumentNameIf error is present, successful construction fails the case and the other
fields must be absent. Message error codes are stable ASCII identifiers.
compareConformanceMessage and message.conformanceEquals must occur together.
The former is an expected transport message; messageApi.message is constructed
through the public API and supplies the actual message. The runner applies the
same comparison operation used for runtime message expectations, under the
case’s comparison options, and asserts its boolean result. The expected
transport’s numeric storage variants and units remain exact, including inside
containers and at infinity. A false expectation is a negative conformance
comparison test, not a request to treat a failed case as passing. This pair is
unavailable with an error expectation.
valueApi
Requires value-api and no GES source. valueApi.value is constructed through
the portable public value API. Optional equalTo and notEqualTo values test
semantic equality. mutateSourceAfterCreate: true mutates source arrays after
construction to verify defensive storage.
valueApi: value: type: ":List" items: [{ type: ":Number.int64", value: "1" }] equalTo: type: ":List" items: [{ type: ":Number.int64", value: "1" }] mutateSourceAfterCreate: trueThe expectation requires normalized and may constrain flags, boolean
conversion, length, custom type name, equality and the equal-value hash
invariant.
value: normalized: type: ":List" items: [{ type: ":Number.int64", value: "1" }] hasValue: true length: 1 equal: true equalHash: trueexternalTypeApi
Requires external-types and no GES source. V1 constructs a catalog from the
ordered typeNames list. Its expectation contains either typeCount or the
stable error duplicateTypeName.
externalTypeApi: typeNames: [Sample, Sample]externalType: error: duplicateTypeNamecompileMetadata
Requires compiler and source. Its expectation contains one or more of:
metadata: messageDefinitions: - name: Start count: 1 signatureIds: ["Start(value)"] programResources: requiredRegisterCount: 7 requiredCallStackDepth: 2 handlerResources: - name: Start signatureId: "Start(value)" requiredRegisterCount: 7 requiredCallStackDepth: 2Every present property is an exact constraint. Sequence order is significant where the underlying program contract defines order.
bytecode
Requires compiler and source. Its expectation is:
opcodes: contains: [Multiply, EmitMessage] excludes: [Move] counts: { RandomTake: 1 } minimumCounts: { PropertyAccess: 7 }At least one constraint is required. Opcode names are exact canonical names.
contains means count greater than zero; excludes means zero.
bytecodeSnapshot
Requires compiler, the optional capability bytecode-snapshot, and source. It
has exactly one gesa block and no gesBlock: expect block. The compiler’s
canonical dumper output and the block payload are normalized to logical LF
and compared byte-for-byte as UTF-8. No whitespace, address, symbol,
source-comment, or metadata field is ignored.
programBinary
Requires program-binary, one binaryFixture mapping, and one binary
expectation. Source fences are optional provenance and are never compiled.
compile.binaryRoundTrip is invalid because the input is already binary.
A case with binary.outcome: valid may also contain one gesa block. This
implicitly requires the optional capability bytecode-snapshot. After reading
and validating the fixture, the runner compares its dump with the block using
the same exact, LF-normalized UTF-8 comparison as bytecodeSnapshot. It never
substitutes compiler output for the fixture. This form isolates portable dump
formatting from legal differences in compiler optimization. Error outcomes
cannot contain a gesa block.
binary: outcome: valid rewriteByteExact: true rewriteSha256: 594808EB171F039AF2182A22360C39183CC4960412EF3D76B97E3632603EECF8 moduleName: BinaryFixture requiredRegisterCount: 3 requiredCallStackDepth: 0 opaqueSectionCount: 0outcome is exactly valid, readError, or validationError. Read errors
cover header, framing, section representation, strict UTF-8, and bounded table
materialization (GameEventScriptProgramFormatErrorCode 1 through 21).
Validation errors cover cross-reference, opcode/operand, call-graph, resource,
and debug/source invariants (codes 22 through 38). Error outcomes require the
canonical errorCode name and may constrain byteOffset, sectionType, and
entryIndex; they reject all success fields.
A valid outcome may constrain the fields above. rewriteByteExact compares the
fixture with a fresh canonical writer result, while rewriteSha256 identifies
that result independently of whether the input order was canonical. The
fixture’s programVersion is always compared. When BuildMetadata is present,
its compiler identity is compared with the manifest; runtime behavior never
depends on it.
A valid case may contain Steps and ordinary step expectations. That form also
requires host, vm, observer, and publish-sink; the parsed fixture Program
is loaded and executed instead of compiling the provenance source. Error cases
cannot contain Steps. Initialization expectations are valid only together with
Steps.
performance
Requires the same core capabilities as scriptApi plus the optional capability
performance. It uses ordinary sources, steps, and correctness expectations.
Its case metadata additionally contains:
performance: iterations: 1000 warmupIterations: 10 compileWarmupIterations: 3 observeRuntime: trueAll counts are non-negative UInt32 and iterations is positive.
observeRuntime is an optional Boolean, defaulting to true. It controls only
measured execution: true installs a counting runtime observer whose callbacks
do not allocate; false installs no runtime observer. The separate correctness
execution still observes the complete declared outputs and traces. A provider
that cannot honor the requested observer configuration reports an error.
The expectation adds profile-specific baselines:
performance: profiles: csharp-dotnet-release-macos-arm64: metrics: run.per-invoke-elapsed: reference: 0.002669 unit: ms toleranceRelative: 0.20 toleranceAbsolute: 0.001 run.per-invoke-allocated: reference: 0.172 maximum: 0.172 unit: KiBProfile IDs use the normal ID grammar. Metric IDs use the same grammar with
additional dot-separated components. V1 performance metrics are lower-is-better.
Units are ns, us, ms, s, B, KiB, count, or those units followed
by /iteration. One KiB is exactly 1024 bytes.
reference is required and is the value updated in received output. At least
one of maximum, toleranceRelative, or toleranceAbsolute is required.
Relative tolerance is a non-negative ratio, not a percentage. The permitted
upper bound is the smallest supplied bound among maximum,
reference * (1 + toleranceRelative), and
reference + toleranceAbsolute. A measured value at or below the bound passes;
an improvement never fails. All values are finite non-negative Binary64 values.
Workload and correctness are shared across ports. Profiles and their baselines may be language-, runtime-, configuration-, OS-, and architecture-specific.
The performance.low-allocation suite exercises the warmed allocation contract
owned by Public API. Its run.allocated
metric counts cumulative allocated bytes across all measured iterations; a
zero maximum allows no tolerance or rounding of a positive count to zero.
Reusing inputs and avoiding new payloads isolates dispatch and VM work.
Paired 1000/2000-iteration cases test recurring allocation, with completion,
frame pause/resume, exact/message-name matching, and observer off/on.
Measurement coverage is qualified separately from semantic correctness, as
defined in Runner. Nonzero payload-allocation budgets
remain profile-specific and do not define portable object sizes.
Normalized immutable document model
Parsing and schema validation produce one immutable normalized document with:
- format version and one suite with resolved defaults;
- cases in document order, each with stable local and full ID, display title, resolved kind, level, categories, tags, capabilities, options, source inputs, steps, and typed expectations;
- messages and values in their explicit portable ordered forms;
- original UTF-8 source ranges for the frontmatter, every test, every semantic
block, each performance
referencescalar, and thegesapayload.
The normalized model contains no Markdown AST, YAML AST, filesystem path, testframework object, executable delegate, or platform exception. All exposed collections are immutable snapshots. Parsers may retain the original document bytes only as an explicit immutable source-document value used for received output.
Invalid documents and versioning
Structural, YAML, schema, duplicate-ID, cross-reference, and kind/cardinality violations are errors. The parser returns no executable partial document. Diagnostics have stable codes, original UTF-8 byte ranges, and line/column locations. At minimum, implementations distinguish:
conformance.markdown.invalidUtf8,conformance.markdown.missingFrontmatter,conformance.markdown.unterminatedFrontmatter,conformance.markdown.invalidTestHeading,conformance.markdown.unterminatedFence,conformance.markdown.unknownSemanticFence, andconformance.markdown.invalidStepsTable;conformance.yaml.syntax,conformance.yaml.unsupportedFeature,conformance.yaml.duplicateKey,conformance.yaml.invalidScalar, andconformance.yaml.limitExceeded;conformance.schema.unknownField,conformance.schema.missingField,conformance.schema.invalidValue,conformance.schema.duplicateId,conformance.schema.unknownReference,conformance.schema.invalidCardinality,conformance.schema.unknownKind, andconformance.schema.unsupportedVersion.
Format version 1 is closed. A parser that does not support the declared version must reject the document. New fields or semantics require an explicitly documented compatible revision strategy or a new format version; they must not be inferred from prose.
Minimal example
---formatVersion: 1suiteId: runtime.mathtitle: Runtime mathkind: scriptApilevel: atomiccategories: [conformance]tags: [runtime, math]requires: core: [compiler, host, vm]---
# Runtime math
## Test: Integer addition
```yamlgesBlock: caseid: integer-add```
```geson Start(value) { let result be value + 5 emit Done(result: result)}```
### Steps
| step | receive | pump | budget || --- | --- | --- | --- || add | Start | completion | |
```yamlgesBlock: expectsteps: add: input: args: - name: value value: { type: ":Number.int64", value: "7" } local: - name: Done args: - name: result value: { type: ":Number.int64", value: "12" }```Virtual time actions
Since: 0.2.0
Runtime cases use a virtual microsecond clock initially at zero. A host action
advanceMicroseconds: "200000" advances it without pumping; its argument is a
canonical nonnegative Int64 decimal string. Overflow returns false without
changing time. The action can appear in stepActions or native-handler actions
and supports expectResult. Unlike lifecycle actions, its argument is a duration,
not a program/subscription ID. Subsequent completion/frame steps release due work.
Step expectations may include waiting: true|false, asserting whether the last
pump returned Waiting. An enqueue-only step has no Waiting result. Frames stop
on Waiting instead of spinning. Tests must assert downstream emissions or trace
entries to distinguish acceptance from actual delayed delivery.
Product JSON message API checks
Since: 0.2.0
messageApi optionally accepts json (a complete product message envelope) and
roundTripJson (Boolean, default false). json replaces the ordinary message
fixture before checks. roundTripJson serializes and deserializes the message
before the existing signature, equality and structured message assertions.
The existing signature and message metadata remain required. The expectation
message.json optionally asserts the exact canonical serialized envelope;
message.error can assert the stable message.* codec failures. This exercises
the independent product codec, never the Conformance value transport.