Commit Graph

179 Commits

Author SHA1 Message Date
gamer147
54bd9a7006 docs: scene-entry state snapshot (capture_global_writes + --state)
engine-re.md: the write-log method, plaintext-value insight (store is obfuscated),
vm_operand_write ABI, the packer poll-until-unpacked fix, and the validation
(34k-global capture seeds the VM to match the engine's whole opening). Plus the
resync-tolerant align() / operand-hook blind-spot notes. tools-reference: the new
tool + trace --state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 13:32:52 -04:00
gamer147
d2e9cc5a99 re: record differential oracle outcome + first divergence
engine-re.md: the offset-path diff method, the working capture (operand hook
0x41b940; tick 0x410fb0 sees ecx!=ctx), the two capture caveats (hook-before-load
gating + operand-mode argc>=1 filter), SC0000 codebase id, and the first
divergence found (op 0xa0 jcc @ 0x8d on uninitialized G[0x6c1], the ADV-chrome
enable — a two-boot-gap state hole, not the predicted coroutine yield).
tools-reference.md: trace_engine_ops.py, diff_optrace.py, --trace-json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 12:00:12 -04:00
gamer147
5cd5abb00a re: hot-helper naming (lever #2, pivoted from STL/CRT auto-naming)
Function ID recon = WEAK (0 matches; bundled FidDbs don't cover the VC9
static runtime) and our library workers are already named. Pivoted to
naming the hottest documented-but-unnamed helpers by call-count: named 5
(~2400 call sites) in Ghidra — vm_operand_fetch@0x41b940 (1021 refs),
vm_operand_write@0x425fb0, vm_operand_lvalue@0x415f30,
__security_check_cookie@0x54f981 (692), operator_new@0x5502be (533).
Validated: gfx_op_0x215 reads near source-level. Spec/plan + engine-re.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 10:38:29 -04:00
gamer147
1b151f6e17 re: apply EngineCtx struct to /v2 handlers; doc + canonical-map wiring
Created the EngineCtx Ghidra struct (35 fields, 0xa1000) and retyped all
419 dispatch handlers' this -> EngineCtx* (CUSTOM_STORAGE, ctx in ECX).
Handlers now decompile ctx->cur_ctx_index / ctx->run_state_flags etc.
Validated: sleep_op_0xc8, gfx_op_0x215. tools-reference + engine-re.md
wired; CLAUDE.md canonical-map updated (root, outside repo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 10:08:06 -04:00
gamer147
e166cab2d5 re(frida): import-map applied to /v2 — 248 Win32 APIs named; validated
Labeled the module-resident IAT at RVA 0x16f000 (imp_<dll>_<func> x248,
0 clobbers). Validated in decomp: FUN_0044f390 -> imp_kernel32_CreateFileA
/ SetFilePointer; sleep_timer_arm -> imp_winmm_timeGetTime (pins
DAT_0056f3d4). d3d9/heap-resolved DLLs unlabeled by design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 09:26:19 -04:00
gamer147
23f518f39b re: document pe-sieve IAT failure on packed AGE.EXE; queue Frida import-map
pe-sieve /imp on the running game gave ~17 genuine imports (packer
bootstrap + one-per-DLL seed) and 300+ spurious stray-DWORD guesses:
the exe ships a zeroed IAT resolved via GetProcAddress, so there is no
conventional IAT to rebuild. Do not graft the output. Task B re-scoped
to a Frida live import-map (runtime_addr->dll!Func, RVA->name, label the
/v2 image); design captured in engine-re.md runbook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 08:54:08 -04:00
gamer147
ace8ddf8eb re: auto-label all opcode dispatch handlers from FUN_00413860
Extract op->real-handler map (handler(op)=ctx[0x26c93+op]) from the
registration routine's override stores; ghidra_handler_map.py +
build/op-handler-map.json (420 overrides). Cross-check vs opcodes.toml
found 0 real drift. One-shot Ghidra pass then labeled the /v2 image:
281 raw FUN_/LAB_ handlers -> op_0xNN_handler, 107 bare VAs -> functions,
31 hand-named preserved, opcode plate comment on every handler.

Includes the Task A spec + plan and the two-program (/v2 vs SMM) gotcha.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 08:32:01 -04:00
gamer147
51f6a421dc docs(re): scene-coroutine framework RE head-start (0x7b/0x7c/0x140 + gate) + magic-circle-stuck note
Head-start for the next slice: the fix for the slot-0 collapse is running label_125bd
via the scene-coroutine framework. 0x7b=yield-save, 0x7c=resume, 0x140=LABEL (target
TBD), G[0xaba5c] gate. Plus the revealed magic-circle-persists-across-transition issue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:04:40 -04:00
gamer147
ac481b4002 diag+docs: confirm grey-BG root cause = unfilled slot-table (label_125bd via stubbed coroutine op 0x140)
Traced end-to-end (AGE_DIAG_SETTEX): set-texture slot=G[0x62452] <- query-gfx-object?
(-1 for unregistered CG handles) -> fallback lookup-array-2d(rec[s3]=G[0x3239])=0
because the slot table is never filled: label_125bd (SC0000 0x50f, slots 4..13) is
gated behind the scene-coroutine framework (0x140 coroutine-yield, stubbed). Adds
env-gated VM set-texture/query slot diagnostics + GfxState.IsRegistered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:55:50 -04:00
gamer147
9dd96bbc84 docs: grey-BG root cause (color-op alpha=tint-strength; slot-0 collision) + --gfx-log tool
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:42:20 -04:00
gamer147
130eadee3d docs(re): SC0000 anim cluster op->field map (in-scope setters/queries + deferred)
Reversed via dispatch table: 0x22f/0x229 position, 0x239/0x231 spritesheet,
0x232 animated-color glow, 0x228/0x23f queries. Deferred 0x21f/0x223 matrix/scale
+ 0x236 movie. Ghidra renamed+annotated+saved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:58:55 -04:00
gamer147
34cdfeda0e docs(re): interpolation RE pass stalled — anim-interp is ping-pong on frame clock; 0x202 consumer unlocated
Confirms both slice-A deferrals (smooth color-anim interp + additive). Ghidra
gfx_object_anim_interpolate annotated+saved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 18:48:50 -04:00
gamer147
fe61f7287c docs(plan): blend & transparency slice A implementation plan + colorkey RE
TDD plan (BlendMath -> RenderObject resolution -> host colorkey/alpha/tint blit
-> surfaceless fade fill -> docs). Records the reversed colorkey format and the
0x202/0x203 color workers in engine-re.md (Ghidra annotated+saved).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 18:10:32 -04:00
gamer147
1f9bde3739 docs+godot: finish frame-cadence RE notes + headless shot null-guard
Prior-session WIP: RE findings on the engine frame cadence (engine-re.md,
phase-a-slice-plan.md, tools-reference.md) and a null-guard so headless
--shot-sequence advances without a rendered viewport texture.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:26:27 -04:00
gamer147
37fbbd278d docs: correct the overstated 'opening animates' claim across all docs
The frame-paced-sleep slice did NOT make the opening burst animate (only the
one-shot dramatic pauses). Correct the canonical result (phase-a-slice-plan),
the RE doc (engine-re), the opcode source+generated ref (opcodes.toml 0xc8),
and add correction banners to the point-in-time spec/plan. Also folds in the
diagnostics + headless halt-at-wait results into phase-a-slice-plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:53:08 -04:00
gamer147
6044a0d412 re(gfx): decode sleep 0xc8 timing (non-blocking ms timer) + 0x20c present
- sleep_op_0xc8 @0x420ec0: arms a non-blocking main-loop-polled timer
  (sleep_timer_arm @0x44cff0); operand = milliseconds. Also carries
  anti-tamper + gfx cmd-type 3 (not needed host-side).
- 0x20c = gfx_op_0x20c_present_frame (host presents continuously -> noop_headless).
- Ghidra annotated + saved; opcodes.toml rebuilt (lint clean); engine-re.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 08:57:22 -04:00
gamer147
e52186a268 docs(gfx): correct 'immediate-mode slot-0' -> engine is RETAINED (native-verified)
The animation-slice note mis-called the SC0000 opening 'immediate-mode slot-0
blits', inferred from our own gfx oracle (which mis-reported slot 0). Verified from
native code + raw bytecode: draw-texture (0x1fb) -> gfx_object_bind_draw@0x47e870
binds a RETAINED object by handle (stores the slot INDEX, a live per-frame ref, not
a snapshot). The opening is a sleep-paced sequence of retained objects with distinct
handles + per-object working slots (CG loader: handle=CG_array[G[0x62450]] INIT2
array, slot=G[0x62452]). Our VM collapses the paced sequence -> only the final state
shows -> needs frame-pacing (scene-coroutine/sleep), not this alpha channel.

engine-re.md: new 'opening render path is RETAINED' subsection. Ghidra: annotated
gfx_op_0x1fb_draw_bind (gfx_object_bind_draw already documented retained).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 08:24:30 -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
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
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
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
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
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
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
14ad23b8c7 Resolve call-script dispatch: id = raw SYS4INI file index
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>
2026-07-07 12:44:51 -04:00
gamer147
1323ec8852 chore: pe-sieve32.exe -> bin/ (3rd-party binary convention) + doc refs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:46:17 -04:00
gamer147
47dbdbd5fc docs: correct u00428010/op-0x1a2 finding via anchored dispatch table
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>
2026-07-07 11:35:42 -04:00
gamer147
0ae4f607c0 docs: native-engine RE — Ghidra+MCP loop + opcode dispatch table + u00428010 correction
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>
2026-07-07 11:29:02 -04:00