mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 12:47:58 -10:00
fix(gpu): internal-RAM LOADB/LOADW perform a full 32-bit read (JTRM)
Per the Jaguar Technical Reference v8 ("Load Byte"): "The destination
register will have the byte loaded into bits 0-7 ... This applies to
external memory only, INTERNAL MEMORY WILL PERFORM A 32-BIT READ."
GPU local RAM is one long per row; byte and word loads return the
ENTIRE long containing the address -- no lane extraction.
We returned `GPUReadLong(RM) & 0xFF` -- the byte at RM+3 (GPUReadLong
composes a big-endian long STARTING at the unaligned address) -- and
`& 0xFFFF` for LOADW.
Atari's own Cinepak for Jaguar decompressor (JTRM vol. 12) depends on
the hardware behaviour: its CRY/RGB clamp tables in GPU local RAM
store one entry per 32-bit long (value in the low byte, upper bytes
zero) precisely so that LOADB at an arbitrary byte offset returns the
entry via the 32-bit read. Under our wrong-lane read every colour
lookup fetched garbage: all decoded FMV came out as structured noise
-- user-confirmed corrupted playback on BrainDead 13 and the other
ReadySoft FMV titles, reproduced and diagnosed headlessly (compressed
input in RAM verified bit-correct; framebuffer garbage under every
16bpp reinterpretation; full evidence in
docs/cd-diagnosis/fmv-corruption-diagnosis.md).
After the fix the FMV pipeline renders pixel-perfect frames: BrainDead
13's ReadySoft logo and castle intro, Dragon's Lair's title screen
(verified visually via test/tools/cd_visual_verify screenshots).
Blast radius:
- make TEST_EXPORTS=1 test: 56103/56103 pass (incl. test_gpu_ops,
framebuffer integrity, audio pair). c89-lint clean.
- Full private cart library A/B vs the pre-fix build: crash-detect
signatures byte-identical on every title; FPS delta within noise.
- The DSP has the same wrong-lane masking (dsp_opcode_loadb/loadw);
that fix is deliberately NOT bundled here -- it changes audio-engine
behaviour and needs the full audio validation gauntlet (tracked
separately).
Also documents (handoff section 5) the newly device-traced Baldies
stall: back-to-back DSA commands ($7001 Set DAC Mode + $150A Set Mode
in one tick) overwrite the single cdCmd response latch, so the game
never sees its $70nn echo and wedges -- next fix in line.
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
1948b78bf8
commit
f13892b3a8
2 files changed
+21
-9
No files matched your search
@@ -98,6 +98,7 @@ The `stop`-wait idiom is **not** CD-only. Static scan of the local cart library
|
||||
## 5. Backlog (independent, each needs its own diagnosis)
|
||||
|
||||
0. **FMV pixel corruption (NEW top target, user-confirmed on device)** — decoded FMV frames are value-garbage with intact block structure on BrainDead 13 (+ other ReadySoft titles). Diagnosed to the **GPU decode stage**: compressed input in RAM is bit-correct (byte-order verified against executing code), display mode/OP list healthy (RGB16, no VARMOD), no 16bpp re-interpretation of the output renders clean, and disc-loaded logo assets through the same FIFO are pixel-perfect. Pre-existing (unreachable before the §4 fix). Full evidence + next steps: [`cd-diagnosis/fmv-corruption-diagnosis.md`](cd-diagnosis/fmv-corruption-diagnosis.md).
|
||||
0b. **DSA back-to-back command response overwrite (device-traced on Baldies bios, 2026-07-15).** After transfer #2 completes, Baldies sends `$0200` (Stop), then `$7001` (Set DAC Mode) and `$150A` (Set Mode) **in the same tick**; our single `cdCmd` latch (cdrom.c ~line 951) lets `$150A` clobber the pending `$7001` echo. The game polls DS_DATA three times, receives `$170A` each time, never sees the `$70nn` echo, and wedges (GPU spin at `$F0305E`, cd_seek_wedge with drains frozen at 45896, seeks stuck at 2/2 — no new play command ever issued). Fix direction: queue DSA responses (the seek/stop queue already exists) instead of a single latch. Likely the same mechanism behind other post-transfer stalls where a game interleaves mode/DAC commands.
|
||||
1. **Highlander / IS2 (bios)** stall earlier than the sentinel scan — drains freeze at 54/0. Different mechanism from every fix above.
|
||||
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.
|
||||
|
||||
+20
-9
@@ -1464,7 +1464,21 @@ INLINE static void gpu_opcode_storep(void)
|
||||
INLINE static void gpu_opcode_loadb(void)
|
||||
{
|
||||
if ((RM >= 0xF03000) && (RM <= 0xF03FFF))
|
||||
RN = GPUReadLong(RM, GPU) & 0xFF;
|
||||
{
|
||||
/* JTRM (Technical Reference v8, "Load Byte"): "The destination
|
||||
* register will have the byte loaded into bits 0-7 ... This applies
|
||||
* to external memory only, internal memory will perform a 32-bit
|
||||
* read." Internal RAM is one long per row; a byte load returns the
|
||||
* ENTIRE long containing the address -- no byte extraction at all.
|
||||
* Atari's own Cinepak decompressor (JTRM vol. 12) depends on this:
|
||||
* its CRY/RGB clamp tables in GPU local RAM store one entry per
|
||||
* 32-bit long (value in the low byte, upper bytes zero) and index
|
||||
* them with LOADB at arbitrary byte offsets. The old code returned
|
||||
* `GPUReadLong(RM) & 0xFF` (= the byte at RM+3): every decoded FMV
|
||||
* frame came out as structured garbage (BrainDead 13 / Dragon's
|
||||
* Lair / Space Ace). */
|
||||
RN = GPUReadLong(RM & 0xFFFFFFFC, GPU);
|
||||
}
|
||||
else
|
||||
RN = JaguarReadByte(RM, GPU);
|
||||
}
|
||||
@@ -1472,17 +1486,14 @@ INLINE static void gpu_opcode_loadb(void)
|
||||
|
||||
INLINE static void gpu_opcode_loadw(void)
|
||||
{
|
||||
#ifdef GPU_CORRECT_ALIGNMENT
|
||||
if ((RM >= 0xF03000) && (RM <= 0xF03FFF))
|
||||
RN = GPUReadLong(RM & 0xFFFFFFFE, GPU) & 0xFFFF;
|
||||
{
|
||||
/* Same JTRM rule as LOADB: word loads from internal RAM perform a
|
||||
* full 32-bit read of the long containing the address. */
|
||||
RN = GPUReadLong(RM & 0xFFFFFFFC, GPU);
|
||||
}
|
||||
else
|
||||
RN = JaguarReadWord(RM, GPU);
|
||||
#else
|
||||
if ((RM >= 0xF03000) && (RM <= 0xF03FFF))
|
||||
RN = GPUReadLong(RM, GPU) & 0xFFFF;
|
||||
else
|
||||
RN = JaguarReadWord(RM, GPU);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user