Commit Graph

21 Commits

Author SHA1 Message Date
gamer147
8c6d188e98 refactor(hosting): drop CallScript/OnStub from IHost (now trace events)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:22:40 -04:00
gamer147
b76e9d2c4f feat(diagnostics): VM emits trace events + CallScriptDispatches stat
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:21:28 -04:00
gamer147
87b23aabe4 feat(diagnostics): ITraceSink seam + TraceEvent + Null/Text sinks
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:19:40 -04:00
gamer147
e29314c89c Test on synthesized scenes with full handling, not crippled real scenes
- 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>
2026-07-07 13:45:42 -04:00
gamer147
21de102d74 Wire Sys4ScriptProvider into CLI run/play/sweep; validate real subroutine execution
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>
2026-07-07 13:18:30 -04:00
gamer147
825294dacc Execute call-script: nested frame, shared globals, return to caller
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 13:13:57 -04:00
gamer147
39e5fa544e Add IScriptProvider + Sys4ScriptProvider (call-script id -> Script)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 13:09:50 -04:00
gamer147
0f4f44ebf5 feat(phase-b): GameSession snapshot save/load (JSON)
ToJson/FromJson round-trip the persistent global bank; play --save-state <file>
persists it, --state <file> restores it — so an expensive booted state (23646
globals) is captured once and reused without re-running *INIT, and it's the
foundation for real save-file work. Tests: unit round-trip + booted-state
survives snapshot (skill data intact). Engine 18/18.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 00:26:23 -04:00
gamer147
0738f044c1 feat(phase-b): boot data tables — play --boot runs *INIT into session state
All 9 *INIT scripts run clean and populate the game's data tables into the
global bank. play --boot prepends them before scenes so scenes see real
skill/item/unit/map/stage state (23646 globals for SC0000). Test cross-checks
the VM's SKINIT population against the static extraction (build/data/SKINIT.json:
skill 0 '飛行'@0x23a3, G[0xa6e5b]=30) — validates both the VM and extract_init.
Engine 16/16.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 00:23:22 -04:00
gamer147
156a52884f feat(phase-b): cross-scene state — GameSession + play runner
Persistent global store carried across scenes (the engine's flat global bank),
the substrate for cross-scene flow and state seeding. GameSession seeds a fresh
VM from session state, runs, merges back; VM untouched so trace/selftest parity
holds. Age.Cli play <SCENE...> [0xADDR=VAL] runs a sequence carrying state.

Tests (engine 15/15): state persists A->B; seed visible to scene; SC0000 via
session byte-identical to single run; seeding form flag 0xa57=1 changes behavior.
Demonstrated: play SC0000 0xa57=1 -> 186->229 lines (Lily's form-gated dialogue
executes); SC0000->SC0030 carries 76 globals. Operationalizes the state-
divergence finding. Godot selftest OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 00:19:11 -04:00
gamer147
d1d470920c feat(a2b): BmpHeader.ReadDims + gfx diagnostic (headless geometry oracle)
BmpHeader.ReadDims reads texture dims from the pre-converted BMP header (VM-
thread-safe, no pixel decode) — the data source for GetTextureSize. `Age.Cli gfx
<SCENE>` runs a scene and dumps set-texture/get-texture-size/draw-texture in
order with resolved file + computed geometry, mirroring the `audio` diagnostic.

Confirms 0x208 now yields real dims (800x600/800x800/800x227) and sane UI
geometry; surfaces the first-load anchor edge (unseeded state, Phase B).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 22:50:25 -04:00
gamer147
3512045916 feat(a2b): implement 0x208 get-texture-size (geometry keystone)
Promote 0x208 from stub to a real VM op that writes the loaded texture's
width/height into its two output globals, via new IHost.GetTextureSize. This is
the single native primitive the CG-load subroutine (SC0000 label_12649) needs;
all downstream centering/anchor geometry is already computed in bytecode.

Non-Godot hosts return (0,0) so trace/selftest parity holds (engine 9/9 incl.
TraceDiffTests). Godot host gets a temporary (0,0) stub; real impl in the
compositor task.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 22:48:10 -04:00
gamer147
f18f3a20e9 feat(a2b): audio — wire play-bgm/play-voice, fix BGM addressing
Wire audio end-to-end (OGG plays natively in Godot; no Frida, no decode):
- IHost.PlayBgm/PlayVoice + VM dispatch for play-bgm(0xbf)/play-voice(0xc4).
  Non-Godot hosts no-op them so --selftest + engine 8/8 stay byte-identical.
- GodotAdvHost resolves id->OGG; Main plays via two AudioStreamPlayer nodes
  (BGM looping; voice interrupt-on-new).

Key finding (by-ear, systematic-debugging): the two audio ops use DIFFERENT
addressing — the earlier "unified manifest" assumption was wrong for BGM.
- play-voice -> per-scene manifest files[base+id] (offset 0, same as textures).
  Confirmed by ear; upgraded med->HIGH.
- play-bgm  -> DIRECT LITERAL NAME BGM{id:03d}.OGG, NOT the manifest.
  Real game: play-bgm 5->BGM005, 8->BGM008 (manifest gave +1). Proven by
  play-bgm 0x23->BGM035 (real standalone track; BGM set skips 030-034) that the
  manifest mis-resolved to a graphics entry. Fix is BGM-only:
  ResourceMap.BgmPathById; voices/textures unchanged.

Also: Lily's silence root-caused as correct form-gating (G[0xa57/0xa58/0xa59]),
left unseeded by choice (no dummy state). Added diagnostic
`Age.Cli audio <SCENE.BIN> [0xADDR=VAL ...]`. Corrected opcodes.toml (play-bgm
direct-name, play-voice HIGH) + docs/memory (dropped the bogus unified-manifest
/ Frida-BGM006 claims).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 22:26:54 -04:00
gamer147
f0d75fa0dc feat(a2b): first-pass texture render — full-screen event-CG layer
Wire asset resolution into a live Godot render. VM executes set-texture ->
ResourceMap resolves (scene,resId) -> files[section_base+resId] across all
archives -> pre-converted BMP -> composite behind the dialogue. The full-screen
event-CG layer (EV052*) renders end-to-end from the executed bytecode.

- Age.Engine/Sys4/ResourceMap.cs: Resolve + BMP TexturePath; Paths: asset JSONs
- GodotAdvHost: create/set/draw-texture -> TextureRect in a _stage layer
- IHost.DrawTexture + VM dispatch extended with dst x/y (draw-texture args 7/8)
- project.godot 800x600; convert_agf.py all-archive + --scene batch
- engine 8/8, C# --selftest still byte-matches vm0 trace (VM behaviour unchanged)

Known limitations (next chunk = graphics geometry/blend subsystem):
- sprites + BG* via the CG-load subroutine get garbage dst/size — native ops
  stubbed (0x208 get-texture-size + sprite position/anim chain)
- AE* fades draw opaque/instant (no alpha); no chromakey
- slot model approximates the game's immediate-mode blit-onto-slot-0 canvas
- AGF pre-converted to BMP offline (runtime decoder deferred)
See docs/phase-a-slice-plan.md (A2b section).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 21:22:42 -04:00
gamer147
2b31e0945e feat(a2b): promote create/set/draw-texture to IHost methods (trace parity kept)
Runtime finding: SC0000's bg is a slot-0 full-screen slideshow (res 0x23 first), not the
static-path res 0x21. Target corrected to 0x23. CaptureHost/CountHost no-op the new methods.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 15:49:07 -04:00
gamer147
9d9b5e4e45 feat(a2a): add IHost.WaitForInput hook (wait-for-input 0x72); trace parity preserved
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 14:48:46 -04:00
gamer147
6feba77b8e feat(a1): CLI (run/trace) + vm0.py --trace + per-scene differential test (A1 green)
C# VM is byte-identical to vm0.py across all 297 SC/SP scenes (offsets+halt+steps);
RECOVER passes; SC0000 = 27994 steps / 186 lines matching the Python prototype.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 13:55:11 -04:00
gamer147
8df5536529 feat(a1): VM core (operands+pointer semantics, handlers, emit-cap) + RECOVER
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 13:53:03 -04:00
gamer147
cb5527cdd4 feat(a1): SYS4 container parser (header + code decode + data_array_end shrink)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 13:51:32 -04:00
gamer147
876e92facc feat(a1): SYS4 XOR-FF cp932 string codec
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 13:50:35 -04:00
gamer147
ae495967f9 feat(a1): .NET 8 solution scaffold + Model + OpcodeTable loader
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 13:49:02 -04:00