Boot anchors (reset, the kernel copy, the first A0/B0/C0 calls, the shell)
pass within the first frames, before a TCP cyc_watch arm can land, so a
native-vs-Beetle boot comparison could not be run from the tree.
psx-runtime (debug_server.c) and psx-beetle (beetle_main.cpp) now read
PSX_CYC_WATCH ("<pc>" or "<pc>-<end>" for region mode) and PSX_CYC_WATCH_N
(default 16) at start-up and arm the watch before the first guest
instruction. The native arm shares the TCP handler's code path
(cyc_watch_arm); psx-beetle calls beetle_cyc_watch_arm. With neither
variable set nothing changes. tools/cycle_compare.py --no-arm then reads
both rings.
PSX_BEETLE_UNPACED=1 skips psx-beetle's 59.94 Hz wall-clock pacing for
headless oracle runs. Guest timing is unaffected.
docs/beetle-macos.md step 7 documents the variables and the anchor-key
difference for BIOS copy windows declared dispatch_key = "rom": the shell
is keyed natively at its ROM address (OpenBIOS 0xBFC0A500, SCPH-1001
0xBFC18000) while Beetle sees 0x80030000. Checked on macOS: the documented
example records the OpenBIOS shell entry at 5,015,670 in Beetle, the value
of the #429 land gate, and the native watch arms from boot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cyc_watch + cycle_compare.py now take an optional END anchor: each recorded value
is Δcycles of one START->END pass (cost of the known code path between two dispatch
points). [A,A] = Δ between consecutive A hits (call-to-call cost). Mirrored EXACTLY
on both backends (native debug_server.c + Beetle libretro.cpp/beetle_debug_server.c,
the latter uncommitted in main checkout, additive). VALIDATED: region [0x80017FC4,
0x80017FC4] = native Δ 56 vs Beetle Δ 91, gap -35, ZERO variance across 8 passes —
a stable, offset-independent per-region gate to drive Stage-2 components to
convergence (faithful components shrink the gap monotonically; can't overshoot).
Note: [A,B] with distinct B needs B to reliably follow A (a dispatch point on the
deterministic path); B=0x8001EFFC failed (func has multiple exits) — pick wrapper/
single-exit pairs or use [A,A].
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Native cyc_watch (ported from agent branch) + the missing universal hook:
debug_server_log_call_entry now samples cyc_watch_observe at every compiled
function entry (game AND BIOS; the agent's debug_server_trace_dispatch hook only
covered the BIOS trampoline, so game-text anchors fired 0). tools/cycle_compare.py
arms the same anchor on native(4500)+Beetle(4382) and diffs per-hit cycles.
Beetle side (libretro.cpp cyc_watch state/observe + cpu.cpp RunReal loop hook +
beetle_debug_server.c commands) is in the MAIN checkout (additive diagnostic,
uncommitted) and built into psx-beetle.
VALIDATED end-to-end: both backends capture 16 hits at game-text anchor 0x80017FC4.
FINDING: native is ~121M cycles BEHIND Beetle at that anchor = a gross boot-cycle-
path divergence (turbo-loads/fast-CD + overlay/dirty charge far fewer cycles than
Beetle's real-time CD boot), NOT the -8 fine drift. => absolute-cycle comparison
through boot is dominated by load-model differences; the -8 needs a controlled
anchor (early/pre-divergence or an interp-convergence point). Relocated BIOS-shell
funcs (phys 0x30000-0x5AFFF, e.g. 0x80050B0C) dispatch at a different native phys —
anchor on game-text or BIOS-ROM funcs, or the relocated phys.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>