Commit Graph

471 Commits

Author SHA1 Message Date
gamer147
165a9f616c feat(godot): drive the anim interpolator off FrameClock (paced spritesheet+glow)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:09:02 -04:00
gamer147
a014b90189 feat: wire SC0000 anim cluster ops (0x22f/0x229 pos, 0x239/0x231 sheet, 0x232 glow, 0x228/0x23f query)
Coverage 67->74/129; parity held (79/79, sweep 284/13).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:07:51 -04:00
gamer147
b9ca284f90 feat: port anim interpolator (ping-pong spritesheet cell + color glow) on nowMs
BlendMath.PingPong + SnapshotVisibleObjects(nowMs); no-arg overload keeps
headless callers deterministic (parity held, 76/76 + sweep 284/13).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:05:00 -04:00
gamer147
7ca51bfdf3 feat: add src-rect (spritesheet) + animated-color channel fields/mutators
Adapted to Task-1 RE: position is a direct V24 set (not ping-pong); the
oscillating channels are src-rect scroll (0x231/0x239) and color glow (0x232).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:02:46 -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
a6377c6a60 docs(plan): SC0000 anim/transform/spritesheet cluster implementation plan
TDD plan: RE workers (known dispatch addresses) -> channel model -> ported
ping-pong interpolator on FrameClock -> wire setter ops -> host apply. Fixes
motion + spritesheet-cell + 'mach 5' pacing. Rotation/scale gated on Task 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:25:07 -04:00
gamer147
4589b98be5 docs(spec): SC0000 anim/transform/spritesheet cluster design
One-unit scope for the gfx animation cluster (0x1fd, 0x21c-0x243): channel
model + faithful gfx_object_anim_interpolate port on FrameClock (fixes 'mach 5')
+ spritesheet cell selection. Grounded in the recovered dispatch table
(handler(op)=ctx[0x26c93+op]; Kelebek labels are drift) + the reversed
interpolator. Bounded RE Task-0 with known handler addresses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:06:05 -04:00
gamer147
f33c824981 Merge feat/blend-transparency: colorkey + per-object alpha/tint blend (graphics slice A)
Fixes the SC0000 opening's opaque grey-wall: layers now alpha-blend and honor
per-surface colorkey. Hybrid arch (engine resolves RenderObject blend plan,
Godot host blits). Deferred: smooth color-anim interpolation + additive/glow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 19:13:18 -04:00
gamer147
689d3ef433 docs: record blend/transparency slice A (colorkey + alpha/tint + fade fill)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 19:10:31 -04:00
gamer147
ce6be0ee9a feat(godot): surfaceless color-fill quads for AE* fades (opening no longer opaque-grey)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 19:09:18 -04:00
gamer147
6992f303f6 feat(godot): colorkey bake + per-object alpha/tint blit
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 19:07:48 -04:00
gamer147
143fe8258c feat: resolve per-object alpha/tint/blend into RenderObject (colorkey retained)
0x202/0x203 now route through GfxState.SetObjectColor (sets HasColor);
SnapshotVisibleObjects resolves Alpha/Tint/BlendKind. Drops the stale
'alpha deferred' trace stub — alpha/tint is now consumed by the compositor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 19:06:34 -04:00
gamer147
4ee7a26e3f feat: add BlendMath (colorkey match + ARGB unpack)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 19:04:12 -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
d5ea8aa84e docs(spec): blend & transparency slice A design (colorkey + alpha/tint + blend mode)
Hybrid architecture (engine resolves RenderObject blend plan, host blits).
Grounded in fresh Ghidra RE of gfx_object_composite/blit + the 0x202/0x203
color workers (renamed+plate-commented, saved). Includes a bounded RE Task-0
(colorkey format, blend-mode source, color/alpha anim coupling).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 18:02:35 -04:00
gamer147
0f81ab248f docs(slice): record frame-stepped VM as done+merged; note residual graphics gap
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:39:17 -04:00
gamer147
cdc50b571f docs: frame-stepped-vm spec+plan + frame-cadence Frida probes
Design artifacts for the merged frame-stepped VM work (throttle the Godot VM
to a per-frame op budget). Probes measured the native ~1788 ops/sec cadence
and uncapped D3D9 Present that motivated the wall-clock-op-rate approach.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:34:52 -04:00
gamer147
2f4527553b Merge feat/frame-stepped-vm: throttle Godot VM to per-frame op budget
Fixes the SC0000 opening speeding through: FrameClock (virtual clock + op
budget) + IHost.FrameYield per-opcode hook (no-op headless, parity held) +
Godot host throttle/Sleep on the clock.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:33:35 -04:00
gamer147
dfaad2e95a feat: throttle Godot VM to a per-frame op budget on FrameClock (fixes opening speed-through)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:32:29 -04:00
gamer147
cd93ebae2e feat: add IHost.FrameYield per-opcode hook (no-op headless, parity held)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:29:50 -04:00
gamer147
82492d8a6c feat: add FrameClock (virtual clock + per-frame op budget)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:27:41 -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
791ea2ca0f fix(vm): faithful headless — halt at wait-for-input instead of plowing
The headless divergence that sent us chasing a phantom 'sleep' spin: op 0x72
wait-for-input was a no-op headless, so a run plowed past all 166 of a scene's
prompts into code no real playthrough reaches (SC0000 -> the name-entry poll
loop, spinning sleep 1 493k x to STEP-LIMIT). That path is a fiction.

