Commit Graph

235 Commits

Author SHA1 Message Date
gamer147
89e4b26215 docs(gfx): record the animation opening slice results + honest AE* finding
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:56:11 -04:00
gamer147
08b5dbdbb6 feat(gfx): Godot per-object alpha tween + alpha-aware blit + --shot-settle
Retained-object animation channel wired into the compositor: wall-clock tween of
the anim channel over the GLOBAL clock (0x238), opacity from the 3rd vec component,
applied via an alpha-aware BlitLayer. Non-regressing (SC0000 opening page 2 pixel-
identical at settle 3 and 300; selftest OK). Added --shot-settle <frames> to capture
mid-tween.

NOTE: this drives RETAINED-object animations; the opening AE* explosion (AE001D/
AE002B/AE003B) is an immediate-mode slot-0 frame sequence paced by sleep/the render
loop, which our instant execution burst collapses -> needs frame-pacing (scene-
coroutine backlog), not this per-object channel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:48:23 -04:00
gamer147
bcb95f3bb5 feat(gfx): surface anim channel to the compositor via RenderObject.AnimState
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:39:18 -04:00
gamer147
0495628b99 feat(gfx): dispatch anim-start 0x234 + set-anim-clock 0x238 (revised global-clock model)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:38:15 -04:00
gamer147
328c25bc56 feat(gfx): label + dispatch set-anim-transform 0x21e/0x220 into GfxState
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:37:28 -04:00
gamer147
6339b93c72 feat(gfx): GfxState animation-channel model (per-object transform + global clock)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:34:52 -04:00
gamer147
53b84b8daf docs(gfx): decode anim_start/set_anim_clock + confirm SC0000 opening anim subset
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:19:15 -04:00
gamer147
bc9b817301 docs(gfx): implementation plan for the sprite animation subsystem (opening slice)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:05:43 -04:00
gamer147
f69b295c0e docs(gfx): RE the 0x21c-0x243 sprite animation cluster + subsystem spec
Native RE of the largest remaining SC0000 rendering GAP band: resolved every
handler in 0x21c-0x243 (+0x2bd/0x2bf) via the dispatch table (ctx[0x26c93+op]
from FUN_00413860). The cluster is ONE subsystem = sprite transform + animation/
tween; two members already named (0x234 anim_start, 0x238 set_anim_clock).
Decoded representative ops 0x220/0x21e (argc6, cmd-type 0xd, transform worker
(handle,op2,op3,f4,f5,f6); 0x21e normalizes floats /_DAT_00571c28 -> scale%);
worker gfx_anim_set_channel@0x47eaa0 arms an anim channel on the same object
model GfxState tracks. anim_start+set_anim_clock => a per-frame clock that makes
AE* fades animate rather than snap.

engine-re.md: full op->handler map + contract. Ghidra annotated (renamed
gfx_op_0x220_set_transform3_abs / gfx_op_0x21e_set_transform3_norm /
gfx_anim_set_channel + plate comments, saved).

Add the approved brainstorming spec for the opening-slice implementation
(wall-clock tween, opening-driven subset, passive GfxState + compositor tween +
alpha-aware blit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:57:42 -04:00
gamer147
a8f742cf57 feat(tools): per-scene opcode completeness tracker + 0x259 marker fix
Add tools/scene_opcode_coverage.py: histograms a scene's static opcodes and
classifies each vs the C# VM as impl / safe-noop / GAP (effectful op the VM
silently stubs). Implemented set is parsed live from VirtualMachine.cs case arms
(no drift); metadata from build/opcodes.json. Makes a half-rendered scene legible
("N ops still stubbed") instead of implying everything runs.

SC0000 baseline: 129 distinct ops, ~94.8% instruction-weighted handled, 68 GAP.
The tracker cross-checks opcodes.toml vs VM behavior and surfaced 0x259
(script-entry marker) missing its noop_headless flag -> reconciled in opcodes.toml
and rebuilt (regen: age_opcodes_himegari.py, opcode-reference.md).

Docs: tools-reference.md (tool row), phase-a-slice-plan.md (completeness gauge).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:57:25 -04:00
gamer147
962e55b43d fix(gfx): op 0x215 query registry is separate from the geometry store
The retained-mode "2nd CG renders off-screen" bug: GfxState conflated two
distinct native structures. It assigned a fabricated AcquireSlot() slot on
every GetOrCreate (called by all geometry/draw ops) and returned it from
QuerySlot (op 0x215). But Ghidra (gfx_op_0x215_register_query @0x42a0b0 /
gfx_op_0x1a2_registry_insert @0x42d360) shows 0x215 does map.find(handle) over
a registry populated ONLY by op 0x1a2 -- it never allocates a slot.

