Skip to content

Converge Phantasm/Extra native replays + perf/CI infra - #4

Merged
AgentMystia merged 8 commits into
mainfrom
fix-youmu-slowmo-flicker
Jul 15, 2026
Merged

Converge Phantasm/Extra native replays + perf/CI infra#4
AgentMystia merged 8 commits into
mainfrom
fix-youmu-slowmo-flicker

Conversation

@AgentMystia

Copy link
Copy Markdown
Owner

Summary

Brings the engine to byte-exact fidelity for both native replays and adds the perf + verification/CI infrastructure. All gates green locally: check, build, test (288/288), replay:verify Stage 1-6 6/6 (RNG Δresidue 0), clean browser boot.

Extra (MarisaB th7_udHm54) — FULLY CONVERGED: score byte-exact to native every frame to 59060; kill/collect/contact counts exact. (op103 bomb-contact tally, high-spell collision suppression, op161 pause, MarisaB laser owner swap + 999 sentinel + interrupt fade, Master Spark 0.4 slow-rate + ANM beam spacing, border-break id29, bomb-active graze-counter & small-cherry scoring, bomb cherry drain, vars 10024/10050-10052.)

Phantasm (ReimuA th7_udSg10): RNG byte-exact 0→47022 (was 5850). (ReimuA orb r=128 clear-circles via FUN_0043e7e0 +0x17dc pool, detonation r64 region, region-survives-teardown, bullet state-5 lifetime, cull-before-clear, op79/homing float32, concurrent screen-shakes, bomb-cleanup-defers-border, border state-3 clock reset.) + ReimuA orb spawn angle float32 (FUN_00407840).

Perf: GC-free pools (slotsConsistent/compactLive), desynchronized+opaque canvas, opt-in Float64 cost-rings. CI/verify: unified verify.mjs (fast/edit/full), browser-boot + pixel-gate + content-addressed test-build cache, 4-job deploy workflow (core/replay/browser/pages).

Known residual

Phantasm tail divergence at f51879 (second bomb, slot756 clears ~3 frames late → cascade). Diagnosed as accumulated sub-px precision entanglement (byte-exact-stream wall), NOT a localized bug — a 4-agent static-RE sweep confirmed the entire bullet-clear mechanism (gate, distance test, r128 region lifecycle, per-bullet loop order, iteration, item spawn/collect) is bit-exact faithful vs the decompiled exe. Pinning the exact accumulating source needs native bomb-2 runtime geometry, which the Wine+gdb trace can't reach in this environment (stub crashes past ~47000; dialogue forces the per-frame input bp). Full closure needs the byte-exact-stream rewrite (documented separate effort). RNG stays byte-exact to 47022 and contacts match to 51803, so this does not regress Stage 1-6 or the Phantasm range already converged.

🤖 Generated with Claude Code

AgentMystia and others added 8 commits July 14, 2026 20:55
FUN_004074e0 (all.c:3461) casts orb velocity to float32 and FUN_00407840
(all.c:3656-3706) stores speed/position as float32 each frame:
  speed = (float)(speed - 0.4*rate)
  vx/vy = (float)(cos/sin(angle)*speed)
  x     = (float)(rate*vx + x)
The web accumulated these as JS doubles — the lone major motion subsystem
not yet float32-ified (player.ts and eclvm.ts already are). Wrap each
accumulation in Math.fround to match exe storage.

Genuinely more exe-faithful. Verified no regression: replay:verify Stage 1-6
6/6, 254 tests green, Phantasm byte-exact RNG stream still 0->10486 (the
f10487 wall is a deeper sub-pixel event flip, not orb-position drift — see
handoff).

Co-Authored-By: Claude <noreply@anthropic.com>
syncFixedPools ran 5× `.filter()` + `.reduce()` every 60Hz tick even when the
pools were already consistent, and 3 mid-update sites reassigned the dense
arrays via `.filter()`. That was ~8 fresh arrays + 5 reduce closures per frame
of steady-state nursery garbage.

- Add slotsConsistent(): an allocation-free validity check (single-pass live
  count + back-pointer verify + slot count) byte-identical to the old filter-
  based check. The rare rebuild path still allocates (correctness unchanged).
- Add compactLive(): order-preserving in-place compaction replacing the 3
  mid-update `.filter()` reassignments (stable partition keeps poolSlot order;
  no new array).

Replay-safe: replay:verify Stage 1-6 6/6, 254 tests green, Phantasm RNG stream
unchanged at 0->10486. (The dense-items synchronous stress draw-p99 didn't
move here — that spike is render/driver-side, addressed by render decoupling
and draw batching; this change still removes real per-frame allocation.)

Co-Authored-By: Claude <noreply@anthropic.com>
…e capture

The synchronous advance() loop had no idle time, so V8 ran stop-the-world GC
→ phantom 35ms draw spikes that don't occur in the real rAF-driven game.

- --paced: drives advance(1) per requestAnimationFrame, giving V8 idle time
  for incremental GC (matches real-game cadence).
- p99 + dropRate (fraction of samples > 16.67ms) added to all stats; summarize
  across runs takes the worst (binding) percentile.
- --throttle <rate>: CDP Emulation.setCPUThrottlingRate (emulate half-speed).
- --baseline: captures rate 1 & 2 to scripts/perf-baseline.json.

Headline result (rAF-paced): at half-speed (rate 2) the dense-items stress
(total p99 3.5ms, dropRate 0) and stage-entry (total p99 ~2ms, dropRate 0)
both meet the stable-60 / ≤0.1%-drop target with ~4.5x headroom. The earlier
sync numbers (draw p99 35-70ms) were a measurement artifact.

