denise: sprite DMA fetches land in a hardware-true display-latch view#174
Merged
Conversation
Sprite DMA fetches write the same SPRxPOS/CTL/DATA/DATB registers a CPU/Copper write hits, but the emulated fetches only poked the Agnus-side FSM: the Denise display latches never saw them, so denise.spr_armed and the data latches stayed whatever the last manual write left, forever. Since the armed-latch redisplay landed (armed sprite latches keep serializing across DMA-idle frames), that stale state became visible: software that runs a DMA sprite scene, lets the null terminator's CTL fetch disarm every channel, turns SPREN off, and re-arms with the SPRxDATA=$0000 idiom expects invisible sprites (the latches hold the DMA-written zeros on real hardware), not a full-height redisplay of a manual pattern written thousands of frames earlier. Denise now keeps two views of the sprite registers: - sprpos/sprctl/sprdata/sprdatb/spr_armed stay the CPU/Copper write shadow. The render replay's manual-sprite model and the live collision path are calibrated against it and are bit-identical to before (seeding the replay from hardware-true registers instead regressed the vAmigaTS oldsprtimdat render, which a real-A500 photo pins). - spr_hw_pos/ctl/data/datb/armed are the registers as the hardware holds them: last writer wins across manual writes AND DMA fetches (a DATA fetch arms, the vstop control fetch - including the 0/0 terminator - disarms). The DMA-idle latched redisplay seeds from this view. Only the authoritative sprite-DMA pass for a line writes the hardware view through: pre-display lines are computed twice (a provisional live pass, then the pre-display replay at the display start re-runs them with the frame's final DMACON/SPRxPT event timeline and owns the result), and the provisional pass can fetch bogus data lines when SPREN is enabled near the frame wrap, before the vertical-blank reset's control fetch nulls stale comparators. STATE_VERSION 27 -> 28: Denise and RenderRegisterSnapshot gained the hardware-view fields. Regression example: Hamazing's kaleidoscope scene (~132s) painted the transition scene's sprite bars over the right 40% of the display; it now renders clean from boot, byte-identical to before at every other scene. The vAmigaTS Denise/Sprites family (214 renders) is byte-identical to the pre-fix build.
sprprobe-latch renders the Hamazing scene-switch sequence as a static display: full-height bars armed manually with SPREN off, a DMA phase whose null-terminator CTL fetch disarms every channel and overwrites SPR0's data words, then SPREN off again with three manual re-arms. The steady frame must show exactly two bars: SPR2 (full manual re-arm after the DMA disarm) and SPR6 (DATA-only re-arm - its DATB latch still holds the phase-A word because control fetches never touch the data latches). SPR0's arm-with-zero must stay invisible (both data latches hold DMA-written zeros) and SPR3, left alone, must stay disarmed; a bar at either position is a latch write-through regression. DMACON is toggled on line $136, after the last display line, to keep the probe off the SPREN-enable frame-wrap edge where the provisional pre-display sprite pass diverges from the authoritative replay. Cross-checked against vAmiga (tools/vamiga-ref.sh): the steady render's bar positions, widths, and colour runs match pixel for pixel. Verified static (40s shot == 44s shot). Wired into tests/probe_golden.rs and blessed.
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.
Problem
Hamazing's kaleidoscope scene (~132s) rendered full-height vertical bars over the right 40% of the display. Bisect landed on the armed-latch redisplay change (
66476e3), but that only exposed an older modelling gap: sprite DMA fetches never landed in the live Denise sprite registers - they only poked the Agnus-side FSM.The demo arms 8 sprites manually in an early scene (bar patterns in DATB), runs a DMA-driven transition whose null-terminator CTL fetches disarm every channel on real hardware, then re-arms with the
SPRxDATA=$0000idiom and turns SPREN off for the kaleidoscope. Real Denise: armed with A=B=0, invisible. Copperline: the latches still held the manual bar patterns from thousands of frames earlier, and the armed-latch redisplay painted them.Fix
Denise keeps two views of the sprite registers:
spr*stays the CPU/Copper write shadow; the calibrated manual-sprite replay and live collision paths read it and are bit-identical to before. (Seeding the replay from hardware-true registers instead regressedoldsprtimdat, which a real-A500 photo in the vAmigaTS tree pins.)spr_hw_*fields are the registers as the hardware holds them: last writer wins across manual writes and DMA fetches (a DATA fetch arms, the vstop control fetch - including the 0/0 terminator - disarms). The DMA-idle latched redisplay seeds from this view.Write-through only fires in the authoritative sprite-DMA pass for a line: pre-display lines are computed twice, and the pre-display replay at the display start owns them (the provisional live pass can fetch bogus data lines when SPREN is enabled near the frame wrap, before the vertical-blank reset's control fetch nulls stale comparators - that fetch-order divergence itself is left as a known gap).
STATE_VERSION27 -> 28 (Denise and RenderRegisterSnapshot gained the hardware-view fields).Regression probe
New
timing-test/sprprobe-latchgolden renders the scene-switch sequence as a static display: exactly two bars must survive (a full manual re-arm and a DATA-only re-arm showing the persisted DATB latch); the arm-with-zero channel and the untouched channel must stay invisible. Cross-checked pixel-for-pixel against vAmiga, verified static (40s == 44s shot).Verification
Denise/Spritesfamily: 214/214 renders byte-identical to the pre-fix build.sprite_dma_fetches_land_in_hardware_latch_viewbus test), all 19 golden probes, savestate round-trip determinism.cargo clippyandcargo fmt --checkclean.docs/internals/video.mdandtiming-test/README.mdupdated.