Commit Graph

9 Commits

Author SHA1 Message Date
Matt Stanley c0ffd6d4c5 Support guarded mod address adapters and atomic extended memory cards
Extract the audited adapters and card transport onto master without the held selector/menu stack. Keep adapters and codecs tied to mod activation, use overlay ABI 33, retain rejected-load protection on codec registration, and refuse extended-card codecs in netplay before frame zero. Preserve default atomic save publication and guest-visible write failures.
2026-10-19 16:00:11 -07:00
Alex Vanderveen 8f266efe27 netplay: bring-your-own memory card, lobby chat, and the fullscreen lobby glue
Seat 1 (P2) can bring its LOCAL slot-1 card to a match: it uploads the card
to the host at launch over the new recomp-net MEMCARD op, the host installs
it as the match's slot-2 card, and the usual card broadcast carries it to
everyone. The host's real slot-2 file is rebound to netplay/guest_card2.mcd
for the session and restored on shutdown; guests already sandbox both slots.
Lobby side: a per-seat memcard_offer on set_ready (has_card, share), a host
allow flag in match_caps, and guest_memcard_active settled ONCE by the host
at start and shipped with the launch, so a toggle racing the start cannot
split the room. LAN carries the same over MOTK5 UPDATE / MEMCARD / JOIN
tail / a trailing START line. PSX_NET_GUEST_MEMCARD=1 for CLI sessions;
a host waiting on a card that never comes stalls at a named phase
(mc_guest_wait), not a silent "ok".

Two latent guest-sandbox bugs fixed on the way: a disabled slot 2 had no
bound path, so a host blob carrying a card for it failed to import and the
guest stalled forever; and leaving the sandbox re-bound a disabled slot to
the sandbox file. Both slots are now bound in the sandbox and restored per
slot (memcard_rebind_path, which can also unbind).

Lobby chat: the client keeps a ring fed by the server's echo (op:chat);
LAN rooms relay through the host (MOTK5 CHATREQ guest->host, MOTK5 CHAT
host->everyone). Wired to the launcher's chat_send/chat_count/chat_get.

Also carries the spectator-seat work that was in the tree alongside this
(spectator seats in the lobby client, spectator_wire_slot in the launch).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1dT6h1ZshMVUUVKTPxsCD
2026-09-05 21:18:20 -04:00
Alex Vanderveen 31015ceab1 feat: launcher polish and recomp-net lobby netplay
Wire MotK-style lobby (recomp-net submodule, WS client, match_caps),
netplay delay-sync, and launcher UX so host sim settings stay aligned
across peers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 21:37:57 -07:00
Matthew Stanley c86482cc65 launcher: Phase 4 — memory-card introspection
Wire the launcher's Memory Card 1/2 panels to real on-disk data and make
the per-card enable/path choices flow through to the runtime.

- memcard.c/.h: add stateless introspection (memcard_summary_path parses the
  directory frames for used-block count + mtime + validity, no live state),
  memcard_format_file for "New", and memcard_init_slots for per-slot paths +
  enable flags. memcard_init now delegates to it.
- config_loader: settings.toml [memcard] gains card1/card2 (explicit paths)
  and enable1/enable2; load + save round-trip them (UserSettings extended).
- main.cpp: resolve the effective memcard dir before the launcher so it can
  introspect the real files; seed/apply per-card paths+enable; boot via
  memcard_init_slots (a disabled slot is left absent — SIO reports no device).
- launcher: MC cards now show real filename, size, used-block count, last-
  modified date, and a live 15-cell block grid; Browse picks an existing
  .mcd, New formats a fresh one (overwrite-prompt save dialog). Per-card
  enable + paths are committed to settings on launch.
- The block grid is injected via data-rml (the structural data-for view does
  not capture inner-xml in this RmlUi build; data-rml is the robust path and
  the markup is fully launcher-controlled).
- Add LauncherSystemInterface so RmlUi warnings/errors reach stdout (they
  otherwise go to OutputDebugString on Windows); drop an unsupported
  text-overflow RCSS declaration it surfaced.

Verified on Tomba: card1 3/15 + card2 1/15 match the on-disk directory
frames; disabling slot 2 in settings.toml leaves it not-present via mc_status.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 13:55:58 -07:00
Matthew Stanley 6f7fcdc89b Remove all psxrecomp-v4 naming — rename to psxrecomp throughout
The "v4" suffix was a holdover from iterative project restarts.
There is now one psxrecomp; all identifiers, include guards, cmake
variables, function names, log strings, schema tags, and tool paths
drop the v4 suffix.