Co-Authored-By: Claude <noreply@anthropic.com>
getContext('2d', { desynchronized: true, alpha: false }):
- desynchronized: on supporting browsers (Chrome/GL) this pipelines Canvas 2D
  to the display compositor, directly cutting input-to-photon latency.
- alpha:false: the playfield is fully redrawn every frame (clear + draw), so
  the page-level alpha channel is unused; dropping it skips the per-pixel page
  blend. (capturePlayfield still reads correct pixels — verified by the
  capture tests, all 254 green.)

Both are hints — unsupported browsers ignore them and render exactly as before.
Render-only: replay:verify 6/6 unaffected (never calls draw), 254 tests green.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…ate)

Co-Authored-By: Claude <noreply@anthropic.com>
…rb float32 angle

Closes the deterministic gaps between the web engine and Th07.exe v1.00b for
the Phantasm (ReimuA) and Extra (MarisaB) native replays, audited against the
decompiled exe (reference/re-specs/tools/all.c).

Extra (th7_udHm54): now FULLY converged — score byte-exact to native every
frame to 59060; kill/collect/contact counts exact. Fixes: op103 enemies still
tally bomb contacts; high-spell (op>=118) bomb-collision suppression; op161
pauseDuringBombOrBorder; MarisaB laser owner replacement on power-record change
+ 999-infinite sentinel + interrupt(1) fade + focus-teardown helper skip;
Master Spark slow-rate 0.4 + frame-0 setup + ANM-derived beam spacing; border-
break id29 burst; bomb-active graze-counter suppression (counters only);
bomb-active small-cherry flat-10 scoring (player+0x23dc); bomb cherry drain;
var 10024 pi/2 fallback + vars 10050-10052.

Phantasm (th7_udSg10): RNG byte-exact 0->47022 (was diverging at 5850).
Fixes: ReimuA orb state-1 r=128 clear-circles published explicitly into the
bombClearRegions pool (FUN_0043e7e0 @ +0x17dc, not inferred from attack slots);
detonation expanding r64 region; regions survive bomb teardown (FUN_0043d8f0
owns aging); bullet state-5 clearFade lifetime (12 ticks); offscreen-cull
before bomb-clear; op79 accel/angle-change/ramp + homing amulet float32;
concurrent screen-shakes (independent clocks); bomb cleanup defers border;
natural border expiry resets state-3 collision clock.

Also: ReimuA orb spawn angle stored float32 (wrapNativeAngleF32 over
TAU_F32/8-HALF_F32) matching FUN_00407840 @ all.c:3639-3641 — the last
float32 gap in the orb motion chain.

Stage 1-6 SakuyaA replay stays 6/6 byte-exact (RNG dResidue 0); 288/288 tests.

Note: a residual Phantasm tail divergence at f51879 (second bomb) remains —
diagnosed as accumulated sub-px precision entanglement (byte-exact-stream
wall), NOT a localized bug. A 4-agent static-RE sweep confirmed the entire
bullet-clear mechanism (gate, distance test, r128 region lifecycle, per-bullet
loop order, iteration order, item spawn/collect) is bit-exact faithful vs
native. Documented in memory/handoff.

Co-Authored-By: Claude <noreply@anthropic.com>
…job deploy

Performance (replay-safe, all gates green):
- GC elimination: syncFixedPools zero-alloc slotsConsistent() fast path +
  in-place compactLive() replacing per-tick filter+reduce/closures.
- Low-latency canvas: desynchronized + alpha:false context attributes.
- Loop cost-rings are Float64Array and opt-in (?perf=1); default avoids
  performance.now. Renderer takes RendererOptions.desynchronized.
- LatencyRecorder (src/core/latency.ts) + input-edge observer (?latency=1),
  test-only.

Verification + CI infrastructure:
- scripts/verify.mjs unified runner: verify:fast (check+build+test+
  replay:verify parallel + clean dev-shot boot), verify:edit (--test filter),
  verify:full (pixel-gate frames + replay:browser + static Pages boot).
- scripts/browser-boot.mjs (static-dir boot asserts >=1 enemy, 0 pageErrors),
  scripts/pixel-gate.mjs + lib/pixel-stats.mjs (extracted regions),
  scripts/lib/browser-harness.mjs (shared chromium launch + diagnostics),
  scripts/lib/test-build-cache.mjs (content-addressed esbuild cache replacing
  per-pid races), scripts/perf-smoke.mjs, scripts/latency-probe.mjs.
- dev-menu/dev-replay/dev-shot/perf-probe/pixel-report refactored onto the
  shared harness; all exit 4 on page errors; paused=1 boot URLs.
- NOTE: scripts/lib/replay-harness.mjs killEnemy observation wrapper now
  forwards variadic args ((g,e,...args)=>origKill(g,e,...args)) — load-bearing:
  the prior (g,e)=>origKill(g,e) silently dropped FUN_0041ed50's bomb-contact
  flag and changed replay behavior while the engine stayed correct.
- package.json: verify:{edit,fast,full}, perf:smoke, latency:trace scripts.
- .github/workflows/deploy.yml: 4-job (core check+build+test, replay, browser
  boot+pixel+Pages, pages deploy on push:main); per-job permissions;
  tests/.build/cache action cache; PR trigger.
- Remove superseded FIX-REPORT.md (per-commit messages + handoffs replace it).

Stage 1-6 replay:verify 6/6 (dResidue 0); 288/288 tests; tsc clean.

Co-Authored-By: Claude <noreply@anthropic.com>
@AgentMystia
AgentMystia merged commit 424aa43 into main Jul 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant