Portable diagnostics specification
Since: 0.1.0
This document defines the language-neutral diagnostic contract shared by the
compiler, .gesb decoder, dynamic linker, host, and VM.
Diagnostic shape
Every diagnostic contains these required fields:
phase:parse(1),validate(2),compile(3),decode(4),link(5), orruntime(6);code: a stable ASCII identifier such asvalidate.missingCallable;message: a human-readable explanation that may change and must never decide portable conformance.
It may additionally contain symbol, symbolKind, sourceLocation,
programName, handlerName, and technicalDetails. Missing data is represented
by absence/null, never by a magic string. technicalDetails is intended for
logs and may contain platform-specific exception information; portable tests
must not match it.
Source locations use the compiler’s portable source contract: source name plus
1-based Unicode-scalar line/column positions. Program and handler identify the
runtime/link context. Symbol kinds are the numerically specified
GameEventScriptSymbolKind values.
Stable codes
Parse uses parse.syntax for malformed syntax and parse.sourceNestingExceeded
for the portable source limits defined in Language.
Validation uses the following semantic codes:
validate.duplicateType,validate.duplicatePredicate,validate.duplicateFunction,validate.predicateFunctionConflict;validate.missingCallable,validate.invalidPredicate,validate.wrongPredicateArity,validate.wrongFunctionArity;validate.duplicateHandlerParameter,validate.duplicateConstant,validate.duplicateDefinitionParameter,validate.duplicatePublishArgument,validate.duplicateVariable,validate.shadowedVariable;validate.invalidIdentifierCase,validate.invalidMessageCase,validate.invalidTypeConstructor.
Compiler lowering/resource analysis uses compile.unsupportedConstruct,
compile.invalidArity, compile.unresolvedSymbol,
compile.numericLimitExceeded, compile.cyclicCallGraph,
compile.invalidResourceMetadata, and compile.invariantViolation.
Every existing GameEventScriptProgramFormatErrorCode maps losslessly to
decode.<lowerCamelErrorCode>, for example InvalidMagic maps to
decode.invalidMagic. The format exception keeps byte offset, section type, and
entry index as its specialized structured fields as well.
Linking uses link.requiredRegisterCountExceeded,
link.requiredCallStackDepthExceeded, link.invalidExtensionReference,
link.missingExtension, link.missingExternalTypeConstructor,
link.mismatchedExternalTypeConstructor, link.cyclicCallGraph, and
link.invalidProgram. link.initializationQueueFull rejects loading when the
host cannot enqueue the instance’s initialization snapshot. It carries the
Program’s module name in programName; its atomicity and retry rules are defined
in Host runtime.
Runtime uses these stable codes:
runtime.vmStateConflict,runtime.preparationFailed,runtime.instructionPointerOutOfRange, andruntime.illegalOpcode;runtime.registerOverflow,runtime.callStackOverflow,runtime.randomStackUnderflow, andruntime.randomScopeImbalance;runtime.invalidRecordConstructor,runtime.invalidExtensionBinding,runtime.invalidExternalTypeBinding,runtime.invalidMessageShape, andruntime.invalidSeriesKind;runtime.unhandledFailure,runtime.nativeHandlerFailure, andruntime.publishSinkFailure;runtime.extensionCallFailed,runtime.externalConstructorFailed, andruntime.externalFieldAccessFailed.
runtime.randomStackUnderflow reports an attempted pop across an active runtime
boundary, while runtime.randomScopeImbalance reports scopes left open by a
successfully returning native or extension callback. Exceeding
MaxRandomScopeDepth is a runtime-limit event rather than a diagnostic. New
codes may be added; an existing code must not be repurposed.
If an extension callback fails, random-scope cleanup still restores its parent stream, but must not replace the callback’s original failure diagnostic with a scope-cleanup diagnostic.
An extension function, external-type constructor, or external-value field
accessor may throw GameEventScriptExtensionFaultException to deliberately
report a defined failure with its own caller-chosen code, message, and optional
symbol; that code must not use the reserved runtime. prefix and is therefore
never one of the codes enumerated above. Any other exception from the same
boundary is unanticipated and is reported with the matching
runtime.extensionCallFailed, runtime.externalConstructorFailed, or
runtime.externalFieldAccessFailed code instead. Both outcomes end only the
current handler; the host remains reusable for subsequently enqueued messages,
as for any other runtime error. Neither ever evaluates to nothing.
Native handlers may likewise deliberately report a caller-chosen fault with
GameEventScriptExtensionFaultException. Unanticipated native-handler exceptions
use runtime.nativeHandlerFailure.
Transport and execution
Exceptions are only the C# transport mechanism. GameEventScriptCompileException,
GameEventScriptProgramFormatException, and
GameEventScriptDynamicLinkException expose portable diagnostic data; callers
must branch on phase/code rather than exception message or CLR subtype details.
Runtime handler failures abort and reset only the failing handler. Remaining
handlers from the immutable subscription snapshot continue in deterministic
order. The observer receives every runtime diagnostic. The execution result has
state RuntimeError and carries the first handler diagnostic observed during
that pump call. Successful, paused, and runtime-limit results carry no
diagnostic.
For handler diagnostics, the Host and VM fill missing programName and
handlerName from the active Program and handler before reporting the error,
including failures during VM preparation and native callbacks. Native handlers
have a dispatch signature but no owning Program, so they supply handlerName
without inventing a programName.
Context already supplied by the diagnostic is preserved independently for each
field. The original diagnostic remains immutable; unavailable context stays
absent.
A publish-sink exception remains a rejected outbound attempt: local dispatch is
unchanged, the observer receives runtime.publishSinkFailure, and the sink
failure alone does not turn the execution result into RuntimeError.
Diagnostics are allocated only on error paths. Successful queue dispatch, handler selection, VM stepping/resume, and execution-result creation retain the zero-allocation hot-path contract after warmup.
Conformance expectations
Portable Markdown/YAML error expectations match phase plus code and may additionally
match symbol/source/program/handler fields. messageContains is forbidden.
Runtime steps use expectedRuntimeDiagnostics in observer order. Human-readable
messages and technical details are deliberately excluded from pass/fail rules.
Initialization safety-limit failures expose runtime.initializationLimitReached
in StartResult, with the instance context when available. Limit name and bound
remain structured runtime-limit observations. Ordinary init faults preserve their
original runtime phase/code. The owning lifecycle rules are in HostRuntime.