Fix: VmOptions.HaltAtWaitForInput -> the VM halts (reason 'wait-for-input') at
0x72. run/play default to faithful (SC0000 now halts at ~402 steps, 0 sleeps,
matching the real run's path to the first prompt); --plow opts into the old
walk-every-page coverage. sweep stays plow by default (dialogue oracle, 284/13
unchanged); --halt-at-wait makes all 297 scenes halt cleanly at their first
prompt (0 STEP-LIMIT). Godot unaffected (really blocks on input; flag false).

Engine 58/58 (2 new); sweep default 284/13 unchanged; Godot selftest OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:45:32 -04:00
gamer147
9ccfc43959 feat(diag): aggregating + filtered trace sinks (histogram, op-filter, per-script)
The existing trace framework only had a flat text formatter, so every question
became 'dump millions of lines, then grep'. This session that cost a long wrong
detour. Add, all observe-only (parity preserved):

- HistogramTraceSink: execution counts per opcode AND per call-site (script:pc)
  with a sample operand. Dumped sorted after the run. This is what instantly
  showed the 493k headless 'sleep's are INPUTNAME.BIN:0x1c3 (a name-entry poll
  loop), not the opening.
- TraceSinkBase: tracks the frame stack -> attributes each step to its REAL
  script (nested call-script frames included) = the 'which script is this pc in?'
  answer a bare step trace can't give.
- TextTraceSink: op-filter (--trace-ops sleep,draw-texture,...) + script:pc tags.
- CompositeTraceSink: fan-out (text + histogram + Godot's call-script queue).
- OpcodeTable.ByLabel: mnemonic -> opcode for --trace-ops.
- CLI: --trace-histogram, --trace-ops, robust --trace-file (mkdir -p).
- Godot: --trace-histogram <file> profiles the REAL run (headless flow diverges:
  real run to page 1 is 562 steps / 0 sleeps vs headless 2M steps / 493k sleeps).
- Also: --sleep-scale <f> debug knob to slow the paced opening for inspection.

Engine 56/56 (4 new); sweep parity 284/13; Godot builds + dogfooded end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 09:50:07 -04:00
gamer147
35d3e97b27 chore: remove stray *.bak files (perl -i backups swept in by a dir git-add)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 09:13:11 -04:00
gamer147
52fccd25b9 docs(gfx): record the frame-paced sleep slice + SC0000 GAP shrink
sleep 0xc8 -> impl, 0x20c present -> safe-noop; handled 65->67/129 (51.9%).
Opening now animates through distinct sleep-paced frames (shot-sequence verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 09:08:31 -04:00
gamer147
2ed5eed261 feat(godot): --shot-sequence per-frame capture for verifying paced animation
Dumps one PNG per _Process frame (auto-advancing past input waits), so a
time-based sleep-paced effect can be verified as distinct frames. Confirmed
the SC0000 opening now steps through paced AE*/character/CG frames instead of
jumping straight to the final state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 09:07:22 -04:00
gamer147
0d666523c7 feat(godot): Sleep blocks the VM thread so the compositor presents paced frames
op 0xc8 -> Thread.Sleep(duration ms) on the VM background thread (capped 10s);
main thread keeps compositing -> the sleep-paced opening burst gets frames to show.
Unit = ms (RE-confirmed). Selftest OK (plumbing parity; synthetic scene has no sleep).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 09:02:01 -04:00
gamer147
8ac9e6675b fix(gfx): serialize GetOrCreate/Register/Release on _lock (compositor race)
Once sleep paces the VM thread, the main-thread compositor's SnapshotVisibleObjects
truly overlaps VM-thread _objects/_registry writes. GetOrCreate/Register/Release were
unlocked -> 'Destination array is not long enough' under concurrent enumeration.
_lock is re-entrant so BindDraw/EraseRange (already locked) stay correct. 52 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 09:01:13 -04:00
gamer147
5f245d4cf4 feat(vm): IHost.Sleep seam + dispatch 0xc8 (headless hosts no-op; parity held)
- IHost.Sleep(long duration); VM case "sleep" forwards the raw operand.
- 8 non-Godot hosts no-op Sleep; RecordingHost records it.
- SleepDispatchTests (51 green); sweep unchanged 284 exit / 13 STEP-LIMIT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 08:59:52 -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
22a972cc85 docs(gfx): implementation plan — frame-paced sleep
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 08:49:29 -04:00
gamer147
3d263a289a docs(gfx): spec — frame-paced sleep so the SC0000 opening animates
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 08:44:27 -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
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