tengen: auto-reload + trigger-on-reach-0 for RAMBO-1 IRQ (#544)

The Tengen 800032 (RAMBO-1) IRQ counter on real hardware auto-reloads
from its latch when it would otherwise underflow, and the IRQ
triggers when the counter REACHES zero (going from 1 to 0) rather
than on the underflow step that follows.

Upstream's earlier model triggered on underflow (count 0 -> 0xFF)
and then continued decrementing through 0xFE, 0xFD, ... until the
game wrote a reload register ($C000/$C001/$E000/$E001 with
rmode==1).  For titles that don't write the reload register on
every IRQ - Skull & Crossbones in 2-player mode is the documented
case (libretro docs Compatibility entry), but Hard Drivin', Klax,
Rolling Thunder, Road Runner, Xenophobe etc. all use the same
mapper - subsequent IRQs after the first underflow are silently
swallowed, displacing the status-bar split point and producing the
screen-shake / glitch reported in #544.

Backport reference: negativeExponent's libretro-fceumm_next commit
7f277a8 ("Cleanup mapper 64/158") plus the Mesen RAMBO-1 model.
This patch is the minimal algorithmic delta against upstream:

  - RAMBO1IRQHook (CPU-cycle mode, IRQmode==1): trigger on
    IRQCount reaching 0, auto-reload from IRQLatch on the next
    clock after the trigger.
  - RAMBO1HBHook (scanline / A12-approximation mode, IRQmode==0):
    same change.  rmode is still set on IRQ fire so existing
    register-reload semantics on $C000/$C001/$E000/$E001 are
    preserved bit-for-bit - the writes do exactly what they did
    before, the only behaviour change is what the IRQ hook itself
    does between writes.
  - IRQLatch == 0 edge case handled (reload yields 0, which is the
    reach-0 trigger condition, so IRQs fire on every clock - the
    documented hardware behaviour at latch=0).

Crucially, this does NOT change:
  - SFORMAT chunk layout (REGS/CMDR/MIRR/RMOD/IRQM/IRQC/IRQA/IRQL
    chunks all still present, same byte sizes, same order).  Old
    savestates continue to load correctly.
  - The write-side dispatch (RAMBO1_Write) for $A000, $8000,
    $8001, $C000, $C001, $E000, $E001 - all behave exactly as
    before, including the rmode==1 conditional reload.
  - Mapper 158 (TENGEN 800037, Alien Syndrome Unl) wiring - it
    shares the RAMBO1 IRQ hook and gets the same fix for free.
  - PRG/CHR/mirroring sync logic.
  - Power-on and StateRestore behaviour.

Build verified: tengen.c -O2 / -O3 / aarch64 -O2 clean.
This commit is contained in:
libretroadmin
2026-06-14 15:30:30 +00:00
committed by U-DESKTOP-SPFP6AQ\twistedtechre
parent 5f8a864bf3
commit 71bbd10ad0

View File

@@ -48,9 +48,28 @@ static void FP_FASTAPASS(1) RAMBO1IRQHook(int a) {
smallcount += a;
while (smallcount >= 4) {
smallcount -= 4;
IRQCount--;
if (IRQCount == 0xFF)
if (IRQa) X6502_IRQBegin(FCEU_IQEXT);
/* Mesen / _next reference: the Tengen RAMBO-1 IRQ counter
* auto-reloads from the latch when it would otherwise
* underflow, and the IRQ triggers when the counter
* REACHES zero (going from 1 to 0) rather than on the
* underflow step that follows. Upstream's earlier model
* triggered on underflow (count 0 -> 0xFF) and then
* continued decrementing through 0xFE, 0xFD, ... until
* the game wrote a reload register. Match the hardware
* model: trigger on reach-0, and on the following clock
* reload from latch automatically so the counter cycles
* continuously without needing per-IRQ register writes. */
if (IRQCount == 0) {
IRQCount = IRQLatch;
if (IRQCount == 0 && IRQa) {
X6502_IRQBegin(FCEU_IQEXT);
}
} else {
IRQCount--;
if (IRQCount == 0 && IRQa) {
X6502_IRQBegin(FCEU_IQEXT);
}
}
}
}
}
@@ -58,9 +77,20 @@ static void FP_FASTAPASS(1) RAMBO1IRQHook(int a) {
static void RAMBO1HBHook(void) {
if ((!IRQmode) && (scanline != 240)) {
rmode = 0;
IRQCount--;
if (IRQCount == 0xFF) {
if (IRQa) {
/* See comment in RAMBO1IRQHook above for the auto-reload /
* trigger-on-reach-0 model. Same change applies here for the
* scanline-clocked (A12 approximation) IRQ path. */
if (IRQCount == 0) {
IRQCount = IRQLatch;
/* Latch == 0 means fire on every clock - reload yields
* 0 again, which is the reach-0 trigger condition. */
if (IRQCount == 0 && IRQa) {
rmode = 1;
X6502_IRQBegin(FCEU_IQEXT);
}
} else {
IRQCount--;
if (IRQCount == 0 && IRQa) {
rmode = 1;
X6502_IRQBegin(FCEU_IQEXT);
}