mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 18:52:11 -04:00
d8f4861bc9
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>