Files
psxrecomp/docs/LOAD_TIME_ZERO.md
2026-10-25 11:33:40 -07:00

424 lines
24 KiB
Markdown

# LOAD_TIME_ZERO — proposal checklist for loads-toward-0
Branch: `spike/load-time-zero` (worktree `_wt-loadtime-zero`).
Status: **exploration only — no code changes.** This doc is the burn-down
list; each experiment gets its own future session, its own flag, and a
stated kill criterion. Written 2026-07-13/14 from a memory sweep, a code
survey of the live tree, and a ChatGPT consult (project thread
"PSXrecomp workspace", 2026-07-13 — full 8-minute Extra-High answer in
that thread; merged into §1/§4/§5 here).
---
## 1. The frame (get this right before proposing anything)
```
wall_time(load window) = guest_time(window) x host_seconds_per_guest_second
```
Under `turbo_loads` there is **no wall-clock pacing**: the window is
emulation-throughput-bound *by definition*. CD sector deadlines are
scheduled in **guest cycles** (`cdrom.c sector_delay_cycles()`), so turbo
compresses them like everything else; the measured ~2x is the **host's
achievable emulation rate** inside the window, not a CD-cadence wall.
Both we and ChatGPT independently flagged that our earlier "loads are
bounded by authentic CD cadence" phrasing conflates guest-time duration
with the wall-time ceiling. A 2MiB transfer at 2x CD speed is ~6.8s of
guest time; at a 20x turbo multiplier that is ~340ms of wall time with
every VBlank, IRQ, and sector deadline intact.
Terms our first decomposition missed (ChatGPT):
- **Pipeline overlap**: per-sector guest time ≈ max(sector interval,
guest per-sector processing) — decompression can hide under cadence,
or dominate it. At 2x the interval is ~6.67ms guest.
- **Explicit game dwell**: fade timers, minimum-display periods, audio
sync waits — guest time no CD or CPU trick removes.
- **Seek/spin/command latency** as a term separate from bulk rate
(Sony's own figures: ~250ms average seek).
- **Host device/event cost**: per-block device stepping, IRQ checks,
SPU/GPU/event machinery can bottleneck turbo even with native CPU code.
- **Residual wall-clock coupling**: audio queue pressure, SDL pumping,
present deadlines can silently cap turbo.
Three levers, in ascending order of danger:
- **Lever A — cut host cost per guest second.** Timing-invariant, safe
by construction. Data shards, overlay coverage, event-horizon
acceleration (§4 E4), device-batch advancement, renderer cost.
Any Lever-A win multiplies with turbo and is invisible without it.
- **Lever B — cut guest time of the window.** Title-sensitive, the
proven wedge zone. ChatGPT, from DuckStation/PPSSPP/Dolphin evidence:
**there is no universally safe guest-visible CD speedup** — DuckStation
ships per-game disable traits for read AND seek speedup plus
MDEC/CDDA exclusions; no public breakage denominator exists.
- **Lever C — skip guest work / synthesize state.** Twice-proven
hardlock class (yield pumps r1/r2). The only credible TRUE near-zero
is here: a validated state-transition cache (conditional savestate) —
explicitly Phase-2, never foundation.
---
## 2. Prior-attempt ledger (do not re-litigate; verbatim outcomes)
1. **turbo_loads — SHIPPED, production path.** Engage = sustained
`cdrom_load_in_progress()` + game-started, 4-frame entry debounce +
6-frame release debounce
(`main.cpp:2062-2084`); presents 1-in-30; audio deliberately NOT
muted (queue capped). ~2x wall on Tomba loads with a healthy overlay
cache. NOTE (survey 2026-07-13): the primary SDL event pump now runs
BEFORE the turbo early-return (`main.cpp:2006-2009`), so the historic
"Not Responding" starvation appears fixed in code. A 2026-07-14
MMX5 debug-tools+turbo smoke reached live gameplay with normal load,
video, and audio; recurrence/root cause of the historic intermittent
0xE10 exception storm remains a longer-soak foundation issue.
2. **disc_speed=4x/instant — PROVEN UNSAFE as a divisor.** Changes how
many VBlanks fall between guest state transitions; MMX6
VSync-callback tick counter freezes → boot wedge, even with the
response arbiter (overwrites 24→0) and fixed (non-scaled) cmd
latency. `instant` today is bounded (`instant_max_per_frame`,
default 32/frame) — an IRQ-storm cap, not a safety rule.
⚠ Tree split: the live checkout (`feat/vigilante8-widescreen`) runs
the Ape direct-delivery CD model (no arbiter); master's model differs
(debt clamp at `master:cdrom.c:1633`). Any CD experiment must first
name its canonical tree.
3. **Instant-loads round 1 (gen-time yield pump) — ABANDONED.** Tomba's
loader `FUN_80021340` is also the live world-streamer; skipping its
cooperative yields starves co-thread/IRQ progress → gameplay hardlock.
4. **Instant-loads round 2 (syscall-level yield skip) — PARKED.** By
ChangeTh-syscall time the guest wrapper has already committed
green-thread yield bookkeeping; ONE skipped switch is eventually
fatal. Kept: the `[instant_loads]` inert scaffold (load-grid
detector, window plumbing, TCP counters) and the measure-first method.
5. **Data shards — REJECTED/QUARANTINED after live Tomba test.** Memoized
pure-function replay: read-set/write-set/
reg-out/cycle-cost capture at emitter-hooked funcs, byte-verified
replay, recorded cycles credited in slices through the normal
advance+IRQ path. Purity poisons: MMIO, DMA write, thread switch,
4MiB budget, staleness. Persists per-shard `.dss`. Known v1 fidelity
deltas (documented in DATA_SHARDS.md): ISR cycles double-credited;
IRQ arriving mid-credit observes the completed write-set; GTE/COP0
not captured. Live replay of `FUN_8003EF50` corrupted title and gameplay
textures despite zero verifier failures, proving the v1 temporal verifier
unsound. Runtime default is off and derived shards were removed.
6. **Generic idle-loop skip — SHIPPED, strictly per-game opt-in.** Requires
repeated same-PC interrupt boundaries with no stores, no MMIO, and stable
registers; advances to the next device deadline in whole loop quanta.
Tomba live A/B: 4,599 skips / 765M guest cycles, warm main burst
2.19 s -> 1.73 s, zero CD overwrites. Opt in per game with
`[runtime] idle_skip=true`, per process with `PSX_IDLE_SKIP=1`, or live
through the `idle_skip` debug command. Known accepted cosmetic tradeoff:
an occasional partial texture frame may flash under aggressive load turbo
and resolves on the following complete frame.
7. **Strict warm CD routes — SHIPPED, per-game opt-in only.** The reusable
`[[runtime.warm_cd_routes]]` schema supports up to 16 exact LBA sequences.
A mismatch immediately restores normal timing. Only non-XA data-read cadence
changes; XA/CDDA, seek, motor, IRQ, DMA, callback, and decompression paths
remain authoritative. Tomba regression: three route matches, 1,944 accelerated
sectors, zero response overwrites. The legacy `[runtime.warm_cd_route]`
singular table is deprecated but remains readable with a warning.
8. **Turbo host-audio sink — SHIPPED, per-game opt-in only.** With
`[runtime] turbo_audio_sink=true`, the canonical SPU still renders every
guest-time sample during an active turbo load, preserving voice and CD state,
but the accelerated buffers stop before SDL playback. Normal playback fades
back in from the current guest state. Tomba listening QA passed with
1,100,752 sink-discarded SPU frames, two accelerated load-route matches, and
zero CD response overwrites. This does not revive the rejected mute/freeze
model, which paused voice state and produced audible cuts/restarts.
Also relevant, shipped: FMV auto-skip (unpaced + muted + game-native
teardown), HLE boot shell-skip + boot-turbo window (`bios_hle.c`),
kernel bless (byte-verify-then-trust precedent).
---
## 3. Verdicts on the session's naive musings (be honest in future sessions)
- **"o2r equivalent — see it once and shard it"** → this IS the
data-shards spike, already designed and built; the open work is
measurement and a first live run. True o2r (AOT asset transformation)
is unavailable to a recomp — no source-level asset knowledge.
ChatGPT: the transferable o2r lesson is the **cache identity +
validation model**, not the timing model; no prior art does
read-set-replay-with-cycle-credit.
- **"HLE out the decompression"** → dominated by data shards for
repeated inputs, but ChatGPT identified real niches where HLE-out
wins: inputs mostly unique (shard hit-rate poor), read-set compare
approaching decode cost, algorithm SIMD-friendly (bitstream code can
beat generated C by >>2-5x), or — notably — **the decompressor runs
from dirty RAM under the interpreter** (overlay region!). Decision
rule: profile first; shards if inputs repeat + verify cheap; HLE if
unique inputs or expensive verify; neither if decompression isn't
host-hot. HLE shares the same temporal-write-visibility flaw as
shards — it is not automatically more faithful.
- **"Skip decompression" literally (don't charge cycles)** → Lever B/C
in Lever A's clothes; the cycle credit is what keeps the scheduler,
IRQ phase, and CD interlocks sane. Any instant-shards mode is a
Phase-2 per-game enhancement.
---
## 4. The burn-down checklist (merged: ours + ChatGPT's session plan)
Rules of engagement, every experiment: (1) measure FIRST via always-on
rings; (2) behind a flag, default off; (3) one experiment per session;
(4) kill criterion stated before the first line of code; (5) corpus
gate: Tomba load+world-streaming, MMX5 dev-tools boot, MMX6 boot/VSync
progression, repeated load/save cycles, attract demos, XA/CDDA, MDEC
FMV, a seek-heavy path, and (once identified) a direct-CD-hardware
title.
Standard probe report for every load window: guest cycles; wall time;
effective multiplier; CD commands/LBAs/deadline-vs-exposure times; seek
durations + buffer depth; VBlank/IRQ sequence; scheduler switches;
top host CPU consumers; SDL pump gaps; audio queue depth; canonical
architectural state hashes at checkpoints (never raw host structs).
### E0 — `load_probe_v2`: window decomposition [GATES EVERYTHING]
The decisive question: **why does the machine advance at only ~2x
during a window whose presentation is already suppressed?**
Segment guest time (seek/start, sector intervals, per-sector
processing, explicit waits) and host time (game/BIOS native code,
decompressors, dirty-RAM interp, CD/event machinery, SPU, GPU/present,
scheduler/IRQ checks, OS/UI pump). ChatGPT decision thresholds: if
decompression ≥ ~40% of host time → shards are serious; ≤ ~8% → kill
shards immediately; if CD/event/SPU machinery dominates → do E4; if a
wall-clock limiter is still active → everything else is a distraction.
Prereq: master's LOAD GAME in-exception wedge may block the Load-Game
repro — use area transitions if so.
### E1 — Turbo hardening (Lever A hygiene; ChatGPT rank #1 with E4)
(a) `turbo_event_pump`: re-validate the pump fix live (gate: max UI
pump gap ≲50ms, input recognized <100ms, <2% load regression).
(b) `turbo_audio_sink`: fix at the HOST SINK only — keep SPU/XA/CD
audio emulation exact; consume/discard excess host samples, keep a
small tail, crossfade on turbo exit; NEVER touch guest mute registers
or skip guest audio processing (kill: any guest-state manipulation).
(c) MMX5 dev-tools+turbo 0xE10 storm: root-cause as a foundation
timing bug (kill: any proposal that masks the storm instead of
explaining the first divergence).
### E2 — `turbo_next_event`: event-horizon acceleration [NEW AXIS — ChatGPT]
Advance through *provably* side-effect-free polling/idle regions
directly to the next scheduled observable event, with exact cycle
credit and identical event ordering. NOT heuristic idle-skipping —
requires proof: no memory writes beyond stack churn, no MMIO reads
whose value changes before the event, no IRQ deliverable earlier,
exact ordering at the destination. Candidates: CD status polls, thread
idle loops, BIOS waits, GPU/SPU completion polls. Companion change:
**batch device advancement to the next deadline** instead of ticking
every device after every recompiled block. This attacks the ~2x
ceiling directly. Gate: ≥20% guest-cycle throughput gain with identical
event ordering + state checkpoints. Kill: <10% gain or ONE divergence.
### E3 — Load-path overlay coverage (Lever A, boring, possibly free)
If E0 shows dirty-RAM interp share >~10% in-window (overlay-region
decompressors are ChatGPT's own HLE-niche flag): coverage-capture the
load path, reshard, re-run E0. Interp is 10-50x native. Zero timing
risk. Kill: interp share already ≤~10% (skip).
### E4 — Data shards: verify-only shadow mode, then replay [the spike]
Phase 1 `shards_shadow` (ChatGPT's addition): execute the original,
PREDICT the replay result, compare full outputs + live-outs + event
assumptions over a large sample — falsify before trusting. Gate to
proceed: decompressor ≥~20-25% of window host CPU (Amdahl: 20% share
caps the whole-window win at 1.25x), predicted hit rate ≥~80%, verify
cost ≤~20-25% of execution.
Phase 2 `shards_replay`: cold/warm A/B on the same transition. Gate:
>10% AND >100ms load reduction, no divergence across repeated loads +
world streaming.
**Correctness bar before ANY replay (ChatGPT, sharper than our v1):**
temporal write visibility — an atomic write-set + sliced cycle credit
cannot reconstruct what a mid-window IRQ observes. Replay is only
sound when IRQs are disabled across the recorded window OR the
recorded duration is strictly below the next observable device/IRQ
event; otherwise a time-indexed write log is required. Also: function
code/content hash in the key; full relevant entry registers (not just
a0-a3); live-outs beyond v0/v1; no pending exception; no DMA overlap;
code-invalidation for written pages; no reentrancy.
Kill: E0 says <~20% host share; hit rate <80%; verify ≈ execution
cost; any event lands inside a supposedly-atomic interval.
### E5 — Authentic drive backlog / never-early catch-up [RECLASSIFIED: correctness, not acceleration]
ChatGPT's adversarial verdict on our parked "leaning fix": never-early
+ catch-up **cannot make a correct pipeline faster** — a healthy loader
already receives every sector on its authentic deadline. It only
removes *artificial lateness* if our model restarts sector timing from
guest ACK instead of the continuous physical-drive timeline (real
drives buffer sectors; a guest that services sector N late should find
N+1, N+2 already bufferable). Probe FIRST: measure deadline-vs-exposure
deltas — if baseline already exposes nearly every sector on time, this
has NO acceleration value and is pure drive-model correctness work.
Prereq: resolve the CD-model tree split (Ape direct-delivery vs master
model) — pick ONE canonical model.
**Tomba verdict (2026-07-14): KILLED as acceleration.** The passive
`cdrom_timing` ring observed 1,304 data sectors: every buffer fill landed on
its exact scheduled cycle (zero early, zero late), and every exposed INT1 used
the intentional fixed +5,000-cycle controller-presentation latency. There were
zero read holds, pending/lost INT1s, or response overwrites. The current model
already maintains the continuous physical timeline; no catch-up work can make
this load faster.
### E6 — `cd_seek_speedup` (Lever B, bounded, per-game opt-in)
Probe first: seek/pause/spin share of window guest time. Upper bound =
number_of_seeks x ~250ms — a seek-only hack cannot touch a sequential
2MiB transfer. NOT proven-safe (DuckStation carries per-game disable
traits for seek speedup too; PPSSPP precedent: UMD-delay changes fixed
some titles, broke others). `pause_complete_delay_cycles` is a known
CPU-visible ordering contract (Ape memcard wedge) — stays authentic.
Kill: seek share <10% or <~250ms, or any callback/VBlank divergence.
**Tomba verdict (2026-07-14): KILLED by the gate.** An automated, authentic
New Game sample covered 792 data sectors over 298,130,657 guest cycles and
issued zero seek commands. Seventeen read-start delays totalled 7,676,928
cycles (2.57% of the data span), below the 10% threshold. Seventeen pause
delays totalled 24,167,597 cycles (8.11%), but pause completion is the
CPU-visible ordering contract explicitly excluded above. The combined
31,844,525-cycle figure is only a theoretical upper bound: it includes that
unsafe pause term and assumes impossible zero-cost, non-overlapping removal.
No seek-speedup implementation is justified for this path.
### E7 — `cd_read_speedup_exp` (Lever B, Phase-2 only)
Per-title, with explicit XA/CDDA/MDEC and timing-sensitive exclusions
(the DuckStation trait architecture). First unexplained wedge ⇒
permanently non-default. Never foundation.
**Implemented scope (2026-07-14):** strict, config-declared warm routes only.
This is not automatic global read acceleration and remains absent unless a game
opts in. Multiple routes share the same fail-closed matcher and consumer-paced
delivery path; XA/CDDA and non-read drive timing are excluded.
### E8 — Decompressor HLE (only via E4's failure)
Entered only if E0 shows decompression host-hot AND shards fail for an
identifiable reason (low reuse, expensive verify, interp-resident).
Kill: no material advantage over generated C or replay.
### E9 — BIOS CD-subsystem HLE — REJECTED (documented)
A faithful CdRead HLE must still preserve command submission, IRQ
ordering, callbacks, thread wakes, sector cadence, DMA-visible data,
retry behavior — and games bypass the BIOS anyway. The BIOS is already
native code; no host win unless E0 names BIOS CD bookkeeping as hot
(nothing suggests it). Its only credible payoff would be a centralized
validated CD state machine — that's an architecture question, not a
load-time one.
### E10 — Load-transition state cache (Lever C — the only TRUE near-zero; Phase-2)
known pre-load state → cached post-load state (conditional savestate
transition). Skips all intermediate observables by design ⇒ outside
the faithful foundation tier, per-game, opt-in. Validation bar
(ChatGPT): differential execution for THOUSANDS of frames after
restore, through at least one further load/scene transition — matching
the first rendered frame is not enough. Kill: any input, streaming
audio, memcard, or pending CD transaction inside the skipped interval.
**Burn-down order (= ChatGPT's session plan, merged):**
E0 → E1(a,b,c) → E2 → (E3 if E0 says so) → E4-shadow → E4-replay →
E5-probe → E6-probe → {E7, E8, E10 as Phase-2/conditional} — E9 stays
rejected on paper.
---
## 5. ChatGPT consult — key verdicts (2026-07-13 thread)
- **Decomposition check:** our three paths hold with one refinement —
the foundation-compatible set is (1) advance authentic guest time
faster, (2) remove host costs at identical guest cycles, (3)
eliminate *accidental lateness* in the CD pipeline. Only these
preserve the complete timeline. Everything else changes it.
- **Data shards presumed non-dominant until profiled.** Amdahl table:
decompressor at 10/20/40/50/70% of window host CPU caps the
whole-window replay ceiling at 1.11/1.25/1.67/2.0/3.33x. Feelable
only if ~8 conditions hold simultaneously (§4 E4 gates).
- **Temporal write visibility is the spike's major correctness flaw**
(§4 E4). Run verify-only shadow mode before any real replay.
- **No universally safe guest-visible CD acceleration exists.** Risk
ladder: host preload/readahead (safest, host-only) → authentic
buffering/backlog (safe if accurate) → seek-only (lower risk, NOT
proven safe) → faster data rate → instant (highest). No public
breakage denominator; claims like "seek speedup is 99% safe" would
be invented numbers.
- **Never-early+catch-up is correctness work**, not acceleration (§4 E5).
- **New axis: event-horizon acceleration + device-batch advancement**
(§4 E2) — the best unexplored host-side candidate; attacks the ~2x
ceiling cause directly.
- **Turbo audio: fix at the host sink** (§4 E1b); never in guest state.
- **Prior art:** o2r/OTR → take the cache-identity/validation model;
DuckStation preload = host-side robustness, not guest timing; Dolphin
warns disabling disc-speed emulation crashes titles; PPSSPP UMD-delay
history = storage latency is observable behavior. Closest true-zero
analog = validated deterministic state-transition cache (§4 E10).
- **Plan of record (its words):** fix and profile turbo → prove/reject
event-horizon acceleration → falsify data shards with verify-only
instrumentation → treat authentic backlog as correctness work → keep
seek/read acceleration per-game and non-foundational → reserve true
near-zero for an explicitly classified load-transition cache.
---
## 6. Facts a future session will want (survey 2026-07-13)
- Live checkout = `feat/vigilante8-widescreen` (05fb36e); master =
dde268d. CD models differ between them (§2 item 2).
- turbo detection/engage: `main.cpp:2062-2084`; burst present cadence
`:2174-2180`; event pump `:2006-2009` (before early-returns); second
pump `:2210` skipped in burst (input freshness only).
- CD scaling: `apply_speed` `cdrom.c:301-309` (XA exempt; divisor 0 =
bounded instant via `instant_period()`); sector cadence base 451584
cyc single-speed; `CDROM_IRQ_PRESENT_DELAY=5000` (ISR-teardown
contract); pause completion deliberately NOT speed-scaled;
seek-complete ≈ 4x sector period far / 0x800 near.
- Data shards wiring: emitter hook `code_generator.cpp:2211-2216` +
`jr $ra` finalize `:1706`; chokepoints `memory.c:1135,1241,1363,1399`,
`traps.c:540`; store `data_shards.c` (494 LOC, no TODOs); config
`[data_shards] funcs` `config_loader.cpp:716-723`. `.dss` per shard,
no manifest (doc/impl drift).
- bios_hle: B0 events 0x07-0x0D only; boot = one-shot shell-skip at the
image's `shell_entry_phys` + boot-turbo until game start. A0/C0 observed,
fall through. The boot-skip and the B0 tier are independent axes
(`bios_hle_plan.h`): the skip needs only `shell_entry_phys` and so lands
the same load-time win under OpenBIOS as under retail, even though
OpenBIOS refuses the B0 tier for want of `deliver_event_ret`.
- `_wt-tomba-loadshards-fw` worktree dir is EMPTY/unregistered — the
spike lives only as branch `spike/tomba-load-shards` (fw) +
`spike/load-shards` (TombaRecomp). Recreate worktrees before E4.
- `cdrom_bursts` ring (`cdrom.c:311-360`) already measures load
durations — E0's window boundary source.
## Incremental resident sectors
`psx_mod_resident_cd_sector(lba, user_data)` admits exactly one verified data
sector through the actual controller FIFO and IRQ path. It keeps ReadN active,
advances the drive cursor by one sector, and restores ordinary timing for the
next sector. The original guest Ready callback, header/payload DMA, resource
consumers and final Pause acknowledgement still run. It never writes guest
RAM, calls a guest callback or retains a host pointer/transfer cursor.
The title must prove its stock file/span, original code and live callback,
and that the next destination is free of pending consumers. A zero ring
occupancy byte alone is insufficient when an SDK queue retains its source
pointer. Check both the software GPU queue and active GPU/SPU DMA before each
admission. The shared helper checks CD ownership only; it does not know the
title's upload queues or grant a whole-file transfer.
Admission requires the original ReadN ACK and response to be fully consumed,
mode 0x80 or 0xA0, the exact next drive LBA, and no pending command, IRQ, CD DMA
or unread payload. Mode 0xA0 may retain only the already-consumed sector's ECC
tail. The helper witnesses the real raw sector's mode, submode, duplicate
subheader and BCD address and compares every user byte with resident data.
XA, FMV, Form2, realtime, changed bytes and busy ownership decline without
controller mutation. This first version still reads raw sector metadata from
the mounted image for verification; it does not claim removal of all host I/O.
No extra snapshot fields are needed: every accepted effect lives in the
ordinary controller buffers, cursor, status and IRQ state. The real-controller
`cdrom_resident_sector_test` covers both modes, repeated one-sector admission,
24 current-format snapshot restores, subsequent original-stream delivery and
real Pause completion, plus 33 byte-for-byte atomic decline cases. Existing
FIFO and IRQ-mask tests also pass. These tests qualify the helper's controller
contract, not a title adapter or load-time improvement. Tron consumer integration
and measured runtime outcomes remain pending under beads-eio.3.412/eio.27.3.