Skip to content

Advance SNES CPU and ROM coverage#146

Open
chalharu wants to merge 405 commits into
masterfrom
feature/snes
Open

Advance SNES CPU and ROM coverage#146
chalharu wants to merge 405 commits into
masterfrom
feature/snes

Conversation

@chalharu

Copy link
Copy Markdown
Owner

Summary

  • Advance SNES CPU/PPU bring-up with ROM-driven regression coverage.
  • Add SNES rom_test screen-hash/screenshot coverage for VMAIN, Mode 7, INIDISP, HDMA, autojoy/IRQ/latch, and related hardware behavior.
  • Vendor generated public snes-test-roms artifacts under roms/snes-test-roms with provenance.

Validation

  • cargo test -p nerust_snes_core
  • cargo test -p nerust_snes_rom_test
  • Focused rom_tool validate runs for newly added SNES ROM cases

Notes

  • Final PR target is master; this branch contains the incremental SNES bring-up slices committed and pushed as requested.

chalharu added 30 commits May 28, 2026 03:52
chalharu added 30 commits June 10, 2026 18:56
When WH0 > WH1 (inverted), the window covers everything except
[WH1, WH0] (the complement). Handle this correctly for both
inside and outside masking modes.
- Inverted windows (WH0 > WH1) now correctly cover ALL pixels
- W12SEL bit 0 = 1 interpreted as inside mode (mask when pixel in window)
- Use per-scanline window data with fallback to current register values

Window tests (0203, 0204) now correctly mask for scanlines 0-15.
Remaining diff at scanline 16 suggests HDMA counter off-by-one.
Implement the SNES mosaic effect via the MOZA register (106).
Mosaic quantizes pixel coordinates to NxN block boundaries
(size = register bits 0-3 + 1) for BG layers enabled by bits 4-7.
When no HDMA channel targets INIDISP (100), the captured per-scanline
INIDISP may contain stale values (e.g., forced blank from SNES_INIT).
The backdrop renderer now falls back to current register values in this case.

Also fixes the 'no pixel' sentinel for the transparent backdrop path
and adds mosaic effect support.
When HDMA doesn't target INIDISP, scanline 0's captured data may have
stale forced blank from SNES_INIT (before the test modifies INIDISP).
Only override scanline 0 when the captured INIDISP has forced blanking
but the current does not. Also use scanline 1's CGRAM colour for
scanline 0 when its colour is 0 (uninitialized), to pick up the first
HDMA-written value.

Fixes RedSpaceHDMA, RedSpaceIndirectHDMA, and prevents stale forced
blank from affecting backdrop rendering for non-HDMA-INIDISP tests.
Add cgram_hdma_active parameter to render_presented_backdrop to
detect when HDMA updates CGRAM. When active and scanline 0's
captured colour is 0, use scanline 1's value which reflects the
first HDMA entry.
In Mode 5/6, the 512-pixel wide image is created by interleaving
main screen (even pixel columns) and sub screen (odd pixel columns).
The sub screen BG1 is rendered with an extra +1 HOFS offset so that
odd columns show data shifted by one pixel.

- Added hofs_extra parameter to render_bg1
- Sub screen BG1 gets hofs_extra=1 in high_res_mode
- Composite loop interleaves main_raw and sub_raw at pixel level
When CGRAM HDMA is active, scanline 0's captured colour is always
stale (from the previous frame). Replace it with scanline 1's value
when the two differ, regardless of use_presented_inidisp.
- M7SEL bit 7 (EXTBG) masks VRAM pixel data to 3 bits (0-7) for CGRAM lookup
- backout(backdrop): restore conservative CGRAM HDMA color0==0 guard to avoid regressions
- M7SEL bits 1-0 control out-of-bounds behavior:
  0=None: tile/palette always read from VRAM (wraps via masking)
  1=Repeat: same as None
  2=Mirror: palette=0 when out of bounds (transparent)
  3=Reserved: tile=0 when out of bounds
- VRAM addressing always wraps via uint7/uint3 masks
- out-of-bounds flag uses same 13-bit check as bsnes: (coord | y) & !1023
- Compute per-scanline origin with & ~63 sub-pixel truncation (matching
  bsnes hardware pipeline precision)
- Use >> 8 arithmetic shift instead of / 256 division to match C++ behavior
  for negative coordinate values
- Remove old mode7_source_coordinates / Mode7RenderContext infrastructure
  no longer needed after restructuring
- Add pseudo_hires_enabled() to PPU2, Bus, and Core
- SETINI register (133) bit 3 enables pseudo-hires mode
- Pseudo-hires doubles render width to 512px by interleaving
  main screen (even columns) and sub screen (odd columns)
- Extend color_math_supported to include pseudo-hires mode
- Unpack packed indexed pixels (nibble/bit extraction)
- Look up palette colors from PLTE chunk
- Apply transparency from tRNS chunk
- Falls back to original behavior for 8-bit and grayscale PNGs
…ed width

