Files
psxrecomp/runtime/include
Matthew Stanley d8f4861bc9 Add co-op enhancement capability: scratch RAM, per-slot pads, per-actor stages
Three generic, default-off runtime capabilities that together let a 2D action
game run more than one simultaneously-controllable player actor. Nothing here
activates on its own: with no game opting in, every path is inert and behaviour
is unchanged.

1. Enhancement scratch RAM (memory.c, psx_runtime.h)
   Guest-addressable memory ABOVE the 4x main-RAM mirror (phys >= 0x00800000),
   a window that is unmapped on real hardware, so the game can never write it.
   This exists because the obvious alternative does not work: MMX6's documented
   free heap->stack gap turned out to be the CD-DMA stage-load arena, and a
   reservation there was overwritten wholesale on the first stage load (measured
   with the always-on RAM-write ring; DMA ch3 covered every word). The checks sit
   AFTER each accessor's `phys < RAM_SIZE` fast path, so ordinary main-RAM
   traffic pays nothing at all and a game that never calls configure() is
   byte-identical. TCP: enh_scratch.

2. Per-slot debug input overrides (main.cpp, debug_server.{c,h})
   set_input/press/clear_input take an optional `slot` (0 = port 1, default;
   1 = port 2). Previously a single shared override meant arming one port
   STARVED the other: sample_pad_into_sio early-returned, so the untargeted slot
   froze holding its last injected value. State is now per-slot arrays, and a
   slot with no override falls through to normal host sampling. Old wire format
   is unchanged (absent `slot` => 0). Live-validated: both slots held
   independently, selective clear, back-compat, bad-slot rejection.

3. Per-actor stage replay ([coop] config, code_generator, coop.c)
   A game's per-frame player update is a SET OF STAGES in its frame pipeline,
   only some of which are per-player. `replay_sites` names the delay slot of
   each per-actor stage's jal; the generator rewrites the link the CPS emitter
   just stored so the callee returns to its own jal and the stage repeats for
   the next actor, then takes the natural link once the list is exhausted. That
   is the guest's own return path -- deliberately NOT a native call into guest
   code, which does not return under CPS and silently skips any restore, latching
   the actor selection (observed live). `actor_base_sites` names every addiu that
   forms the actor pointer so each pass operates on the selected actor, and
   arg-taking stages get it in a0 on selection because they inherit it from the
   pipeline driver, which runs only once per frame.

   A CONTIGUOUS SPAN IS THE WRONG MODEL and the config cannot express one: the
   pipeline interleaves per-actor and shared stages, and replaying a span that
   swept in one shared stage ran it twice per frame and corrupted the game --
   the player's spawn beam drew as a column of duplicated sprites and physics
   stepped twice, dropping him through the floor.

   Sites are opcode-validated and fail the build on mismatch; a partially
   declared [coop] block throws. Omitting the block emits nothing at all, which
   is the pristine standard-asset build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 00:38:41 -07:00
..
2026-07-21 21:38:04 -07:00
2026-07-17 03:54:27 -05:00
…
2026-07-21 14:29:38 -07:00
2026-07-21 21:38:04 -07:00
2026-07-21 21:38:04 -07:00
2026-07-21 21:38:04 -07:00