Repository navigation
Conversation
On NTSC without interlace, line 240 of every other field lasts 1360 master clocks, not 1364, with no long dots: fields alternate 357,368 and 357,364 clocks (MesenCE from power-on; fullsnes). Snes.fieldParity toggles at each wrap to V=0, false after reset, and every beam walk in snes.c takes the line's length from snes_line_clocks: the advance, the clocks to a line or an IRQ, the next IRQ clock, the $2137 latch and $4212 bit 6. The parity is saved after the RDNMI latch (RTLS v12; v11 files load with reset's parity). The beam frame driver starts the CPU at master clock 186, the 65816's reset sequence, with the beam at 0. SNESRECOMP_SHORT_LINE and SNESRECOMP_RESET_CLOCK switch them; both default to on only with the master-clock APU timeline (RtlApuUseMasterClock). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SNESRECOMP_HVBJOY_EXACT (default: on with the master-clock APU timeline) reads vblank and the automatic joypad read's busy bit at the access's clock, as bit 6 already is (SNESRECOMP_HBLANK_EXACT), so no host has to walk the beam ahead of the CPU to show an edge early. The automatic read runs from the first master clock 128 past a multiple of 256 and at least 3 after line 225 began, for 4352 clocks (17 x 256; MesenCE autoReadClockStart). snes_line_clocks is public: a host that places events on the beam's lines needs the short line too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SNESRECOMP_REFRESH_AT_BEAM (default: on with the master-clock APU timeline): snes_refresh_charge stalls the CPU 40 master clocks per refresh point it crossed since the last charge, each at H = 538 - (the line's start & 7) of every line (MesenCE dramRefreshPosition), instead of 40 per 1364 clocks of CPU time at no fixed place. snes_next_refresh gives the next point. An APU port access whose instruction crosses a point sees the stall first, after any host access-clock hook. Test: tests/dma/hvbjoy_refresh_test.c, $4212 edges at the access and the refresh points (run from tests/test_beam_events.py, next commit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SNESRECOMP_BEAM_EXACT (default: on with the master-clock APU timeline) runs snes_beam_frame_driver_run_frame on an exact path; the old path is kept unchanged for every other host: - the interrupt sequence costs 8 cycles and 60 master clocks, and a refresh point inside it stalls it; - the field's NMI follows the first instruction that starts a cycle from V=225 H=6 (snes/last_cycle.c: the length of an instruction's last bus cycle), not the first to end past V=225; - an NMI enabled inside vblank, RDNMI still set, fires after the next instruction in that field, not a field later; - CPU slices end at each DRAM refresh point and the beam follows the CPU after each one. SnesBeamFrameHooks lets a host add its own work (field start, interrupt entry, NMI taken, a field NMI it takes itself, slice end); snes_beam_frame_driver_deadlines gives the running slice's deadlines. docs/FRAME_MODEL_HOSTS.md lists all five switches. Tests: tests/frame_driver/exact_rules_test.c (a scripted CPU on the real beam), tests/dma/last_cycle_test.c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/dma/refresh_charge_test.c runs snes_refresh_charge, cut out of common_cpu_infra.c: a stall exactly when a refresh point is crossed, two in one charge, none for a teleport; with SNESRECOMP_REFRESH_AT_BEAM=0 the old 40 per 1364 clocks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
this is kind of part 2 of "why on earth does Evermore produce RNG with mid-frame memory" (I'd expect some minor merge conflicts with PR #278)
It used to be handled in
src/soe_frame_driver.c, but that solution was very volatile. The recommended way for this to be merged was 3 small PRs that depended on each other, which I overruled for simplicity.Summary
Five hardware timing rules that let a frame driver match MesenCE clock for clock. Each sits where its hardware lives, has its
own switch, and is on by default only with the master-clock APU timeline (
RtlApuUseMasterClock, #241). Without that timelinenothing changes.
A host needs no timing code of its own for these: no beam alignment after a field, no walking the beam ahead for
$4212, norefresh top-ups.
docs/FRAME_MODEL_HOSTS.mdlists the switches.Why
Each rule broke Secret of Evermore in a way a player or a replay sees; each touches code most SNES games contain.
$F6for$F8: another demo from NMI 11986$2137/$213C; anything that counts on the field length over minutes$4200 = $81at V=226; the NMI waited a field, so the NMI count was one behind from frame 5 (6 times in the first 4 minutes, at scene loads)CMP $0100 / BEQwas interrupted at the other instruction; the 52-clock phase carried into the next fields$4212at the accessBIT $4212 / BNEauto-read wait ended up to 9 passes early;LDA $4212 / BMIleft a pass late in the last 20 clocks before line 0CMP $2140 / BNEpolled one pass early about every 25 handshakesThe cost of not having them: a host that wants hardware timing has to rebuild them outside the framework, as we did (several
hundred lines that moved the beam after the fact), and that is fragile: walking the beam ahead and syncing back quietly moved
the field grid by 2 clocks, which our own alignment hid until this work removed it. Only Evermore is measured; for other games
the right column says where to look, not what we tested.
Evidence
ahead at vblank edges, topping up refresh). With that code removed and any one switch off, replay 01 (power-on, attract demo)
parts from MesenCE 2.2.1:
power-on:
pytest tests/v2 tests/test_snes_cycles.py tests/test_beam_events.py tests/test_beam_frame_state.py tests/snapshot_guard:645 passed (macOS). Same speed: 322 frames/s, 94.06 % native (318, 94.05 % before).
Merge Danger
Door: two-way
Every rule has a switch, and the default turns them on only with the master-clock APU timeline. One part does not revert by
switch: savestate v12 adds
Snes.fieldParity, so a v12 state does not load in an older build (v11 states load here with afresh reset's parity).
Blast Radius: opt-in
Only hosts that call
RtlApuUseMasterClock. For them, field lengths,$4212and refresh timing change, and the frame drivertakes its exact path. Conflicts with #278 in
snes_beam_at(a few lines); the resolution is ready, whichever merges second.Checked on one game and on macOS only.