Commit Graph

13 Commits

Author SHA1 Message Date
Matthew Stanley 40f2fa84bd feat(input): restore DualShock rumble output 2026-08-06 22:36:15 -07:00
Alex Vanderveen 3da61df355 Revert "ok"
This reverts commit b836f2198c.
2026-07-29 03:18:10 -04:00
Alex Vanderveen b836f2198c ok 2026-07-29 03:15:42 -04:00
Matthew Stanley 45dd4210cf Remove Tomba legacy hybrid controller path 2026-07-26 20:52:12 -07:00
Matthew Stanley 60801820fd cleanup: genericize hardcoded dev-machine paths in tracked docs/tools
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).
2026-07-06 11:51:36 -07:00
Matthew Stanley 85876ec7cb gpu: textured polygons update draw-mode state from their texpage word
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>
2026-07-05 12:44:29 -07:00
Matthew Stanley 82e0523749 fix(sio): faithful DualShock 0x43/0x45 + analog-mode lock (axis 5)
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>
2026-06-27 19:40:10 -07:00
Matthew Stanley d5977976e5 timing: GTE-read + MFC0 + muldiv-stall load-delay give-back (ruler#2 100%)
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>
2026-06-27 14:42:47 -07:00
Matthew Stanley fade5603fb ruler#2: add give-back probes (load_use, gte_read_use, ld_div) + Δ-gates
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>
2026-06-27 14:29:31 -07:00
Matthew Stanley d8c4a8ec9f timing: faithful R3000A load-delay interlock (ReadFudge/LDAbsorb)
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>
2026-06-27 14:14:47 -07:00
Matthew Stanley da3ab3bdd1 docs(timing): empirically-derived load ReadFudge/LDAbsorb model + impl spec
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>
2026-06-27 13:12:57 -07:00
Matthew Stanley 1d7ae91468 docs(accuracy): consolidate 5-axis research findings into burndown
Parallel research subagents cross-referenced our impl vs in-tree Beetle + psx-spx
and wrote accuracy/{axis5_sio_controller,axis5_gpu,axis5_spu,axis5_mdec,
axis4_memory_mmio}.md. Headlines folded into ACCURACY_BURNDOWN.md with priorities.
Key results: hybrid-pad bug ROOT-CAUSED (0x43 sends zeros = phantom all-pressed;
0x45 lies about analog mode; fixing both retires the g_pad_legacy_cfg hack); GPU
dithering entirely missing (P1); SPU reverb/sweeps/noise/FM missing (weakest axis);
MDEC IDCT+YUV+dequant math diverges (off FMV); RAM 8MB mirror + IsC scratchpad gaps.
I_STAT/DICR ack + many GPU/SPU pieces confirmed FAITHFUL. All non-overlapping →
parallelizable; validate serially via native<->Beetle oracle diffs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:01:59 -07:00
Matthew Stanley edf3641a12 feat(timing): route BIOS emitter through single-source psx_instr_base_cycles
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>
2026-06-26 19:56:20 -07:00