How-To: Write GessaScript
v1.0.111 · docs-public.v0Add behavior to a world by authoring GessaScript. The normal authoring path is the Script Semantic Patch — typed, intent-level operations applied to the script — not hand-writing Script IR and not hand-editing generated source. You declare what should change; the backend compiler owns the rest.
This page describes the canonical authoring flow for v1. Source and visual authoring both round-trip through Script IR; generated TypeScript is runtime/debug output, not normal creator truth.
This page is verified against the generated Script Semantic Patch, Script Node, and Script SDK references. Runtime behavior still requires Play or publish proof.
Media placeholder: add a capture showing the same script in Visual and GessaScript source, then a Play trace mapped back to the authored node/source span.
What is canonical: the IR, not its projections
Script IR is the canonical behavior contract. It is the single source of truth for what a script does. The two surfaces creators usually look at — GessaScript source and the visual node graph — are both constrained authoring surfaces over the IR. Generated TypeScript is an execution and debug projection. Editing source or visual means editing the IR underneath; neither surface is allowed to drift into a separate truth.
The IR's root is versioned (an irVersion literal), so a script declares which IR contract it conforms to. You never hand-write that IR. Instead you express intent through the semantic patch layer, and the compiler projects it into canonical IR, owning node ids, ports, and layout. See Explanation: Script IR and GessaScript.
The semantic patch is the normal path
Semantic patches are creator-facing edit operations derived from packages/script-semantic-patch. The backend compiler projects them into canonical Script IR, and the normal Project Graph script-update path persists the result. The catalog is Tier-2 generated and currently exposes 39 operations:
docs/spec/generated/script-semantic-patch.md— the operation catalog, packs, and effect tags.docs/spec/generated/script-node-catalog.md— the canonical Script IR node declarations (event, core, and host nodes).docs/spec/generated/script-sdk.md— the generated Script SDK surface (ScriptContextand thectx.*host namespaces).- Reference: Script Nodes — the thin pointer page.
You apply a patch through the canonical action project.script.patch.apply, surfaced to agents as the MCP tool project_apply_script_semantic_patch. Each operation carries typed effect tags — readState, writeState, durable, spawnEntity, despawnEntity, emitEvent, requiresAuthority — so the authority layer knows exactly what a behavior touches.
Representative operations (read the generated catalog for the full set and each operation's schema): findEntitiesByTag, forEachEntityInResult, incrementNumericState, declareWinCondition, emitCustomEvent, spawnEntityFromTemplate, despawnEntity, attachComponentWithValidatedDefaults, wireHudStateBinding, and the higher-level addShooterCollectorLoop, which expands a common gameplay loop from semantic intent alone. Handler operations — addActionHandler, addTriggerHandler, addTimerHandler, addTickHandler, createCustomEventHandler — attach the entry points behavior runs from.
The visual-editor primitives — addNode, connectExec, connectData, setInlineValue, addHostOperation, and raw graph node ids — are import / visual-editor / test-only. Do not send them through project_apply_script_semantic_patch. Behavior edits use the typed semantic operations (the ai_safe / mcp_safe packs); hand-wiring nodes bypasses the contract that lets the compiler own ids, ports, and layout.
The compiler owns ids, ports, and layout
This is the point of the semantic patch. You declare intent (add a tick handler, spawn an entity from a template, declare a win condition); the compiler decides node ids, wires execution and data ports, and lays out the graph. That is why the source and visual graph stay in lockstep — they both re-project from the IR the compiler produced. If you hand-wired nodes, the two surfaces would drift and the IR would no longer be a single authority. Let the compiler do it.
The authoring recipe
- Open or create the script shell. For an existing script, resolve its
id_or_key. To create an empty IR-backed shell, use the workbench orproject_create_script— then do behavior edits with the patch tool, not by editing the shell's source. - Discover the right operation. Read the semantic-patch catalog (or call
engine_list_script_patterns/engine_get_script_node) to choose typed operations that lower into Gessa services rather than raw IR. - Express the change as a patch. Build the operation(s) — e.g. add a handler, then
findEntitiesByTag→forEachEntityInResult→despawnEntity, thendeclareWinCondition. Keep the patch intent-level. - Apply through the canonical action. Call
project_apply_script_semantic_patch(actionproject.script.patch.apply). The compiler projects your intent into canonical IR; the source and visual graph re-project from it; the normal script-update path persists it. - Validate, then Play. Run the script publish gate and enter Play to confirm the behavior. See How-To: Publish and Play.
Use project_apply_script_semantic_patch for behavior edits; use project_create_script only to make an empty IR-backed shell. Choose operations from the ai_safe / mcp_safe packs. Never send addNode, connectExec, connectData, setInlineValue, addHostOperation, or raw graph node ids through this path — they are visual-editor/import/test-only. Read effect tags (writeState, spawnEntity, requiresAuthority) to understand what an operation touches before applying it.
Related
- Explanation: Script IR and GessaScript — why the IR is canonical and source/visual are projections.
- Reference: Script Nodes — the generated node catalog and SDK surface.
- How-To: Use MCP Tools — discovery-first authoring from an agent.
- How-To: Publish and Play — validating a behavior change in a Play session.
Status: this page documents the canonical GessaScript authoring flow for v1. The exact operations, node declarations, and SDK methods live in generated references; use runtime proof for runtime claims.