Skip to content

`.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 u16
RequiredRegisterCount u16
RequiredCallStackDepth u16
Reserved u16 = 0
ProgramVersion u64

ProgramVersion is always present. Zero means development or unspecified. The module name is a stable program identity and must exist in StringConstantSegment.

StringConstantSegment:

EntryCount u32
repeat 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 u32
repeat 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 u32
repeat EntryCount:
Kind u8
Flags u8 = 0
Id u16
NameStringIndex u16
ArgumentNamesListIndex u16
RequiredTagsListIndex u16
ExcludedTagsListIndex u16
EntryAddress u16
RequiredRegisterCount u16
RequiredCallStackDepth u16
Reserved u16 = 0

0xFFFF 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 u32
repeat InstructionCount:
OpCode u8
UnitAndFlags u8
Word0 u16
Word1 u16
Word2 u16
Payload u64

Each 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 u32
repeat NameCount: ByteLength u32, Utf8Name byte[ByteLength]
SymbolCount u32
repeat SymbolCount:
SymbolKind u8 // 1 parameter, 2 local
Reserved u8 = 0
RegisterId u16
NameIndex u32
CodeStart u32
CodeLength u32

Only 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 u32
repeat SourceCount:
SourceNameLength u32, SourceName UTF-8
SourceByteLength u32
Sha256 byte[32]
LineCount u32
LineStartByteOffsets u32[LineCount]
MappingCount u32
repeat MappingCount:
CodeStart u32
CodeLength u32
SourceId u32
SourceStartByteOffset u32
SourceByteLength u32

The 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 u32
repeat 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-8
CompilerVersionLength u32, CompilerVersion UTF-8

The 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.