mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
docs: add host optimization and upstream audit notes
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
# Host optimization contract
|
||||
|
||||
This document is the acceptance contract for changes intended to make
|
||||
psxrecomp run faster on the host. It applies to generated code, the runtime,
|
||||
the flat CPS trampoline, overlay dispatch and validation, graphics and audio
|
||||
support code, and host-side caches or fast paths.
|
||||
|
||||
Correctness fixes and deliberate guest-visible enhancements still need focused
|
||||
verification, but the 5% performance retention gate below applies specifically
|
||||
to optimizations.
|
||||
|
||||
## Non-negotiable rules
|
||||
|
||||
1. Measure on a quiet host with zero compiler processes. Finish all AOT,
|
||||
overlay, shader, and helper compilation before the timed region. Confirm
|
||||
that no compiler, linker, build driver, or autocompile worker is alive while
|
||||
sampling. A run in which one appears is contaminated and is discarded, not
|
||||
explained away.
|
||||
2. Interleave A and B. Never measure all baseline runs and then all candidate
|
||||
runs. The minimum acceptable order for three pairs is `A1 B1 A2 B2 A3 B3`;
|
||||
reversing the first member of alternate campaigns is encouraged. Keep the
|
||||
same binary and use a forced runtime selector when practical.
|
||||
3. Report every raw run, the min-of-N for A and B, the percentage change
|
||||
between those minima, both medians, and the percentage change between the
|
||||
medians. N is at least three. State whether lower or higher is better.
|
||||
4. Discard a pair when either member is contaminated. Reasons include a
|
||||
compiler process, overlay compilation, shader-cache construction, a changed
|
||||
power or thermal state, unexpected foreground load, a different overlay
|
||||
population, or a different pacing owner. Record the discarded pair and its
|
||||
reason in the experiment ledger.
|
||||
5. A candidate that adds complexity, persistent state, generated-code ABI,
|
||||
configuration or debug ABI, or another maintained fast path is retained
|
||||
only when the agreed primary metric improves by at least 5%. A pure
|
||||
simplification may be retained at flat cost, but its measured numbers must
|
||||
still be recorded. Correctness fixes are judged on correctness, not this
|
||||
speed threshold; do not disguise an optimization as a correctness fix.
|
||||
6. With the optimization forced on for every eligible operation, guest-visible
|
||||
state must be byte-identical to the faithful path at deterministic
|
||||
checkpoints. An automatic fallback is not proof: it can hide the exact case
|
||||
the fast path mishandles.
|
||||
7. The faithful reference computation remains linked and selectable. A fast
|
||||
path is an implementation of that same computation, not a replacement model
|
||||
that merely looks plausible for one title.
|
||||
8. Do not copy another console project's opcode handlers. It is valid to reuse
|
||||
its experimental process, evidence format, test structure, or review
|
||||
checklist. Implement R3000A behavior from PlayStation evidence and
|
||||
psxrecomp's own faithful path.
|
||||
|
||||
## Preparing comparable psxrecomp runs
|
||||
|
||||
Prefer one executable containing both paths and an explicit selector with
|
||||
three states: faithful, candidate forced on, and normal policy. Record the
|
||||
selector and assert in telemetry that the requested state took effect. If two
|
||||
binaries are unavoidable, build both before the campaign and record their
|
||||
commits, toolchains, options, and artifact hashes.
|
||||
|
||||
Static recompilation makes build quietness part of the benchmark definition.
|
||||
Pre-generate the game and BIOS images, warm every overlay needed by the
|
||||
workload, and prevent runtime autocompile during the measured interval. Record
|
||||
the static image identity and the exact overlay inventory. A benchmark is not
|
||||
comparable if A interprets an overlay while B uses a compiled shard.
|
||||
|
||||
Content-verified overlay variants require an additional check. A and B must
|
||||
use the same captured bytes, identity ranges, flavor, cache generation, and
|
||||
selected variant at each checkpoint. Pin and report the overlay flavor. A fast
|
||||
identity check may avoid a CRC or range scan, but the faithful verifier must
|
||||
remain callable and both must select the same content-verified variant.
|
||||
|
||||
The flat CPS trampoline is part of observable execution semantics. Compare the
|
||||
published continuation PC, dispatch result, exception redirect, dispatch depth,
|
||||
and same-site resume consumption at block and function boundaries. A host
|
||||
function returning with `cpu->pc == 0` is not equivalent to tail-publishing the
|
||||
next guest PC, even if common images never cross that boundary.
|
||||
|
||||
Use a deterministic workload with fixed inputs and a fixed capture or replay
|
||||
when possible. Keep renderer, audio backend, window state, display refresh,
|
||||
vsync/pacer ownership, CPU overclock, CRTC multiplier, mods, RAM size, and
|
||||
instrumentation identical. Warm caches outside the timed region unless cold
|
||||
behavior is the stated metric.
|
||||
|
||||
## Correctness proof
|
||||
|
||||
The forced-candidate run must exercise the candidate, and its checkpoints must
|
||||
match a run forced through the faithful reference. Hash or serialize all state
|
||||
that can affect later guest execution, including:
|
||||
|
||||
- GPR, HI/LO, PC, COP0, GTE registers and completion timestamps;
|
||||
- main RAM, scratchpad, VRAM, SPU RAM, and relevant device registers;
|
||||
- the R3000A cycle counter, deferred cycle charges, event deadlines, IRQ state,
|
||||
load-delay state, and pending exception continuation;
|
||||
- dispatch and overlay identity state that changes which guest bytes execute;
|
||||
- deterministic mod-owned guest state and savestate-visible sections.
|
||||
|
||||
Host-only counters may be excluded only when they cannot influence selection,
|
||||
timing, invalidation, or later guest state. Document every exclusion.
|
||||
|
||||
Compare at more than the final frame. Include checkpoints before the candidate
|
||||
first fires, immediately after it fires, at overlay or exception boundaries it
|
||||
touches, and after a long soak. For caches and memos, force hits, misses,
|
||||
invalidation, wrap or saturation, and savestate/rewind restore. For generated
|
||||
code, cover the game emitter, BIOS full-function emitter, compiled overlays,
|
||||
static overlay bakes, and the dirty interpreter wherever the contract crosses
|
||||
execution tiers.
|
||||
|
||||
For cycle-affecting work, use `tools/cycle_testrom/measure.py` as the existing
|
||||
measurement seam. Run the same test-ROM anchors against the native compiled
|
||||
path, `PSX_FORCE_INTERP=1`, and the Beetle oracle as applicable. The script's
|
||||
per-iteration deltas establish guest-cycle correctness; they do not replace a
|
||||
separate interleaved wall-time or throughput campaign for host performance.
|
||||
The faithful per-instruction R3000A cycle model remains authoritative: an
|
||||
optimization may reduce host work but may not remove base charges, wait states,
|
||||
interlocks, GTE completion timing, I-cache behavior, or peripheral deadlines.
|
||||
|
||||
## Performance campaign and evidence
|
||||
|
||||
Choose the primary metric before running the campaign. Frame or workload time
|
||||
is preferred when the work is bounded; throughput is acceptable for a fixed
|
||||
steady-state loop. Report secondary metrics, but do not change the retention
|
||||
decision after seeing the results.
|
||||
|
||||
The evidence block for every candidate contains:
|
||||
|
||||
```text
|
||||
Candidate: <short name>
|
||||
Commits/artifacts: A=<hash> B=<hash> selector=<setting>
|
||||
Workload: <capture, interval, overlay inventory, backend and host>
|
||||
Primary metric: <name and unit; lower/higher is better>
|
||||
Run order: A1 B1 A2 B2 A3 B3
|
||||
Raw A: <a1>, <a2>, <a3>
|
||||
Raw B: <b1>, <b2>, <b3>
|
||||
Min-of-3: A=<min-a> B=<min-b> change=<percent>
|
||||
Medians: A=<median-a> B=<median-b> change=<percent>
|
||||
Correctness: faithful=<hashes> forced-on=<hashes> result=<identical/not identical>
|
||||
Contamination: <none, or discarded pairs and reasons>
|
||||
Complexity/ABI: <none, or exact added surface>
|
||||
Decision: ACCEPT/REJECT - <reason>
|
||||
```
|
||||
|
||||
Worked example, using workload time in milliseconds where lower is better:
|
||||
|
||||
```text
|
||||
Run order: A1 B1 A2 B2 A3 B3
|
||||
Raw A: 12.000, 11.000, 13.000 ms
|
||||
Raw B: 9.744, 9.493, 10.100 ms
|
||||
Min-of-3: A=11.000 B=9.493; improves 13.7%
|
||||
Medians: A=12.000 B=9.744; improves 18.8%
|
||||
Correctness: faithful=7a... forced-on=7a...; byte-identical
|
||||
Complexity/ABI: two per-variant words plus one generated helper call
|
||||
Decision: ACCEPT - both summaries exceed the 5% retention gate
|
||||
```
|
||||
|
||||
Do not report only a profiler share, a best screenshot, an FPS range, or a
|
||||
sequential before/after observation. Those are useful for finding candidates,
|
||||
not for accepting them.
|
||||
|
||||
## Experiment ledger
|
||||
|
||||
Every investigated candidate gets a ledger row, including rejected,
|
||||
superseded, reverted, and flat candidates. The ledger is append-only evidence,
|
||||
not a highlights page. Keep it beside the optimization or campaign notes and
|
||||
include links to raw data when available.
|
||||
|
||||
Required fields are:
|
||||
|
||||
| Field | Required contents |
|
||||
|---|---|
|
||||
| ID and date | Stable candidate ID and campaign date |
|
||||
| Change | Commit(s), artifact hashes, selector, and concise hypothesis |
|
||||
| Scope | Workload, renderer/audio backend, host, toolchain, config, overlay inventory and flavor |
|
||||
| Correctness | Faithful and forced-on checkpoint hashes; exact exclusions |
|
||||
| Runs | Interleaved order, all raw accepted runs, and every discarded pair with reason |
|
||||
| Summary | A/B min-of-N and medians with percentage changes |
|
||||
| Cost | Added code, state, ABI, configuration, maintenance burden, or simplification |
|
||||
| Verdict | Accepted or rejected, threshold used, and reason |
|
||||
|
||||
Accepted and rejected results are equally visible. A compact ledger index may
|
||||
look like this, with each row linking to its full evidence block:
|
||||
|
||||
| ID | Min-of-N change | Median change | State result | Cost | Verdict |
|
||||
|---|---:|---:|---|---|---|
|
||||
| EX-A | improves 13.7% | improves 18.8% | byte-identical | two per-variant words and one helper call | ACCEPT - clears 5% |
|
||||
| EX-R | improves 3.0% | improves 1.0% | byte-identical | new cache and invalidation path | REJECT - below 5% |
|
||||
|
||||
Use an explicit rejected entry even when the candidate was reverted before a
|
||||
formal campaign. Record the numbers that motivated the experiment and state
|
||||
which required evidence is missing. Do not silently delete failed ideas: the
|
||||
ledger prevents the same attractive but invalid optimization from being tried
|
||||
again without new evidence.
|
||||
|
||||
## Review checklist
|
||||
|
||||
- Host was quiet and had zero compiler processes during every retained run.
|
||||
- A/B was interleaved; contaminated pairs were discarded and recorded.
|
||||
- Raw runs, min-of-N, and medians are present.
|
||||
- The forced candidate and faithful reference produced byte-identical
|
||||
guest-visible state.
|
||||
- The faithful path remains linked and selectable.
|
||||
- Overlay identities and execution tiers were held constant and reported.
|
||||
- Cycle-model changes were checked through the cycle-test-ROM seam.
|
||||
- Added complexity or ABI clears 5%; flat pure simplifications have numbers.
|
||||
- Accepted and rejected candidates are both in the experiment ledger.
|
||||
- R3000A implementation was derived independently; no other console's opcode
|
||||
handlers were copied.
|
||||
@@ -0,0 +1,262 @@
|
||||
# Upstream contribution audit — 2026-08-20
|
||||
|
||||
## Summary
|
||||
|
||||
This worktree is a cross-cutting psxrecomp framework hardening and host-performance pass discovered while bringing up WipEout 3 SE: it fixes overlay-DLL generation and byte-variant identity, makes cycle/IRQ/cache shortcuts preserve guest-observable timing, closes several PGXP/GTE provenance holes, extends HD-texture tracking to copied VRAM, separates presentation content policy from the configured outer canvas, and adds reusable profiling/test surfaces. The mechanisms are generally applicable to recompiled PS1 titles; the few title-specific observations and enablement conclusions are isolated below and should not be upstreamed as framework policy.
|
||||
|
||||
## Audit basis and limits
|
||||
|
||||
- The audited delta is the **current working tree against local `HEAD` `4f0b07b1491e` on `feat/ws-cull-widen`**, because that is the boundary that matches the reported session body: 58 tracked files (`3,068` insertions, `871` deletions) plus 16 meaningful untracked source/doc/test files = **74 meaningful paths**. Untracked `build-pgxp-focused/` and `build-texpack-focused/` are build artifacts and were excluded.
|
||||
- `fork/feat/ws-cull-widen` is `e27eac73663f`, an ancestor 27 commits behind local `HEAD`. Diffing to that remote ref would mix those already-committed changes into this working-tree contribution and produce 81 meaningful paths instead of the reported approximately 76.
|
||||
- Per instruction, no build, test, benchmark, or executable was run during this audit. “Covered” below means the test source exists and targets the invariant; it is **not** a fresh pass result.
|
||||
- A number is called “measured” only when the audited files contain an actual observation/result. Loop counts and cache capacities are reported as designed coverage/structure, not as performance results. Where the delta has no result, the measurement is **UNKNOWN**.
|
||||
|
||||
## Corrections to the supplied headlines
|
||||
|
||||
1. **Overlay shim anchor: mechanism supported; manifestation counts unverified.** `patch_generated_c` now anchors after the unconditional `#include "psx_runtime.h"` and fails if it is missing. The delta does not contain a linker transcript or result establishing “~10k undefined runtime symbols” or “zero overlay DLLs”; those numbers are **UNKNOWN** here.
|
||||
2. **Overlay stale-PC/variant identity: supported.** An executed-word overwrite retires the whole page epoch, incoherent capture roots are rejected against current bytes, and static requests are keyed by `(load address, size, bytes)`.
|
||||
3. **Cycle specialization: supported with a qualification.** Generation selects arity 0/1/2/3 helpers, but masks with more than three nonzero dependencies deliberately retain the generic mask helper. Main-RAM load charges and the pre-deadline fast path are present.
|
||||
4. **LTO: feature supported; quoted deltas unsupported.** `PSX_ENABLE_LTO`, the no-inline cycle wrappers, and a deterministic benchmark target exist. No checked-in output contains `-45.92%`, `+10.45%`, or `+32.69%`; all three are **UNKNOWN** for this audit.
|
||||
5. **PGXP/GTE: the correctness changes are supported; “fake never-incremented counters removed” is not.** The old `s_nclip_corrected` counter was incremented on both sign-disagreement branches, and the removed `s_texcorr.z_interp` field was incremented when missing Z values were mean-filled. The change converts NCLIP to non-mutating disagreement telemetry, removes partial-Z interpolation, and adds real primitive/provenance counters; no demonstrably never-incremented counter removal was found.
|
||||
6. **HD texture pack: supported.** Tile occupancy gates invalidation scans, and VRAM copies can become upload-equivalent candidates, with deferred readback for unresolved copies.
|
||||
7. **HD text gate: headline mechanism is wrong.** The change uses an atomic aggregate boolean (`g_dispatch_armed`) derived from capture, string-table, and pending-patch state. It is **not** a monotonic mutation epoch. The separate native-text guard does gain a monotonic epoch, but that is a different subsystem.
|
||||
8. **Presentation: supported as an opt-in capability.** With `fixed_outer_aspect=true`, scene classification selects only the inner content rect; the outer rect comes from configured aspect. Legacy behavior remains when the option is false.
|
||||
9. **Card-write diagnostic: only true for release builds.** `card_data_writes_check` and `card_data_writes_arm` preprocess away under `PSX_NO_DEBUG_TOOLS`; debug builds still execute them.
|
||||
10. **GTE bridge narrowing: supported.** The command exporter stops rewriting inputs/control registers and its source comment counts **40 redundant stores removed per COP2 command**; per-write whole-state canonicalization is removed from data/control writes.
|
||||
11. **Widescreen `items_join`: not in this delta.** `runtime/src/ws_ui_group.c` and its test are unchanged against local `HEAD`. Submission-order joining was implemented earlier in ancestor commit `572b921`; it must be summarized from that commit separately, not claimed as part of this worktree.
|
||||
|
||||
Significant changes missing from the supplied list are the exact IRQ-pending cache and writer audit, I-cache replay isolation, two generated-dispatch caches, cycle-observer flushes, live CRTC cadence reconciliation/config exposure, PGXP rectangle-state disarming, release diagnostic pruning, and new host-optimization/keyboard regression surfaces.
|
||||
|
||||
## Improvement inventory
|
||||
|
||||
Tests named in the last column were inspected but not executed.
|
||||
|
||||
### Correctness bugs
|
||||
|
||||
| ID | Title | Files touched | What it fixes | Why it is general | Test(s) that cover it |
|
||||
|---|---|---|---|---|---|
|
||||
| C1 | Overlay shim anchored to an unconditional include | `tools/compile_overlays.py`; `runtime/include/{cpu_state.h,psx_cycles.h}`; overlay shim tests | A conditional include could make the whole DLL callback shim disappear under `PSX_OVERLAY_DLL_BUILD`, leaving unresolved direct runtime calls. The new anchor is the emitter-guaranteed unconditional runtime include and absence is fatal. | Any generated overlay can acquire conditional headers; no title address or symbol is involved. | `recompiler/tests/test_overlay_store_barrier_codegen.py`; `test_overlay_shim_compile.py` |
|
||||
| C2 | Overlay execution epoch and exact byte-variant identity | `runtime/src/memory.c`; `tools/compile_overlays.py`; overlay discovery/perf-guard tests | Prevents an outgoing image's recorded PCs from being compiled against a later image's bytes; current-byte CFG rejection and exact-image static requests fail closed to interpretation. | Self-modifying code, streamed overlays, and same-address variants are generic PS1 behaviors. | `recompiler/tests/test_aot_overlay_discovery.py`; `runtime/tests/test_interpreter_perf_guards.py` |
|
||||
| C3 | Exact cached INTC output and Cause.IP2 ownership | `runtime/include/interrupts.h`; `runtime/src/{interrupts,boot_state,memory,overlay_loader,sio,psx_interpreter}.c`; IRQ tests | Replaces repeated `i_stat & i_mask` hot reads with one cache while making a single refresh owner update both the full pending value and Cause.IP2 after every writer. | Every title uses the PS1 INTC; correctness depends on hardware-register mutation, not game logic. | `runtime/tests/test_cause_ip2_combinational.c`; `test_perf_round3_guards.py` |
|
||||
| C4 | Deferred-cycle observers flush before reading time | `runtime/src/psx_cycles.c`; cycle tests | MUL/DIV and GTE issue/read/stall paths no longer observe a guest clock that is still sitting in the deferred batch. | These are architectural R3000A/GTE completion timestamps shared by all guests. | `runtime/tests/test_psx_cycle_event_boundaries.c`; `test_perf_round3_guards.py` |
|
||||
| C5 | I-cache replay cannot mutate tags or charge misses | `runtime/include/psx_icache.h`; `runtime/tests/test_psx_icache_fastpath.c` | Replay now suppresses both tag evolution and miss charges while preserving normal cold initialization, hit/miss behavior, and the disabled-cache giveback path. | Lockstep/replay correctness is independent of title code. | `runtime/tests/test_psx_icache_fastpath.c` |
|
||||
| C6 | Primitive-atomic PGXP position/depth and frame-canonical endpoints | `runtime/include/pgxp_vertex_cache.h`; `runtime/src/{pgxp_vertex_cache,gpu}.c`; `ENHANCEMENTS.md`; cache test/CMake | Eliminates corrected/native cracks on shared raster endpoints, prevents the two halves of a quad from making different decisions, and rejects incomplete depth instead of mean-filling missing Z. First use of `(frame, final native x, final native y)` owns the endpoint for the frame. | Shared-edge identity, quad splitting, and incomplete projection depth occur in any PGXP-enabled title. | `runtime/tests/test_pgxp_vertex_cache.c`; depth completeness is exercised indirectly by existing GTE/PGXP tests, but **no focused incomplete-Z primitive test was found.** |
|
||||
| C7 | Per-component PGXP projection identity | `runtime/src/pgxp.cpp`; `runtime/include/pgxp.h`; PGXP tests | Identical packed bits from different projections can no longer be mistaken for coherent X/Y/Z provenance, including after halfword reconstruction. | Packed-word aliasing is a generic provenance problem in MIPS dataflow. | `runtime/tests/test_pgxp.cpp` |
|
||||
| C8 | Destructive PGXP writes invalidate stale provenance everywhere | `recompiler/src/{pgxp_hook_emitter.cpp,pgxp_hook_emitter.h}`; `runtime/include/{pgxp.h,pgxp_hooks.h,overlay_dispatch_preamble.c.inc}`; `runtime/src/pgxp.cpp`; `runtime/src/{memory,psx_interpreter,dirty_ram_interp,overlay_loader}.c`; emitter/PGXP coverage tests | AND/XOR/NOR/SLT families, link writes, COP0/COP2 destinations, and raw RAM/scratch stores now kill old provenance even when the resulting integer bytes are unchanged. Sparse GPR/page-live gates avoid paying the callback when no value is live. | All recompilers/interpreters must agree on writer semantics; no opcode handling is title-specific. | `recompiler/tests/full_function_emitter_test.cpp`; `runtime/tests/test_pgxp.cpp`; `test_pgxp_destructive_write_coverage.py` |
|
||||
| C9 | SXYP shadow FIFO follows architectural FIFO movement | `runtime/src/{gte.cpp,pgxp.cpp}`; `runtime/include/pgxp.h`; GTE test | A write to register 15 shifts shadows `12 <- 13`, `13 <- 14`, then invalidates the new `14/15`, instead of retaining stale SXY0/SXY1 provenance. | SXYP alias/FIFO semantics are architectural GTE behavior. | `runtime/tests/test_gte_register_access.cpp` |
|
||||
| C10 | Precise NCLIP is telemetry-only and guest-bit-exact | `runtime/src/{gte.cpp,gpu.c,debug_server.c}`; `runtime/include/cpu_state.h`; `recompiler/src/config_loader.h`; GTE test | Exact 16.16 winding is still observed, but it never changes guest MAC0 or face visibility; the old saturation-rail clamp rescue can no longer smuggle far-off precise coordinates into rasterization. | Host precision enhancements must not alter guest branches or bypass native GTE saturation in any title. | `runtime/tests/test_gte_register_access.cpp::test_precise_nclip_never_flips_guest_sign` |
|
||||
| C11 | Polygon-to-rectangle boundary clears triangle-only PGXP state | `runtime/src/{gpu.c,gpu_render.c}` | PGXP-armed polygon fast paths are no longer collapsed to rectangles, and native GP0 rectangles clear any prior triangle precision, perspective, or depth state. | Renderer state leakage and primitive fast-path conversion can affect any sequence mixing polygon and rectangle paths. | **No focused test found.** |
|
||||
| C12 | Live CRTC changes reconcile host pacing at VBlank | `runtime/include/gpu.h`; `runtime/src/gpu.c`; `runtime/src/main.cpp`; `runtime/tests/test_host_refresh_sanitize.py` | Host pacing/swap policy is no longer startup-latched: a GP1(08h), restore, or plugin period change resets old pacing debt and is applied before waits; banners defer until the guest programs display mode. | PAL/NTSC switching, restores, and timing plugins are framework concerns. | `runtime/tests/test_host_refresh_sanitize.py` |
|
||||
|
||||
### Performance
|
||||
|
||||
| ID | Title | Files touched | What it fixes | Why it is general | Test(s) that cover it |
|
||||
|---|---|---|---|---|---|
|
||||
| P1 | Generation-time cycle dependency arity | `recompiler/src/{code_generator,full_function_emitter}.cpp`; `runtime/include/psx_cyc.h`; `runtime/src/psx_cyc_steps.c`; emitter/cycle tests | Removes the runtime dependency-mask loop for the common 0–3 register cases while retaining a generic fallback for larger masks; shared no-inline wrappers bound LTO cloning. | Every generated instruction uses the load-delay/cycle dependency machinery. | `runtime/tests/test_psx_cyc_batch.c`; `recompiler/tests/full_function_emitter_test.cpp`; `runtime/tests/test_perf_round3_guards.py` |
|
||||
| P2 | Fused load charge and exact pre-deadline fast charge | `runtime/include/{psx_cyc.h,psx_cycles.h}`; `runtime/src/{memory,psx_cycles,psx_interpreter,dirty_ram_interp}.c`; cycle/perf tests | Main-RAM loads publish base + read fudge + wait/completion as one exact charge; eligible CPU charges advance directly only through `next_service_cycle - 1`, including exact Q16 overclock remainder handling. | Load timing, event deadlines, replay, and overclock transforms are common runtime machinery. | `runtime/tests/test_psx_cyc_batch.c`; `test_psx_cycle_event_boundaries.c`; `test_interpreter_perf_guards.py` |
|
||||
| P3 | Exact-address compiled dispatch caches | `recompiler/src/{full_function_emitter,main_psx}.cpp`; emitter test | Adds a 256-slot TLS cache in generated static dispatch and a 1,024-slot TLS positive/negative cache in game lookup; every hit rechecks the full canonical address before bypassing binary search. | BIOS services, tail-call loops, and large dispatch tables occur across recompiled games. | `recompiler/tests/full_function_emitter_test.cpp` covers the generated-dispatch cache; **no focused test found for the game lookup cache.** |
|
||||
| P4 | Two-level native-text guard epoch | `runtime/src/memory.c`; structural guard test | Stable no-write intervals return after one epoch comparison; after a mutation, the existing exact page-generation sum decides whether the cached verdict remains valid. | Dirty-RAM/native-code guards are shared by every title using mutable text. | `runtime/tests/test_perf_round3_guards.py` |
|
||||
| P5 | Aggregate text-translation dispatch gate | `runtime/include/text_xlate.h`; `runtime/src/{text_xlate.cpp,fntrace.c}`; structural guard test | Avoids invoking the translation callback at every dispatch once capture is off and no string-table or unsettled RAM patch needs it. This is an atomic boolean, not an epoch. | Capture, shipped translations, and pending patch classes are framework state. | `runtime/tests/test_perf_round3_guards.py` |
|
||||
| P6 | Tile-indexed texture invalidation | `runtime/include/tex_pack_rect_index.h`; `runtime/src/tex_pack.cpp`; index test/CMake | VRAM writes first query conservative 16×16-tile occupancy instead of always linearly scanning up to 8,192 upload/palette/pending-copy records; uncertainty returns “maybe” and preserves the slow exact scan. | VRAM invalidation volume and upload count vary by title, while the VRAM geometry is common. | `runtime/tests/test_tex_pack_rect_index.cpp` |
|
||||
| P7 | Narrow GTE command export and incremental canonicalization | `runtime/src/gte.cpp`; `runtime/tests/test_gte_register_access.cpp`; `runtime/CMakeLists.txt` | Commands export only architectural results (`D7..D29`, `C31`) and writes maintain aliases/masks incrementally instead of sweeping the entire backing state after every write. | COP2 register semantics are architectural and shared. | `runtime/tests/test_gte_register_access.cpp` |
|
||||
| P8 | Debug-only store diagnostics compile out of release | `runtime/src/{memory,sio}.c`; structural guard test | Removes universal RAM-store calls to card-write and debug trace consumers, plus SIO arming, when `PSX_NO_DEBUG_TOOLS` has no consumer. Debug behavior is retained. | This is build-mode ownership, not a title exception. | `runtime/tests/test_perf_round3_guards.py` |
|
||||
|
||||
### New capability
|
||||
|
||||
| ID | Title | Files touched | What it fixes/adds | Why it is general | Test(s) that cover it |
|
||||
|---|---|---|---|---|---|
|
||||
| N1 | Opt-in CMake IPO/LTO and generated-hotpath benchmark | `runtime/runtime.cmake`; `runtime/CMakeLists.txt`; `runtime/tests/bench_psx_runtime_hotpath.c`; `runtime/src/psx_cyc_steps.c` | Adds toolchain-checked `PSX_ENABLE_LTO` (default off), applies IPO to runtime targets, and provides a semantic-checksummed generated-style benchmark. | Toolchain optimization and code-shape regression measurement apply to every game target. | Benchmark is deliberately not registered in CTest; **no result is checked in or run here.** |
|
||||
| N2 | HD replacements from VRAM-to-VRAM copies | `runtime/include/tex_pack.h`; `runtime/src/{tex_pack.cpp,gpu_render.c}`; copy test/CMake | Copy destinations can reuse an exact tracked source preimage or become a lazily read-back candidate; CPU upload and copy records share the same position/size/hash semantics and replacement lookup. | Many PS1 engines stage textures in VRAM rather than uploading final destinations directly. | `runtime/tests/test_tex_pack_copy.cpp` |
|
||||
| N3 | Configurable CRTC refresh multiplier | `recompiler/src/{config_loader.cpp,config_loader.h}`; `runtime/include/{gpu.h,mod_plugins.h}`; `runtime/src/gpu.c`; `runtime/src/main.cpp`; host refresh test | Exposes `[video] crtc_refresh_multiplier` in the range 1..8 as the baseline timing setting, with activation plugins allowed to override it. | It turns an existing runtime timing control into a validated per-project framework option. | `runtime/tests/test_host_refresh_sanitize.py` covers live x2 cadence ownership, not parser bounds 1..8. |
|
||||
| N4 | Stable configured outer aspect with scene-specific inner content | `WIDESCREEN.md`; `recompiler/src/{config_loader.cpp,config_loader.h}`; `runtime/include/{gpu_gl_renderer.h,gpu_vk_renderer.h,present_ring.h,ws_present_layout.h}`; `runtime/src/{gpu.c,gte.cpp,main.cpp,gpu_gl_renderer.c,gpu_vk_renderer.c,ws_present_layout.c}`; layout test/CMake | Adds opt-in `fixed_outer_aspect`: menus/FMV can remain centered 4:3 content without changing the outer configured canvas; GL, Vulkan, interpolation, hold-last, and CPU paths use the separated policy. | Outer user preference and content correction are independent concepts for any widescreen title. | `runtime/tests/test_ws_present_layout.c` |
|
||||
|
||||
### Diagnostics and verification surfaces
|
||||
|
||||
| ID | Title | Files touched | What it fixes/adds | Why it is general | Test(s) that cover it |
|
||||
|---|---|---|---|---|---|
|
||||
| D1 | Actionable PGXP and copied-texture telemetry | `runtime/include/{cpu_state.h,pgxp.h}`; `runtime/src/{gpu.c,gte.cpp,pgxp.cpp,debug_server.c,tex_pack.cpp}` | Reports primitive armed/rejected/mixed decisions, endpoint promotion/demotion/cache overflow, memory invalidations, mixed projection rejects, NCLIP sign disagreements, and copy queued/resolved/tracked/invalidated/dropped counts. | These counters diagnose framework coverage and fail-closed behavior without game addresses. | Counter transitions are partially asserted by `runtime/tests/{test_pgxp.cpp,test_gte_register_access.cpp,test_tex_pack_copy.cpp}`. |
|
||||
| D2 | Host optimization acceptance contract | `docs/HOST_OPTIMIZATION_CONTRACT.md` | Defines an interleaved A/B protocol, semantic equivalence requirement, raw-run retention, and a 5% acceptance gate for complexity/ABI-bearing host optimizations. | It is a reusable maintainer process rather than title behavior. | Documentation only. |
|
||||
| D3 | Keyboard Select+Start chord regression | `runtime/tests/test_keyboard_pad_chord.c`; `runtime/CMakeLists.txt` | Adds a focused test for default bindings, simultaneous and consecutive polls, release, and re-press behavior. It does not change production keybind code. | Multi-button active-low pad words are common input behavior. | `runtime/tests/test_keyboard_pad_chord.c` |
|
||||
|
||||
## Concrete evidence and measurements
|
||||
|
||||
### Correctness evidence
|
||||
|
||||
**C1 — overlay shim anchor.** The root cause and new invariant are stated at `tools/compile_overlays.py:2078-2089`: the syntactically last include may be inside the per-instruction I-cache `#ifdef/#else`, and `PSX_OVERLAY_DLL_BUILD` would preprocess away the callback definitions. The insertion now uses the exact unconditional include at `tools/compile_overlays.py:2084-2091`; absence raises `ValueError`. `recompiler/tests/test_overlay_store_barrier_codegen.py:109-118` asserts the flush definition precedes the DLL conditional, and `test_overlay_shim_compile.py:43-59` compiles the expanded PGXP hook surface with the DLL macros when that test is run. Windows exports for the cycle barrier are at `runtime/include/cpu_state.h:76-81` and `runtime/include/psx_cycles.h:192-198`. **Measured result:** UNKNOWN. The supplied ~10k unresolved-symbol and zero-linked-DLL claims are absent from the audited delta.
|
||||
|
||||
**C2 — overlay epoch/identity.** `runtime/src/memory.c:1080-1114` preserves evidence for data-only writes but clears both full-page PC bitmaps and the page gate when any executed word is replaced. `tools/compile_overlays.py:1546-1572` rejects capture-derived roots whose current capped CFG reaches invalid instructions; `tools/compile_overlays.py:2867-2930` keys static demand by `(load_addr, size, data)` and resolves it only against current-byte range identities; isolated generation also reports `INCOHERENT_VARIANT_BYTES` at `tools/compile_overlays.py:3074-3083`. Focused regressions begin at `recompiler/tests/test_aot_overlay_discovery.py:340` and `:366`. **Measured result:** UNKNOWN; no shard ok/failed count or coverage percentage for this change is present.
|
||||
|
||||
**C3 — IRQ cache.** The owner computes `pending = i_stat & i_mask`, stores the full value, and then derives Cause.IP2 at `runtime/src/interrupts.c:149-160`. The structural audit at `runtime/tests/test_perf_round3_guards.py:22-46` currently finds exactly **19** direct `I_STAT`/`I_MASK` mutations across five writer files and requires a refresh near each; it also verifies the cache is stored before the nullable Cause pointer can return (`:55-63`). `runtime/tests/test_cause_ip2_combinational.c:97` repeatedly asserts exact cache equality. **Measured result:** UNKNOWN; 19 is a static writer-set count, not a runtime speed result.
|
||||
|
||||
**C4 — cycle observers.** Flushes precede cycle-dependent work in `psx_muldiv_set`, `psx_muldiv_stall`, `psx_gte_read`, `psx_gte_set`, and `psx_gte_stall` at `runtime/src/psx_cycles.c:696-795`. `runtime/tests/test_psx_cycle_event_boundaries.c` includes explicit delayed GTE read/stall assertions and ends by checking cross-device `D-1 + 1` causality. **Measured result:** UNKNOWN.
|
||||
|
||||
**C5 — I-cache replay.** `runtime/include/psx_icache.h:20-32` separates active-state initialization from replay and calls the miss path only outside replay. Warmed-hit and would-be-miss replay cases are at `runtime/tests/test_psx_icache_fastpath.c:79-103`. **Measured result:** UNKNOWN.
|
||||
|
||||
**C6 — primitive/canonical endpoint.** The cache is a fixed **65,536-entry** table (`runtime/include/pgxp_vertex_cache.h:21-35`) and resolves frame/native-coordinate identity at `runtime/src/pgxp_vertex_cache.c:10-38`. Whole primitive resolution, including all four quad vertices, starts at `runtime/src/gpu.c:3434-3609`; depth arms only when `depth_count == count` (`:3570-3605`), replacing the old mean-filled partial-Z path. The focused cache test covers corrected→native, native→corrected, transform identity, and frame identity; its native→corrected candidate is deliberately **0.75 px**, above the old 0.5 workaround (`runtime/tests/test_pgxp_vertex_cache.c:12-64`). **Measured result:** no performance or in-game crack count is recorded; UNKNOWN.
|
||||
|
||||
**C7 — component IDs.** `PGXPValue` gains independent X/Y/Z projection IDs at `runtime/src/pgxp.cpp:62-68`; coherence predicates require matching nonzero IDs at `:287-307`, and reconstructed halves propagate their component's ID around `:450-526`. `runtime/tests/test_pgxp.cpp:205-220` asserts that mixed projections are rejected even when packed integer geometry can otherwise look usable. **Measured result:** UNKNOWN.
|
||||
|
||||
**C8 — destructive writes.** The hot GPR macro consults `g_pgxp_gpr_live_mask` before calling the invalidator (`runtime/include/pgxp_hooks.h:93-101`); the invalidator itself is at `runtime/src/pgxp.cpp:396-403`. Raw word invalidation and partial-half handling begin at `runtime/src/pgxp.cpp:371`, while six central RAM/scratch write branches are gated in `runtime/src/memory.c:1968-2520`. `runtime/tests/test_pgxp_destructive_write_coverage.py:69-77` requires two 1-byte, two 2-byte, and two 4-byte paths plus both interpreter/emitter surfaces. `runtime/tests/test_pgxp.cpp:137-203` includes byte-identical GPR and RAM writes. The existing source comment says the earlier broad ALU-hook gate accounted for **3.6% of the emulation thread** in a high-refresh workload (`runtime/src/pgxp.cpp:91-101`), but there is no measured delta for the new sparse invalidation gates. **Measured result for this change:** UNKNOWN.
|
||||
|
||||
**C9 — SXYP.** Architectural backing shifts at `runtime/src/gte.cpp:2027-2047`; the shadow shift/reset is implemented in `runtime/src/pgxp.cpp:1033-1053`. `runtime/tests/test_gte_register_access.cpp:501-526` checks the precise FIFO before and after register 15 and all SXY write classes. **Measured result:** UNKNOWN.
|
||||
|
||||
**C10 — NCLIP.** The old double-based sign replacement is removed; `runtime/src/gte.cpp:962-983` computes an exact `int64_t` determinant, increments disagreement telemetry, and always writes native `out` to MAC0. The GPU-side comment at `runtime/src/gpu.c:3644-3650` makes NCLIP audit-only and removes the saturation-rail rescue route to far-off precise raster coordinates. The regression constructs native `+1` versus exact negative and requires guest MAC0 to remain `+1` while the disagreement counter increments (`runtime/tests/test_gte_register_access.cpp:575-602`). The removed code incremented `s_nclip_corrected` in both mismatch branches, and the removed partial-Z path incremented `s_texcorr.z_interp`, so the “never-incremented” description is false. **Measured result:** one deterministic disagreement fixture; no field count or performance result.
|
||||
|
||||
**C11 — rectangle boundary.** PGXP-aware polygon fast paths require position/depth to be unarmed before collapsing a quad to a rectangle (`runtime/src/gpu.c:4049-4054`, `:4287-4330`). `runtime/src/gpu_render.c:135-141` clears precise position, perspective, and depth state; each flat/textured/scaled rect entry invokes it at `:270`, `:288`, and `:296`. **Test/measurement:** no focused test found; UNKNOWN.
|
||||
|
||||
**C12 — live CRTC cadence.** Guest display-mode observation is recorded on GP1(08h) and restore at `runtime/src/gpu.c:6018` and `:6202`. `refresh_live_crtc_cadence` (`runtime/src/main.cpp:2916-2949`) keys transitions by exact guest period, resets old wall-clock debt, reapplies present cadence, and logs only after guest programming; it is called before frame performance/pacing work at `runtime/src/main.cpp:6250`. The structural test's exact arithmetic establishes PAL×2 = **100.0 Hz** matches a 100 Hz panel while NTSC×2 = **120.0 Hz** does not (`runtime/tests/test_host_refresh_sanitize.py:73-86`). These are formula assertions, not a live-run result. **Measured runtime result:** UNKNOWN.
|
||||
|
||||
### Performance evidence
|
||||
|
||||
**P1 — arity helpers.** Both emitters choose specialized helpers and retain the generic fallback at `recompiler/src/code_generator.cpp:51-69` and `recompiler/src/full_function_emitter.cpp:35-53`. The shared wrappers and deliberate no-inline boundary are at `runtime/src/psx_cyc_steps.c:5-68`; overlay DLLs retain inline equivalents at `runtime/include/psx_cyc.h:219-247`. `runtime/tests/test_psx_cyc_batch.c:244-264` defines **98,304 oracle comparisons** (`4,096 seeds × 6 modes × 4 arities`) and compares every `CPUState` byte plus deferred cycles. **Measured performance:** UNKNOWN.
|
||||
|
||||
**P2 — fused/fast charge.** Main-RAM word and half loads compute a single `charge + fudge + 5` publication (`wait 3 + completion 2`) at `runtime/include/psx_cyc.h:280-337`; slow paths carry the same base charge through `runtime/src/memory.c:2316-2383`. `psx_cycles_try_fast_charge` previews and commits both scaled cycles and Q16 remainder only below the published limit (`runtime/include/psx_cycles.h:110-139`), which is set to `next_service_cycle - 1` at `runtime/src/psx_cycles.c:189-194` and invalidated at MMIO/exact/restore boundaries. Tests define **4,096 fused-load oracle cases** (`2,048 × 2 overclock modes`, `runtime/tests/test_psx_cyc_batch.c:267-307`) and **1,152 fast-charge oracle cases** (`2 scales × 16 accumulators × 3 charges × 12 deadline rooms`, `:311-334`). **Measured performance:** UNKNOWN.
|
||||
|
||||
**P3 — dispatch caches.** Generated static dispatch emits the 256-slot table and exact-address hit check at `recompiler/src/full_function_emitter.cpp:1834-1865`; the game lookup emits 1,024 TLS slots and caches both hits and misses at `recompiler/src/main_psx.cpp:1594-1629`. `recompiler/tests/full_function_emitter_test.cpp:199-204` checks the generated helper and address validation. **Measured result:** UNKNOWN; no hit rate, comparison count, or benchmark delta is present.
|
||||
|
||||
**P4 — native-text guard epoch.** The monotonic epoch and global mutation helper are at `runtime/src/memory.c:617-624`; the one-compare hit and page-generation fallback are at `:772-829`. The existing source comment records a motivating profile in which `psx_game_text_native_ok` was **6.8% of the emulation thread** with the older memo never hitting (`runtime/src/memory.c:625-632`). That is a baseline attribution, not a measured post-change gain. **Measured gain:** UNKNOWN.
|
||||
|
||||
**P5 — text-translation aggregate gate.** `refresh_dispatch_armed` derives one atomic gate from capture, string-table, and three pending patch counts (`runtime/src/text_xlate.cpp:441-453`); `fntrace_record` checks it at `runtime/src/fntrace.c:170-171`. The source records two historical baselines: capture scanning at roughly **33k dispatches/frame**, **18.6% of samples**, with capture disabled yielding **+22% fps over guest frames 800–2200** (`runtime/src/text_xlate.cpp:423-435`), and the otherwise-useless fixes-only argument scan at **4.2% of the emulation thread** (`:898-905`). None is a direct A/B of the new outer aggregate gate. **Measured gain:** UNKNOWN.
|
||||
|
||||
**P6 — texture tile index.** The conservative grid uses 16×16 tiles over a 2,048×1,024 domain and returns “maybe” for unindexed/uncertain rectangles (`runtime/include/tex_pack_rect_index.h:15-83`). `invalidate_locked` checks upload, pending-copy, and palette occupancy before any vector scan (`runtime/src/tex_pack.cpp:562-620`); the tracker cap remains **8,192** records (`runtime/src/tex_pack.cpp:54`). The deterministic test runs **20,000 randomized add/remove/query iterations** and asserts there are no false negatives (`runtime/tests/test_tex_pack_rect_index.cpp:47-77`). **Measured performance:** UNKNOWN.
|
||||
|
||||
**P7 — GTE bridge.** `runtime/src/gte.cpp:1702-1729` exports only command results and explicitly counts **40 redundant stores removed per command**. Data/control writes now maintain their own masked/aliased state at `:2012-2081`; full canonicalization remains only at the raw import boundary (`:2099`). The oracle suite covers **2,112 command cases** (`22 functions × 96`), **256** MVMVA selector combinations, and **8,192** stateful stream steps (`64 × 128`) at `runtime/tests/test_gte_register_access.cpp:382-473`, in addition to register-write tests. **Measured throughput:** UNKNOWN.
|
||||
|
||||
**P8 — release diagnostics.** Declarations and call sites are guarded around `runtime/src/memory.c:1298-1301`, `:2006-2012`, `:2142-2150`, and `:2485-2493`; SIO arming receives the same build guard. `runtime/tests/test_perf_round3_guards.py:65-73` scans every card diagnostic call for a release guard. **Measured performance:** UNKNOWN.
|
||||
|
||||
### Capability evidence
|
||||
|
||||
**N1 — LTO/benchmark.** `runtime/runtime.cmake:26-44` defines default-off, C/C++ toolchain-checked `PSX_ENABLE_LTO`; target IPO is applied at `:937-939`. The wrappers resist whole-program cloning at `runtime/src/psx_cyc_steps.c:28-38`. The benchmark models **32 guest instructions/block**, defaults to **4,000,000 blocks** (**128,000,000 guest instructions/sample**), takes **7 samples**, reports median/min/max, and rejects checksum drift (`runtime/tests/bench_psx_runtime_hotpath.c:25-28`, `:227-284`). It is intentionally not a CTest (`runtime/CMakeLists.txt:46-58`). **Measured result:** UNKNOWN; the supplied `-45.92%`, `+10.45%`, and `+32.69%` values are not in the audited repository state.
|
||||
|
||||
**N2 — VRAM copies.** `gr_copy_rect` classifies mask/wrap/overlap preservation and reports the copy before backend mutation (`runtime/src/gpu_render.c:143-156`). `tex_pack_on_copy` (`runtime/src/tex_pack.cpp:817-893`) shares exact full-source preimages, hashes subrects, invalidates the destination in the original order, and otherwise queues a fail-closed candidate. Draw-time query/resolution is at `:896-989`; CPU and copy records meet in the shared builder at `:625-657`. `runtime/tests/test_tex_pack_copy.cpp` checks CPU/copy key parity, invalidation, and replacement lookup. Its optional, unregistered benchmark is designed for **200,000 copies**, **5,000,000 no-pending queries**, and **50,000 hashes** (`:168-208`), but no output is checked in or run. **Measured performance:** UNKNOWN.
|
||||
|
||||
**N3 — CRTC config.** Parsing and range rejection are at `recompiler/src/config_loader.cpp:573-579`; the default/range contract is at `recompiler/src/config_loader.h:412-414`. Config is applied before activation plugins at `runtime/src/main.cpp:11534-11538`. The available test checks x2 live cadence only; it does not test TOML parser bounds or multipliers 3–8. **Measured compatibility/coverage for 3–8:** UNKNOWN.
|
||||
|
||||
**N4 — outer/content layout.** The API contract explicitly forbids scene classification from changing the outer rect when fixed mode is enabled (`runtime/include/ws_present_layout.h:20-32`); the independent rect calculation is at `runtime/src/ws_present_layout.c:33-60`. GL uses it at `runtime/src/gpu_gl_renderer.c:3452-3461`; Vulkan uses it at `runtime/src/gpu_vk_renderer.c:1688-1706`; scene classification was renamed/content-scoped in `runtime/src/gpu.c:324-390` and `runtime/src/main.cpp:7022-7391`. The focused test checks exact 32:9-in-16:9 and 21:9 layouts plus unchanged legacy behavior (`runtime/tests/test_ws_present_layout.c:14-43`). **Measured visual/performance result:** UNKNOWN.
|
||||
|
||||
### Diagnostic evidence
|
||||
|
||||
**D1 — telemetry.** Primitive counters are defined and exported at `runtime/src/gpu.c:3460-3498`; PGXP memory/mixed-projection counters are maintained in `runtime/src/pgxp.cpp`; NCLIP disagreements are at `runtime/src/gte.cpp:941-983`; debug JSON publishes them at `runtime/src/debug_server.c:5216-5276`. Copy lifecycle counters are defined at `runtime/src/tex_pack.cpp:204-207` and published at `:1465-1487`. **Measured field values:** UNKNOWN; the change adds counters but the delta contains no captured report.
|
||||
|
||||
**D2 — optimization contract.** `docs/HOST_OPTIMIZATION_CONTRACT.md:29-44` defines the 5% retention and semantic-equivalence gates; `:103-110` requires a separate interleaved performance campaign; `:185-200` specifies evidence retention. The example percentages at `:181-182` are explicitly examples, not results from this work. **Measured result:** not applicable.
|
||||
|
||||
**D3 — keyboard chord test.** `runtime/tests/test_keyboard_pad_chord.c:24-50` checks default Return/RShift bindings and six pad states: released, Select held, first and consecutive Select+Start polls, Start release, and Start re-press. `runtime/CMakeLists.txt:86-95` registers it. **Production change/measurement:** none; this is regression coverage only, and it was not run here.
|
||||
|
||||
## RISK / REVIEW FOCUS
|
||||
|
||||
1. **Overlay byte identity and shim placement (C1/C2) — highest review priority.** Preserve three invariants: (a) the callback preamble is outside every conditional and missing the guaranteed runtime include is a hard failure; (b) a compiled body satisfies a request only when load address, size, and exact bytes/range identities agree; (c) overwriting an executed word retires all PC/dispatch evidence for that page's outgoing image, while an unrelated data-only write does not erase valid execution evidence. False positives execute wrong bytes; overly broad invalidation silently destroys AOT coverage.
|
||||
2. **Guest cycle exactness and device deadlines (C4/P1/P2) — highest review priority.** Specialized helpers must be byte-for-byte equivalent to the generic dependency mask, preserve GPR0, read-absorb and load-delay ownership, and retain COSIM/replay/conservative/device-service fallbacks. A fused load must apply base, read fudge, wait, completion, and Q16 overclock remainder exactly once. Fast charge must never reach or cross `next_service_cycle`, overflow the clock, bypass a local/deferred batch, or leave stale eligibility after MMIO, exact service, or restore. Every observer of absolute guest time must flush first.
|
||||
3. **IRQ pending cache and live CRTC timing (C3/C12/N3) — highest review priority.** Every direct `I_STAT`/`I_MASK` writer, including init, restore, acknowledgment, and temporary CD/SIO masking, must call the refresh owner. The full cached value must remain exactly `i_stat & i_mask`; only Cause.IP2 is masked to the architectural 11 bits. Cached snapshots must be refreshed after any idle skip/device service. CRTC period changes must be reconciled before the next host wait without changing plugin override precedence or inheriting pacing debt from the old period. Adding a twentieth writer without extending the structural audit is a correctness bug.
|
||||
4. **PGXP provenance and guest visibility (C6–C11).** NCLIP telemetry must never write a host-corrected sign to guest MAC0. Projection IDs must survive only operations that prove component identity; every destructive writer must invalidate across both interpreters, generated code, and overlay DLLs. Endpoint-cache collision/overflow behavior must remain deterministic and fail to native geometry. Rect paths must clear every triangle-only backend latch.
|
||||
5. **VRAM copy tracking (P6/N2).** Masked, wrapping, or overlapping copies must never claim a pre-copy source hash as the post-copy destination. Pending readback must use the authoritative post-copy backend, be invalidated by later writes/restores, and preserve CPU-upload key semantics including position. Tile indices may have false positives but must never have false negatives.
|
||||
6. **LTO/code-shape claims (P1/N1).** Review generated symbol visibility on MSVC/Clang/GCC and ensure IPO does not clone the guarded wrappers or drop DLL exports. Do not accept the quoted LTO percentages without raw commands, compiler/linker versions, interleaved samples, and semantic checksums; none is present in this delta.
|
||||
7. **Presentation backends (N4).** Fixed mode must produce the same outer rect for gameplay, menu, FMV, interpolation, hold-last, native VRAM, CPU, and wide paths; scene state may change only the inner rect. Legacy mode must remain byte/geometry compatible for projects that do not opt in, especially Vulkan's pre-existing 4:3 policy.
|
||||
|
||||
## NOT UPSTREAMABLE
|
||||
|
||||
- The WipEout-specific conclusion in `ENHANCEMENTS.md:879-883` that `pgxp_tolerance = 1.0` is valid for WipEout 3 is title validation/configuration, not a framework default. Upstream the canonical-endpoint mechanism and neutral explanation; keep the title's chosen value in the game repository.
|
||||
- The explanatory phrase “the WipEout 8 MB payload depends on that” at `runtime/src/memory.c:1090` should be generalized before submission. The whole-page execution-epoch mechanism itself is upstreamable.
|
||||
- `docs/internal/upstream/WIPEOUT3_SESSION_ENGINE_DRAFTS_2026-08-20.md` is a WipEout session/commit audit, not framework product documentation; do not include it in an upstream code PR.
|
||||
- `build-pgxp-focused/` and `build-texpack-focused/` are generated build trees and must not be committed.
|
||||
- Enabling `fixed_outer_aspect`, a non-stock CRTC multiplier, PGXP tolerance 1.0, or any other title profile belongs in the game repository. The parser/runtime capabilities and default-off framework behavior are upstreamable.
|
||||
- No new WipEout-specific executable address hook was found in the working-tree delta. Existing title references outside the added lines are not evidence that the general mechanisms above are per-game hacks.
|
||||
- The `items_join` UI grouping change is potentially upstreamable, but it is **not part of this delta**; submit/review it from ancestor commit `572b921` with its own history and evidence rather than folding it into this worktree summary.
|
||||
|
||||
## Suggested PR split and landing order
|
||||
|
||||
1. **Overlay DLL shim placement/export fix (C1).** Small, independently testable, and prerequisite to trusting any overlay-DLL validation.
|
||||
2. **Overlay byte-identity correctness (C2).** Land after the shim fix so generated overlay tests exercise a valid DLL surface. Keep the page-epoch producer fix and byte-keyed consumer/static-request logic together because either half alone leaves a stale-image path.
|
||||
3. **IRQ pending-cache ownership (C3).** Separate high-risk PR. Include all current writers and the 19-writer structural audit in the same change.
|
||||
4. **Cycle exactness foundation (C4, C5, P2 without code-shape claims).** Land observer flushes, replay-safe I-cache behavior, fused load accounting, deadline publication/invalidation, and oracle tests together. This establishes equivalence before performance-oriented code generation changes.
|
||||
5. **Cycle arity specialization and LTO surface (P1, N1).** Depend on PR 4. Keep the benchmark and no-inline rationale with the specialization; publish new raw benchmark results instead of the unsupported percentages in the supplied headline.
|
||||
6. **Dispatch/text/release hot-path pruning (P3, P4, P5, P8).** These are independent of guest timing after PR 4. Prefer three reviewable commits/PRs if maintainers want narrower invalidation reasoning: dispatch caches; text guards/gates; debug-only store hooks.
|
||||
7. **GTE/PGXP provenance correctness (C7–C10, P7).** Land component IDs, destructive-write coverage, SXYP FIFO, NCLIP telemetry-only behavior, and narrowed architectural export first. These define trustworthy provenance and guest-bit-exact boundaries.
|
||||
8. **PGXP primitive/canonical endpoint and renderer boundary (C6, C11, D1 PGXP portion).** Depend on PR 7. Add a focused rectangle-state regression before submission; keep the WipEout tolerance conclusion out of the framework PR.
|
||||
9. **Texture-pack index, then copy candidates (P6 followed by N2 and copy telemetry).** The copy queue uses the conservative index and shared upload storage, so land/test the no-false-negative index first. Review copy/mask/wrap/overlap and savestate invalidation as a separate capability PR.
|
||||
10. **Presentation layout separation (N4).** Independent UI/rendering PR covering the shared layout helper and both GL/Vulkan paths. Preserve default-off legacy behavior.
|
||||
11. **CRTC configuration and live cadence (C12/N3).** Separate timing-sensitive PR after IRQ/cycle work. Add parser tests for 1, 8, 0, and 9 and either validate or narrow the advertised 1..8 range before landing.
|
||||
12. **Maintainer diagnostics (D2/D3).** The optimization contract can land as documentation if the project accepts that policy; the keyboard regression can land independently. Neither should block correctness PRs.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: session measurements (recorded 2026-08-20)
|
||||
|
||||
The audit above correctly marks several deltas UNKNOWN because no result transcript is
|
||||
checked in. These numbers WERE observed live during the session that produced this work.
|
||||
They are recorded here so a reviewer can see the basis; they are single-machine
|
||||
observations, not a portable benchmark suite.
|
||||
|
||||
**Host:** AMD Ryzen 7 9800X3D (8C/16T, 4.7 GHz, X3D cache), Windows 11, pinned
|
||||
retcomm cmake-clang-v1 (Clang 22.1.8). Title: WipEout 3 SE (PAL, SCES-02845) with
|
||||
hueponik's official PAL 100 fps patch, 8 MB RAM, max in-game detail,
|
||||
`cpu_overclock = 900`, `crtc_refresh_multiplier = 2` (PAL 50 Hz x2 = 100 Hz).
|
||||
|
||||
### LTO / codegen benchmark
|
||||
`runtime/tests/bench_psx_runtime_hotpath.c`, median-of-three rotated runs, seven samples
|
||||
each, 256M guest instructions per sample. EVERY variant produced identical results
|
||||
(guest cycles 33,774,169; oc accumulator 60,416; services 0; digest 3d88e743440954f1),
|
||||
which is the equivalence evidence for this workload.
|
||||
|
||||
| Variant | Guest instructions/s | vs baseline |
|
||||
|---|---:|---:|
|
||||
| Baseline `-O3` | 451,062,216 | - |
|
||||
| Plain ThinLTO | 243,919,125 | -45.92% |
|
||||
| Plain full LTO | 585,251,881 | +29.75% |
|
||||
| ThinLTO + guarded wrappers | 498,195,210 | +10.45% |
|
||||
| **Full LTO + guarded wrappers** | **598,526,036** | **+32.69%** |
|
||||
| `-march=native` only | 457,336,000 | +1.39% |
|
||||
| `-march=znver5 -mtune=znver5` | 441,476,740 | -2.13% |
|
||||
| Helpers moved to headers/unity | 162,246,886 | -64.03% |
|
||||
|
||||
Plain ThinLTO REGRESSES badly by cloning hot interlock bodies; the `noinline` guard at
|
||||
`runtime/src/psx_cyc_steps.c:27` is what makes LTO a win. ThinLTO executable size fell
|
||||
97,148,928 -> 86,249,472 bytes (-11.22%) once guarded, confirming the over-cloning.
|
||||
|
||||
### End-to-end guest speed (identical BIOS boot phase each run, so directly comparable)
|
||||
| Build state | Guest speed |
|
||||
|---|---:|
|
||||
| Session start (no static bake; overlay autocompile degraded) | 0.36x |
|
||||
| + partial static bake (25/62 shards) | 0.42x |
|
||||
| + clean static bake (26 shards, 0 failed) | 0.49x |
|
||||
| + overlay DLL link fix; PGXP hooks and rewind disarmed | 0.58x |
|
||||
| + cycle-accounting specialization, PGXP RE-ENABLED, 100% texture pack | 0.50x |
|
||||
| **+ full LTO** | **0.68-0.76x** |
|
||||
|
||||
LTO's end-to-end contribution on this identical phase was ~+35%, consistent with the
|
||||
+32.69% synthetic result. 3D attract-mode scenes were observed sustaining
|
||||
`VBlank 100/s 1.00x, render 100 fps`.
|
||||
|
||||
### Overlay pipeline
|
||||
| Metric | Before | After |
|
||||
|---|---:|---:|
|
||||
| Overlay DLL link errors | 10,395 diagnostics / 20 symbol names | 0 |
|
||||
| Overlay DLLs produced by autocompile | 0 | 1,442 in cache |
|
||||
| Static bake shards | ok=25 failed=23 skipped=14 | ok=26 failed=0 skipped=14 |
|
||||
| Runtime dispatch | `disp_native=0`, `disp_interp=4,456,079` | native dispatch active |
|
||||
|
||||
The `disp_native=0` figure is from the runtime's own `psx_last_run_report.json`, which also
|
||||
carried `autocompile_degraded=1` with the reason string naming the cause.
|
||||
|
||||
### HD texture pack
|
||||
| Metric | Before | After |
|
||||
|---|---:|---:|
|
||||
| Requested (texture,palette) pairs covered | 39 / 439 (8.88%) | 439 / 439 (100%) |
|
||||
| Pack entries | 292 | 445 generated (692 live incl. legacy) |
|
||||
| PNG payload | 2.61 MiB | 4.32 MiB |
|
||||
|
||||
Verification on the generated set: alpha subset of source 402/402, RGB subset 402/402,
|
||||
all dimensions <= 4096, F500 font atlases byte-identical (untouched).
|
||||
|
||||
### PGXP
|
||||
Hook cost measured by the 1 kHz IP sampler on the emulation thread, in race:
|
||||
`psx_pgxp_load` 7.66% + `psx_pgxp_alu` 3.63% + `psx_pgxp_store` 2.35% +
|
||||
`psx_pgxp_cop2` 1.20% = **14.84%** while both correction features were DISABLED, because
|
||||
`precise_nclip` and `pgxp_cpu_mode` arm the hook surface independently. After disarming,
|
||||
the same buckets fell to 0.69% total.
|
||||
|
||||
### Font atlases (game-side asset work, listed for completeness)
|
||||
| Metric | Before | After |
|
||||
|---|---:|---:|
|
||||
| Detached ink components (ringing lobes) | 986 | 0 |
|
||||
| Dark ink pixels (black halo) | 87,650 | 0 |
|
||||
| Alpha masks | - | byte-identical before/after |
|
||||
Reference in New Issue
Block a user