Fix native retained presentation batching
This commit is contained in:
@@ -522,18 +522,23 @@ Both branches finish with
|
||||
`0x224`, which clears the native gfx command queue at `ctx+0x418`. These handlers are now named,
|
||||
commented, and saved in the Ghidra image.
|
||||
|
||||
None of `0x21c/0x224/0x243` waits for the per-object transform duration. The normal-path boundary is
|
||||
the engine's rate-limited one-op interpreter cadence plus continuous retained compositing. This matters because
|
||||
the earlier Frida probe hooked `vm_operand_fetch`: its ~1,788/s result counts **operand reads**, commonly
|
||||
several per completed opcode. Feeding that number to the port's per-completed-opcode `FrameYield` made
|
||||
the `0xcbc0` section reach only scale 1.44 in 226 ms before teardown.
|
||||
The matching native presentation trace corrects the earlier cadence model. Ordinary opcode execution is
|
||||
**burst-fast between presentation services**, while `0x21c` is the render/wait boundary: it parks the
|
||||
interpreter and `gfx_render_frame` repeatedly samples visible finite one-shot channels and queued surface
|
||||
commands until dirty presentation state clears; `0x224` then clears the command queue. `0x20c` is a single
|
||||
explicit publication on the skip branch.
|
||||
|
||||
The corrected host limiter is refresh-independent and runs at 200 completed opcodes/s. It resets accumulated
|
||||
credit after sleep/input parking, and clicks are accepted only while actually waiting, so clicks during a
|
||||
visible animation cannot pre-arm the next wait. A normal-clock replay retained `0xcbc0` for 1,798 ms at
|
||||
the intermediate 215/s calibration; the final 200/s replay kept it alive for 2,014 virtual ms, beyond the native
|
||||
1,890 ms endpoint. `--speed` scales VM, sleep, and animation clocks together for comparison without
|
||||
changing these virtual-time relationships or auto-advancing waits.
|
||||
At the opening AE001D passage, native `0x125a6` rendered the preceding state, then both object binds plus
|
||||
mode-1 `0x203` and target `0x202` writes (`0xd5a/0xd63/0xd73/0xd8a`) completed in about 5 ms with **no render
|
||||
between them**. Their first composition was the following repeated `gfx_render_frame` loop at `0x21c`.
|
||||
Likewise, the explicitly presented mode-0 white CG at `0x125a6` survived only about 10 ms before that next
|
||||
boundary. The port's former 200-completed-op/s throttle stretched the same burst across many display frames;
|
||||
that average had folded service waits into execution time and was not an opcode scheduler rate.
|
||||
|
||||
The Godot host therefore leaves ordinary `FrameYield` non-blocking and publishes retained mutations only at
|
||||
`0x20c`, `0x21c`, sleep, and stable input waits. `0x21c` waits for visible finite color/matrix channels plus
|
||||
`0x223`; ambient cyclic/spritesheet pulses do not block, and click forcing remains restricted to `0x223`.
|
||||
This is a native-evidenced scheduler correction, not a guessed duration sleep.
|
||||
|
||||
##### The opening render path is RETAINED, not immediate-mode (2026-07-08, ground-truth correction)
|
||||
|
||||
|
||||
@@ -86,13 +86,13 @@ Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_t
|
||||
- **evidence:** Ghidra handler 0x427330 calls vm_operand_write(1, ctx+0x6dbd4). Producer recovered 2026-07-10: adv_update_read_text_skip_state@0x406cd0 and op 0x6e/0x71/0x72 maintain the field from message_ReadTextSkip plus current-PC read-history lookup; adv_interpreter_tick consumes it in click/read-skip control. It is not the 0x223 surface-transition progress flag.
|
||||
|
||||
### 0x21c `mark-frame-yield` (mark-frame-yield, argc 0)
|
||||
- **summary:** Set native run-state bit 0x400; in normal ADV playback this is the queued foreground-transition yield/resume boundary.
|
||||
- **summary:** Set native run-state bit 0x400; in normal ADV playback this is the retained-presentation render/wait/resume boundary.
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
- **depends on:** 0x223, 0x1c7, 0x1cc
|
||||
- **depended on by:** 0x223
|
||||
- **evidence:** Ghidra handler 0x417520 sets cmd-type 1 and ORs ctx+0xa0ce4 with 0x400. SC0000 label_1235a's jcc reaches it when the OR of op 0x1c7/0x1cc is zero; synchronized port trace confirmed this is the normal path after 0x223.
|
||||
- **evidence:** Ghidra handler 0x417520 sets cmd-type 1 and ORs ctx+0xa0ce4 with 0x400. capture_presentation_trace.py: after 0x125a6 render, 0xcb8e/0xcb98 bind and 0xd5a/0xd63/0xd73/0xd8a mode+targets execute without render; repeated gfx_render_frame begins only at 0x21c. 2026-07-10.
|
||||
|
||||
SC0000 label_1235a reaches this when the OR of 0x1c7 message-skip and 0x1cc read-skip state is zero (normal playback). Native run-state bit 0x400 yields the interpreter while the queued foreground presentation advances. The interactive port starts pending 0x223 commands here, parks only the VM thread while per-frame compositing continues, and resumes after natural or click-forced completion.
|
||||
SC0000 label_1235a reaches this when 0x1c7/0x1cc are zero. Native run-state bit 0x400 parks the interpreter while gfx_render_frame repeatedly samples finite one-shot object channels and queued surface commands; op 0x224 follows after dirty state clears. Native trace proves AE001D bind, mode-1 0x203, and 0x202 targets complete in one 5 ms batch with no render, then first compose here. The port publishes and waits for visible finite one-shot channels or 0x223 commands; click forcing remains limited to the latter.
|
||||
|
||||
## draw
|
||||
|
||||
@@ -153,7 +153,7 @@ SC0000 label_1235a reaches this when the OR of 0x1c7 message-skip and 0x1cc read
|
||||
- **depended on by:** 0x20d, 0x223
|
||||
- **evidence:** Ghidra: dispatch table FUN_00413860 param_1[0x26e9f]=gfx_op_0x20c_present_frame; 0x26e9f-0x26c93=0x20c. 2026-07-08.
|
||||
|
||||
Native handler gfx_op_0x20c_present_frame (dispatch ctx[0x26c93+0x20c]) -> gfx_render_frame @0x4820b0. Godot composites continuously. In label_1235a the OR of 0x1c7/0x1cc is nonzero on the read/message-skip branch, which resets the animation service then presents; the port starts and snaps any pending 0x223 transition to its endpoint here. Normal zero-state playback branches to 0x21c, which owns wait/resume. Headless hosts remain non-blocking. Kelebek label u00416200 was VA-drift.
|
||||
Native handler gfx_op_0x20c_present_frame -> gfx_render_frame @0x4820b0. This is an explicit retained-state publication boundary, not a continuously visible object-store mutation. The read/message-skip branch resets the animation service then presents; the port publishes and snaps pending 0x223 state here. Normal playback branches to 0x21c, which owns repeated render/wait/resume. Headless hosts remain non-blocking.
|
||||
|
||||
### 0x212 `set-gfx-field64` (set-gfx-field64, argc 2)
|
||||
- **summary:** 0x212 (obj_idx)(val) — gfx cmd-type 5. Handler gfx_op_0x212_set_field64 @0x4230c0: obj=[ctx+0x14d54 + obj_idx*4]; if obj: *(obj+0x64)=val. Sets one per-object field. See docs/engine-re.md gfx op-contract table.
|
||||
|
||||
@@ -980,3 +980,31 @@ opcode/ctx lint, 481-script decode validation, and RECOVER clean. SC0000 coverag
|
||||
(65.9%)**, 44 GAP ops / 598 GAP instructions. The differential oracle retains its prior branch-state
|
||||
divergence after `0x12031`; it agrees through the executed `0x202`/`0x203` sequence and does not implicate
|
||||
this slice. Windows CR-aware whitespace validation is clean.
|
||||
|
||||
### A2b — retained presentation batching / native scheduler boundary ✅ DONE (2026-07-10)
|
||||
|
||||
The synchronized native trace resolves the white-hold residual. At the AE001D passage, the native engine
|
||||
binds `0xcb8e/0xcb98`, applies mode 1 at `0xd5a/0xd63`, and arms `0x202` at `0xd73/0xd8a` within one
|
||||
roughly 5 ms opcode burst. No `gfx_render_frame` occurs between those mutations; the first composition is
|
||||
the following `0x21c` service loop. The preceding explicit `0x20c`/`0x125a6` mode-0 white frame lasts only
|
||||
about 10 ms. The port's 200-completed-op/s throttle had stretched that between-present burst across multiple
|
||||
window frames, making retained intermediate state look like a long white stall.
|
||||
|
||||
Godot now leaves ordinary opcode `FrameYield` unthrottled and publishes retained state only at proven
|
||||
presentation-capable boundaries: `0x20c`, `0x21c`, sleep, and input wait. `0x21c` renders while visible finite
|
||||
one-shot channels or `0x223` commands remain active; hidden stale records and ambient cyclic channels cannot
|
||||
hold it open. The first implementation incorrectly included hidden records and parked at the first CG; the
|
||||
visible-only correction was re-run through 14 pages and the full AE burst.
|
||||
|
||||
Windowed before/after evidence at the same SC0000 sites: the old capture exposed AE001D in mode 0 for six
|
||||
compositor frames and held one identical white PNG state for 25 frames. The corrected capture executes bind,
|
||||
mode 1, and color targets in frame 64 and first publishes AE001D already in mode 1 at `0x21c`; the old mode-0
|
||||
AE object state is absent from the object/pixel timeline. Remaining short white flashes are explicit native
|
||||
present/color effects, not the prior between-op hold. Draw-string `0x204/0x7a`, movie `0x236`, and SFX remain
|
||||
separate slices.
|
||||
|
||||
**Validation:** engine **109/109**; full sweep unchanged at **284 exit / 13 STEP-LIMIT**; Godot build and
|
||||
threaded `SELFTEST OK`; all seven Python suites, opcode/ctx lint, 481-script decode, and RECOVER clean.
|
||||
Normal-speed windowed capture wrote 220 PNGs and progressed through the complete AE sequence; at the target
|
||||
frame, bind + mode-1 + `0x202` setup share one VM frame and the first published object state is mode 1.
|
||||
Ghidra `/v2` comments were updated and saved. No commit was made.
|
||||
|
||||
@@ -177,6 +177,8 @@ texture ops (no GPU context) — run windowed for real scenes. User args (after
|
||||
| `tools/frida/trace_engine_ops.py` | **Engine op-path tracer** for the differential oracle (`docs/engine-re.md` "Differential offset-path oracle"): per executed op, read `cur_ctx_index@0x53d14`/`frame_pc@0x53d2c`/`frame_codebase@0x53d28` → emit `(codebase, offset=(pc−codebase)/4)`. **Use `--hook operand` (0x41b940, proven-safe)** — `--hook tick` (0x410fb0) sees `ecx≠ctx` (0 entries). Writes `build/tracer-live.flag` when the hook is installed → launch in the background, gate the New-Game trigger on the flag (else the scene-entry burst is missed). | `py -3.11 -u -X utf8 tools/frida/trace_engine_ops.py [--hook operand\|tick] [secs]` | running game → `build/engine-optrace.jsonl` |
|
||||
| `tools/frida/capture_global_writes.py` | **Scene-entry state capture** → auto-seed for single-scene runs (`docs/engine-re.md` "Scene-entry state snapshot"). Hooks `vm_operand_write@0x425fb0` and logs `(codebase, index, PLAINTEXT value)` for global-ints (the helper sees the value before the obfuscated store — no de-obfuscation needed). **`--spawn` captures from boot** (packer-aware: polls until `0x425fb0` unpacks, then attaches; kills the spawned pid on setup failure so no suspended orphan). `--attach` = partial (misses pre-attach writes). Validated: a real boot→New-Game→SC0000 capture seeds the VM to match the engine's whole opening. | `py -3.11 -u -X utf8 tools/frida/capture_global_writes.py --spawn [secs]` | running/spawned game → `build/global-writes.jsonl` (raw) + `build/scene-entry-state.json` (GameSession snapshot) |
|
||||
|
||||
| `tools/frida/capture_presentation_trace.py` | **Retained-state presentation trace:** correlates the current script offset with native draw/color writes, object composition, surface-command consumption, `gfx_render_frame`, queue clear, and D3D9 Present count. Read-only; distinguishes live retained state from state actually published to the window. | `py -3.11 -u -X utf8 tools/frida/capture_presentation_trace.py [secs] [pid\|AGE.EXE]` | native game → `build/native-presentation-trace.jsonl` |
|
||||
|
||||
*(Static disassembly of `build/engine-dump/range_00400000.bin` uses **capstone** — `py -3.11 -m pip install capstone`; VA `X` → file offset `X−0x400000`.)*
|
||||
|
||||
## Native engine RE (Ghidra)
|
||||
|
||||
Reference in New Issue
Block a user