Changes:
- PSXRECOMP_V4_ROOT → PSXRECOMP_ROOT (cmake)
- PSXRECOMP_V4_RUNTIME_SOURCES/_INCLUDE_DIRS/_BIOS_GENERATED → no V4
- psxrecomp_v4_add_runtime_target → psxrecomp_add_runtime_target
- PSXRECOMP_V4_*_H include guards → PSXRECOMP_*_H
- "psxrecomp-v4" log/window strings → "psxrecomp"
- Schema version strings in recompiler JSON output
- Tool script hardcoded absolute paths updated

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 21:14:30 -07:00
Matthew Stanley 9b332ba5be Persist memcards and add SPU audio output 2026-05-09 06:32:22 -07:00
Matthew Stanley 8b0fc34714 phase4: SELECT-deassert protocol reset + slot-state instrumentation
Real card protocol restarts on every fresh 0x81 — SELECT deassert is
a bus-wide reset signal. Earlier model preserved mc_state across
deassert via mc_save_slot, which caused every subsequent 0x81 to land
on a stale non-IDLE saved state and trigger abort_reselect bookkeeping.
Audit confirmed the BIOS-visible RX was still correct (the abort path
itself reset mc_state), but the retained sector/cmd/counter context
across logically-independent transactions was wrong per no$psx.

Clear volatile protocol state on SELECT deassert (state, cmd, sector,
sector_msb, sector_lsb, data_idx, checksum on both slots). Preserve
mc_slots[].flag (persistent "new card" metadata) and mc_slots[].data
(sector buffer, harmless to retain).

Result on a 4-press boot run: 6 transactions reach READ_DATA (ts=10)
and successfully read the "MC" header magic (RX bytes 0x4D 0x43);
remaining txns continue as 4-byte presence pings. Pre-fix: 0 reads
of card data ever; post-fix: card protocol can complete the setup
phase and enter data phase.

The byte-15 abort that follows is a separate blocker — kernel chain
dispatcher interleaves pad polling into the card data read. Tracked
for next session via the chain dispatcher disassembly.

Also includes:
- Slot-state capture in SioTraceEntry + sio_trace JSON emission
- dirty_ram_interp: replace mid-block fprintf+fflush with a counter
  (CLAUDE.md §3 — synchronous Windows console I/O at high rate
  starves the dispatch loop)
- Passive Python TCP probes (raw_tcp, trace_audit, trace_range,
  txn_summary, txn_dist, txn_bytes, drs_filter, readram_test,
  step_audit{,2}, verify_card_buffer, wtrace_filter)
- docs/MEMCARD_AUDIT_2026_04_28.md (prior session's 7-layer audit)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:46:26 -07:00
Matthew Stanley 3d364091e7 fix(memcard): clear FLAG byte after read — the four-day stall
This is the bug. Single line. Four days lost.

# The fuck-up

`runtime/src/sio.c` returned `mc_flag` (init 0x08) on every 0x52 (read
command) but NEVER cleared it. Only writes cleared the flag.

Per no$psx spec: real PSX cards return FLAG=0x08 ONCE — first read
after fresh-battery / power-cycle / write — then clear it to 0x00 for
all subsequent reads.

# Why this stalled directory loading

BIOS state-3 chain handler (func_0000579C / 0xBFC152DC) tests bit 3:

    andi $t1, $a0, 0x8
    beq  $t1, $0, BFC15650    # bit 3 NOT set: success (v0=0, advance)
                              # bit 3 SET:     "fresh card" (v0=-1)

Our card returned 0x08 forever, so EVERY read attempt landed in the
"fresh card" path: state-3 returned v0=-1, dispatcher's $v0 cascade
(BFC1499C) reset the chain counter, infinite loop. The directory
literally could not be read.

Beetle's card sim returns 0x00 in steady-state. That was the
canonical first divergence per recomp-template/NES/PRINCIPLES.md #3.

# Why we couldn't find it for four days

We didn't have a transaction-aligned SIO byte diff. The existing
sio_trace dumps were timing-dependent and didn't survive the
press-X window. We were chasing symptoms (dispatch misses, missing
state-12/13 seeds, chain counter reset paths, RAM divergences at
0x74F8 / 0x7560) — all real but all DOWNSTREAM of this one byte.

Per principle "BUILD TOOLS, NOT GUESSWORK" we built
`sio_first_divergence` (transaction-aligned ours-vs-Beetle byte diff,
splits each ring at 0x81 select, compares same-position TX/RX),
which immediately reported byte 1 of the read transaction:

    ours:    tx=0x52 rx=0x08
    beetle:  tx=0x52 rx=0x00

Two-line fix in mc_process_byte's MC_CMD case: clear `mc_flag = 0x00`
after returning it.

# Measured impact

Before:
- chain_trace state-3 returned v0=-1 every cycle
- 0x7514 counter cycled 0->1->2->3->4 then reset (BFC1499C path)
- mc_max_state stuck at 10 with mc_aborts ~30
- Both runtimes stuck at MAIN MENU shell selector

After:
- chain_trace states 1..11 all return v0=0, run 14+ times each
- counter no longer resets at state-3
- Both runtimes navigate to MEMORY CARD screen (matching layout)
- byte-1 SIO RX divergence eliminated (both return 0x00)

Memory cards still don't show save data (state-12/13 read-data
handlers don't fire because outer counter never reaches >=5 to enter
the read-chain path at BFC1588C). That's a separate downstream
divergence which the new diff tool will pinpoint next.

