Skip to content

Return what the PPU fetches for OAM and CGRAM reads during rendering - #278

Open
r-bin wants to merge 2 commits into
RetroPortingToolKit:mainfrom
r-bin:ppu-render-reads-main
Open

r-bin wants to merge 2 commits into
RetroPortingToolKit:mainfrom
r-bin:ppu-render-reads-main

Conversation

@r-bin

@r-bin r-bin commented Nov 10, 2026

Copy link
Copy Markdown

for reasons I don't comprehend Evermore takes random numbers too serious and creates them in the middle of rendered frames.

Summary

During active display the PPU owns OAM and CGRAM. A CPU read of $2138/$213B returns the byte at the address the PPU is
fetching at that moment; the CPU's address still advances. snesrecomp always answers with the CPU's address.

 ppu_read($2138 / $213B)
-  return mem[cpu_address]
+  if rendering and (h, v) = beam at this access's own clock is not in a free window
+    return mem[ppu_fetch_address(h, v)]
+  return mem[cpu_address]
   cpu_address++

Rules, as MesenCE 2.2.1 has them:

  • OAM, H ≤ 1020: the range phase's sprite, one per two dots from the priority-rotation start.
  • OAM, later: the time phase, walking this line's in-range sprites backward from dot 270, two dots per sliver.
  • CGRAM: free in H 0–87 and 1096–1363, on line 0, from the NMI line on, and in forced blank. Otherwise the backdrop
    while the sub screen has no layer enabled.

snes.c installs g_ppu_beam_hook, the beam at the access's own clock (from #241). It only answers on the master-clock
timeline (RtlApuUseMasterClock), so other games keep today's reads. SNESRECOMP_PPU_RENDER_READS=0 turns it off.

Evidence

Secret of Evermore seeds its RNG at reset from reads on lines 124 and 125, so one wrong byte changes the rest of the run.
Attract replay, WRAM/VRAM/CGRAM/OAM hashed at every NMI against MesenCE 2.2.1, on main e9c20732:

  • Before (SNESRECOMP_PPU_RENDER_READS=0): WRAM parts at NMI 5998, native and interpreted.
    After: identical at all 8,592 NMIs, native and interpreted.

<test: tests/ppu/… reads $2138/$213B at chosen H/V and checks the fetched byte> ← to write

Merge Danger

Door: two-way. One switch turns it off, and it is inactive unless the master-clock timeline is on.

Blast Radius: narrow. Only games on the master-clock timeline that read $2138/$213B during active display.

Limits: checked against one game's reads only. CGRAM with sub-screen layers on is not modelled (keeps the CPU's address).
Each $2138 read during rendering walks the lines above the beam, which would cost a game that polls it.

r-bin and others added 2 commits November 10, 2026 16:47
During active display a $2138 or $213B read gets the byte at the address the
PPU itself is using, not the CPU's (the CPU's address still advances). Rules
as MesenCE 2.2.1 has them:

- OAM, H clock <= 1020: the range phase's sprite (one per two dots from the
  priority-rotation start). Later: the time phase walking this line's
  in-range sprites backward from dot 270, two dots per sliver; before dot 270
  where the previous line's walk ended.
- CGRAM: free in H clocks 0..87 and 1096..1363, on line 0, from the NMI line
  on and in forced blank. Otherwise the backdrop (0) while the sub screen has
  no layer enabled; with sub screen layers the CPU's address is kept (not
  modelled).

snes.c installs g_ppu_beam_hook, the beam at the access's own clock; it
answers only with the master-clock APU timeline, so other games keep the old
reads. SNESRECOMP_PPU_RENDER_READS=0 turns it off.

Secret of Evermore, all interpreted, against MesenCE: the attract replay was
identical through NMI key 5997 (its reset-time RNG seed reads OAM on line 124
and CGRAM on line 125); now at every key, 9,287 of 9,287. The new-game replay
stays at every key, 9,227 of 9,227. Switch off: parts at 5998 again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 3b73e98c0332c0aead2b279492882d09c2f8578b)
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