Files
psxrecomp/docs/performance/EXPERIMENTS.md
T

631 lines
35 KiB
Markdown

# Performance experiment ledger
Campaign: [OpenGL performance campaign](../PERFORMANCE_CAMPAIGN.md).
Live work status: central Beads `beads-jod2`.
Record unsuccessful, incomplete and rejected experiments as well as accepted
ones. Local game-derived artifacts are not release fixtures. Hashes below make
the retained local evidence identifiable without distributing a savestate/ROM.
## CRAB-DISCOVERY-001: verify a usable starting scene
Date: 2026-09-04 (session date). Verdict: exploratory evidence only; no candidate
optimization, A/B comparison, production claim or correctness qualification.
- Host: Windows, Ryzen 7 9800X3D, 8 cores / 16 logical processors. Graphics
adapters reported by the OS: RTX 3080 Ti, AMD integrated graphics, Meta virtual
monitor. Actual GL device and power state were not recorded in this capture.
- Historical binary source: `_wt-ape-crabperf-20260901/build-dbg`, CMake Release
with `PSX_DEBUG_TOOLS=ON`. Existing report identifies build
`v0.3.2-alpha-139-g1bf70960-dirty`. Current source HEAD is not proof of binary
provenance. This executable was not built from the campaign branch.
- Executable SHA256:
`3b27bda4417522eb1ab331e4d2b194828f1d38fb9bbb165d1a78eb2156142ba1`.
- State: `saves-dbg/scph1001/state_800A3660_slot01.pst`, version 5, BIOS wordsum
`F67ECB99`, entry `800A3660`, codegen hash `A4319B6F`, ABI 21, codegen version 10.
SHA256 `2dfc142e65eb32e179b67d352f510d15f6d97efd22b3b43a9eef24301f3ad5a9`.
- Executable, saves, assets, BIOS assets and mods were copied into the campaign
worktree's `.local/crabby-baseline`. Writable cards/settings use that copy.
CLI selected OpenGL, no launcher, debug port 4680. Settings selected 2x SSAA
and antialiasing. `PSX_RUNTIME_PERF_DIAG=1`; no host CPU cap was applied.
- Restore receipt: `savestate_status` generation advanced from 0 to 1 with
`pending=0,last_ok=1,last_op=load,last_slot=1`. Presented screenshot visibly
confirms the sandcastle area on the beach facing the water.
- Original `ApeEscapeRecomp/saves/scph1001` slots 00/01 were v3 and slots
06/07/08/09/11 were v2; they cannot be used as v5 checkpoints.
The passive 10-second sample is retained locally at
`.local/crabby-baseline/stall-10s.json`, SHA256
`448d6a77b08479e0cca9bf11ffa21af1b3c2408ba70b9a3f1a0a7696f4a10b28`.
Snapshot start timestamps differ by 10.038749 seconds; commands are sequential,
so these are not atomic snapshots of a fixed amount of guest work.
| Observation | Value | Interpretation limit |
| --- | --- | --- |
| Overlay native dispatch delta | 0 | Does not include statically compiled game execution |
| Overlay interpreter fallback delta | 33,702 | Dispatch events, not CPU-time share |
| Interpreted instruction delta | 425,410 | Some kernel fallback is expected |
| Interpreter block delta | 33,714 | No claim that reducing calls changes the primary bottleneck |
| Compilation runs/configured | 0 / 0 | Cache/compilation provenance differs from a prepared release campaign |
| Rolling phase samples: interpreter / static / GPU | 1.59% / 80.11% / 18.30% | Instrumented phase stamps, not an independent host CPU profiler |
This observation rejects the inference that zero overlay-native dispatches
alone prove interpretation dominates the slowdown. It does not reject overlay
optimization as a candidate. No controlled compiler/process/power-state audit
was performed during this exploratory sample.
Separately queried `frame_perf` reported approximately 16.68 ms total and
16.65 ms `emu_cpu_ms_avg`. The latter is calculated from total wall time minus
present wall time, not OS thread CPU time. GPU queries span command-stream
intervals; do not interpret these fields as independent exclusive CPU/GPU busy
time or add their values together. Run without timer-query profiling and obtain
host-thread CPU samples before choosing an optimization from these values.
## HOST-BUDGET-001: verify the throttle denominator
Date: 2026-09-04 (session date). Verdict: launcher calibration, not an accepted
runtime optimization or a substitute for low-end hardware.
On the same 16-logical-CPU host, a single Python process busy-looped until
`time.process_time()` advanced by 2.5 seconds. Every run used affinity mask
`0x1`. The game was stopped. The sequence below was a calibration sequence,
not an interleaved optimization campaign. Reports live in
`.local/host-calibration/{unrestricted,core50,core25}.json`.
| Requested budget | Applied Job CpuRate (out of 10,000) | Wall seconds | Process CPU seconds |
| --- | --- | --- | --- |
| Unrestricted | none | 2.580994 | 2.500000 |
| 50% of one logical CPU | 313 | 4.679147 | 2.531250 |
| 25% of one logical CPU | 157 | 9.515031 | 2.500000 |
`--cpu-percent` is a percentage of the whole host's CPU scheduling budget.
`--core-percent` normalizes to one logical CPU equivalent independently of
affinity; it limits the whole launched job, including helper threads/processes.
This does not selectively slow the emulation thread, lower CPU frequency, or
simulate an older GPU/cache/memory subsystem. Reports retain requested and
applied rates, including integer rounding.
An earlier 0.5-CPU-second witness did not demonstrate effective throttling even
at a 3% host budget. Longer witnesses did, so sub-second smoke timing is not a
valid cap calibration. Repeat calibration on each host and use sustained game
windows rather than assuming the scheduler's short-term behavior is linear.
## ROUTE-SMOKE-001: live runner protocol validation
Date: 2026-09-04 (session date). Verdict: successful diagnostic smoke only.
No optimization candidate, target-provenance manifest, or acceptance comparison.
Used the isolated executable/state from CRAB-DISCOVERY-001, OpenGL, no host cap,
`PSX_RUNTIME_PERF_DIAG=1`, and explicitly `PSX_GL_PERF=0`. A local shortened
route held neutral input for 120 ticks. The runner confirmed completed slot-1
restore (generation 1, `last_ok=1`) and route completion (`active=false`, one
step consumed, `remaining=0`). Raw evidence:
`.local/crabby-baseline/runner-smoke.json`.
- Route wall time: approximately 2.141 seconds, including setup/status overhead.
- Counter observation window: approximately 2.672 seconds and 161 frame ticks,
including sequential snapshots/control traffic, not exactly 120 guest ticks.
- GL frame timing correctly reported unavailable with timer queries disabled.
- No native overlay compilation was configured; cache provenance remains
unsuitable for a prepared-release acceptance baseline.
The process exited after the smoke. Original saves/cards were not modified.
The committed 7,200-tick route is a longer stationary workload manifest; this
smoke does not validate its full-duration timing or the analog approach route.
See [tool usage and remaining gates](TOOLS.md).
## MEMORY-TRACE-001: remove empty production write-trace calls
Dates: 2026-09-04 to 2026-09-05. Verdict: retain as a narrowly scoped dead-work
simplification. Local write benchmark improves; gameplay gain is unproven.
This does not complete the campaign or establish the cause of the beach slowdown.
### Change and correctness evidence
Guard the six RAM/scratchpad byte, halfword and word calls to
`debug_server_trace_write_check` with the existing `PSX_NO_DEBUG_TOOLS` switch.
That function already returns immediately in production. This also removes its
old-value argument loads. Debug builds keep the original calls. No functional
memory observer, store, cycle charge, MMIO path, runtime setting, ABI or state
layout changes. Separate `-O2 -fno-lto` object inspection found the baseline's
undefined trace symbol and confirmed its absence from the candidate object.
`runtime/tests/test_memory_debug_trace_guard.py` compiles actual `memory.c`
against real runtime headers, with external device/observer stubs. It checks
all six writes through four RAM mirrors and scratchpad aliases, expected bytes
and adjacent sentinels, observer/store counts, and production/debug trace-call
witnesses. Optional `--baseline-memory` also checks the old production source.
The compact state hash covers touched bytes and selected counters, not complete
guest/device state. Structural checks preserve functional RAM observer order.
An earlier synthetic store model and then a fake-header fixture were rejected
during review; neither is used by the committed test.
### Build provenance
Framework baseline is `8ec6498d323a7abd04a3058afcbb5e36e62a8ec3`; candidate differs
only by the six guards in `runtime/src/memory.c`. Source SHA256:
- Baseline: `693be507fa53fded3e8e276f4ac0f7293d3879f3d53a8044fa8c383b4e097b99`.
- Candidate: `4fdb391d4f74da1ef2d31ce7cd61a0e0b958abab8304da37d83a2c1bb0e85253`.
Both title executables were rebuilt, using the same copied Ape Escape generated
code and assets from `_wt-ape-crabperf-20260901`. Provenance trees/build logs are
retained under `.local/ape-build-provenance`. CMake Release uses MinGW GCC/G++
15.2.0 from `C:/msys64/mingw64/bin`, `-O3 -DNDEBUG`, debug tools OFF, static runtime ON,
SDL3, recomp UI and rewind ON, netplay OFF, and block cycles ON. Vulkan was
enabled in the build but was not the selected/tested renderer. The first build
failed with the wrong windres shim; successful builds explicitly selected
`C:/msys64/mingw64/bin/windres.exe`.
- A executable SHA256: `f0c893a5743d8dfddcde75fe275d525c99c66294c5333fdf48b797de0b21096e`.
- B executable SHA256: `6fd00dc4ed8a7e9cf66f1bdd64ebaafd376d867dd5687c126bcd56f8e03dbf12`.
- A successful build-log SHA256: `c9a042d2ccab65d248a9b91b0b74d1106fc80fe74f611ea5eccfb27f350ad252`.
- B successful build-log SHA256: `3d5326d5fa5de696cb02fd3c021a2336b92362c04fcfc3f00a560a79ec34ad8e`.
The generated BIOS warned that it was stale against the current emitter
fingerprint. It was deliberately identical in A/B to retain compatibility with
the available v5 checkpoint. No native overlay compilation/loading was
configured. These are controlled local integration artifacts, **not** clean
release builds or qualification of regenerated code and shipped overlay caches.
### Local write benchmark
Actual memory implementation, external device stubs, and a genuinely empty
trace function in a separate translation unit; GCC C11 `-O2 -fno-lto`, production
macro enabled. Each run executes 50 million loops of six writes. Both artifacts
warm up with one million loops before measured A1/B1/A2/B2/A3/B3 order. Host is
the Ryzen 7 9800X3D, High Performance power plan, affinity `0x1`, no CPU cap.
Primary metric is launch-to-completion wall seconds, lower is better.
| Pair | A seconds | B seconds |
| --- | --- | --- |
| 1 | 1.4162425 | 1.1504406 |
| 2 | 1.4181166 | 1.1467513 |
| 3 | 1.4099258 | 1.1160858 |
| Minimum | 1.4099258 | 1.1160858 |
| Median | 1.4162425 | 1.1467513 |
Median improvement: 19.03%; minimum improvement: 20.84%. All six runs produced
identical selected state/counters: `state=dcc19de5`, 300 million stores,
150 million parity calls, 150 million card checks, zero trace calls. This
workload emphasizes write-call overhead and does not predict gameplay gain.
The compiler-process monitor reported no observed compilers; thermal/frequency
telemetry was not collected.
### Crabby Beach OpenGL comparison
Same v5 state as CRAB-DISCOVERY-001, staged copies of saves/cards/settings.
OpenGL context reported NVIDIA 3.3.0 driver 610.74, 2x SSAA and antialiasing,
165 Hz display, wall-clock 59.94 Hz pacing, driver vsync OFF. Both uncapped
smokes sustained approximately 60 guest Hz with no reported audio underruns.
A screenshot outside the acceptance runs confirmed the water-facing beach
scene. Smoke timings are excluded: they are not three pairs and B's screenshot
overlapped its smoke window.
Measured A/B uses `PSX_LOAD_SLOT=1`, `PSX_RUNTIME_PERF_DIAG=1`, `PSX_GL_PERF=0`,
`PSX_BENCH_WINDOW=120:420`. Primary metric is the runtime's exact 300-frame
`BENCH wall_ms`, not launcher lifespan. Every run uses a 25%-of-one-logical-core
Job budget (raw CpuRate 157/10000) and affinity `0xFFFF`, limiting the entire
job. Every process is intentionally stopped after 35 seconds. Timed windows
complete before timeout. This scheduling cap is not low-end hardware emulation.
| Pair | A wall ms | B wall ms |
| --- | --- | --- |
| 1 | 8458.662 | 8588.874 |
| 2 | 8312.503 | 8274.390 |
| 3 | 8341.652 | 7946.127 |
| Minimum | 8312.503 | 7946.127 |
| Median | 8341.652 | 8274.390 |
Median improvement: **0.81%, within observed noise**; minimum improvement:
4.41%. No convincing scene-level gain. Each window reports 180,114 interpreted
instructions, 15,564 fallback dispatches, and zero overlay loads, invalidations,
or captures. Those matching counters are not full-state parity. Under the
severe budget both builds slow down and underflow host audio. No compilers or
monitor errors were observed; configuration/mod hashes remained unchanged.
No screenshots were taken during these six runs. No runs were discarded.
### Retained evidence and remaining gates
All following paths are relative to the worktree and are intentionally ignored:
| Artifact | SHA256 |
| --- | --- |
| `.local/memory-trace-bench/campaign.json` | `fd1944a1fc3a8ea750e37ceda9fb12367d9bb85adc8f12d01fdc28df3a3b06ad` |
| `.local/game-memory-ab/campaign.json` | `9d0c70ae9b9d221bc601e24cf7563dcdc61f84fecbae97806e7a7d068d8b5e1d` |
| `.local/run_memory_bench.py` | `90f6fec928f33f3d5e6395d31ce25d7f86ba16359d79851dd8903ef9ef48057d` |
| `.local/run_game_ab.py` | `e3e9fd310600c3dbde14d4b2b4fff741cee2a063c14bbfc9ffe7f8416f71bff9` |
JSON retains per-run evidence and artifact/configuration hashes; game/state
assets are not distributed. The focused compiled regression passes, as do the
FMV quiet and dirty-text admission/continuation guard tests. The combined
campaign/host-launcher/stall-report suite passes all 63 tests, and CMake's
registration guard finds all 120 runtime/recompiler test files registered.
These are not a full runtime CTest pass or cross-platform validation. Two unrelated
structural tests also fail on the immutable baseline; details are in
[the audit](RUNTIME_AUDIT.md#validation-gaps).
Retain the change only as removal of already-empty production work, under the
contract's simplification allowance, not as a 5% gameplay optimization. Full
checkpoint parity, multi-title/low-end hardware, moving/transition routes,
long-window frame consistency, clean-install and cross-platform release gates
remain open. Broader GPU/audio history and GL costs remain separate candidates;
the flat gameplay result does not justify focusing the campaign on memory.
## PACER-COST-001: attribute idle pacing CPU cost
Date: 2026-09-05. Verdict: baseline attribution only, no pacing change or
gameplay speed claim. Compile actual `frame_pacing.c` and `host_time.c` against
SDL3 with GCC 15.2.0 `-O2 -DPSX_SDL3`. The driver calls `frame_pacer_wait` 600
times at 59.94 Hz with no guest work, using `GetThreadTimes` for calling-thread
CPU time and SDL's performance counter for wall time. The first call anchors
without waiting, so 599 periods take approximately 9.9933 seconds.
| Run | Wall seconds | Thread CPU seconds |
| --- | --- | --- |
| 1 | 9.993298 | 0.812500 |
| 2 | 9.993297 | 0.859375 |
| 3 | 9.993297 | 0.765625 |
This is approximately 7.7-8.6% of one logical CPU spent on idle pacing on the
Ryzen 7 9800X3D, High Performance plan, unrestricted scheduling and inherited
affinity. The existing sleep decision stops sleeping below two milliseconds,
then spins until the deadline. This local cost makes the spin tail worth
measuring in gameplay; it does not establish an actual pacer bug or explain
exclusive wall-time phase attribution under a Job cap. No candidate was tested.
Agents held compilation during these runs; no continuous compiler-process or
thermal monitor was attached to this exploratory probe.
Local retained driver `.local/pacer-cost.c`, SHA256
`d98305437fb4e8c86769f31893e43bf63ade8568d761f522e0842817a92ac105`;
executable `.local/pacer-cost.exe`, SHA256
`7aa80698ca5d3a831d0d651601b3c5b2e8bc3665e2eec82f5e426c308eae7ddb`;
raw output `.local/pacer-cost-results.txt`, SHA256
`749fc5185fa512e3a00390f0022fa8939993763ca3b9f6e123ba76ff6654fea1`.
## HISTORY-002: isolated GPU and audio history candidates
Date: 2026-09-05. Correctness milestone; controlled resource/time comparison
pending. No gameplay gain accepted from the correctness runs below.
### Candidate boundaries
- `GP0-HISTORY-001`: compile the GP0 command-history allocation, recording and
diagnostic guest-stack scan only when debug tools are enabled. Production
history accessors remain linkable but report zero capacity/count and empty
dumps. Keep source tracking, opcode counters, WS census, functional overhang
detection, and all command execution unchanged. Removes a 104 MiB allocation
request; debug builds retain always-on history.
- `AUDIO-PCM-001`: compile out four PCM history arrays and their writes in
`PSX_NO_DEBUG_TOOLS` builds. Preserve every tap's head, nonzero/audible/peak
statistics, rates, event records and pump/underrun/mute counters. Production
WAV dump returns the existing error result without opening a file; debug
WAV history is unchanged. Actual GCC object BSS is 83,886,272 bytes with debug
history and 16,777,408 without it: exactly 64 MiB less backing capacity.
Both use the existing build switch, not a new runtime selector or lazy allocator.
Capacity is not equivalent to resident memory or a measured speed improvement.
Packed production audio tap counters may share cache lines between callback and
emulation threads; require the real bridge-audio path in performance checks.
### Focused checks
The GPU test compiles actual `gpu.c` and real headers with external renderer
stubs. It compares exact serialized GPU snapshots and the entire VRAM at eleven
checkpoints across environment, fill, upload, copy, polyline and NOP commands.
It also checks debug history count, copy source/stack pointer and polyline
header, and production unavailability. This tests parser/state behavior, not
the rendering correctness of a stubbed backend. Known pre-existing polyline
source attribution was not changed or silently redefined by the test.
The audio test compiles actual `audio_trace.c` in both modes. All four taps'
observed statistics/events compare equally, including a new event after ring
wrap, signed extremes, audible thresholds, rates and resets. Debug WAV headers
and sample bytes are checked for all taps and for wrap/clamped eviction;
production must not create the requested WAV. Review rejected the first test's
hard-coded output comparison and replaced it with real observations.
Both focused tests passed root execution. The 63 campaign/host/stall-report
tests passed, as did the memory-trace regression. CMake's registration guard
finds 122 test files registered. This is not a full cross-platform CTest pass.
### Isolated build provenance
B is the previous memory-guard baseline, C adds only the GP0 gate, and D adds
only the audio gate. All use identical local-only endpoint memory/restore/
renderer/audio witnesses and an optional checkpoint probe. Original B from
MEMORY-TRACE-001 is preserved. Source trees, exact options/commands/logs and
hashes are in `.local/ape-build-provenance/builds-bcd-manifest.json`, SHA256
`176ed3d54939fab3498ced5d1e3dd38566a9ef76e7e4cb23f5514a8d9a740530`.
| Variant | Executable SHA256 |
| --- | --- |
| B metrics | `f1310b19eb3633d81e01717f36ab60f4d6cd4d53aa3838adf0e614d10bbf4914` |
| C GP0 | `74063d9dadef152d023c0dead18eabde854773c4399f7f52f49713fb67315045` |
| D PCM | `e9f525296dfdb0be2a50596a1198bb9735be0371d21afa627cc6e08dfe05bd39` |
Release GCC 15.2.0, SDL3, static runtime, debug tools OFF, rewind ON, netplay OFF,
same donor generated game/BIOS and saved scene as MEMORY-TRACE-001. The stale
generated-BIOS warning persists. A default all-target build failed in an
unrelated donor title test on a long Windows dependency-file path; explicit
`psx-runtime` builds succeeded. No release qualification is claimed.
### Real OpenGL checkpoint comparison
Separate uncapped 20-second processes restore the same slot-1 v5 beach state.
All variants report active OpenGL (`gl_active=1`, `gr_backend=1`), completed
restore (`generation=1,pending=0,last_ok=1,last_op=load,last_slot=1`), and active
non-legacy bridge output with advancing host-tap frames. The local-only probe
writes raw boot-state blobs at frames 120, 121 and 420, plus CPU timing sidecars.
For **both C and D versus B, every blob and sidecar is byte-identical**.
| Frame | Raw guest snapshot SHA256 (all B/C/D) |
| --- | --- |
| 120 | `505f7d94c5c7e518e099669ed1623296d9210abc30eab4bc907956e91ff652c6` |
| 121 | `1e0b19c3a913733aba9bb403b1e01a1c9d309cc919e3accd9c52f3d341d3429b` |
| 420 | `55ebbe6a36ac2674952a4672575dfb619547ffec0a059f9a277bb955b9d074e2` |
Each blob is 3,685,569 bytes. The existing save format covers CPU registers,
RAM, scratchpad, VRAM, SPU RAM and serialized device/IRQ/clock/cache/dirty state.
The 411-byte sidecars add multiply/divide and GTE completion timestamps, all 33
read-absorb bytes and load-timing scalars. CPU function pointers, host audio
queues, SDL/pacer/presentation histories and diagnostics are excluded. This is
one scene's checkpoint evidence, not exhaustive state/restore or multi-title
qualification. Full campaign correctness gates remain open.
Evidence: `.local/history-correctness-002/report.json`, SHA256
`7feb56b0f0119688e460ff46b027d3aaf9549021e996a935830e26a6154b21f2`;
driver `.local/run_history_checks.py`, SHA256
`f6f29b505225fc0ff8688d9a86921be60590a81288980c5fd7d4c8b56961d0f4`.
Raw game-derived snapshots remain local and uncommitted.
The first attempt, `.local/history-correctness-001`, failed closed because the
staged checkpoint hook was absent despite an earlier build report. Explicit
source and binary-string checks plus forced rebuilds corrected the artifact.
That failed attempt is retained, not counted as successful state comparison.
Checkpoint saves can publish deferred cycles and force GL readback. These runs
also overlapped unrelated builds, so **none of their timing or memory values is
acceptance evidence**. The timed runner explicitly unsets checkpoint capture.
The preregistered primary metric is exact-frame-420 process PrivateUsage; the
secondary metric is frames-120:420 wall time, with three interleaved pairs for
each independent candidate and a validated 25%-one-core Job cap. It requires
stable input/artifact hashes, active OpenGL/audio and completed restore, and
aborts on compiler contamination or missing/nonfinite metrics.
### Timing gate outcome and disposition
The benchmark agent monitored the host for ten minutes without finding 30
consecutive seconds free of compiler/benchmark activity. Concurrent SNES builds
included Mega Man X and Super Mario Kart. No timed candidate process was
launched and `.local/game-gp0-memory-ab` was not created. Unrelated work was
not stopped. The power plan remained High performance before and after;
thermal state was not measured.
The prepared twelve-run driver is `.local/run_game_gpu_memory_ab.py`, SHA256
`ce01b28c627dd5a8d0abe3217a49e2fd1d576fc7128153de502487f69a96f64b`.
It includes `cc.exe` in compiler-contamination detection. Resume the paired
campaign in a quiet host window before making retention or speedup claims.
Disposition: committed candidate implementation in the isolated performance
worktree, **pending performance acceptance**, not release qualification. The
104 MiB GP0 allocation request and 64 MiB PCM static-storage capacity removals
are established; resident-memory savings and gameplay speedups are not.
Focused parity tests and real OpenGL checkpoints passed, but the broader
correctness matrix and the quiet-host comparisons remain open.
## UNCAP-HISTORY-003: uncapped OpenGL throughput
Date: 2026-09-05. User-requested follow-up to HISTORY-002. Uncapped frame
throughput removes a limiter that can hide spare capacity; it does not isolate
the process from concurrent host work or simulate lower-end hardware.
### Mechanism and provenance
`PSX_VSYNC=0` alone leaves the wall-clock pacer active. Debug/manual/load turbo
can skip presentation, and Ape's historical `ape.framerate.uncapped` plugin
actually selects frame interpolation. None is appropriate here. The native
VBlank-rate API already permits zero frontend pacing without changing guest
cycle accounting (`mod_plugins.h`, `psx_mod_set_native_vblank_rate`).
Identical local-only staged `main.cpp` hooks set `g_frame_period_ms=0` and
`g_video_vsync=0` after host-refresh selection and before GL context creation
when `PSX_PERF_UNCAPPED=1`. These are the same host-pacing effects as the native
API; the hook does not call the API or change guest clocks. It refuses active
interpolation. A separate endpoint `[BENCH_PACING]` reports actual period,
effective swap interval, wall-pacing decision, interpolation and load-turbo
state. Normal presentation and the audio bridge remain enabled.
The three incremental Release `psx-runtime` builds succeeded. Original paced
executables and the previous manifest remain unchanged; staged original
`main.cpp` files are backed up. Tracked runtime sources were not changed for
this experiment. New manifest:
`.local/ape-build-provenance/uncapped-builds-manifest.json`, SHA256
`184dfa8a1d0305fc9f1d5aafdd6dda9cec4d0d24b8a73bf684775eefa7103ca4`.
| Variant | Uncapped executable SHA256 |
| --- | --- |
| B baseline | `8050206bda873fd42a8cc790c0b9b556680cdcb966893a1414d3774bbbf11dd6` |
| C GP0 gate | `a43233c44f9c6f6f1a8b7115bc77260de0292571c556e44a5afd97bb4b00d042` |
| D PCM gate | `566297db442e5e062b2b90864302a20d4cbdbe769496d6cdf7d2560089aff5f3` |
### Uncapped correctness smoke
Root ran separate 12-second uncapped processes with checkpoint capture enabled.
All B/C/D raw guest snapshots and CPU sidecars at frames 120, 121 and 420 are
byte-identical to one another **and to the paced B checkpoints in HISTORY-002**.
All restore, OpenGL and bridge-audio witnesses passed. Endpoint pacing reports
zero period, swap interval, wall pacing, interpolation and load turbo. These
checkpoint runs are not timing or memory acceptance measurements.
Evidence: `.local/uncapped-correctness-001/report.json`, SHA256
`551732b3411c74a113d14edf46f185d364684863ec199d223f3b444ee9474709`;
driver `.local/check_uncapped_history.py`, SHA256
`8acb9e3894286237cfbba6b3f3e468c5ff3698c9762c2de488164fa151bd3e91`.
The existing save-format coverage/exclusions from HISTORY-002 still apply.
### Timed protocol
Three interleaved B/C and B/D pairs, reversing within-pair order for pair two:
`B,C,B,D; C,B,D,B; B,C,B,D`. Each process restores the same beach save and runs
with unrestricted requested host CPU budget, affinity `0xFFFF`, the same
OpenGL 2x SSAA/AA settings and diagnostic counters, and no checkpoint capture.
Primary metrics are wall time for guest frames 120:420 and guest VBlanks/s
(`300000 / wall_ms`), not unique rendered game frames or physical display FPS.
PrivateUsage at frame 420 is secondary. Twelve-second process timeouts provide
bounded cleanup; whole-process CPU totals are not the fixed-window CPU cost.
The driver requires unchanged artifacts/settings/game/state/mod inventories,
completed restore, active OpenGL/audio, effective uncapped pacing and matching
guest-work counters. Compiler observations are retained instead of waiting for
a quiet host; monitor failures invalidate results. No result is automatically
accepted based only on the absence of observed compilers. Eight synthetic
driver checks passed root execution.
Local driver `.local/run_uncapped_history_ab.py`, SHA256
`ab3fd3efd56b3088a11221d907a5202101dbfff21fb19186b647cef97bf7c8d3`;
tests `.local/test_run_uncapped_history_ab.py`, SHA256
`3a4b19138e1272490534b3bea84099e32a856d9d922c368fa9f85329abf23044`.
Uncapped audio production outruns the physical playback timeline. An active
audio device and advancing host tap do not establish identical callback counts,
absence of overflow or normal-speed listening quality. Neither title-wide
correctness nor low-end realtime pacing/audio acceptance follows from this
throughput experiment.
### Short-window results
All twelve runs passed the fixed-window, effective-pacing, restore, backend,
audio and inventory checks. No monitored compiler processes or monitor errors
were observed. High performance power plan before/after; no thermal telemetry
or continuous monitoring of all other host workloads. Classification remains
`exploratory_no_compiler_observed`, not automatic acceptance.
| Comparison | Baseline wall ms (three runs) | Candidate wall ms (three runs) | Median VBlanks/s B -> candidate | Median throughput change |
| --- | --- | --- | --- | --- |
| GP0 gate | 1205.440, 1247.664, 1181.284 | 1178.411, 1181.626, 1177.182 | 248.872 -> 254.580 | +2.29% |
| PCM gate | 1212.670, 1185.505, 1215.057 | 1165.711, 1208.837, 1179.628 | 247.388 -> 254.317 | +2.80% |
Within-pair throughput changes were +2.29%, +5.59%, +0.35% for GP0 and +4.03%,
-1.93%, +3.00% for PCM. This spread, especially the PCM sign reversal, prevents
treating the modest median gains as robust. Fastest-run throughput changes were
only +0.35% and +1.70%, respectively. No additive combined gain is established:
each candidate was tested independently.
Endpoint PrivateUsage fell in every pair: GP0 by 102.87, 103.63 and 105.49 MiB;
PCM by 62.00, 63.31 and 64.07 MiB. This measures process private commit in this
scene, not resident physical memory. The amount includes allocator/driver
variation and is not an exact measurement of the removed arrays alone.
Raw report `.local/game-uncapped-history-ab/campaign.json`, SHA256
`5e6477c3969eb8799aee300ffa153903d39c48010a814be76f2cfc19413afb4d`;
power witness `.local/game-uncapped-history-ab/power_plan.json`, SHA256
`034c141dd4dd57cdbd10819bf884aa01c628e1fb496dc1bee83979aa52a55da4`.
All stdout/stderr and host reports are retained beside the report. Root
recomputed paired throughput and memory deltas from the raw rows.
Because the measured windows last only about 1.2 seconds, a separate replication
uses frames 120:1920 (1,800 guest VBlanks), identical executables/settings and
the same twelve-run order. This extends the workload past the GP0 history ring's
wrap point without changing game speed settings or rendering quality. It uses
a separately named driver/output directory and 15-second process timeouts;
the short-window artifacts are preserved.
### Longer-window replication
All twelve runs passed the same evidence checks at frames 120:1920. B/D GP0
history totals reached 1,180,535 commands, beyond the 1,048,576-entry capacity;
C remained zero/zero. Work counters matched within each candidate comparison.
This is a workload/wrap witness, not a new byte-level checkpoint at frame 1920.
| Comparison | Baseline wall ms (three runs) | Candidate wall ms (three runs) | Median VBlanks/s B -> candidate | Median throughput change |
| --- | --- | --- | --- | --- |
| GP0 gate | 7102.496, 7222.937, 7202.571 | 7108.128, 7020.108, 6990.751 | 249.911 -> 256.406 | +2.60% |
| PCM gate | 7295.579, 7127.430, 7366.212 | 6982.187, 7070.005, 6977.142 | 246.725 -> 257.799 | +4.49% |
Within-pair throughput changes: GP0 -0.08%, +2.89%, +3.03%; PCM +4.49%,
+0.81%, +5.58%. Fastest-run throughput changes: +1.60% and +2.15%.
PrivateUsage fell in every pair: GP0 by 105.18, 106.13 and 106.69 MiB;
PCM by 63.12, 61.83 and 63.25 MiB.
Compiler activity was observed in two runs: GP0 pair-one C (32 observations)
and PCM pair-one B (30). Observed names included `cc.exe`, `cc1.exe`,
`cmake.exe`, `collect2.exe`, `g++.exe`, `gcc.exe`, `ld.exe` and `ninja.exe`.
The monitor covers the full process lifetime, not precisely the measured
frame window. No monitor errors occurred. All runs remain in the report;
no post-hoc removal of the slower/contaminated runs was used. Classification:
`exploratory_contaminated`. High performance power plan before/after;
thermal state was not measured.
Evidence:
- `.local/run_uncapped_history_long_ab.py`, SHA256
`459c852c71fd0f4c927a331a91867566704e1fb811b4f2be38fb1a55db6a19d9`.
- `.local/game-uncapped-history-long-ab/campaign.json`, SHA256
`f71891ad99b20ab5bb1f2e148931494c90951f713d25c8ddd6d4107b436007e7`.
- `.local/game-uncapped-history-long-ab/power_plan.json`, SHA256
`7f115cc8e9fa6998f4e01f557098113881fbe2b1264f0434060fd0621a7179a0`.
Root reviewed the longer driver's delta: only window/frame-count/FPS numerator,
output path, metric labels and timeout differ from the short driver. Synthetic
checks accepted a correctly converted 1,800-frame observation and rejected the
old 300-frame observation. Root recomputed paired changes from raw results.
### Disposition
Uncapped benchmarking is useful here: it exposes approximately 250 guest
VBlanks/s of throughput instead of hiding spare capacity behind the 60 Hz
limiter. Both passes suggest modest gains, but run variation and the longer
pass's contamination prevent claiming a reliable general speedup. These are
not additive gains or evidence that Crabby Beach slowdowns are fixed.
Reduced process private commit is consistent across all twelve candidate pairs.
Together with focused parity and the three exact guest checkpoints, this
supports retaining the candidates in the isolated burndown branch for further
qualification. Normal-speed audio/pacing, constrained-host comparisons,
multi-title correctness and actual low-end hardware remain open. Do not ship
the local uncap hook as a gameplay default.
## MANUAL-INTEGRATION-004: user sign-off and master integration
Date: 2026-09-05. The user reviewed the Tomba 2 and Mega Man X6 manual builds,
reported that they "look good", and explicitly authorized committing and
merging the work to main/master. This is user acceptance of the supplied test
builds, not an instrumented audio comparison, full-title playthrough or a new
performance measurement.
Both manual builds used the combined guards from `04371d60`, normal pacing,
OpenGL, recomp-ui, production debug-tools OFF, rewind ON, existing generated
game/BIOS code and isolated copied memory cards. Recomp-ui displayed verified
discs with BIOS paths supplied. Final executable SHA256 values:
- Tomba 2: `6d978d8e2a736aa8b94cb076091117edbe76f6fa7c7f061df227233be8a648ad`.
- Mega Man X6: `4c99e7a6092122b6675daf83f52eddb76a71b0b5290b474c7f4d494df84135b2`.
The initial Tomba build disabled rewind to bypass a dependency lookup; review
rejected that workaround and the final build restored rewind. Mega Man X6's
older recomp-ui headers were incompatible with the runtime, so its manual
build used the compatible Tomba 2 recomp-ui checkout. Neither original game
installation was edited. Local build logs/settings/screenshots remain under
the original burndown worktree's `.local/manual-validation` directory.
Integration uses `origin/master` at `17f49ad3` as its base. Only the six campaign
commits `6488414a`, `d38f97e1`, `8ec6498d`, `28e9c844`, `0cf93d57`, and
`04371d60` were transplanted; the older review-branch ancestry was excluded.
All six applied without conflicts. The runtime delta remains only the three
production diagnostic guards; benchmark uncapping and local game artifacts
are not part of the merge.
The first integration GPU test failed because its old polyline fixture supplied
only one vertex before the terminator. Upstream now correctly requires two
vertices. The fixture now supplies both; the upstream runtime fix is unchanged.
On the integrated sources, all three actual-source guard regressions and the
63 campaign/host/stall-report tests pass. The previously tested game executables
are not rebuilds of this newer upstream base; no such equivalence is claimed.
The CMake registration guard also passes with 133 test files registered.
Disposition: user-approved integration of the scoped optimizations, tests and
measurement tools. The original WIP label records the historical state at
implementation time. The broader burndown, general speedup qualification and
actual low-end hardware coverage remain open after this merge.