So a CG handle (never 0x1a2-registered) read back as "existing", took the
existing branch of label_12649, ran get-texture-size on the wrong slot (0),
got size 0, and computed dst = pos(0,0) - (w/2,h) = (-400,-600) -> off-screen.
The real engine returns -1 -> the fresh branch -> anchor from the INIT2 arrays
-> dst=(0,0).

Fix: GfxState keeps a separate _registry (HashSet) populated only by
Register() (op 0x1a2); QuerySlot returns the handle if registered else -1, and
no longer consults the geometry store or invents slots. Drop AcquireSlot /
GfxObject.Slot / the free-list.

Verified: Age.Cli gfx --boot SC0000.BIN -> all event CGs dst=(0,0), zero
(-400,-600) draws; Godot --boot pages 1/2/4 render opening CGs full-screen;
engine 44/44; sweep parity 284 exit / 13 STEP-LIMIT unchanged.

Docs: engine-re.md (query-registry-vs-geometry-store section), opcodes.toml
0x1a2/0x215 rebuilt; Ghidra helpers gfx_registry_map_find/hash_insert
annotated + saved. Tests rewritten to the native contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:09:11 -04:00
gamer147
0458d8e5f2 feat(gfx): Godot per-frame compositor on the surface/object model + restore --boot
Main._Process composites visible objects in ascending-handle order from their live
surface (resId->BMP); GodotAdvHost.DrawTexture no-op'd (retained), ResolveResIdTexture
added. Restored the --boot system-boot handling that the revert had dropped (Main.cs
was running cold, which is why nothing matched the oracle). Verified: booted opening
event CG renders correctly. Selftest green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 20:38:07 -04:00
gamer147
6775be2ac5 feat(gfx): surface+object render model (replaces flat layers)
GfxState: SurfaceStore (slot->{resId,colorkey} from create/set-texture) + object
SourceSlot/SrcRect/Visible (from draw-texture bind); SnapshotVisibleObjects returns
visible objects in ascending-handle order (=z-order) with their live surface resolved.
VM set/create/draw-texture wired to it. Oracle dumps visible objects. Erase removes the
object from the registry (faithful). Engine 44 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 20:28:47 -04:00
gamer147
48d1284a75 docs(gfx): RE the full render model (surfaces+objects+handle-order composite); redo spec+Phase1
Reversed create/set/draw-texture handlers + gfx_render_frame: surfaces at ctx+0x52bd4[slot]
(set-texture loads a file with a colorkey); objects in the ctx+0x408 registry reference a
surface by slot (live) + rect + position (V24) + visible bit; render iterates the registry
in ASCENDING HANDLE ORDER (= z-order) and composites visible objects. Answers both unknowns
(z-order = handle; slot 0 not special). Design spec + Phase 1 plan rebuilt on this model,
replacing the flawed flat-layer version. Ghidra annotated (gfx_op_0x1f8/9/b, gfx_object_bind_draw,
gfx_render_frame, gfx_object_composite, gfx_op_0x20c_present_frame).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 20:22:40 -04:00
gamer147
b24655ecec feat(gfx): gfx oracle dumps the retained layer list
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:46:54 -04:00
gamer147
6dda931bc0 feat(gfx): draw-texture records a retained layer in GfxState
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:46:21 -04:00
gamer147
fc9e46ee85 feat(gfx): retained DrawLayer list in GfxState (thread-safe)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:45:28 -04:00
gamer147
339d1bab4c docs(gfx): animated-compositor design spec + Phase 1 plan + animation RE
engine-re.md: the gfx animation/effects subsystem (gfx_anim_start/0x234, 0x1fd,
0x238 non-blocking anim clock, render model — the fades are host-loop-drivable, no
VM/host lockstep). Design spec: retained per-frame animated compositor, 4-phase.
Phase 1 plan (TDD): retained DrawLayer model + per-frame clear/recomposite. Ghidra
handlers/workers annotated (0x234/0x1fd/0x238/gfx_anim_start).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:41:13 -04:00
gamer147
9859fd88b9 docs(gfx): render drift RESOLVED (native ops + system boot); align living docs
engine-re.md: 0x1f7 erase correction + 'The render drift's SECOND half' (missing
INIT2 boot state via SYSTEM4) + gfx-command-buffer backlog marked DONE + worker
annotations noted. phase-a-slice-plan A2b: drift RESOLVED (both halves; CGs render
screenshot-confirmed; residual = AE* alpha/blend + cold anchors). tools-reference:
gfx --boot + godot --boot. (Status memory + MEMORY.md + CLAUDE.md updated on disk.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:09:34 -04:00
gamer147
62fa3231bc fix(gfx): 0x1f7 is a registry ERASE, not a create
RE correction: op 0x1f7's worker gfx_registry_erase_range (@0x47d8b0) loops
gfx_registry_erase over [handle, handle+count) — a teardown, not a create.
Objects are created lazily by the geometry SET ops (gfx_object_get_or_create).
GfxState.EraseRange + VM case + tests; opcodes.toml renamed gfx-elem-create ->
gfx-elem-erase; Ghidra handler + workers annotated. Booted SC0000 CG geometry
unchanged for the working CGs (no regression). Engine 40 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:05:31 -04:00
gamer147
a6b07356c7 feat(gfx): gfx --boot runs SYSTEM4 state prefix (INITCONFIG/INIT2/INIT) before the scene
Reuses GameSession to carry boot state into the target scene. INIT2 sets the gfx
handle array (0x62455..) SC0000 assumes; with --boot the objects de-collapse (15
distinct) and several CGs render correctly (EV049AA/EV052DA dst=(0,0)). Residual:
object-slot CGs still start with anchor (0,0) — cold gfx objects vs the real game's
warm ones. Confirms the root cause: missing system-boot state, not a gfx-op bug.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 18:44:39 -04:00
gamer147
e9818b9d71 feat(gfx): CLI gfx oracle reports per-object slots
Dumps vm.Gfx object->slot assignments after a scene run. Confirms 0x215 now
returns distinct slots (SC0000: 8 objects, slots 4-11) — but also reveals the
CG geometry (dst) is still drifted, driving the Phase 4 investigation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 18:08:41 -04:00
gamer147
fc55532ade test(gfx): drift regression — independent per-object geometry
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 18:03:30 -04:00
gamer147
85e821a4f4 feat(gfx): colored-draw ops store packed ARGB; alpha blend deferred + guarded
0x202/0x203 pack (alpha,color) via GfxState.PackColor onto the object; the actual
alpha/additive blend is deferred and surfaced once through the trace sink (observe-
only, parity held). Full suite 37 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 18:02:59 -04:00
gamer147
648ccca2e7 feat(gfx): execute gfx command-buffer query/set/lifecycle ops in the VM
Gfx property on VirtualMachine + dispatch cases for the query ops (0x215/0x216/
0x218/0x21a), set ops (0x217/0x219/0x1ff/0x212/0x213) and lifecycle (0x1a2/0x1f7/
0x1fa). Query ops write results to operands like get-texture-size; set ops mutate
GfxState. Round-trip + distinct-slot tests pass; full suite 36 green (parity held).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 17:48:31 -04:00
gamer147
7c03371519 chore(gfx): set dispatch labels for the gfx command-buffer ops
VM dispatches on the opcodes.toml label field (OpcodeTableJson), so the 14 gfx
ops' labels are set to their dispatch strings. Ops still stubbed until the VM
cases land (Task 3.3+); full suite green (34), lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 17:44:32 -04:00
gamer147
f66b5e7f16 feat(gfx): GfxState host-agnostic command-buffer model
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 17:33:51 -04:00
gamer147
dac684a5e6 docs(gfx): Phase 3 TDD plan + resolve vector-pairing risk in the spec
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 17:24:44 -04:00
gamer147
a4e173fc0f docs(gfx): Phase 2 design spec — host-side gfx command-buffer model
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 17:14:29 -04:00
gamer147
e0ac60e668 docs(gfx): reverse the full gfx command-buffer op contract (Phase 1)
Ghidra-read all 13 still-stubbed gfx-family handlers via the dispatch table
(0x1a2/0x1f7/0x1fa/0x1ff/0x202/0x203/0x212/0x213/0x216/0x217/0x218/0x219/0x21a);
recovered 0x219 (was unanalyzed). Every op writes a cmd-type into the current
object record then SETs fields (native worker) or QUERYs fields (returns to
operands). Recorded the op-contract table in engine-re.md, set opcodes.toml
semantics (source=investigation, confidence=high), renamed handlers in Ghidra.

Key finding: the drift has TWO stubbed drivers, not one — 0x215 (slot-select)
AND 0x218/0x21a (per-object geometry vectors feeding the anchor-preserve math).
The subsystem is a per-object record model (slot + position/anchor 3-vectors +
color) queried by the ops; native DirectDraw workers need not be modelled. This
is the spec for Phase 2 (host-side model design).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 17:01:14 -04:00
gamer147
e6f6803a15 docs(gfx): implementation plan for the gfx command-buffer subsystem
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:50:29 -04:00
gamer147
ea4bf99fab docs(gfx): settle render drift as native cmd-buffer op 0x215 (Ghidra), align docs
Read op 0x215's real handler FUN_0042a0b0 (resolved via the dispatch table
ctx[0x26c93+op]; Kelebek's 0x421160 is VA-drift). It writes cmd-type 5 into the
current gfx-object record and returns a std::map::find over an engine-internal
registry populated by sibling gfx ops (0x1a2 hash insert). The return is native
command-buffer state, not the VM global bank -> seeding story-state cannot fix
the drift. Verdict: (b) a genuine native op, NOT (a) state-divergence.

Reconcile the previously contradictory drift accounts onto one canonical home
(engine-re.md op 0x215), with opcodes.toml carrying the opcode-level semantics
and phase-a-slice-plan / tools-reference / frida README corrected to point at it
instead of repeating the disproven state-divergence conclusion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:40:59 -04:00
gamer147
72a43c9298 docs(roadmap): backlog the state-divergence differ (trace-facility consumer)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:50:07 -04:00
gamer147
f700a61213 Merge feat/engine-diagnostics: engine-level diagnostics trace facility
Typed ITraceSink/TraceEvent seam in Age.Engine; CallScript/OnStub relocated
off IHost to trace events; Null+Text sinks; CLI --trace; Godot dispatch hack
retired onto GodotTraceSink. Default null sink keeps parity byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:29:35 -04:00
gamer147
ad5fa85d82 chore(godot): track GodotTraceSink.cs.uid (Godot import artifact)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:27:51 -04:00
gamer147
527fba02e8 feat(cli): --trace flag streams engine diagnostics (run/play/sweep)
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>
2026-07-07 15:25:59 -04:00
gamer147
d202990b7b refactor(godot): report subroutines via GodotTraceSink, not the IHost queue
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:23:25 -04:00
gamer147
2a71847a5d 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
e010393b8a 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
10cf79a41b 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
6981bd0e41 docs: engine diagnostics implementation plan
6 TDD tasks: seam types -> VM emission -> IHost slimming -> Godot rewiring
-> CLI --trace -> verify+memory. Parity guarded by default NullTraceSink.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:14:23 -04:00
gamer147
59a67a240d docs: engine diagnostics / trace-facility design spec
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>
2026-07-07 15:05:13 -04:00
gamer147
41663c8116 docs: mark A1/A2a vm0-parity + 186-selftest records as superseded
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 14:22:04 -04:00
gamer147
7c751200bb docs: sync living references + roadmap to call-script solved/executing
- tools-reference: run/play/sweep execute call-script (provider); trace/audio/gfx
  stay provider-less; corrected stale sweep numbers (284 exit/13 STEP-LIMIT) and the
  Godot --selftest description (synthetic vs headless, not vm0/186); added --scene.
- roadmap: call-script marked SOLVED (resolution + execution) throughout; no longer a
  'long pole'; per-game registry now automatic from SYS4INI.
- phase-a-slice-plan: SC0240 live-demonstration note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 14:21:09 -04:00
gamer147
7f92a35966 Add subroutine-execution diagnostics + Godot --scene
- 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>
2026-07-07 14:10:55 -04:00
gamer147
6597dae746 docs: record provider-everywhere wiring + synthesize-test-data principle
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 13:50:09 -04:00
gamer147
8031f8e33a Wire provider into Godot (play + selftest); honest full-handling selftest
- 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>
2026-07-07 13:48:47 -04:00
gamer147
6604a1fa54 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
96d12992a6 Merge feat/callscript-resolution: call-script resolution + C# VM execution
- 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>
2026-07-07 13:22:44 -04:00
gamer147
3b50dab0d2 docs: record call-script execution results (slice plan) + impl plan
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 13:20:05 -04:00