fix(crash_detect): cd_seek_wedge only while the drive still owes data (#741)

cd_seek_wedge counted every frame with frozen FIFO drains. Under
cd_boot_mode=bios it fired 15 times across Baldies, Battle Morph,
BrainDead 13, Highlander, Primal Rage and Myst (+ both Myst demos), all of
which go on to run: the transfer had simply finished.

Measured at each fire (temporary dump, not committed): BUTCH low byte $02
every time -- the game's CD ISR clears the master interrupt enable to
signal completion. Philia (bios) ends its transfer the other way, leaving
BUTCH $03 but clearing I2CNTRL bit 2 (FIFO data enable) while it waits for
a button (it responds to B). The real IMASK-stuck transfer wedge
(7c98e16 re-broken behind an env flag as a control) has both still on:
BUTCH $03, I2CNTRL $05. fifoDataReady / cdPlaying do not separate them --
both are 1 in the false fires and in the wedge -- so the issue's proposed
"FIFO idle" test would not have worked.

A frame now counts only if a seek is outstanding (seek_starts !=
seek_dones), or the transfer is still open: BUTCH bits 0+1 armed AND
I2CNTRL bit 2 set. The predicate is CrashDetectCDSeekWedgeFrame(), pinned
by test/test_crash_detect_cd_wedge (measured rows from both sides, runs
in make test). test/tools/cd_seek_wedge_regress.sh checks the 8 titles
against the private corpus (SKIPs without it) and asserts on the log line,
not [PASS] -- test_cd_bios_boot reports [PASS] on the real wedge too.

Controls on the final code: Primal Rage and BrainDead 13 with the
delay-slot fix disabled still fire (butch_int=$03 i2s_ctrl=$05).

The log line now prints butch_int= and i2s_ctrl=.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Joseph MattielloandClaude Opus 5.5 authored and Joe Mattiello committed 2026-10-04 09:45:09 -04:00
1 parent 1c846937ba
commit b28b89bfdd
12 files changed
+291 -46

No files matched your search

+6 -2
View File
@@ -1345,7 +1345,7 @@ clean:
test/test_eeprom_read_race \
test/test_tom_visible_window test/test_framebuffer_integrity \
test/test_butch_cd test/test_bios_config test/test_boot_config \
test/test_cart_format test/test_cart_needs_bios \
test/test_cart_format test/test_cart_needs_bios test/test_crash_detect_cd_wedge \
test/test_cd_boot test/test_cd_hle_boot test/test_cd_bios_boot test/test_cd_toc_contract test/test_cd_fifo_stream test/test_cd_ssi_stream test/test_cd_second_transfer test/test_cd_hle_idempotent test/test_cd_lost_wakeup test/test_cd_pregap test/test_cd_chd test/test_chd_unit test/test_cd_synth_read test/test_cd_synth_butch test/test_cd_synth_cdda test/test_cd_synth_subq \
test/test_audio_dac test/test_blitter \
test/test_state_compat test/test_frontend_pacing test/test_jgd \
@@ -1425,7 +1425,7 @@ test: test/test_dram_timing test/test_cheat test/test_event_queue test/test_jlin
test/tools/test_runahead_determinism test/tools/test_wedge_spin test/tools/test_texdump test/tools/test_texreplace \
test/tools/dsp_idle_probe_falsify test/tools/gpu_idle_probe_falsify \
test/test_butch_cd test/test_bios_config test/test_boot_config \
test/test_cart_format test/test_cart_needs_bios test/test_cart_bios_loader \
test/test_cart_format test/test_cart_needs_bios test/test_cart_bios_loader test/test_crash_detect_cd_wedge \
test/test_cd_boot test/test_cd_hle_boot test/test_cd_bios_boot test/test_cd_toc_contract test/test_cd_fifo_stream test/test_cd_ssi_stream test/test_cd_second_transfer test/test_cd_hle_idempotent test/test_cd_lost_wakeup test/test_cd_pregap test/test_cd_chd test/test_chd_unit test/test_cd_synth_read test/test_cd_synth_butch test/test_cd_synth_cdda test/test_cd_synth_subq \
test/test_audio_dac test/test_blitter \
test/tools/test_memory_map test/tools/test_op_gpu_object test/tools/test_option_visibility test/test_memtrack test/test_nvmbios test/test_uart_core test/test_netlink_host \
@@ -1825,6 +1825,7 @@ test: test/test_dram_timing test/test_cheat test/test_event_queue test/test_jlin
./test/test_bios_config && ./test/test_boot_config
./test/test_cart_format ./$(TARGET)
./test/test_cart_needs_bios ./$(TARGET) --quiet
./test/test_crash_detect_cd_wedge ./$(TARGET)
./test/test_audio_dac
./test/tools/test_memory_map ./$(TARGET)
@# $F14000/$F14002 identity guardrail for the input-devices track
@@ -2780,6 +2781,9 @@ test/test_cart_format: test/test_cart_format.c
$(CC) -O2 -Wall -Wno-unused-function -Wno-unused-variable -std=c99 $(INCFLAGS) \
-o $@ test/test_cart_format.c -ldl
test/test_crash_detect_cd_wedge: test/test_crash_detect_cd_wedge.c
$(CC) -O2 -Wall -std=c99 -o $@ test/test_crash_detect_cd_wedge.c -ldl
test/test_cart_needs_bios: test/test_cart_needs_bios.c test/harness/harness.c test/harness/harness.h
$(CC) -O2 -Wall -Wno-unused-function -Wno-unused-variable -std=c99 $(INCFLAGS) \
-o $@ test/test_cart_needs_bios.c test/harness/harness.c -ldl -lm
+6 -5
View File
@@ -169,11 +169,12 @@ heartbeat every 600 frames). Cost enabled: one indirect call + ~256-px hash/fram
wait/spin loop (Super Burnout spins ~446k GPU ops/frame at one sampled PC — the #378 false
positive; regression `test/tools/test_wedge_spin`).
- `video_stall` — FB hash unchanged 300 frames while a processor runs.
- `cd_seek_wedge` — a CD seek started but FIFO drain frozen 300 frames while a processor runs;
dumps the CD trace ring. **Known benign:** a title going CD-idle >5s after a transfer fires
too — Myst (bios) fires during the intro movie's ~6s all-black pause (drain parked at payload
end LBA 21189, movie from RAM, clock ticking); HLE shows the same black window (fires
`video_stall`). Corroborate before treating a lone line as a wedge.
- `cd_seek_wedge` — FIFO drain frozen 300 frames while a processor runs AND the drive still owes
data: a seek outstanding, or a transfer still open (BUTCH low byte bits 0+1 = master + FIFO IRQ
armed, AND I2CNTRL bit 2 = FIFO data enabled); dumps the CD trace ring. A game ends a transfer
by clearing BUTCH bit 0 (Myst, Primal Rage, BrainDead 13 …) or I2CNTRL bit 2 (Philia); those
idle drives used to fire it (#741). The line prints `butch_int=`/`i2s_ctrl=`. Pinned by
`test/test_crash_detect_cd_wedge` (CI) + `test/tools/cd_seek_wedge_regress.sh` (private discs).
Triaging "X crashes/hangs/black screen": the RetroArch log shows the signature — no save state
or input recording needed. **Add new signatures here when you find a recurring failure mode not
+10 -8
View File
@@ -200,10 +200,11 @@ prompted the addition:
frames the sampled PC is already well past that code -- `final_pc=$00CA64`
(hle), `$00F1A4` (bios), both outside the band. Sampling Myst much earlier
would be needed to land in it.
- **The bios row's `cd_seek_wedge` line is the documented benign case**
(CLAUDE.md, "Runtime crash watchdog"): Myst goes CD-idle for ~6s during the
intro movie's all-black pause. The hle row shows the same window as
`video_stall`. Neither is a failure.
- **The bios row's `cd_seek_wedge` line was a false positive** (fixed in
#741): Myst goes CD-idle for ~6s during the intro movie's all-black pause,
with the transfer finished (BUTCH bit 0 cleared). The watchdog no longer
counts that window. The hle row shows the same window as `video_stall`.
Neither is a failure.
Adding a title also exposed a latent bug in this script, fixed in the same
commit: new rows were appended to EOF instead of into the results table. On a
@@ -830,10 +831,11 @@ engine, $F0327x BrainDead / Dragon's Lair / Space Ace class). Contract test:
transfer #2 runs to completion (drains 17,996; the engine's ISR signals
done by clearing BUTCH bit 0), gpu waits at $F031F6 with IMASK clear --
byte-for-byte the same wait state as the Primal Rage (hle) control row,
payload 6790B vs hle 6679B. The row still logs `cd_seek_wedge`: drains
legitimately stop after transfer completion (BUTCH bit 0 off), which the
watchdog cannot yet distinguish from a freeze -- watchdog refinement
candidate, not a boot defect.
payload 6790B vs hle 6679B. The row logged `cd_seek_wedge` at the time:
drains legitimately stop after transfer completion (BUTCH bit 0 off), which
the watchdog could not then distinguish from a freeze. Fixed in #741 -- the
watchdog now requires BUTCH's master + FIFO interrupts to still be armed
and I2CNTRL FIFO data enabled.
- **Highlander (bios)** -- ADVANCED. Was: cd_seek_wedge with drains frozen
at 54 right after the first fills (same clobber, first ISR invocations).
Now: no seek wedge, gpu active at $F031C2, dsp running, final_pc=$0036BE
+1 -1
View File
@@ -103,7 +103,7 @@ The `stop`-wait idiom is **not** CD-only. Static scan of the local cart library
2. **Battle Morph** — 68K PC lands in data (`$AFDE`, Line-A) **before any CD I/O** (`seeks=0`, ~frame 437, right after the `$050176` stub injection). Its TOC link tested **negative**. Note: in CD-BIOS mode, vectors 2–255 are PRNG garbage (`jaguar.c` vector-stub block is gated `!vjs.useJaguarBIOS`). **Populating them would MASK real failures** — boot stubs execute deliberate ILLEGAL halts on error paths. Fix the underlying error, not the halt. (Same for `jagcd_hle.c`'s ADDQ skip-handler.)
3. **IS2 (HLE)** — `CD_poll` signals completion but the game re-reads forever. Downstream of the idempotency fix.
4. **`baldies.cdi`** — CDI parser segfaults at load (pre-injection). CUE variant unaffected.
5. **`cd_seek_wedge` false-positive** on *legitimate* transfer completion (BUTCH bit 0 cleared). Watchdog refinement; cosmetic log noise, not a hang.
5. ~~**`cd_seek_wedge` false-positive** on *legitimate* transfer completion (BUTCH bit 0 cleared).~~ FIXED (#741): the watchdog now only counts frozen drains while a seek is outstanding or the transfer is still open (BUTCH master + FIFO interrupts armed, `$03`, and I2CNTRL bit 2 set).
## 6. Toolkit — everything runnable from the CLI
+1
View File
@@ -56,6 +56,7 @@ _perf_counters_find
_CDROM*
_CDIntf*
_CDTrace*
_CrashDetect*
_BUTCH*
_GetRamPtr
_bootConfig
+1
View File
@@ -59,6 +59,7 @@
CDROM*;
CDIntf*;
CDTrace*;
CrashDetect*;
BUTCH*;
GetRamPtr;
bootConfig;
+15
View File
@@ -837,6 +837,21 @@ void CDROMDiagGetSeekWedgeState(uint32_t *seekStarts, uint32_t *seekDones,
if (fifoDrains) *fifoDrains = cdFifoDrainCount;
}
/* Low byte of the BUTCH interrupt-control register ($DFFF03): bit 0 =
* master interrupt enable, bit 1 = FIFO half-full interrupt enable (butch.v
* map, test/mister_ground_truth.h). Read-only, for cd_seek_wedge. */
uint8_t CDROMDiagGetButchIntCtrl(void)
{
return cdRam[BUTCH + 3];
}
/* Low byte of I2CNTRL ($DFFF13): bit 2 = I2S FIFO data enable (the same
* bit BUTCHExec gates FIFO fill on). Read-only, for cd_seek_wedge. */
uint8_t CDROMDiagGetI2SCtrl(void)
{
return cdRam[I2CNTRL + 3];
}
uint32_t CDROMDiagGetFirstSeekBlock(void)
{
return diag_firstSeekBlock;
+8
View File
@@ -91,6 +91,14 @@ void CDROMHLEDataReadBegin(void);
void CDROMDiagGetSeekWedgeState(uint32_t *seekStarts, uint32_t *seekDones,
uint32_t *fifoDrains);
/* Low byte of BUTCH ($DFFF03): bit 0 master IRQ enable, bit 1 FIFO
* half-full IRQ enable. Read-only, for cd_seek_wedge. */
uint8_t CDROMDiagGetButchIntCtrl(void);
/* Low byte of I2CNTRL ($DFFF13): bit 2 = I2S FIFO data enable.
* Read-only, for cd_seek_wedge. */
uint8_t CDROMDiagGetI2SCtrl(void);
/* First (boot-relevant) $12xx seek target, post-redirect, as an absolute
* disc LBA. Returns 0xFFFFFFFF if no seek has been issued since reset.
* Consumed by test/test_cd_fifo_stream.c to locate the sync mark the GPU
+58 -30
View File
@@ -66,26 +66,31 @@ extern uint32_t dsp_exec_opcode_count;
/* cd_seek_wedge: a CD seek was issued but the FIFO drain counter (see
* CDROMDiagGetSeekWedgeState()) hasn't advanced in this many frames while
* a processor is still running. SEEK_DELAY_TICKS in cdrom.c is ~100
* a processor is still running AND the drive still owes data (see
* CrashDetectCDSeekWedgeFrame()). SEEK_DELAY_TICKS in cdrom.c is ~100
* halfline ticks (~0.2 frames at NTSC's ~524 halflines/frame) even for a
* from-scratch seek, so 300 frames (5 sec) is far beyond any legitimate
* seek.
*
* KNOWN BENIGN CASE: a title that legitimately goes CD-idle for >5s
* fires this too -- the signature cannot tell "game stopped draining
* because it wedged" from "game finished the transfer and doesn't need
* the disc right now". Ground truth: Myst (bios mode) fires
* cd_seek_wedge at ~frame 2260 with the drain frozen at the intro
* movie's payload end (LBA 21189) while the game plays the movie's
* ~6-second all-black dramatic pause (Cyan logo -> black -> match
* strike -> burning MYST logo) entirely from RAM; the movie clock
* (TOM PIT -> GPU IRQ2 -> $1D58C accumulator) keeps ticking at 12 Hz
* throughout and the next asset loads right on schedule. HLE mode
* shows the identical black window (fires video_stall instead).
* Corroborate with the trace ring / a longer freeze window before
* treating a lone cd_seek_wedge line as a real stall. */
* Drains frozen alone is NOT a wedge: a game that finished its transfer
* stops draining and plays from RAM (Myst's ~6 s black pause after the
* intro movie, Primal Rage / BrainDead 13 after their FMV loads). Issue
* #741 measured 15 such fires across 7 bios-mode titles that all went on
* to run: every one had BUTCH low byte $02 -- the engine's ISR clears the
* master interrupt enable (bit 0) to signal completion. Philia (bios)
* ends its transfer the other way: BUTCH stays $03 but it clears I2CNTRL
* bit 2 (I2S FIFO data enable) and waits for a button. The real
* IMASK-stuck transfer wedge (7c98e16, re-broken as a control) has both
* still on -- BUTCH $03, I2CNTRL bit 2 set -- with nobody servicing the
* interrupt. FIFO-level state does not separate them (fifoDataReady=1 and
* cdPlaying=1 in the BUTCH-$02 fires and in the wedge alike). */
#define WEDGE_FRAMES_CD_SEEK 300
/* BUTCH interrupt-control low byte: master enable | FIFO half-full enable. */
#define CD_BUTCH_FIFO_XFER_IRQS 0x03u
/* I2CNTRL low byte: I2S FIFO data enable. */
#define CD_I2S_FIFO_ENABLE 0x04u
/* Halfline expectation per frame: 524 NTSC, 624 PAL. Anomaly band is +/- 4. */
/* Verbose-mode heartbeat: dump current state every N frames. */
@@ -393,6 +398,22 @@ void CrashDetectSetMode(int mode)
cd_mode = mode;
}
int CrashDetectCDSeekWedgeFrame(uint32_t seek_starts, uint32_t seek_dones,
uint32_t fifo_drains, uint32_t last_fifo_drains,
int processor_running, uint8_t butch_int,
uint8_t i2s_ctrl)
{
if (seek_starts == 0 || fifo_drains != last_fifo_drains || !processor_running)
return 0;
/* Shape (a): the seek-complete response never arrived. */
if (seek_starts != seek_dones)
return 1;
/* Shape (b): seeks done, but the game still has a FIFO transfer open
* (interrupts armed, FIFO data enabled) and no drain has happened. */
return (butch_int & CD_BUTCH_FIFO_XFER_IRQS) == CD_BUTCH_FIFO_XFER_IRQS
&& (i2s_ctrl & CD_I2S_FIFO_ENABLE) != 0;
}
void CrashDetectFrameTick(const uint32_t *fb, unsigned w, unsigned h)
{
uint32_t cur_gpu_pc;
@@ -403,6 +424,8 @@ void CrashDetectFrameTick(const uint32_t *fb, unsigned w, unsigned h)
uint32_t cd_seek_starts;
uint32_t cd_seek_dones;
uint32_t cd_fifo_drains;
uint8_t cd_butch_int;
uint8_t cd_i2s_ctrl;
if (!cd_initialized) return;
if (cd_mode == CRASH_DETECT_OFF) return;
@@ -516,34 +539,39 @@ void CrashDetectFrameTick(const uint32_t *fb, unsigned w, unsigned h)
/* ---- CD seek wedge: a seek was issued (real-BIOS/BUTCHExec path) but
* the FIFO drain counter has made no progress for WEDGE_FRAMES_CD_SEEK
* frames while a processor is still running. Gated on cd_seek_starts
* > 0 so non-CD games (and CD games before their first seek) never
* evaluate this at all.
* frames while a processor is still running and the drive still owes
* data. Gated on cd_seek_starts > 0 so non-CD games (and CD games before
* their first seek) never evaluate this at all.
*
* Deliberately broader than "SEEK_START without SEEK_DONE": comparing
* fifoDrains instead of seekDones catches both CD seek-wedge shapes
* that need telling apart -- (a) the seek-completion response never
* arrives (seekDones stays behind seekStarts, so fifoDrains obviously
* never advances either), and (b) the seek completes fine but the
* FIFO continuation dies afterward (seekDones catches up, fifoDrains
* still never advances). Either way, the ring dump below shows which
* one happened. */
* Catches both CD seek-wedge shapes -- (a) the seek-completion response
* never arrives (seekDones stays behind seekStarts), and (b) the seek
* completes but the FIFO continuation dies afterward while the game
* still has the transfer open. A finished transfer (the game cleared
* BUTCH bit 0 or I2CNTRL bit 2) is not counted; see
* CrashDetectCDSeekWedgeFrame(). The ring dump below shows which shape
* happened. */
if (bootConfig.isCDGame)
{
CDROMDiagGetSeekWedgeState(&cd_seek_starts, &cd_seek_dones, &cd_fifo_drains);
cd_butch_int = CDROMDiagGetButchIntCtrl();
cd_i2s_ctrl = CDROMDiagGetI2SCtrl();
if (cd_seek_starts > 0 && cd_fifo_drains == last_cd_fifo_drains
&& (gpu_running || dsp_running))
if (CrashDetectCDSeekWedgeFrame(cd_seek_starts, cd_seek_dones,
cd_fifo_drains, last_cd_fifo_drains,
gpu_running || dsp_running,
cd_butch_int, cd_i2s_ctrl))
{
cd_seek_wedge_frames++;
if (cd_seek_wedge_frames == WEDGE_FRAMES_CD_SEEK
&& may_log(&last_log_cd_seek_wedge))
{
LOG_WRN("[CRASH-DETECT] cd_seek_wedge frame=%u seek_starts=%u seek_dones=%u "
"fifo_drains=%u unchanged for %u frames gpu_pc=$%08X gpu_run=%d "
"dsp_pc=$%08X dsp_run=%d\n",
"fifo_drains=%u unchanged for %u frames butch_int=$%02X i2s_ctrl=$%02X gpu_pc=$%08X "
"gpu_run=%d dsp_pc=$%08X dsp_run=%d\n",
frame_no, cd_seek_starts, cd_seek_dones, cd_fifo_drains,
WEDGE_FRAMES_CD_SEEK, cur_gpu_pc, gpu_running, cur_dsp_pc, dsp_running);
WEDGE_FRAMES_CD_SEEK, (unsigned)cd_butch_int,
(unsigned)cd_i2s_ctrl, cur_gpu_pc,
gpu_running, cur_dsp_pc, dsp_running);
CDTraceDump();
}
}
+13
View File
@@ -35,6 +35,19 @@ void CrashDetectSetMode(int mode);
* was started at from a jump into a data buffer (issue #461). */
void CrashDetectNoteGPUGo(uint32_t pc);
/* cd_seek_wedge per-frame predicate (issue #741), split out so a unit test
* can pin it without a disc. Returns 1 when this frame counts toward the
* wedge window: a seek has been issued, the FIFO drain counter did not move,
* a RISC processor is running, and the drive still owes data -- a seek is
* outstanding, or the game still has a FIFO transfer open: BUTCH master AND
* FIFO interrupts armed (butch_int = BUTCH low byte) AND I2S FIFO data
* enabled (i2s_ctrl = I2CNTRL low byte, bit 2). A game ends a transfer by
* clearing either one; that idle drive is not a wedge. */
int CrashDetectCDSeekWedgeFrame(uint32_t seek_starts, uint32_t seek_dones,
uint32_t fifo_drains, uint32_t last_fifo_drains,
int processor_running, uint8_t butch_int,
uint8_t i2s_ctrl);
/* Per-frame hook -- call once at the END of JaguarExecuteNew so all
* subsystems have been advanced. fb may be NULL if no framebuffer
* is available this frame; the stall detector skips that frame. */
+92
View File
@@ -0,0 +1,92 @@
/*
* test_crash_detect_cd_wedge.c -- CrashDetectCDSeekWedgeFrame() table
* (issue #741).
*
* cd_seek_wedge used to count every frame with frozen FIFO drains. That
* fired on 15 finished transfers across 7 bios-mode titles that went on to
* run (BUTCH low byte $02: the game cleared the master interrupt enable);
* Philia ends its transfer by clearing I2CNTRL bit 2 instead. The real
* IMASK-stuck transfer wedge has both still on: BUTCH $03 (master + FIFO
* half-full interrupts armed) and I2CNTRL bit 2 (FIFO data enabled).
* This table pins both sides so the signature can't silently go blind (or
* noisy) again; the disc-backed half lives in
* test/tools/cd_seek_wedge_regress.sh.
*
* Build: make TEST_EXPORTS=1 test/test_crash_detect_cd_wedge
* Run: ./test/test_crash_detect_cd_wedge ./virtualjaguar_libretro.dylib
*/
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
typedef int (*wedge_frame_fn)(uint32_t, uint32_t, uint32_t, uint32_t, int, uint8_t, uint8_t);
struct row {
const char *name;
uint32_t starts, dones, drains, last_drains;
int running;
uint8_t butch;
uint8_t i2s;
int want;
};
static const struct row rows[] = {
/* Measured false positives (bios mode, all alive afterwards).
* starts dones drains last run butch i2s want */
{ "Myst intro pause: BUTCH $02, I2S on", 10, 10, 75081, 75081, 1, 0x02, 0x05, 0 },
{ "Primal Rage FMV done: BUTCH $02, I2S on", 2, 2, 17996, 17996, 1, 0x02, 0x07, 0 },
{ "Philia waiting for a button: BUTCH $03, I2S off", 8, 8, 115875,115875,1, 0x03, 0x01, 0 },
{ "transfer done, all BUTCH IRQs off", 4, 4, 92617, 92617, 1, 0x00, 0x05, 0 },
{ "master on for DSA/subcode, FIFO IRQ off (CDDA)", 3, 3, 500, 500, 1, 0x01, 0x05, 0 },
/* Measured true positives (7c98e16 re-broken as a control). */
{ "Primal Rage IMASK wedge: BUTCH $03, I2S on", 2, 2, 12774, 12774, 1, 0x03, 0x05, 1 },
{ "BrainDead 13 IMASK wedge: BUTCH $03, I2S on", 1, 1, 678, 678, 1, 0x03, 0x05, 1 },
{ "extra BUTCH enable bits don't mask it", 1, 1, 678, 678, 1, 0x0F, 0x05, 1 },
/* Shape (a): seek response never arrived -- fires whatever the regs say. */
{ "seek outstanding, everything off", 3, 2, 900, 900, 1, 0x00, 0x00, 1 },
/* Never counts. */
{ "no seek yet", 0, 0, 0, 0, 1, 0x03, 0x05, 0 },
{ "drains moving", 2, 2, 1001, 1000, 1, 0x03, 0x05, 0 },
{ "no processor running", 2, 2, 1000, 1000, 0, 0x03, 0x05, 0 },
};
int main(int argc, char **argv)
{
const char *core = argc > 1 ? argv[1] : "./virtualjaguar_libretro.dylib";
void *handle;
wedge_frame_fn fn;
unsigned i, fails = 0;
handle = dlopen(core, RTLD_LAZY);
if (!handle)
{
fprintf(stderr, "FAIL: dlopen(%s): %s\n", core, dlerror());
return 1;
}
fn = (wedge_frame_fn)dlsym(handle, "CrashDetectCDSeekWedgeFrame");
if (!fn)
{
fprintf(stderr, "FAIL: CrashDetectCDSeekWedgeFrame not exported (build with TEST_EXPORTS=1)\n");
return 1;
}
for (i = 0; i < sizeof(rows) / sizeof(rows[0]); i++)
{
const struct row *r = &rows[i];
int got = fn(r->starts, r->dones, r->drains, r->last_drains, r->running, r->butch, r->i2s);
if (got != r->want)
{
fprintf(stderr, "FAIL: %s: got %d want %d\n", r->name, got, r->want);
fails++;
}
}
if (fails)
{
fprintf(stderr, "test_crash_detect_cd_wedge: %u failure(s)\n", fails);
return 1;
}
printf("test_crash_detect_cd_wedge: %u cases PASS\n", (unsigned)(sizeof(rows) / sizeof(rows[0])));
return 0;
}
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# cd_seek_wedge_regress.sh -- issue #741 regression: bios-mode titles whose
# CD transfers legitimately finish must NOT log cd_seek_wedge.
#
# Before #741 the watchdog counted any frame with frozen FIFO drains, and
# fired on every one of these titles while they went on to run (15 fires,
# BUTCH low byte $02 each time: the game had cleared the master interrupt
# enable to signal completion). Asserts on the log line itself, not the
# harness [PASS]: test_cd_bios_boot also reports [PASS] on a real
# IMASK-stuck wedge, so [PASS] proves nothing here. The other side (a real
# wedge must still fire) is pinned without a disc by
# test/test_crash_detect_cd_wedge.
#
# Needs the private corpus; SKIPs (exit 0) without it.
#
# Usage: bash test/tools/cd_seek_wedge_regress.sh
# VJ_TEST_CD_ROOT corpus root (default test/roms/private)
# CD_WEDGE_FRAMES frames per title (default 3000; the latest pre-#741
# fire was frame 2716)
set -u
ROOT="${VJ_TEST_CD_ROOT:-test/roms/private}"
FRAMES="${CD_WEDGE_FRAMES:-3000}"
HARNESS=./test/test_cd_bios_boot
# Focus substrings for test_cd_bios_boot (VJ_TEST_CD_FOCUS). Frog Feast
# was in the original report but stopped firing on develop before #741; it
# stays as a guard. Philia surfaced while fixing it: it ends its transfer
# by clearing I2CNTRL bit 2 (FIFO data enable) rather than BUTCH bit 0.
TITLES=(
"Baldies (USA) (Rev 1)"
"Battle Morph"
"BrainDead 13"
"Highlander"
"Primal Rage"
"Myst (USA)"
"Frog Feast"
"Philia"
)
if [ ! -d "$ROOT" ]; then
echo "SKIP: cd_seek_wedge_regress: no CD corpus at $ROOT"
exit 0
fi
if [ ! -x "$HARNESS" ]; then
echo "FAIL: $HARNESS not built (make TEST_EXPORTS=1 $HARNESS)" >&2
exit 1
fi
fails=0
ran=0
for t in "${TITLES[@]}"; do
log="$(DYLD_LIBRARY_PATH=. LD_LIBRARY_PATH=. \
VJ_TEST_CD_ROOT="$ROOT" VJ_TEST_CD_FOCUS="$t" \
VJ_TEST_CD_EXTS=cue VJ_TEST_CD_FRAMES="$FRAMES" \
"$HARNESS" 2>&1)"
if ! printf '%s\n' "$log" | /usr/bin/grep -Eq '^ *\[(PASS|FAIL)\]'; then
echo "SKIP: $t (not in corpus)"
continue
fi
ran=$((ran + 1))
hits="$(printf '%s\n' "$log" | /usr/bin/grep '\[CRASH-DETECT\] cd_seek_wedge')"
if [ -n "$hits" ]; then
echo "FAIL: $t logged cd_seek_wedge:" >&2
printf '%s\n' "$hits" >&2
fails=$((fails + 1))
else
echo "ok: $t"
fi
done
if [ "$ran" -eq 0 ]; then
echo "SKIP: cd_seek_wedge_regress: none of the titles are in $ROOT"
exit 0
fi
if [ "$fails" -ne 0 ]; then
echo "cd_seek_wedge_regress: $fails of $ran title(s) logged a false cd_seek_wedge" >&2
exit 1
fi
echo "cd_seek_wedge_regress: $ran title(s) clean"