`.gesb` binary format specification
Since: 0.1.0
This document is the normative container specification for GameEventScriptProgram.
Opcode semantics and operand shapes are normative in Bytecode.
A program contains portable data only. Linking extensions,
external types, delegates, runtime caches, registries, and VM state is exclusively a
GameEventScriptHost.Load responsibility.
Release and format compatibility
Package release versions, the header’s FormatVersion, section versions and the
application-owned ProgramVersion are distinct identifiers. The following
release guarantees apply independently of how many format versions a runtime
implements.
- Before 1.0, a minor release may change the instruction layout, constant pools or binary contract incompatibly. Migration notes must identify the need to recompile source. Retain source files; pre-1.0 binaries have no guaranteed acceptance by 1.0 or later runtimes, even if particular files remain readable.
- From 1.0 onward, a newer 1.x runtime must read and execute valid binaries produced by earlier 1.x releases with their defined instruction semantics. This rule applies analogously within later major release lines. Acceptance does not bypass normal resource limits or required native bindings.
- There is no forward-compatibility promise: an older runtime may reject binaries using newer formats or instructions. Unsupported formats, required sections and opcode/operand forms must be rejected before execution, never silently interpreted as a different operation. Existing optional-section rules below still apply.
- An incompatible change to an existing instruction encoding, operand layout or
binary representation requires a new
FormatVersion. New opcodes may extend an otherwise unchanged format only if older validators reject unknown forms. A new format version does not waive the requirement for newer runtimes to retain readers/execution support for earlier binaries in the same major line. - Dropping the guaranteed support or changing the defined semantics of those binaries requires a major release. Corrections of nonconforming behavior and explicitly permitted numeric variation are governed by the owning semantics.
The cross-version execution and random-stream guarantee is owned by Determinism. Recompilation is governed separately by Language; identical source need not produce identical binary bytes.
Integer encoding and framing
All multi-byte integers use little endian. Sections are contiguous and have no implicit padding or alignment.
The file starts with this fixed 16-byte header:
| Offset | Size | Field | V1 value |
|---|---|---|---|
| 0 | 4 | Magic | ASCII GESB |
| 4 | 2 | Version | 1 |
| 6 | 2 | Flags | 0, reserved |
| 8 | 4 | HeaderSize | 16 |
| 12 | 4 | FileSize | exact total byte count |
Every following section starts with a 12-byte header:
| Offset | Size | Field |
|---|---|---|
| 0 | 2 | SectionType |
| 2 | 2 | SectionFlags |
| 4 | 2 | SectionVersion |
| 6 | 2 | Reserved, zero |
| 8 | 4 | PayloadLength |
| 12 | n | Payload |
SectionFlags & 0x0001 is Required. SectionFlags & 0x00F0 is the
compression-codec field. Codec zero means uncompressed. V1 writers emit only codec
zero for known sections. A loader rejects unsupported compression on a required
section. It may preserve or skip an optional unsupported section according to its
retention mode. All remaining flag bits are invalid in V1.
Section registry and canonical order
| ID | Name | Cardinality | Required |
|---|---|---|---|
0x0001 |
ProgramMetadataSegment | exactly one | yes |
0x0002 |
StringConstantSegment | exactly one | yes |
0x0003 |
UInt16IndexListSegment | exactly one | yes |
0x0004 |
BindingSegment | exactly one | yes |
0x0010 |
CodeSegment | exactly one | yes |
0x0020 |
DebugSymbolsSegment | zero or one | no |
0x0021 |
SourceMapSegment | zero or one | no |
0x0022 |
SourceArchiveSegment | zero or one | no |
0x0030 |
BuildMetadataSegment | zero or one | no |
0x0040 |
ReservedSignatureSegment | reserved | undefined |
0x0041–0x004F |
security sections | reserved | undefined |
0x8000–0xFFFD |
private/experimental | any | no |
0xFFFE |
NamedCustomSection | reserved | no |
Types 0x0000 and 0xFFFF are invalid. V1 has one linear code address space and
therefore exactly one CodeSegment. Multiple programs are combined by separate
Host.Load calls, not by multiple code sections.
The canonical writer emits the known sections in the table order above, followed by opaque optional sections in their original relative order. Known sections are always encoded from the parsed program model; their original raw bytes are not retained.
Required payloads
ProgramMetadataSegment version 1 has a fixed 16-byte payload:
ModuleNameStringIndex u16RequiredRegisterCount u16RequiredCallStackDepth u16Reserved u16 = 0ProgramVersion u64ProgramVersion is always present. Zero means development or unspecified. The
module name is a stable program identity and must exist in StringConstantSegment.
StringConstantSegment:
EntryCount u32repeat EntryCount: ByteLength u32 Utf8Bytes byte[ByteLength]UTF-8 is strict. Ordering and duplicates are semantic program data. At most 65,535
entries are allowed because bytecode indexes are u16.
UInt16IndexListSegment:
ListCount u32repeat ListCount: ElementCount u32 Elements u16[ElementCount]At most 65,535 lists are allowed. Lists carry argument names, tags, register lists, message shapes, and similar indexed operands; their element interpretation depends on the referencing field or opcode.
BindingSegment:
EntryCount u32repeat EntryCount: Kind u8 Flags u8 = 0 Id u16 NameStringIndex u16 ArgumentNamesListIndex u16 RequiredTagsListIndex u16 ExcludedTagsListIndex u16 EntryAddress u16 RequiredRegisterCount u16 RequiredCallStackDepth u16 Reserved u16 = 00xFFFF is the missing ID/list/address sentinel. Executable binds require a valid
entry address; non-executable binds use the sentinel. The program-level resource
values equal the maxima of the handler metadata. IDs are globally unique per kind,
except handler IDs, which are overload ordinals scoped to their message name.
Function and predicate binding identity is the binding name plus its ordered
argument-name list. Duplicate signatures are invalid. Function and predicate
names may not overlap even when their argument-name lists differ; declared
source types and local parameter names are not encoded identity components.
Matching resource declarations alone do not establish validity. The independent
code-derived bounds and frame/staging rules are defined in
Bytecode. Underdeclarations and inconsistent resource states are
reported as InvalidResourceMetadata by complete Program validation.
CodeSegment:
InstructionCount u32repeat InstructionCount: OpCode u8 UnitAndFlags u8 Word0 u16 Word1 u16 Word2 u16 Payload u64Each instruction is exactly 16 encoded bytes. These fields, not CLR layout, define
the file representation. Payload preserves integer bits, IEEE-754 binary64 bits, or
four overlaid u16 words. V1 permits at most 65,535 instructions.
Opcode assignments and operand constraints are owned by Bytecode.
The canonical valid-parse-literal.gesb fixture and its invalid operand/opcode
variants exercise literal parsing in this same V1 instruction encoding; no
additional section or executable parser state is serialized. The canonical
data-forms.gesb fixture pins ConstructData and both SplitText modes; invalid
variants verify flags, units, indexes, reserved words and mode validation.
Debug and source payloads
DebugSymbolsSegment has its own name pool, followed by fixed symbol records:
NameCount u32repeat NameCount: ByteLength u32, Utf8Name byte[ByteLength]SymbolCount u32repeat SymbolCount: SymbolKind u8 // 1 parameter, 2 local Reserved u8 = 0 RegisterId u16 NameIndex u32 CodeStart u32 CodeLength u32Only explicit script parameters and locals are named. IDs are physical registers after final allocation. Multiple non-overlapping ranges may later name the same physical register; compiler temporaries remain unnamed. Each symbol’s code range lies within one routine, and its register ID is below that routine’s maximum frame length. This also applies to unused routines whose frames exceed the Program’s handler resource bounds.
SourceMapSegment uses stable source IDs assigned in AddScript order:
SourceCount u32repeat SourceCount: SourceNameLength u32, SourceName UTF-8 SourceByteLength u32 Sha256 byte[32] LineCount u32 LineStartByteOffsets u32[LineCount]MappingCount u32repeat MappingCount: CodeStart u32 CodeLength u32 SourceId u32 SourceStartByteOffset u32 SourceByteLength u32The source-table index is its stable SourceId; compiler inputs therefore receive
IDs in AddScript order. All source offsets address uncompressed UTF-8 bytes. The
first line offset is zero; subsequent offsets are strictly increasing UTF-8
boundaries and may equal the source length for a final empty line. SourceMap is
useful without an embedded source.
SourceArchiveSegment is independent:
SourceCount u32repeat SourceCount: SourceId u32 SourceNameLength u32, SourceName UTF-8 ContentByteLength u32 Utf8Content byte[ContentByteLength]Content is the exact logical compiler input encoded as UTF-8. Original BOM and file encoding are not preserved. If map and archive are both present, IDs, names, byte lengths, SHA-256 values, line boundaries, and UTF-8 validity must agree.
BuildMetadataSegment contains exactly two required length-prefixed UTF-8 strings:
CompilerIdLength u32, CompilerId UTF-8CompilerVersionLength u32, CompilerVersion UTF-8The C# implementation emits compiler ID steph.ges.compiler.csharp and version
0.1.0. Other ports use stable implementation IDs. Runtime behavior never depends
on these values. Timestamps, host names, user names, absolute paths, and a separate
language field are forbidden so identical inputs remain reproducible.
Reading, retention, validation, and writing
GameEventScriptProgramReader accepts bytes only. PreserveAll parses known
optional sections and retains unknown optional payloads as immutable opaque sections.
PreserveKnown drops unknown optional sections. RuntimeOnly materializes only the
five required runtime sections. Unknown required sections always fail. A known
optional section with unsupported version or compression is opaque only under
PreserveAll; otherwise it is skipped.
The default reader limits are 64 MiB per file, 256 sections, 32 MiB of source archive, 16 MiB of retained opaque payload, and 65,535 each for instructions, bindings, strings, and index lists. Bounds and arithmetic are checked before allocation.
The compiler’s final materialization, reader, canonical writer, and Host.Load
all invoke the shared program validator. It validates framing, UTF-8, table
references, bind and opcode forms, code targets, the acyclic synchronous call
graph, resource declarations, and debug/source ranges. Failures use
GameEventScriptProgramFormatException with a stable error code and, where
available, byte offset, section type, and entry index.
Call-graph depth within the V1 data limits is not an additional format error.
Validation of an acyclic graph must complete, and a cyclic graph must produce
CyclicCallGraph, without terminating the embedding process through exhaustion
of the native implementation stack. Host execution limits do not replace this
validation at the reader boundary.
The portable numeric error codes are fixed as follows:
| Code | Name | Code | Name |
|---|---|---|---|
| 1 | InvalidMagic | 20 | InvalidUtf8 |
| 2 | UnsupportedFormatVersion | 21 | TooManyEntries |
| 3 | InvalidHeaderFlags | 22 | InvalidStringIndex |
| 4 | InvalidHeaderSize | 23 | InvalidListIndex |
| 5 | FileSizeMismatch | 24 | InvalidBindingKind |
| 6 | FileTooLarge | 25 | DuplicateBindingId |
| 7 | TooManySections | 26 | InvalidEntryAddress |
| 8 | TruncatedSectionHeader | 27 | InvalidOpcode |
| 9 | TruncatedSectionPayload | 28 | InvalidOperand |
| 10 | SectionTooLarge | 29 | InvalidJumpAddress |
| 11 | InvalidSectionType | 30 | InvalidCallAddress |
| 12 | InvalidSectionFlags | 31 | CyclicCallGraph |
| 13 | InvalidSectionReserved | 32 | InvalidResourceMetadata |
| 14 | UnsupportedSectionVersion | 33 | InvalidDebugSymbol |
| 15 | UnsupportedCompression | 34 | InvalidSourceMap |
| 16 | MissingRequiredSection | 35 | InvalidSourceArchive |
| 17 | DuplicateSection | 36 | SourceMetadataMismatch |
| 18 | UnknownRequiredSection | 37 | ReaderLimitExceeded |
| 19 | InvalidPayloadLength | 38 | InvalidProgram |
The writer exposes size calculation, writing into a caller-provided span, and array creation. It emits file size from the encoded result; file size is not program state. For canonical uncompressed data, write/read/rewrite is byte stable. Semantic equality, not original byte identity, remains the product contract.
Deferred V1 extensions
Compression codec bits and security section IDs are reserved only. V1 implements no compression, signatures, certificates, key registry, trust policy, rollback policy, or authenticity guarantee. Adding those features requires separate specifications.
Numeric arithmetic uses the existing 128-bit V1 instruction layout: bit 0x40
on Move/Add/Subtract/Multiply/Divide/Power/IntegerDivide/Modulo/Remainder/Min/Max/Negate/Abs/Clamp selects strict numeric operands. Recompile source
for the new numeric sum/average contract; previously compiled aggregates may still
contain ordinary overloaded arithmetic. Pre-1.0 compatibility guarantees apply.