# Other artifacts in this commit

- recompiler/seeds/phase2_ghidra_seeds.json: added state-12 (0x5A58)
  and state-13 (0x5AB0) chain entries from no$psx-spec read-data
  handler ranges. Necessary for state-12/13 to dispatch when the
  outer chain finally enters the read-chain path. Dispatch table
  4295 -> 4297 entries, no dispatch misses.
- runtime/src/psx_oracle_cmds.c: new sio_first_divergence TCP cmd —
  splits each side's SIO ring into card-protocol transactions
  (0x81-start, 0x01 or next 0x81 ends), compares the most recent K
  transactions byte-by-byte to find first (matching TX, differing RX)
  or transaction-length mismatch.
- runtime/src/debug_server.c: chain_trace ring buffer + mc_status
  TCP cmd for chain-state visibility.
- runtime/src/memcard.c + memcard.h: memcard_debug_info() accessor.
- runtime/include/sio.h + sio.c: MC_READ_MSB_ECHO/LSB_ECHO states
  per no$psx address-handshake spec.
- generated/SCPH1001_*.c: regen with seed additions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 00:46:31 -07:00
Matthew Stanley fba58d76b3 Phase 4 interrupt fixes: RFE, GPR save/restore, DMA IRQ edge-trigger
Three interrupt delivery bugs fixed that were blocking shell progression:

1. RFE after handler exit: The exception handler's normal exit path
   (jmpbuf longjmp at 0xBFC10944) does not execute RFE. In our model
   the handler returns to psx_check_interrupts, not to the WaitEvent
   caller. Without RFE, IEc stays 0 forever after the first interrupt.
   Fix: apply RFE in psx_check_interrupts if IEc is still 0 after the
   handler returns.

2. GPR save/restore around handler dispatch: The handler's jmpbuf exit
   restores callee-saved registers from the kernel jmpbuf (intended for
   a non-local jump to WaitEvent's caller). In our model this corrupts
   the interrupted code's registers. Fix: save all GPRs before handler
   dispatch and restore them after, matching real hardware where the
   interrupted code's registers are preserved via TCB save/restore.

3. VBlank tick gated on !in_exception: The handler runs thousands of
   block leaders, each calling psx_check_interrupts. Without gating,
   dispatch_count advances inside the handler, firing new VBlanks that
   stack up and starve the main code. Fix: only increment dispatch_count
   and fire VBlank/timer ticks when not in exception handling.

4. DMA IRQ edge-triggered: update_master_irq() was level-triggered,
   re-asserting i_stat bit 3 on every call while master_flag=1. Combined
   with no BIOS callback to acknowledge DMA IRQs, this caused an
   interrupt storm. Fix: edge-detect master_flag 0->1 transition, and
   suppress DMA completion IRQs entirely since transfers are synchronous.

Also adds GPU debug diagnostics: GP0 command counters (nop/fill/draw/
env/copy), draw area/offset in gpu_state, vram_peek debug command.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:29:03 -07:00