Explanation: Script IR and GessaScript
Behavior in Gessa is GessaScript, and its canonical form is the Script IR. The IR is the single source of truth for what a script does. The two things creators usually look at — the generated TypeScript source and the visual node graph — are both projections of the IR. They are views; editing them means editing the IR underneath, never the reverse.
This page explains why behavior is modeled as a canonical IR with projections, why the normal way to author it is the Script Semantic Patch layer rather than hand-editing a projection, and how a single authoring intent travels the canonical pipeline from an IR variable all the way to a runtime trace.
One canonical form, two projections
If either the source code or the node graph were canonical, the other surfaces would drift: the source would say one thing while the visual graph showed another, the runtime would execute a third interpretation, and the generated SDK declarations would document a fourth. The engine refuses that. A single canonical IR lets the same behavior drive runtime execution, code generation, generated SDK declarations, AI tooling, and visual authoring — all in lockstep.
Script IR (canonical behavior contract)
│
┌─────────────┼──────────────┐
▼ ▼ ▼
generated visual node runtime
TypeScript graph execution
(projection) (projection) (consumes IR)
This mirrors the broader engine discipline: like the Project Graph, behavior has one authoritative model and many projections, and like backend authority generally, no projection is allowed to become a secret second source of truth. The Unreal lesson the engine cites is exactly this — visual scripting and source are powerful as projections of one program model, dangerous as two competing behavior truths.
The IR is also versioned at its root: irVersion is a literal version coordinate, and IR changes are migrated rather than silently reinterpreted, so old scripts keep their semantics as the catalog evolves.
Why an IR instead of "just code" or "just nodes"
A canonical IR buys properties that neither raw source nor an ad hoc node graph can guarantee on its own:
- Stable node identity. Nodes carry stable ids and typed pins, so a visual edit and a source view refer to the same thing across edits and migrations.
- Trace mapping. Runtime traces can map back to IR nodes and therefore to both the source and the visual graph, which is what makes debugging behavior tractable.
- Effect contracts. Each host operation declares its effects (read/write world, read/write typed state, emit event, spawn/despawn, requires authority, durable). Effects are how the engine reasons about authority and replication instead of trusting free-form code. The canonical effect tags and runtime receipt mappings live in the generated Script Effect Contracts.
- One declaration source for nodes. The canonical Script IR node declarations live in one package and feed runtime, codegen, SDK declarations, AI tooling, and the visual editor. Their inventory is the generated Script Node Catalog.
The normal authoring path: Script Semantic Patch
You author behavior by expressing intent against the IR through the Script Semantic Patch layer (packages/script-semantic-patch), not by hand-editing a projection. A patch says what should change — add a handler, declare a variable, insert a host operation, wire an exec edge — and the backend compiler validates it, applies it to the canonical IR, re-projects the source and visual graph, and records a receipt. This is the canonical authoring verb project.script.patch.apply in the Action Catalog.
The generated Script Semantic Patch reference lists the canonical operations and the operation packs exposed to each surface. Representative operations include addHandler and addTickHandler (event handlers), addVariable and setVariableDefault (first-class IR variables), addNode, connectExec, and connectData (graph structure), addHostOperation (insert a host call from the canonical node catalog), and higher-level semantic operations such as attachComponentWithValidatedDefaults, writeTypedStateValue, and declareWinCondition. The packs matter: ai_safe, mcp_safe, and sdk_basic deliberately expose the higher-level, id-free operations, while visual_editor and raw_graph_import expose the lower-level graph operations.
A patch is a small, typed intent — conceptually, an operation plus its arguments — that the compiler lowers to exact IR mutations:
<!-- example:conceptual -->
{
"scriptVersion": 7,
"operations": [
{ "op": "addTickHandler", "args": { "handlerName": "onTick" } },
{ "op": "incrementNumericState", "args": { "field": "score", "amount": 1 } }
]
}
The exact operation names and argument shapes are defined by the generated Script Semantic Patch reference and its operation catalog version; the block above is illustrative, not a verbatim payload. The point is the shape of the contract: the model (or the visual editor) asks for a semantic edit, and Gessa performs the precise graph mutation. Failed edits produce compiler diagnostics, not broken graphs.
The normal path is Script Semantic Patch. Editing generated TypeScript as if it were the authority, hand-building raw graph mutations, or writing full Script IR directly are forbidden bypasses in the v1 closure contract — they would let source or a node graph become a second behavior truth. Even ProjectScript.create accepts only an empty IR shell; behavior is added through semantic patch, not through a behavior-bearing create payload.
The canonical pipeline
A single authoring intent is not a one-off mutation; it travels a defined pipeline so that every surface that touches a script stays consistent. From the v1 engine closure context dump:
Script IR variable ─▶ exposed field contract ─▶ ScriptComponent config / default ─▶ inspector control ─▶ runtime initialized state ─▶ generated source ─▶ visual node / property projection ─▶ runtime trace / source map
Read this as one story. A variable declared in the IR (via addVariable) becomes an exposed-field contract; that contract drives the ScriptComponent config and its default; the default surfaces as an inspector control on the entity; at play time it seeds the runtime's initialized state; the same IR projects to readable generated TypeScript and to a visual node-and-property view; and runtime traces map back through the source map to those same nodes. There is no source parsing and no ad hoc visual graph anywhere in that chain — every surface is a projection of the one IR.
The ScriptComponent is itself a canonical ECS component (component.script), the atomic behavior attachment in the component inventory; the IR it carries is the behavior resource stored on the Project Graph script resource.
How this is enforced
Three v1 feature-ledger rows govern this model, and all are currently unaccepted:
script.semantic_patch— "Normal script behavior edits use versioned semantic patch operations that lower to Script IR without raw source, raw graph, or full IR bypasses." Required proof:schema,unit,integration,ai-mcp-sdk,guardrail,version-coverage,mutation-authority.script.ir.source.visual_parity— "Script Semantic Patch, Script IR, generated source, visual graph, typecheck, and runtime proof agree for launch-critical behavior." It carries the known blockervisual_graph_readability_not_launch_grade: a visual graph can be technically valid yet not creator-grade readable, which the engine treats as a real defect, not a cosmetic one.script.effect.typed_state— typed gameplay-state effects, surfaced through the Script Effect Contracts and proven via runtime/factory paths.
Source/visual/IR parity is not accepted on prose. Acceptance requires schema, typecheck, frontend, and runtime proof that the semantic patch, IR, generated source, and visual graph agree — plus runtime authority actually executing the behavior, not just the script resource existing in the graph. All governing ledger rows are unaccepted; treat this pipeline as the canonical contract whose runnable status tracks the ledger.
Source
Script IR as canonical behavior, source and visual as projections, and the canonical pipeline come from docs/architecture/v1-engine-closure-context-dump.md (Script/Visual/Runtime Closure Philosophy) and the platform ontology control-plane plan (Semantic Patch Compilers). Generated references: Script Node Catalog, Script Semantic Patch, Script Effect Contracts, Script SDK.
Related
- Authoring recipe: How-To: Write GessaScript.
- Node and SDK tables: Reference: Script Nodes.
- The world model behavior lives in: Explanation: The Project Graph.
- Why no projection may become a second authority: Explanation: Backend Authority.
Status: stable explanation page for engine v1. The Script IR / projection / semantic-patch model is real and canonical; its end-to-end proven status tracks the v1 readiness ledger, where the script rows are currently unaccepted.