Also gate Stub events under TracingSteps: stubbed ops are per-instruction
frequency (0x258/0x259 stmt markers en masse), so --trace stays a clean
high-level flow view; --trace-steps shows step+stub detail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Typed ITraceSink/TraceEvent seam in Age.Engine (zero deps, allocation-free
hot path); relocate CallScript/OnStub off IHost; Null + Text sinks; CLI
--trace; Godot dispatch hack retired onto a sink. Serilog/EventSource
deferred to optional edge sinks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CLI run: report call-script dispatch count + distinct source scripts per run.
- Godot --scene <NAME>: play any scene (not just SC0000).
- Godot reports the call-scripts executed as nested frames at scene end (collected
thread-safely; Godot drops GD.Print from the VM background thread).
Demonstrated live: SC0240 in Godot executes 29 call-scripts (RESETLAND, SETEN,
ADDEN, RENDERMAP, SETOBJ, DRAWOBJ, CALCREVISE, LOOK) as nested subroutine frames.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Play path: VM now gets Sys4ScriptProvider, so call-script executes live on screen
(subroutines run; input-wait loops break on real player input).
- Selftest: was 'run SC0000 stubbed, match vm0-trace (186)'. Now runs a SYNTHESIZED
scene (show-text + wait-for-input + real nested call-script) through the Godot
thread/semaphore/CallDeferred plumbing and asserts it matches a headless run of the
same scene — full handling, expected computed live, no frozen golden, no vm0 dep.
- Verified: godot --headless -- --selftest => 'threaded host matches headless (3 lines)'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ScriptAssembler (Age.Engine/Sys4): assemble code+strings -> Script (inverse of
Sys4Loader; also Phase-D modding-assembler groundwork).
- SyntheticSceneTests: deterministic show-text/wait/nested-call-script/shared-global
scene run with call-script handling ON.
- WaitForInputTests: reworked onto a synthesized two-page scene (was: SC0000 stub=186).
- RecoverTests: full call-script handling via a no-op subroutine double (isolates
RECOVER's ISA semantics from real subroutines' game-state deps).
- Retire TraceDiffTests: it matched the C# VM to vm0.py's stubbed-call-script trace;
vm0.py is retired from oracle duty, and we no longer gate handling to keep it matching.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Native-RE: call-script id = raw SYS4INI file index (name-resolution #1 solved)
- C# VM: call-script now executes (IScriptProvider + ExecFrame + nested run)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integration: ADDILL executes ADDILLSUB + CALCREVISE and returns to its own exit.
Sweep (execution on): 284/297 exit clean, 13 STEP-LIMIT (input/state-gated ADV
scenes that now spin headless once subroutine global-writes drive their loops —
state divergence, not a call-script bug; 0 depth-cap, 0 unresolved, 0 crashes).
Removed the dead _halted field (halt propagates via FrameOutcome).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design for making call-script actually execute (load target .BIN by id,
run nested sharing globals, return to caller). Scope: subroutine
execution only. Drops vm0.py from oracle duty; C# owns golden traces.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Native-RE (Ghidra) cracked call-script <id> (opcode 0x03): its handler
FUN_0041bc90 -> loader FUN_0040e980 -> resolver FUN_0044f390 indexes an
80-byte record table at base + id*0x50 == the SYS4INI record layout. So
`call-script <id>` is a direct RAW index into the SYS4INI global file
table (the asset index we already parse) -- there is no separate on-disk
id->code registry. This resolves name-resolution.md #1, statically, no
Frida.
Confirmed: all 297 distinct corpus call-script ids resolve to a .BIN
script with a semantically-exact name (0x1ab->ADDITEM, 0x2ae7->MES,
0x143->BUNKI, 0x329d->CALCREVISE), 0 out-of-range, 0 alternate-pack.
Companion op 0x8f `call` is an intra-script JSR (FUN_0041fba0), not
cross-script.
- parse_sys4ini.py: preserve `raw_index` per entry (= the engine file id;
index the RAW records incl. '@' placeholders) + emit
build/callscript-names.json (id->name).
- sys4load.py: annotate `call-script 0x1ab =ADDITEM.BIN`.
- opcodes.toml 0x03/0x8f refined (source=investigation, confidence high,
handler VAs) + rebuilt opcode-reference.md.
- docs: engine-re.md (op 0x03 section + backlog re-aimed),
name-resolution.md #1 (SOLVED), script-inventory.md (call graph +
living-reference decision), tools-reference.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
op 0x1a2 resolve-handle? -> gfx-cmd-register (verified handler FUN_0042d360, gfx
command-buffer op). name-resolution: decision->scene hop is native+unidentified, not
u00428010 (disproven). Regenerated opcode-reference + shim.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Anchored the opcode dispatch table: handler(op)=ctx[0x26c93+op] (default FUN_004162b0,
registered by FUN_00413860). Corrects the prior note: raw Kelebek VA 0x428010 is op
0x1ac (a save op, 0x427fb0); op 0x1a2's REAL handler is FUN_0042d360 = a graphics
command-buffer op (cmd-type 3, '%c%8.8x' key). So u00428010 is NOT decision->scene and
NOT save; the FIELD 0x5f0ed/0x62ccf snippet is gfx/UI. decision->scene premise discredited;
real mechanism = call-script/script-load (still unidentified). Lesson: resolve handlers
via the table, never the raw Kelebek VA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ghidra+MCP workflow validated. Found the opcode->handler dispatch table
(*(ctx+0x9b8f4+op*4), registered by FUN_00413860) — the general fix for Kelebek VA
drift. Corrected: u00428010 (op 0x1a2) is a save/resource op, NOT decision->scene;
the SCJUMP consumer persists the visited-decision flag. New doc docs/engine-re.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stand up bethington/ghidra-mcp loop; prove it by reversing SCJUMP's native
decision->scene resolver. Raw-dump-first, pe-sieve fallback; findings -> docs/engine-re.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guarded-DFS decode of SCJUMP's acyclic DAG into (chapter_mode, guards)->decision
table (1755 sites/847 decisions), VM-verified (execution-driven, 0 failures).
Finding: 1732/1755 decisions gated by native op 0x60. Decision->scene (u00428010)
documented as the deferred native boundary.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reworked from static-witness-only to execution-driven: ~99% of SCJUMP decisions
are gated by a native computed value (op 0x60), so witness-synthesis alone can't
cover them. Value-local tracking resolves load-then-compare guards to real globals;
native terms honestly marked opaque. Verify: static 3/3 exact + 279/279 execution-
driven consistent over 2000 seeds, 0 failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guarded-DFS decode of SCJUMP's DAG into (chapter, guards)->decision table;
VM witness cross-check; native decision->scene boundary documented/deferred.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Curated single-source global registry (opcodes.toml analogue) with static
story-flag discovery miner; sys4load labels from merged build/globals.json.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
build/globals.json is generated (build/ is gitignored, regenerable via
globals_build.py --build); docs/global-reference.md is the tracked human view.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Curated single-source global registry modeled on opcodes.toml; static
story-flag discovery miner; sys4load labels from merged build/globals.json.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
godot -- --seed 0xa57=1 seeds initial global state in the ADV frontend (e.g.
Lily's form-gated voiced dialogue plays live; forms A/B/C = 0xa57/0xa58/0xa59).
Additive: no seed = unchanged, selftest OK. Also documents the engine CLI
(run/trace/audio/gfx/play/sweep) + Godot frontend flags in tools-reference.md
(they lived only in memory/commits).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>