Replace absolute F:/C: paths with repo-relative (../TombaRecomp, bios/, beetle-psx/)
or placeholders (<desktop>/<psxref>/<nesrecomp>/<psxrecomp-v4>) across accuracy docs,
WIDESCREEN/SHADOW/RECURSION_BUG/config_schema/ISSUES/README, and tools/*.py|.ps1.
Removed a personal C:\Users\Matthew\Desktop path (ISSUES.md). Left CLAUDE.md/PLAN.md
sibling-project + version-history paths intact (intentional layout documentation).
Real GPU copies the texpage attribute word carried inside every textured
polygon (GP0 0x24-0x3F, texture bit set) into the draw-mode state
(GPUSTAT bits 0-8), exactly like GP0(E1) bits 0-8 — Beetle runs
SetTPage(CB[4 + ((cc>>4)&1)] >> 16) on every such command
(beetle-psx mednafen/psx/gpu.cpp:1070). So the polygon's OWN word (not
the last E1) selects its semi-transparency mode, and later rect/sprite
prims (which carry no texpage word) consume the state the polygon left
behind. We fetched texels from the prim's page but blended with the
stale E1 global, and never updated the page state rects depend on.
Effect: additive glows blended with whatever mode the last E1 happened
to set, flipping bright light halos into dark/black blobs intermittently
(OT bucket order decides which prim precedes, hence camera/distance
dependence). Proven on Ape Escape attract frame 60052: nine op-0x2E
firework quads at the artifact coordinates carry prim mode 1 (B+F) while
the last E1 set mode 0 (B/2+F/2) — rendered as dark maroon blobs;
Beetle renders the same beat as bright fireworks. Post-fix sweep shows
siren-hat glow halos, MONKEY PARK marquee bloom, and lava glow restored
across two full attract cycles.
Poly words do not touch dither/draw-to-display (E1-only bits 9-10) nor
texture-disable (bit 11 latches only under GP1(09h) allow-change).
All backends inherit the fix via gr_set_semi_transparency /
current_texpage(). Faithful-core fix for all titles; documented as D12
in accuracy/axis5_gpu.md (the audit had marked semi-transparency
FAITHFUL — the modes were, their state source was not).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Transcribe the modern-SM pad config semantics from Beetle dualshock.cpp, fixing
the hybrid phantom-input root causes (accuracy/axis5_sio_controller.md):
- 0x43 (enter config) now transmits the LIVE poll frame — id, 0x5A, buttons,
+ 4 stick axes in analog — identical framing to 0x42 (dualshock.cpp:471-490),
instead of an all-zero frame. The zero frame fed any driver that reads the
0x43 response as input a phantom "all-pressed"/centered-stick (active-low
0x00 = all buttons down). In-config 0x43 (exit) keeps the zero frame
(dualshock.cpp:660-674). [D4]
- 0x45 status byte now reports the LIVE analog state (pad_analog?1:0) rather
than a hard-coded analog-on, so a driver tracking the mode parses the right
poll-frame length (dualshock.cpp:743). [D8]
- 0x44 0x..03/0x02 now sets/clears a per-slot analog_mode_locked; a locked slot
ignores the host hybrid auto-flip (the emulated analog button), matching a
real pad that ignores the physical button when locked (dualshock.cpp:203,
714-725). [D6]
Gated to the modern SM; Tomba 1's legacy_pad_config path is untouched. MMX6
(modern SM) gains the phantom-dash fix. Validated no-regression on Tomba 2
(digital pad, 0xFFFF at rest, boots). Retiring g_pad_legacy_cfg (the doc's
fix 4) is gated on a DualShock-game behavioral validation pass — see the
STATUS note in the doc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the last steady-state cycle divergences; native now == Beetle on
the complete 12-loop ruler #2 suite.
- MFC2/CFC2 (GTE reg -> GPR read): new psx_gte_read stalls to gte_ts_done
AND hands the stall amount to following instructions as a load-delay
give-back (Beetle cpu.cpp:1332-1341: LDAbsorb=gte_ts_done-ts,
LDWhich=rt). MTC2/CTC2 (writes) keep stall-only psx_gte_stall.
- MFC0: arms ld_absorb=0 / ld_which_t=rt (Beetle delayed load) — sets
ReadFudge=rt so a load in the next slot gets no fudge.
- psx_muldiv_stall (MFLO/MFHI): now CONSUMES the pending load give-back
while stalling to muldiv_ts_done, plus the muldiv_ts_done-1 off-by-one
shortcut (Beetle cpu.cpp:1723-1736). No-load code is unaffected
(read_absorb==0 -> plain advance).
Wired in both static emitters (code_generator, strict_translator) and
the dirty interp.
METHODOLOGY (Rule 15): Beetle cyc_watch must be sampled at STEADY STATE
— its warm-up window reports the no-give-back value (the source of the
prior "gte_rtps +15 EXACT" mis-record; true steady Beetle = +11).
Validated at steady state, native == Beetle on ALL 12 ruler #2 loops:
the 4 divergences closed (gte_rtps 18->14, gte_nclip 11->7,
gte_read_use 19->14, ld_div 45->49); the 8 CPU-load/alu/div/mult loops
held. Ruler #1 delta 0. Tomba 2 FMV plays (no regression).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds three cycle-isolation loops that exercise the load-delay give-back
TERMINATION and the two not-yet-modeled give-back interactions, each
measured against the Beetle oracle AT STEADY STATE (Beetle's warm-up
window reports the no-give-back value — the source of the handoff's
mis-recorded "gte_rtps +15 EXACT"; true steady Beetle = +11):
- load_use (lw; addu deps the loaded reg): native +5 == Beetle +5 —
Δ-validates the GPR_DEP give-back termination shipped in the prior
commit (load/load2 load into dead regs and never exercise it).
- gte_read_use / gte_rtps / gte_nclip: native OVER-counts by 4-5 — the
MFC2/CFC2 LDAbsorb give-back (gte_ts_done-now) is unmodeled.
- ld_div (lw; divu; mflo): native UNDER-counts by 4 — the muldiv stall
must CONSUME the pending lw give-back (Beetle decrements ReadAbsorb
while stalling MFLO to muldiv_ts_done).
The 8 CPU-load/alu/div/mult components all MATCH at steady state, so the
prior load-interlock commit is solid. The 4 confirmed divergences are
the Δ-gates for the GTE-read/MFC0/muldiv give-back follow-up; spec +
targets in accuracy/load_readfudge_ldabsorb.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements the last unmodeled load-path cycle component — the R3000A
load-delay pipeline interlock (Beetle ReadAbsorb/ReadFudge/LDAbsorb/
LDWhich) — as ONE shared per-instruction model across the dirty interp
and BOTH static emitters, replacing the flat per-instruction +1 base and
the flat +4 main-RAM read wait-state.
Model (transcribed from beetle-psx cpu.cpp + libretro.cpp, verified Δ):
- §1 base: a pending load's per-register give-back (read_absorb) is
consumed INSTEAD of charging +1 (pipeline write-back overlap).
- GPR_DEPRES: each opcode's src/dst regs end the give-back on use.
- DO_LDS: commits the prior load's give-back + ReadFudge.
- load ReadMemory: +2 fudge iff the predecessor committed no load;
main-RAM region +3 + completion +2 (LWC2 +1) become the LDAbsorb
give-back; scratchpad +0.
New runtime/include/psx_cyc.h (inline §1/deps/DO_LDS + step) over new
CPUState timing fields; psx_cyc_load_*/psx_cyc_lwc2_read in memory.c
(region-aware, own the whole load interlock, value via uncharged
psx_read_*); psx_cyc_dep_res_mask in psx_instr_cost.h (per-opcode
GPR_DEP/RES, gen-time literal). cpu->read_* rewired to uncharged in
main.cpp; charge_main_ram_read removed. Wired before each instruction
body in code_generator.cpp (game) and full_function_emitter.cpp +
strict_translator.cpp (BIOS); loads route through psx_cyc_load_*.
Validated against the Beetle oracle:
- ruler #2 load2: native +10 -> +11 == Beetle (all other components
still exact: alu+1/load+5/div+38/mult+15/gte_rtps+15/gte_nclip+8).
- ruler #1 [0x80001C5C->0x80001CA4]: 54 -> 56 == Beetle steady-state
(residual 84/77 spikes = I-cache cold refill, the separate P2 axis).
- Tomba 2 boots to the intro FMV (pixels on screen, no regression).
Follow-up (separate commit): GTE-read (MFC2/CFC2) + MFC0 LDAbsorb
give-back, and muldiv-stall give-back consumption — needed for mixed
game/FMV faithfulness but do not affect the rulers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Measured Beetle's per-instruction cost through the ruler #2 load loops via
adjacent-PC region cyc_watch (Beetle observes per-instruction; native compiled
emitter observes only at block leaders). Nails the last load-path component:
- fudge = +2 iff the previous instruction committed no pending load
(ReadFudge=0x20; (reg>>4)&2 is 0 for all real regs), else 0 — NOT per-register
- region+completion = 5 (main RAM); LDAbsorb give-back excludes fudge
- the load-delay-slot instruction does not absorb (its §1 precedes its DO_LDS);
instructions after it do, until a GPR_DEP/RES of the loaded reg ends it
- Beetle per-insn: load = lw7/addiu1/bne0/nop0; load2 = lw7/lw6/addiu1/bne0/nop0
This is the de-risked spec for a pervasive per-instruction implementation
(ReadAbsorb[]/ReadAbsorbWhich/LDWhich in both emitters + the dirty interp),
validatable via the ruler-loop anchors (baseline 3 / load 8 / load2 14). Read-only
derivation; no runtime/emitter code changed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes P3 step 1 across ALL cost producers: the BIOS emitter
(full_function_emitter.cpp) now sums psx_instr_base_cycles per block like the
interp and game emitter, instead of its own instruction count. Identity (1/insn)
=> BIOS regen byte-identical (verified). Stage-2 real R3000A costs now land in the
single psx_instr_base_cycles function and all three backends update together.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>