When reference PNG and rendered output have different widths, the
pixel-by-pixel comparison must use the PNG's actual row stride,
not the rendered width. Fix by deriving PNG pitch from the RGBA
buffer length and rendered height.
Uses sevenz-rust2 0.21.0 to decompress .msu.7z files on-the-fly
to memory. The decompressed MSU-1 data is then passed to the
existing MSU-1 pipeline in load_data().

- Modifies load_msu1_data_sidecar to try .msu.7z when .msu is absent
- Decompresses the entire 7z archive (single-file) to a Vec<u8>
- Touhou BadApple MSU-1 tests (0193, 0195) no longer crash
Add a guard in step_hdma_channels to skip channels whose line
counter is 0. This prevents a debug-mode subtraction overflow
panic when a channel is activated mid-frame without being fully
reloaded (counter remains 0 from a previous HDMA session).
- BG1 uses full 8-bit pixel values (removed incorrect 3-bit masking)
- BG1 skips pixels where bit 5 is set (BG2 enable)
- BG2 overlay renders after BG1 using the same coordinates
- BG2 extracts color from bits 0-6 (7-bit) and checks bit 5 for enable
- BG2 pixel is transparent when color index is 0 or bit 5 is clear
- Matches Fullsnes EXTBG specification (bit 5 = BG2 enable)
Fullsnes says W12SEL=11 is 'mask inside' (same as 01), but bsnes
and actual hardware tests treat 11 as 'mask outside'. This fixes
window masking for the Window HDMA tests: with WH0=15, WH1=240
(non-inverted range), W12SEL=11 now masks pixels outside [15,240],
so pixel (0,0) is correctly masked and shows the backdrop.
…inverted windows

W12SEL=11 (binary) behavior depends on window inversion:
- Non-inverted window (WH0 <= WH1): 11 = mask outside (like 10)
- Inverted window (WH0 > WH1): 11 = mask inside (like 01)

This fixes Window HDMA test (0204) and is correct per hardware behavior:
scanline 0 has inverted window (WH0=1, WH1=0) -> 11 = mask inside
-> all pixels masked -> backdrop shows. scanline 16 has non-inverted
window (WH0=15, WH1=240) -> 11 = mask outside -> pixel 0 is outside
range -> masked -> backdrop shows green.
…hack

The +1 offset in mode7_screen_y was a workaround for missing sign
extension of 10-bit scroll registers. With proper sign extension:
- Values 0-511 remain positive
- Values 512-1023 map to -512..-1 (signed 10-bit)
- This matches bsnes int13/int10 behavior

The +1 is no longer needed because sign-extended coordinates
produce the same result as +1 with unsigned coordinates.

Updated 5 VMAIN expected_screen_hash values to match the corrected
rendering.
In interlace mode, both even and odd fields should use the same
logical scanline (presented_y) for sprite selection. The +1 for
the odd field was incorrect - it shifted sprites by 1 pixel.
Capture arrays now map scanline 1 → index 0 (skip pre-render scanline 0).
This eliminates the presented_y + 1 hack in bg_y calculation: with the
capture shifted, presented_y = 0 correctly corresponds to hardware
scanline 1, so bg_y = presented_y + vofs is the correct formula.

Updated 95 expected_screen_hash values to match the corrected rendering.
Reference PNG tests (128) will need regeneration; unchanged as requested.
…ords, palette bits, high-res pre-fetch

- obj.rs: Filter sprites by interlace field (SETINI bit 1) using attribute
  bit 0 as even/odd select (not tile# bit 8). Tile number restricted to
  8 bits in interlace mode.
- obj.rs: Fix sprite source-y coordinate — use presented_y (field scanline)
  instead of screen_y (output line) to prevent coordinate mismatch in
  interlace mode.
- ppu2/bus/lib: Add obj_interlace_enabled() for SETINI bit 1 check.
- bg1.rs: Fix Mode 5 palette field — bits 10-11 (was 11-12) per Fullsnes.
- bg1.rs: Skip VBlank pre-fetch +1 compensation in high-res modes (5/6)
  where tiles are 16x8 and the offset does not apply.
…color math

The sub screen BG layers must be rendered when TS enables them, not only when
color_math_supported. Mode 5/6 uses sub_raw for odd-pixel columns in the
512-wide output; without this the odd columns showed backdrop instead of
tile content. Also set hofs_extra=0 for sub screen (same scroll as main).
Per spec Modes 5/6 always use 16-pixel wide tiles regardless of the BG tile size bit. The size bit only controls tile height: 0 = 8px (16x8) or 1 = 16px (16x16). Old code forced tile_height=8 for all high-res modes, making subtile_x always 0 and losing the right half of every tile.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant