From b2291fbb3fbe9c26eaded5b4c61c72e1baa88e85 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Wed, 8 Jul 2026 10:53:08 -0400 Subject: [PATCH] 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 --- docs/engine-re.md | 10 +++- docs/opcode-reference.md | 2 +- docs/phase-a-slice-plan.md | 58 ++++++++++++++----- .../plans/2026-07-08-frame-paced-sleep.md | 4 ++ .../2026-07-08-frame-paced-sleep-design.md | 6 ++ vm-map/opcodes.toml | 2 +- 6 files changed, 63 insertions(+), 19 deletions(-) diff --git a/docs/engine-re.md b/docs/engine-re.md index 1a5f094..510735a 100644 --- a/docs/engine-re.md +++ b/docs/engine-re.md @@ -396,9 +396,13 @@ Handler resolved via the dispatch table (`ctx[0x26c93+0xc8]` = `param_1[0x26d5b] **Port equivalent (implemented):** our VM runs on a background thread (like `wait-for-input`), so blocking that thread for `duration` ms while the main-thread compositor (`Main.Recomposite` in `_Process`) keeps presenting is -behaviorally equivalent to the native non-blocking timer — the `sleep`-paced opening `AE*` burst now gets frames -to display. Headless/CLI hosts no-op `Sleep` (parity). `IHost.Sleep(long)` + VM `case "sleep"`; see -`vm-map/opcodes.toml` 0xc8. +behaviorally equivalent to the native non-blocking timer. This correctly reproduces the **explicit one-shot +sleeps** (the dramatic 1000/750/200 ms holds). Headless/CLI hosts no-op `Sleep` (parity). `IHost.Sleep(long)` + +VM `case "sleep"`; see `vm-map/opcodes.toml` 0xc8. **⚠ It does NOT make the rapid opening `AE*` burst animate** +— execution trace shows the back-to-back `set-texture→draw-texture` swaps have no `sleep`/`wait`/`present`/ +coroutine between them, so bare `sleep` was never their pacer; what advances that burst is still unknown (an +earlier claim that "the opening is sleep-paced" was inherited from this doc and never execution-verified — +corrected). Profile the real Godot run (`--trace-histogram`) to find it. **Related — `present-frame` (op `0x20c`):** dispatch `param_1[0x26e9f] = gfx_op_0x20c_present_frame` → `gfx_render_frame`@`0x4820b0` (buffer flip). Our compositor presents every frame regardless, so `0x20c` is a VM diff --git a/docs/opcode-reference.md b/docs/opcode-reference.md index 1a11f1f..17bbbd2 100644 --- a/docs/opcode-reference.md +++ b/docs/opcode-reference.md @@ -62,7 +62,7 @@ This also names the whole call graph statically (build/callscript-names.json). - **grounding:** source=investigation, confidence=high - **evidence:** Ghidra: dispatch ctx[0x26c93+0xc8]=0x420ec0; sleep_op_0xc8 + sleep_timer_arm decoded/annotated 2026-07-08. docs/engine-re.md sleep section. -Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_timer_arm @0x44cff0 at ctx+0x5f304 = active flag + start tick + duration) that the engine main loop polls, resuming the script when elapsed. Operand UNIT = MILLISECONDS (start = ms tick source DAT_0056f3d4, timeGetTime/GetTickCount class). duration<10 fast-paths via [0x56f0b8]; all real scene sleeps (100/750/1000) are >=10. The handler also writes gfx cmd-type 3 + runs anti-tamper checks, neither needed host-side. Port equivalent: the Godot host blocks the VM background thread ms while the per-frame compositor keeps presenting -> the sleep-paced opening AE* burst animates. Headless hosts no-op it (parity). +Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_timer_arm @0x44cff0 at ctx+0x5f304 = active flag + start tick + duration) that the engine main loop polls, resuming the script when elapsed. Operand UNIT = MILLISECONDS (start = ms tick source DAT_0056f3d4, timeGetTime/GetTickCount class). duration<10 fast-paths via [0x56f0b8]; all real scene sleeps (100/750/1000) are >=10. The handler also writes gfx cmd-type 3 + runs anti-tamper checks, neither needed host-side. Port equivalent: the Godot host blocks the VM background thread ms while the per-frame compositor keeps presenting -> correctly reproduces the explicit one-shot dramatic pauses. NOTE: does NOT pace the rapid opening AE* burst (those draws have no sleep between them; their real pacer is unknown). Headless hosts no-op it (parity). ## draw diff --git a/docs/phase-a-slice-plan.md b/docs/phase-a-slice-plan.md index 083c270..565e3d3 100644 --- a/docs/phase-a-slice-plan.md +++ b/docs/phase-a-slice-plan.md @@ -378,15 +378,28 @@ for the visible opening animation, and a genuinely different subsystem than the here. (**Correction:** an earlier draft of this note called it "immediate-mode slot-0 blits" — wrong; the engine is retained, per the native `draw-texture → gfx_object_bind_draw` bind.) -### A2b — Frame-paced `sleep`: the opening animates ✅ (2026-07-08) +### A2b — Frame-paced `sleep` (2026-07-08) — ⚠ did NOT make the opening animate (corrected) The chunk the animation-subsystem note above flagged as "the clearly-scoped next chunk." Spec/plan `docs/superpowers/{specs,plans}/2026-07-08-frame-paced-sleep{-design,}.md`; RE `docs/engine-re.md` ("sleep (op 0xc8)"). -**Root cause (one line):** the Godot compositor (`Main.Recomposite` in `_Process`) already presented live -`GfxState` every frame — but `sleep` (`0xc8`) was a GAP, so the VM ran the whole draw/`sleep` burst in -microseconds and the compositor only ever caught the *final* state. Nothing else was missing. +> **⚠ CORRECTION (2026-07-08).** The original heading here ("the opening animates ✅") and the "Verified +> visually" claim below were **WRONG** — a misread. The `sleep` op is correctly decoded + implemented and the +> **one-shot dramatic pauses now work**, but the **rapid opening CG/AE\* burst is NOT sleep-paced** and did not +> start animating. Execution trace (via the new `--trace-histogram`) shows the back-to-back +> `set-texture→draw-texture` swaps run with **no** `sleep`/`wait`/`present`/coroutine between them; what actually +> paces them is still **unknown**. The `--shot-sequence` frames I read (arcane → Lily → maid → sky) were the game +> holding on key CGs via the **sparse one-shot sleeps** (slowed further by per-frame PNG-IO), which I mistook for +> the burst stepping. How the mistake happened: I inherited "the opening is sleep-paced" from this repo's own docs +> and treated it as verified instead of tracing execution first. What IS solid: the `sleep` seam, the one-shot +> pauses, the `GfxState` race fix, and the tooling. Related: the "493k sleeps" that confused me were a **headless +> artifact** (the name-entry poll loop), since fixed — see the "Headless divergence" note below. + +**What `sleep` actually is (RE-confirmed, correct):** the Godot compositor (`Main.Recomposite` in `_Process`) +presents live `GfxState` every frame; `sleep` (`0xc8`) was a GAP so the VM ran the whole burst in microseconds. +Implementing it makes the **explicit one-shot sleeps** (1000/750/200 ms) pause correctly — but those are the +dramatic holds, not the rapid burst's pacer. **RE (Ghidra):** `sleep_op_0xc8`@`0x420ec0` is **non-blocking** — it arms a main-loop-polled timer (`sleep_timer_arm`@`0x44cff0`; start = ms tick, duration = operand). **Operand unit = milliseconds.** (Also @@ -404,20 +417,37 @@ overlaps VM-thread `_objects`/`_registry` writes. `GetOrCreate`/`Register`/`Rele them on the existing (re-entrant) `_lock`. New `GfxStateConcurrencyTests` (deterministic repro of the "Destination array is not long enough" crash) + `SleepDispatchTests`; **engine 52/52**. -**Verified visually** via the new `--shot-sequence [--frames N]` (one PNG per frame, auto-advancing past -input waits — a time-based effect can't be verified by a single `--shot`). `godot --boot --shot-sequence` on -SC0000: the opening steps through **distinct, sleep-paced frames** — blank → arcane `AE*` magic-circle (held -~12 frames ≈ 200 ms) → character (Lily) → transitions → settled sky-background CG — each held for its sleep -duration, instead of jumping straight to the final CG. (Residual, **not a regression**: some intermediate -frames still show the cold-object anchor doubling documented under "residual" — a geometry issue independent of -timing; the settled CG renders cleanly.) +**New tool** `--shot-sequence [--frames N]` (one PNG per frame, auto-advancing past input waits — a +time-based effect can't be captured by a single `--shot`). The frames it produced showed the game holding on +distinct CGs (arcane `AE*` → Lily → maid → sky) — but per the correction above, those holds are the **sparse +one-shot sleeps**, not the rapid burst stepping. (Residual, unrelated: intermediate frames show the cold-object +anchor doubling — a geometry issue independent of timing.) **Tracker delta (`scene_opcode_coverage.py SC0000`):** GAP 64→**62** ops (741→**714** instrs), impl 53→**54** (`sleep`), safe-noop 12→**13** (`present-frame`), correctly-handled 65→**67/129 (51.9%)**. -**Still deferred (the next frame-pacing chunk):** the full scene-coroutine framework (`0x7b`/`0x7c`/`0x140` + -the `G[0xaba5c]` re-entry gate + `label_125bd` slot-table setup) for interactive multi-object scenes — out of -scope here (the opening's path is linear). +**Open (the real burst pacer):** what advances the rapid opening CG/AE\* burst frame-to-frame is **unknown** — +not `sleep`, not `present-frame` (only 2× in the whole scene), not the coroutine ops (absent from the burst). +Next: profile the **real Godot run** (`--trace-histogram`) of SC0000's `0x3958–0x3973` loop + gfx-op sequence. +The scene-coroutine framework (`0x7b`/`0x7c`/`0x140` + `G[0xaba5c]` gate + `label_125bd`) remains deferred. + +### Diagnostics framework extended (2026-07-08) + +Motivated by the misread above (a `--trace-steps` dump was 2.5M lines → grep/awk). Added, all observe-only +(parity preserved): **`HistogramTraceSink`** (op + call-site `script:pc` execution counts + sample operand), +**`TraceSinkBase`** (per-script step attribution across call-script frames), **`TextTraceSink`** op-filter +(`--trace-ops`), **`CompositeTraceSink`**, `OpcodeTable.ByLabel`; CLI `--trace-histogram`/`--trace-ops`; Godot +`--trace-histogram ` (profiles the REAL run) + `--sleep-scale`. See `docs/tools-reference.md`. + +### Headless divergence FIXED — faithful halt-at-wait (2026-07-08) + +The histogram pinned the goose-chase root cause: op `0x72 wait-for-input` was a **no-op headless**, so a run +plowed past all 166 of a scene's prompts into the name-entry poll loop (`INPUTNAME.BIN`) and spun `sleep 1` +**493,182×** to STEP-LIMIT — a path no real playthrough reaches. Fix = **`VmOptions.HaltAtWaitForInput`**: the VM +halts (reason `wait-for-input`) at `0x72`. **`run`/`play` faithful by default** (SC0000 → ~402 steps / 0 sleeps, +matching the real path to the first prompt; `--plow` = old walk-every-page); **`sweep` plow by default** (dialogue +oracle, 284/13 unchanged) with `--halt-at-wait` → all 297 scenes halt cleanly (0 STEP-LIMIT). Godot unaffected +(really blocks on input). The corpus's 13 STEP-LIMIT scenes were all this artifact, not VM bugs. `HaltAtWaitTests`. --- diff --git a/docs/superpowers/plans/2026-07-08-frame-paced-sleep.md b/docs/superpowers/plans/2026-07-08-frame-paced-sleep.md index d7d5bc2..bf1c262 100644 --- a/docs/superpowers/plans/2026-07-08-frame-paced-sleep.md +++ b/docs/superpowers/plans/2026-07-08-frame-paced-sleep.md @@ -1,5 +1,9 @@ # Frame-paced `sleep` Implementation Plan +> **⚠ OUTCOME CORRECTION (2026-07-08):** the goal below (make the opening burst animate) was **not** achieved — the +> burst is not sleep-paced (its real pacer is unknown). The plan's tasks did land correctly (sleep seam, race fix, +> tooling); only the animation claim was wrong. Corrected result: `docs/phase-a-slice-plan.md` (A2b frame-paced sleep). + > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Give `sleep` (`0xc8`) real timing so the SC0000 opening's `sleep`-paced retained-object burst animates on screen, using the compositor and alpha-tween subsystem that already exist. diff --git a/docs/superpowers/specs/2026-07-08-frame-paced-sleep-design.md b/docs/superpowers/specs/2026-07-08-frame-paced-sleep-design.md index 0298b0c..8ef027b 100644 --- a/docs/superpowers/specs/2026-07-08-frame-paced-sleep-design.md +++ b/docs/superpowers/specs/2026-07-08-frame-paced-sleep-design.md @@ -1,5 +1,11 @@ # Frame-paced `sleep` — the SC0000 opening animates +> **⚠ OUTCOME CORRECTION (2026-07-08):** this spec's premise — that bare `sleep` paces the opening burst, so +> implementing it would make the opening animate — was **wrong**. `sleep` is correctly implemented and the +> one-shot dramatic pauses work, but the rapid CG/AE\* burst is not sleep-paced (trace shows no sleep between the +> draws) and did not animate. Canonical corrected result: `docs/phase-a-slice-plan.md` (A2b frame-paced sleep). +> Kept as-authored below for provenance. + **Date:** 2026-07-08 **Branch:** continues `feat/gfx-command-buffer` **Slice goal:** make the SC0000 opening's `sleep`-paced retained-object burst (`AE001D → AE002B → diff --git a/vm-map/opcodes.toml b/vm-map/opcodes.toml index f87e91d..30654da 100644 --- a/vm-map/opcodes.toml +++ b/vm-map/opcodes.toml @@ -2038,7 +2038,7 @@ abi_source = "kelebek+decode-validated" name = "sleep" category = "control" summary = "Pause the script for milliseconds while rendering continues (frame pacing)." -details = "Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_timer_arm @0x44cff0 at ctx+0x5f304 = active flag + start tick + duration) that the engine main loop polls, resuming the script when elapsed. Operand UNIT = MILLISECONDS (start = ms tick source DAT_0056f3d4, timeGetTime/GetTickCount class). duration<10 fast-paths via [0x56f0b8]; all real scene sleeps (100/750/1000) are >=10. The handler also writes gfx cmd-type 3 + runs anti-tamper checks, neither needed host-side. Port equivalent: the Godot host blocks the VM background thread ms while the per-frame compositor keeps presenting -> the sleep-paced opening AE* burst animates. Headless hosts no-op it (parity)." +details = "Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_timer_arm @0x44cff0 at ctx+0x5f304 = active flag + start tick + duration) that the engine main loop polls, resuming the script when elapsed. Operand UNIT = MILLISECONDS (start = ms tick source DAT_0056f3d4, timeGetTime/GetTickCount class). duration<10 fast-paths via [0x56f0b8]; all real scene sleeps (100/750/1000) are >=10. The handler also writes gfx cmd-type 3 + runs anti-tamper checks, neither needed host-side. Port equivalent: the Godot host blocks the VM background thread ms while the per-frame compositor keeps presenting -> correctly reproduces the explicit one-shot dramatic pauses. NOTE: does NOT pace the rapid opening AE* burst (those draws have no sleep between them; their real pacer is unknown). Headless hosts no-op it (parity)." noop_headless = false source = "investigation" confidence = "high"