Skip to content

fix(agc): a driver store over an earlier store's range takes its piece slot - #6868

Closed
SleglJan wants to merge 1 commit into
boykopovar:mainfrom
SleglJan:pr/driver-piece-repeat
Closed

SleglJan wants to merge 1 commit into
boykopovar:mainfrom
SleglJan:pr/driver-piece-repeat

Conversation

@SleglJan

@SleglJan SleglJan commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What

The write tracker keeps the byte ranges of up to four partial driver stores per 64 KiB block (noteDriverStore). A store that did not fit forgot the oldest and set the block's dropped generation. From then on StoredOver answered true and StoredAllSince false for every range of the block, for any generation before the forgotten store, whether a store had touched that range or not.

A store whose range contains earlier stores' ranges now takes the place of one of them and frees the others, so a range stored again and again keeps one slot. Generations only grow, so the replaced stores answer nothing the new one does not: StoredOver asks whether a newer store overlaps the range, StoredAllSince whether newer stores join up over it. With slots replaced in place, the round-robin index would no longer point at the oldest slot, so a store that does not fit now forgets the slot with the lowest generation, as the round-robin did before.

Where it shows: a storage image whose last 64 KiB block also holds the start of the next allocation, a buffer that several dispatches write in each frame. MarkDirectWrites records the buffer's written range after each of them, the four slots fill with that one range within a frame, the next store forgets one, and the image's own part of the block reads as stored over although nothing wrote it. Its write-back then leaves that block to the CPU (the shared edge block rule of #441), and the image's texels there are replaced with older contents of that memory. An image uploaded through a CPU copy keeps the uploaded bytes, and #1599's comparison stores its results anyway; an image over a host import keeps no such copy above 1 MiB.

In 007 First Light (PPSA11386) the image is the volumetric fog's temporal history (107x60x64, B10G11R11, 1.75 MiB, its end not 64 KiB aligned, no saved copy). The texels that come back hold bytes of another resource, Inf and NaN among them, and the fog pass blends them into the next frames. The screen shows a magenta and white wash over most of it during gameplay.

Several distinct partial stores in one block still fill the four slots and set dropped; the technical debt entry records it.

Related:

Tested

  • agc_write_tracking, new check CheckRepeatedNeighbourStores:
    • six identical driver stores of a range that starts inside a block leave the rest of the block not stored over, and the stored range keeps its complete coverage (StoredAllSince);
    • a store over three earlier stores of the block answers for its own range only;
    • in a full block, a store that replaces one slot and then a store that fits nowhere forget the oldest store, not the replaced one;
    • a store that overlaps an earlier store without containing it does not replace it.
    • CheckSharedBlock's check that six distinct small stores, more than the four slots, still count as a store over the rest of the block passes unchanged.
  • agc_repeated_neighbour_stores (new, agc_driver_recorder_tests --repeated-neighbour-stores-only): a 1024x512 RGBA8 storage image 8 KiB into its first block is uploaded and settles, is cleared on the GPU, the 24 KiB after its end take five driver stores, and the image is written back. Every texel the GPU wrote must be stored and the bytes after the image must be unchanged. It runs over watched memory and over a watched host import.
  • With main 82ddae7's GuestMemory.cpp and the new tests: agc_write_tracking fails at "repeated stores of the neighbour's bytes count for the first surface's part of the shared block", and agc_repeated_neighbour_stores at "over a watched host import: 2048 of 524288 texels the GPU wrote were not stored" (the image's 8 KiB in the shared block).
  • With containment replacement but round-robin eviction, agc_write_tracking fails at "a full block forgets a store newer than its oldest".
  • Test machine: RTX 3080, NVIDIA 615.78.08, CachyOS (Linux 7.2.10), Ryzen 9 7950X3D, Release. ctest -R '^agc' on this branch: 617 tests, 1 failure. agc_driver_store_over_untracked fails the same way on main (reported on fix(agc): save pending storage results in every 64 KiB block that leaves the write watch #5671). agc_driver_stencil_ref_export skips itself on this machine.
  • 007 First Light, on a local tree (main 4186f06 with 007's bring-up stack, among it perf(agc): stores beside a range in its 64 KiB block leave the range unchanged #6346 at b8251dcbb and fix(agc): free retired depth surfaces nobody samples #6465, and local diagnostics; perf(agc): stores beside a range in its 64 KiB block leave the range unchanged #6346 makes the store slots decide whether the history is uploaded again, on main they decide whether its write-back stores its last block). Each run: the 59 frames after gameplay starts, every fog volume dumped after each frame's fog pass.
    • Without this change, 4 runs: the history read in a frame differed from what the fog pass had written into it in the frame before (the same image) in 14, 21, 12 and 16 of the 58 frame pairs. All 390,240 changed texels were in its last slices (56 to 63), and all but 384 of them in its last rows (48 to 59): the end of its memory. In the run that also logged the store slots, the history was uploaded again 80 times over the whole run. 62 followed stores over whole blocks of it. In the other 18, only its last block read as stored over: all four slots held one range that starts at the image's end, recorded by MarkDirectWrites, and dropped was newer than the image.
    • Share of screen pixels with min(R, B) − G > 60: at least 0.30 in 21, 34, 42 and 42 of the 59 frames, from 29, 9, 13 and 17 frames into gameplay, up to 0.65, 0.94, 0.97 and 0.86.
    • With this change, 1 run: the history read differed from the previous write in 0 of 58 frame pairs; its largest component value was 0.06 (up to 65,024 without the change). Share of screen pixels with min(R, B) − G > 60: at most 0.04, in 11 frames between 8 and 18 frames into gameplay, 0.0025 at frame 19 and 0 from frame 20 on.
    • Uploads of any image where only dropped made a block read as stored over, over the whole run: 4,724 in a run without this change, 1,550 with it, 1,463 of them with four distinct stores in the block (the case in the debt entry), none of them on the fog volumes.
  • Independent checks of this head (aa8f734):
    • jotitadev, NVIDIA GeForce RTX 3070 (595.99.02) and lavapipe (Mesa 26.0.3), Ubuntu 26.04, this head merged into main 4524944: both new tests pass 3/3 with the change and fail with main's GuestMemory.cpp (3/3 on the RTX 3070, 6/6 on lavapipe), with the same two failure messages as listed above: fix(agc): a driver store over an earlier store's range takes its piece slot #6868 (review)
    • simonbbby, Ryzen 9 9950X, Ubuntu 24.04, GCC 13.3, CPU bookkeeping only: the production tracker against an oracle with an unbounded list of driver-store intervals, 5 seeds × 128 trials. With stores that need at most four slots per block, 327,680 paired StoredOver/StoredAllSince queries agree exactly; with further arbitrary partial stores that may exceed the slot capacity, 122,880 more show no missed store and no complete coverage over a gap. The parent fails it (false overlap without history overflow), and a variant that replaces an overlapping, not only a contained, piece fails with lost an actual driver store: fix(agc): a driver store over an earlier store's range takes its piece slot #6868 (comment)

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: PPSA11386 007 First Light (PPSA11386) #5714
  • GPUs tested: NVIDIA GeForce RTX 3080, driver 615.78.08, CachyOS Linux 7.2.10
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

…e slot

A 64 KiB block keeps the ranges of up to four partial driver stores. When a store does not
fit, the oldest is forgotten, and every range of the block reads as stored over before it.
A store whose range contains earlier stores' ranges now takes the place of one of them and
frees the others, so a range stored again and again keeps one slot. Generations only grow,
so the replaced stores answer nothing the new one does not. With slots replaced in place,
the round-robin index no longer points at the oldest one, so a store that does not fit now
forgets the slot with the lowest generation.

A storage image that shares its last block with a buffer that several dispatches write in
each frame no longer reads as stored over there, and its write-back stores its results in
that block instead of leaving the block to the CPU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 862 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1368 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1896 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 2434 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 4448 core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
docs/dev/debt/libSceAgcDriver.md
PR 4449 docs/dev/debt/libSceAgcDriver.md
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5372 docs/dev/debt/libSceAgcDriver.md
PR 5426 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5431 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5883 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6044 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6146 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6182 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6185 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6187 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6249 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6273 core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6300 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6481 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6498 docs/dev/debt/libSceAgcDriver.md
PR 6501 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6612 docs/dev/debt/libSceAgcDriver.md
PR 6642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6743 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6788 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6804 docs/dev/debt/libSceAgcDriver.md
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6820 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6905 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6921 core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
PR 6926 core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6959 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6960 core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
PR 6970 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6971 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6973 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6977 docs/dev/debt/libSceAgcDriver.md
PR 6987 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6989 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6995 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7002 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7048 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7085 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 7201 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp
PR 7223 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7256 docs/dev/debt/libSceAgcDriver.md
PR 7275 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7307 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 7310 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 7395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7446 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 7449 docs/dev/debt/libSceAgcDriver.md
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7464 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7465 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7505 core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7508 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7514 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7535 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7618 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7668 docs/dev/debt/libSceAgcDriver.md
PR 7677 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7705 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7717 docs/dev/debt/libSceAgcDriver.md
PR 7833 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7844 docs/dev/debt/libSceAgcDriver.md
PR 7893 docs/dev/debt/libSceAgcDriver.md
PR 7910 docs/dev/debt/libSceAgcDriver.md
PR 7978 docs/dev/debt/libSceAgcDriver.md
PR 8014 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8089 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8105 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8121 docs/dev/debt/libSceAgcDriver.md
PR 8123 docs/dev/debt/libSceAgcDriver.md
PR 8160 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8161 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8213 docs/dev/debt/libSceAgcDriver.md
PR 8223 docs/dev/debt/libSceAgcDriver.md
PR 8246 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8281 docs/dev/debt/libSceAgcDriver.md
PR 8319 docs/dev/debt/libSceAgcDriver.md
PR 8365 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8374 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 8395 docs/dev/debt/libSceAgcDriver.md
PR 8401 docs/dev/debt/libSceAgcDriver.md
PR 8412 docs/dev/debt/libSceAgcDriver.md
PR 8419 docs/dev/debt/libSceAgcDriver.md
PR 8420 docs/dev/debt/libSceAgcDriver.md
PR 8423 docs/dev/debt/libSceAgcDriver.md
PR 8424 docs/dev/debt/libSceAgcDriver.md
PR 8426 docs/dev/debt/libSceAgcDriver.md
PR 8484 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 8527 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8542 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8552 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8563 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8564 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8592 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8639 docs/dev/debt/libSceAgcDriver.md
PR 8658 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 8672 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8707 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8740 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8756 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 8874 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8930 core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
PR 8939 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8999 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9007 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9013 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9017 docs/dev/debt/libSceAgcDriver.md
PR 9020 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9022 docs/dev/debt/libSceAgcDriver.md
PR 9068 docs/dev/debt/libSceAgcDriver.md
PR 9077 docs/dev/debt/libSceAgcDriver.md
PR 9098 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9159 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9176 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9185 docs/dev/debt/libSceAgcDriver.md
PR 9186 docs/dev/debt/libSceAgcDriver.md
PR 9188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9236 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9243 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9248 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9275 docs/dev/debt/libSceAgcDriver.md
PR 9287 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9289 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9305 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9318 docs/dev/debt/libSceAgcDriver.md
PR 9335 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9348 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9352 docs/dev/debt/libSceAgcDriver.md
PR 9356 docs/dev/debt/libSceAgcDriver.md
PR 9361 docs/dev/debt/libSceAgcDriver.md
PR 9387 docs/dev/debt/libSceAgcDriver.md
PR 9393 docs/dev/debt/libSceAgcDriver.md
PR 9396 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9401 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9402 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9406 docs/dev/debt/libSceAgcDriver.md
PR 9424 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9431 docs/dev/debt/libSceAgcDriver.md
PR 9432 docs/dev/debt/libSceAgcDriver.md
PR 9443 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9445 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9450 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9495 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9508 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9514 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9521 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9533 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9565 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9570 core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
PR 9592 docs/dev/debt/libSceAgcDriver.md
PR 9597 docs/dev/debt/libSceAgcDriver.md
PR 9626 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9633 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9652 docs/dev/debt/libSceAgcDriver.md
PR 9670 docs/dev/debt/libSceAgcDriver.md
PR 9671 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9672 docs/dev/debt/libSceAgcDriver.md
PR 9673 docs/dev/debt/libSceAgcDriver.md
PR 9674 docs/dev/debt/libSceAgcDriver.md
PR 9675 docs/dev/debt/libSceAgcDriver.md
PR 9676 docs/dev/debt/libSceAgcDriver.md
PR 9685 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9696 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9700 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9702 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9703 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9712 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9717 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9721 docs/dev/debt/libSceAgcDriver.md
PR 9723 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9737 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9742 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9748 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9749 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9779 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9789 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9790 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9836 docs/dev/debt/libSceAgcDriver.md
PR 9842 docs/dev/debt/libSceAgcDriver.md
PR 9852 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9856 docs/dev/debt/libSceAgcDriver.md
PR 9858 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9875 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9879 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9890 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9891 docs/dev/debt/libSceAgcDriver.md
PR 9892 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9893 docs/dev/debt/libSceAgcDriver.md
PR 9895 docs/dev/debt/libSceAgcDriver.md
PR 9897 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9898 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9899 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9904 docs/dev/debt/libSceAgcDriver.md
PR 9905 docs/dev/debt/libSceAgcDriver.md
PR 9906 docs/dev/debt/libSceAgcDriver.md
PR 9907 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9913 docs/dev/debt/libSceAgcDriver.md
PR 9915 docs/dev/debt/libSceAgcDriver.md
PR 9940 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9941 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9950 docs/dev/debt/libSceAgcDriver.md
PR 9961 docs/dev/debt/libSceAgcDriver.md
PR 9969 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9988 docs/dev/debt/libSceAgcDriver.md
PR 9989 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9999 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 10000 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10026 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10043 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10044 docs/dev/debt/libSceAgcDriver.md
PR 10046 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10049 docs/dev/debt/libSceAgcDriver.md
PR 10062 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 10064 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 10066 docs/dev/debt/libSceAgcDriver.md
PR 10138 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 10141 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10159 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 10164 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10179 docs/dev/debt/libSceAgcDriver.md
PR 10191 docs/dev/debt/libSceAgcDriver.md
PR 10193 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10194 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10233 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 10304 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 10307 docs/dev/debt/libSceAgcDriver.md

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Red/green on lavapipe, the driver the Linux CI job uses, and on a second NVIDIA Linux machine.

Machine: NVIDIA GeForce RTX 3070, driver 595.99.02, Vulkan 1.4.329, plus lavapipe (Mesa 26.0.3, LLVM 21.1.8) on the same machine. Ubuntu 26.04, i7-8700, GCC 15.2, Release, BUILD_TESTING=ON. Head aa8f73490 merged into main 452494439 (clean merge). Device checked in the Vulkan device: lines.

The write-watch probe differs between the two, so the host-import case runs under different rules: the RTX 3070 logs host imports resolve write protection: no ... imported ranges stay watched, lavapipe logs yes ... imported ranges are compared. Both run the watched-memory and the host-import cases.

Tree RTX 3070 lavapipe
PR agc_write_tracking and agc_repeated_neighbour_stores pass 3/3 pass 3/3
PR tests, main's GuestMemory.cpp both fail 3/3: repeated stores of the neighbour's bytes count for the first surface's part of the shared block and over a watched host import: 2048 of 524288 texels the GPU wrote were not stored both fail 6/6, same messages

So the host-import case catches the regression with either probe result.

agc_driver_store_over_untracked is not affected by this change here. On the RTX 3070 it fails on every run with and without the change (the result did not reach a block the CPU left alone), as on main. On lavapipe it is intermittent on both trees with a different message (the result was never held pending): 1 of 3 runs with the change, 2 of 6 with main's GuestMemory.cpp.

Not run: Windows, AMD, the full suite, any title.

GPUs tested: NVIDIA GeForce RTX 3070 (595.99.02), lavapipe (Mesa 26.0.3, LLVM 21.1.8), Ubuntu 26.04

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@simonbbby

Copy link
Copy Markdown
Contributor

Independent interval-history check at exact head aa8f734907686de67c0f16924e84ef5b4762f606, complementing the GPU report above. Ubuntu 24.04, kernel 7.0.0-38, GCC 13.3, Ryzen 9 9950X; production CPU tracker only.

Compiled the unchanged WriteTracking.cpp with the actual GuestMemory, GuestArena, GuestAllocations and GuestWriteWatch objects (-O2, function/data sections, linker garbage collection). The PR passes. Keeping the same test/support objects and replacing only GuestMemory.cpp with HEAD^ fails at repeated stores of the neighbour's bytes count for the first surface's part of the shared block.

A separate scratch oracle keeps an unbounded list of driver-store intervals and their returned generations, then computes overlap and complete coverage by sorting/merging newer intervals. It uses the public MarkWritten, StoredOver and StoredAllSince_nid_no_patch calls. Memory is untouched by the CPU after initial registration/collection, so the history being compared is driver stores.

Five seeds, each with 128 trials over two 64 KiB blocks:

  • First 64 stores per trial: four disjoint clusters per block, each repeated or expanded to contain its earlier range. This needs at most four retained slots per block. 327,680 paired queries require exact agreement for both StoredOver and StoredAllSince, including unrelated bytes, random prior-generation cutoffs and cross-block spans. All pass.
  • Next 24 stores per trial: arbitrary partial ranges may exceed the slot capacity. 122,880 further paired queries require no missed actual store and no complete-coverage proof over an oracle gap. All pass. Conservative overlap positives and incomplete-coverage answers are allowed here, matching the documented overflow policy.
  • There are 60,248 positive complete-coverage answers, all backed by the independent interval union.

Discriminating controls: the parent production object fails this oracle with false overlap without history overflow. A scratch variant that replaces an overlapping piece instead of only a contained piece fails with lost an actual driver store. This checks that the coalescing repairs precision while retaining the containment requirement, rather than simply suppressing overflow effects.

A traced stock pass shows successful UFFDIO_REGISTER, UFFDIO_WRITEPROTECT and PAGEMAP_SCAN; these runs did not skip for lack of a write-watch backend. The worktree is clean. Scope: CPU range/generation bookkeeping at this head; no GPU write-back, title replay, #6346 composition or build on newer main was tested here.

@SleglJan

Copy link
Copy Markdown
Contributor Author

Thanks @jotitadev and @simonbbby: a second NVIDIA machine plus lavapipe for the red/green, and an unbounded-interval oracle with discriminating controls for the bookkeeping. Both are now listed under Tested.

AI-assisted: yes (Claude Code)

@MarcZX10

MarcZX10 commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

I opened #7233 from current main to separate complete-write evidence from possible-write ranges in the DriverPieces tracker. It keeps their histories in independent slots and routes BDA page reports plus descriptor-bound direct/copy-back ranges through the possible-write path. The change touches the same tracker code as this PR but does not depend on its interval-retention behavior. If #7233 lands first, please rebase #6868 onto it and keep the containment/replacement rule for both histories; if #6868 lands first, I will rebase #7233 and preserve the separate possible-write ring.

@SleglJan

Copy link
Copy Markdown
Contributor Author

@MarcZX100 agreed. If #7233 lands first, I'll rebase this onto it and keep the containment/replacement rule for both histories.

The possible-write ring is where 007 First Light's case ends up. There the four slots fill with one MarkDirectWrites range, which #7233 routes as a possible write. Without containment in that ring, the next identical range sets possibleDropped, and StoredOver again answers true for the image's part of the shared block. I'll run 007 with #7233 on top of this, with the rule on both rings, and report whether the fog-history wash stays fixed.

AI-assisted: yes (Claude Code)

@MarcZX10

Copy link
Copy Markdown
Contributor

I folded the reported retention rule into #7233 at d88fbaa for both the exact-write and possible-write rings, preserving their separate slots. There is also a new assertion for repeated possible writes not invalidating the adjacent range. When convenient, could you try your 007 setup on the combined head and confirm whether the fog-history regression is gone? The PR descriptions now note that these changes need coordination before merge.

@milescoviello

Copy link
Copy Markdown
Contributor

@SleglJan: a title-level datapoint for the containment reuse this PR introduced is on #7233 (comment). In Ruffle (PPSA68091), a word the driver rewrites every frame next to a 4x target's DCC fills the block's four pieces, and five of Ruffle's visual tests stop on writes to a resident multisampled DCC target or its metadata are not implemented. With the shared change they run and match their expected images. I tested #7233 because this PR conflicts with my test stack in the debt file.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

@SleglJan here's a Ruffle case that containment doesn't cover, for when you look at eviction. It's blend_modes__shader_as_mask (Ruffle's visual test SWF) on main 385ed71 + #6543 + #7250 + #7233, with a temporary print at the failing StoredOver.

The resident 4x target's DCC is 0x4004606000+0x14000, so it ends at offset 0xa000 of block 0x4004610000. When the check fails, the range's generation is 6801. The block holds four pieces, all at or past 0xa000:

  • 0xa000-0xa008 (6844)
  • 0xa030-0xa040 (6828)
  • 0xd000-0xe000 (6865)
  • 0xe000-0xf000 (6871)

dropped is 6812. None of those pieces touches the DCC, but dropped > 6801 makes StoredOver true for the whole block. The pieces don't contain one another, so taking a contained piece's slot doesn't help.

Something that keeps the extent of what was forgotten would help. One option: on overflow, merge the two nearest pieces into one range with the newer generation. That range would count for StoredOver, but not as coverage for StoredAllSince. Do you plan to change eviction in this PR or #6346? If not, I can do it as a follow-up once they land. It's the only stop left in the 66 Ruffle visual tests I ran with #6543, #7250, #7233 and #7600.

AI-assisted: yes (Claude Code)

@SleglJan

Copy link
Copy Markdown
Contributor Author

@milescoviello thanks for the clear case. I don't plan to change eviction in this PR or in #6346, so please go ahead with the follow-up. This PR's rule is now part of #7233, which is agreed to land first, so a follow-up on top of #7233 is the natural place.

#6346 doesn't change eviction, but it touches the same code. It moves StoredOver's slot check into a WriteTracker helper that UnchangedSince also uses. And on Windows, a private mapping now sets dropped instead of erasing the block's pieces, so dropped has to keep its whole-block meaning there. Whichever of the two lands second takes the rebase.

Your shape for it sounds right to me, for both rings: on overflow, merge the two nearest pieces into one range with the newer generation, counted for StoredOver but not as coverage for StoredAllSince. One case to test: the two nearest pieces can be a resource's own last store and the next allocation's first store, touching at the resource's end. The merged range then makes the resource read as stored over, although no store after its own touched it. That's the same false answer that cost #6868's fog history its last block.

Your print shows the four kept pieces but not the forgotten one (generation 6812). If that store overlapped the DCC, the stop is real and merging won't remove it. Logging the evicted range would tell you before you build anything.

When you have a branch, I can run it against 007 First Light's fog-history case and our agc_write_tracking checks on the RTX 3080 (Linux, write watching available). The same runs count the uploads where only a forgotten piece made a block read as stored over. In #6868's run with the change that was 1,550, 1,463 of them with four distinct stores in the block, and none on the fog volumes.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

@SleglJan thanks, I'll do it on top of #7233.

You're right about the forgotten piece, so I'll log the evicted range first. If the 6812 store overlapped the DCC, the stop is real and there's nothing to build. I'll add the case you describe to the tests: a resource's own last store and the next allocation's first store, touching at the resource's end, must not merge into a range that reads as stored over the resource. Merging only pieces that leave a gap, or keeping touching pieces apart when another pair can merge, are the two ways I see to avoid it. I'll measure both against the Ruffle case. I'll ping you here with the branch for the 007 fog-history run and the upload counts. Thanks for offering.

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 18, 2026
@milescoviello

Copy link
Copy Markdown
Contributor

@SleglJan the follow-up is ready, with the evicted-range log first as you suggested. Branch fix/driver-store-merge (829be00) is one commit on #7233's head 42cbe0c. I'll open the PR when #7233 lands or is rebased, as MarcZX10 asked there.

The forgotten piece. I logged each ring's last 8 evicted pieces, on the same Ruffle stack (main 385ed71 + #6543 + #7250 + #7233). At the failing check, generation 6753, dropped was 6764, and that came from the piece 0xb000-0xc000, which doesn't overlap the DCC's part (0x0-0xa000). The DCC's own store (0x0-0xa000, generation 6750) had been evicted earlier, and it's older than the check. So the stop is false and worth fixing.

Choosing the rule. I traced every driver piece store, whole-block store, block erase, StoredOver block check and StoredAllSince block check, and replayed the traces offline through candidate rules and through a tracker that keeps every piece. Replaying #7233's rule gives the logged answer in every check (293,996 of 293,996 in filters__bevel_full). No rule missed a store. False stored-over answers:

Rule shader_as_mask (74 checks) bevel_full (293,996) your touching case, 6 / 20 later stores
#7233: forget the oldest into a block-wide generation 5 0 1 / 1
forgotten pieces keep their extent 1 0 1 / 1
8 pieces, forget into a block-wide generation 2 0 0 / 1
merge the nearest pair 13 293,958 1 / 1
merge the nearest pair with a gap (one of my two ideas) 18 0 0 / 0
merge the pair that raises the fewest bytes 0 0 0 / 0

So nearest-pair merging was as bad as you expected. The extent idea fails exactly where you said: the first piece forgotten is usually the surface's own store, and merging it with any newer neighbour makes the surface read as stored over again.

The rule in the branch. When a ring is full, it merges the pair, among the four pieces and the new one, whose union raises the recorded generation of the fewest bytes. For different generations, that's the bytes of the union outside the newer piece. For equal generations, it's the gap. A surface's own store is large, so it stays exact while small stores past it merge with each other. That's your touching case: the next allocation's first store merges with what follows it, not with the surface.

The merged piece spans both with the newer generation. For coverage, it keeps the older of the two coverage generations when the pieces touch or overlap, because every byte was then stored since it. It proves nothing across a gap. My first version proved no coverage for any merged piece, and that broke #7233's "out-of-order split driver stores" check.

Tests (agc_write_tracking, RTX 4070 Ti, Linux with write watching):

  • The new CheckMergedPieces has your touching case, the Ruffle label pattern, and coverage of contiguous merges but not gapped ones.
  • Two existing assertions pinned the block-wide answer: CheckSharedBlock's "forgotten driver stores are taken as missing the range", and the "rest of the block" check in your CheckRepeatedPossibleStores. They now require the opposite. A forgotten store still counts over its own bytes, and a full ring doesn't merge two distant pieces.
  • Green 3 of 3. With fix(agc): separate possible writes from complete coverage #7233's GuestMemory.cpp the new checks fail.
  • The 34-test write-tracker/MSAA set: the same two HTILE-clear tests fail as on fix(agc): separate possible writes from complete coverage #7233's own head (agc_driver_native_depth_bda_clear and agc_driver_native_depth_clears), nothing else.
  • Ruffle: blend_modes__shader_as_mask now runs to the 32 s limit instead of stopping at 11 s. Its last frame still differs from the expected image (coarse 14.8), which I haven't looked at yet. filters__bevel_full is unchanged (coarse 2.8).

For your 007 run, if you still have time: diag/driver-store-trace is the branch plus a trace hook (APS5_DIAG_TRACE=<file>) and tools/driver_store_replay.py <file>. The replay prints false and missed stored-over answers and coverage lost or wrongly claimed for each rule, against the unbounded tracker, and how often each rule differs from the logged answer. The traced rule differs in 0, which checks the replay against the build. The fog-history case and your count of uploads where only a forgotten piece made a block read as stored over would be the real benchmark.

One note for #6346 now on main: its Windows private-mapping path sets dropped with block-wide meaning. When I rebase onto #7233's rebase, I'll keep dropped for that path and only stop the ring from writing it.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

@SleglJan a follow-up on the one open point above: blend_modes__shader_as_mask's last frame differing from its expected image (coarse 14.8) isn't ours. Ruffle's test.toml for it says known_failure = true, with the note "Shader masks unimplemented + a bug in color blending", so Ruffle's own renderer doesn't match that image either. The only thing that test shows here is that the false stop is gone.

AI-assisted: yes (Claude Code)

@SleglJan

Copy link
Copy Markdown
Contributor Author

@milescoviello I ran 007 First Light with your rule and your trace hook, twice. The fog case stays closed under it in both runs. The replay over the two traces splits:

  • In the first run, "merge the pair that raises the fewest bytes" gives more false stored-over answers than "current (4 slots, oldest to block-wide dropped)", fix(agc): separate possible writes from complete coverage #7233's rule: 2,992 against 374.
  • In the second run, the current rule gives 690,534 false answers, against 2,396. 690,188 of them come from one block: its possible ring first overflows after 1,868 of the block's 692,056 checks, and every later check of the block reads as stored over. In the other blocks the current rule gives 346.

No rule misses a stored-over answer in either run.

Setup:

Runs (arm (c), with #7233's rule, for comparison):

run 1 run 2 arm (c)
length, frames of gameplay 926 s, 62 942 s, 64 987 s, 63
faults 0 0 0
fog history pairs that differ 0 of 58 0 of 58 0 of 58
largest finite component read 0.05 0.06 0.04
screen share with min(R, B) − G > 60 0.00 in all frames 0.00 in all frames 0.00 in all frames
  • The upload count from my earlier reply (uploads where only a forgotten piece made a block read as stored over) is 0 here by construction, since nothing writes dropped. I don't count uploads where only a merged piece does.
  • agc_write_tracking fails in this tree at "forgotten driver stores are taken as missing the range". That is the CheckSharedBlock assertion your commit inverts in your test file, and this tree carries the old test. The binary stops there, so the later checks didn't run, among them the "rest of the block" one in CheckRepeatedPossibleStores.

Replay (tools/driver_store_replay.py):

  • Run 1: 33,103,488 records: 539,698 piece stores, 30,800,282 whole-block stores and 1,763,508 StoredOver block checks, of which the unbounded tracker reads 115,643 as stored over.
  • Run 2: 33,428,096 records: 539,932, 31,113,806 and 1,774,358 checks, 69,740 stored over.
  • In both, your rule, which the build ran, differs from the logged answer in 0 checks. The other rules are replayed over the stores this build made.
  • There are no block erasures (every erase site is Windows-only) and no StoredAllSince block checks (its only caller here is the native multisample depth clear), so your coverage change isn't exercised here.
rule false stored-over, run 1 run 2
current (4 slots, oldest to block-wide dropped) 374 690,534
dropped keeps its extent (hull) 309 307
8 slots, oldest to block-wide dropped 124 108
8 slots, dropped keeps its extent 123 108
merge nearest pair 11,024 11,261
merge nearest pair with a gap, else oldest to dropped 3,129 686,203
merge nearest pair with a gap, else oldest to dropped with its extent 3,129 3,510
merge cheapest pair (gap + older piece bytes) 13,071 14,619
merge the pair that raises the fewest bytes, 4 slots 2,992 2,396
merge the pair that raises the fewest bytes, 8 slots 2,056 1,697
4 slots, 4 forgotten ranges merging the two oldest 168 162
8 slots, 4 forgotten ranges merging the two oldest 1 0
8 slots, 2 forgotten ranges merging the two oldest 52 46

Per block, from a small script over the same traces with your tool's rules:

  • What splits the runs is run 2's most-checked block: 692,056 checks, 1,268 piece stores, no whole-block store.
    • Under the current rule, 1,264 of those stores find its possible ring full, the first after 1,868 of its checks. From then on the block-wide dropped answers all 690,188 later checks falsely.
    • Your rule answers none of its checks falsely.
  • Run 1's most-checked block is a different block; neither appears in the other run's trace. It is checked 509,359 times, with 1,551 piece stores and no whole-block store. The current rule never overflows a ring there, and neither rule answers a check there falsely.
  • Your rule's false answers sit in 12 blocks in run 1 and 10 in run 2. Most of them are in one block, the same in both runs, with a few thousand checks (3,926 and 3,343): 2,305 of 2,992 and 1,858 of 2,396. The current rule has 178 and 170 there.

I haven't traced which caller makes run 2's most-checked block's checks, so I can't say what a false answer costs there.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

@SleglJan thank you, that's the benchmark I couldn't run. Two runs that split this way settle it against my rule: it fixes the overflow collapse in run 2's hot block, but it costs thousands of false answers in the one block that has them in both runs, where #7233's rule has under 200.

I replayed my Ruffle traces through the same tool for the rows your runs favour (blend_modes__shader_as_mask, 74 checks; filters__bevel_full, 293,996):

rule shader_as_mask bevel_full 007 run 1 007 run 2
current (4 slots, oldest to block-wide dropped) 5 0 374 690,534
merge the pair that raises the fewest bytes, 4 slots (the branch) 0 0 2,992 2,396
4 slots, 4 forgotten ranges merging the two oldest 0 0 168 162
8 slots, 4 forgotten ranges merging the two oldest 0 0 1 0

So the forgotten-range rules are the only ones that win in all four traces, and none of them misses a stored-over answer. I'll switch the branch to them:

On memory: a block entry is 112 bytes on #7233 and 136 in the branch. Four forgotten ranges per ring add 96 bytes, and four more ring slots per ring another 128. Entries exist only for blocks with driver stores. Unless you see a reason to prefer 8 slots, I'll start with 4 slots and 4 ranges (168 and 162 in your runs) and report the trace counts again; going to 8 slots is a one-line change if the 007 numbers make it worth it.

AI-assisted: yes (Claude Code)

@SleglJan

Copy link
Copy Markdown
Contributor Author

@milescoviello 007 on fix/driver-store-forget, two runs: runs 3 and 4. Runs 1 and 2 were your merge rule, in #6868 (comment). These use the same harness and tree as run 2 there, without the upload logging.

  • The fog case stays closed in both: 0 of 58 frame pairs differ.
  • Run 3 shows a short magenta wash at the top of the screen, and run 4 a dark green block over the upper left. In both, the fog history's reads equal its writes. Details below.
  • Over the same traces, your rule gives 675 and 772 false stored-over answers, against 4,867 and 7,249 for fix(agc): separate possible writes from complete coverage #7233's rule. With 8 slots, the forgotten-range rule gives 2 and 0.
  • No rule misses a stored-over answer.

Setup:

run 3 run 4
length, frames of gameplay 962 s, 62 1002 s, 64
faults 0 0
fog history pairs that differ 0 of 58 0 of 58
largest finite component read 0.04 0.05
screen share with min(R, B) − G > 60 0.01 at gameplay +29; 0.24, 0.18, 0.01 at +47 to +49; 0.00 in the rest 0.00 in all frames
  • Run 3 shows a magenta wash over the upper half of the screen at +47, shrinking into the upper left by +49. It also shows a smaller patch in the upper-left corner at +29, and colored specks along the top edge in +50 to +53. The fog history's reads in those frames equal its writes.
  • Run 4, same build and input, shows no magenta, but a dark green block over the upper left in +49 to +56, which the share above doesn't count. The fog history's reads there equal its writes too.
  • Arm (c)'s frames show the same kind of block in +11 to +13. Runs 1 and 2 show neither.
  • I haven't attributed either. A three- or four-frame magenta flash also showed up in a manual session last night, on a tree with fix(agc): a driver store over an earlier store's range takes its piece slot #6868 and without fix(agc): separate possible writes from complete coverage #7233. That one was seen, not measured.
  • agc_write_tracking fails in this tree at "forgotten driver stores are taken as missing the range". That is the CheckSharedBlock assertion your branch inverts, and this tree carries the old test.

Replay (tools/driver_store_replay.py), false stored-over answers. These traces have 2.9 M and 3.7 M checks, against 1.8 M in runs 1 and 2, so the counts aren't comparable with the 168 and 162 there.

rule run 3 run 4
StoredOver block checks (stored over by the unbounded tracker) 2,935,394 (70,639) 3,699,337 (65,284)
current (4 slots, oldest to block-wide dropped) 4,867 7,249
8 slots, oldest to block-wide dropped 435 527
merge the pair that raises the fewest bytes, 4 slots 5,611 15,615
4 slots, 4 forgotten ranges merging the two oldest (the branch) 675 772
8 slots, 4 forgotten ranges merging the two oldest 2 0
8 slots, 2 forgotten ranges merging the two oldest 195 215

Per block, for the current rule and the branch:

  • The current rule's false answers sit in 9 and 7 blocks. Most come from one block, the same in both runs, with 16,605 and 24,000 checks: 3,574 and 6,425 false there. The branch gives 0 and 2 there.
  • The branch's false answers sit in 3 and 4 blocks. Most come from one block, the same in both runs, with 3,417 and 3,554 checks: 653 and 748 false there. The current rule gives 682 and 768 there.

AI-assisted: yes (Claude Code)

@adriadam10 adriadam10 self-assigned this Oct 19, 2026
@milescoviello

Copy link
Copy Markdown
Contributor

@SleglJan thanks for runs 3 and 4, and for catching the hook. Across your four runs, "8 slots, 4 forgotten ranges merging the two oldest" gives 1, 0, 2 and 0 false stored-over answers, against #7233's 374, 690,534, 4,867 and 7,249, and no rule misses one. So I've moved the branch to 8 slots:

  • Both piece rings hold 8 pieces. The forgotten lists stay at 4 ranges, and their two oldest ranges still merge into their hull when a list is full.
  • The tests that fill a ring now count from a DriverPieceSlots constant, so they still overflow it. Each still evicts the same pieces as it did with 4 slots, in CheckSharedBlock, CheckRepeatedPossibleStores, CheckForgottenPieces and the possible-interval checks.
  • I gave a wrong memory figure before. A piece is 12 bytes, so four more slots per ring add 96 bytes, not 128. An entry goes from 112 bytes on fix(agc): separate possible writes from complete coverage #7233 to 296, which comes to about 4.0 MB at your 21,861 entries.
  • The trace hook now includes forgotten and possibleForgotten in the logged answer, as in your port. The traced build's row ("8 slots, 4 forgotten ranges") should then differ from the logged answer in 0 checks.

On the magenta wash and the green block: no rule misses a stored-over answer, so these rules differ from an unbounded tracker only by extra stored-over answers, never by stale reads. Arm (c) shows the same kind of green block at +11 to +13, so I don't think either comes from the tracker. With 8 slots there are also far fewer extra answers. I haven't attributed either one.

agc_write_tracking ×3, the red check against #7233's GuestMemory.cpp and the write-tracker/MSAA set are queued behind two other jobs. I'll push both branches when they pass and post the new heads here.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

@SleglJan both branches are pushed with 8 slots:

Tests (agc_write_tracking, RTX 4070 Ti, Linux):

  • Green 3 of 3. With fix(agc): separate possible writes from complete coverage #7233's GuestMemory.cpp, the updated tests fail at "driver stores forgotten in the first surface's part count for the second surface's part".
  • One more check needed scaling: the "dropped driver evidence proves complete coverage" loop in the coverage checks now stores DriverPieceSlots + 2 pieces, so it still evicts the piece it checks.
  • The 41-test write-tracker/MSAA set fails only agc_driver_native_depth_clears, as on 42cbe0c alone.
  • I traced the test binary itself with the diag branch and replayed it: "8 slots, 4 forgotten ranges merging the two oldest" differs from the logged answer in 0 checks. fix(agc): separate possible writes from complete coverage #7233's rule differs in 10 there, so the hook now logs what the build answers.

If you run 007 on it, the trace's "8 slots, 4 forgotten ranges" row should show 0 logged differences directly, without your port's change.

AI-assisted: yes (Claude Code)

@SleglJan

Copy link
Copy Markdown
Contributor Author

@milescoviello 007 with 8 slots, runs 5 and 6. Same harness and tree as runs 3 and 4, with the piece rings at 8 as in 4f4729b.

  • The fog case stays closed in both: 0 of 58 frame pairs differ.
  • Over the two traces, "8 slots, 4 forgotten ranges merging the two oldest" gives 0 and 6 false stored-over answers, against 9,131 and 2,695 for fix(agc): separate possible writes from complete coverage #7233's rule. It differs from the logged answer in 0 checks in both, so the build answered exactly that.
  • No rule misses a stored-over answer.
  • Neither run shows the dark green block. Both show magenta: run 5 a small patch at +17 to +20, run 6 a wash over up to 0.81 of the screen in +2 to +14 and a glow at +60. Details, and one correction to "never by stale reads", below.

Setup:

run 5 run 6
length, frames of gameplay 982 s, 63 942 s, 63
faults 0 0
fog history pairs that differ 0 of 58 0 of 58
largest finite component read 0.04 0.05
screen share with min(R, B) − G > 60 0.08, 0.03, 0.02 and 0.01 at gameplay +17 to +20; 0.00 in the rest 0.63 and 0.15 at +2 and +3; 0.71 at +6; 0.73, 0.81 and 0.77 at +8 to +10, falling to 0.01 by +15; 0.09 at +60; 0.00 in the rest
top-left corner (upper 10 %, left 20 %) with G − max(R, B) > 25 0.00 in all frames 0.00 in all frames

The corner measure is new. Where those runs show the block, it gives 0.95 to 1.00 at run 4's +49 to +53 and 0.12 to 0.54 at +54 to +56, and 0.52 to 0.93 at arm (c)'s +11 to +13. It also gives 0.32 at run 4's +6, a green-tinted bloom frame without the block.

Replay (tools/driver_store_replay.py), false stored-over answers. These traces have 2.0 M and 1.8 M checks, so the counts aren't comparable with runs 3 and 4.

rule run 5 run 6
StoredOver block checks (stored over by the unbounded tracker) 1,972,358 (80,420) 1,779,443 (65,206)
current (4 slots, oldest to block-wide dropped) 9,131 2,695
8 slots, oldest to block-wide dropped 127 123
4 slots, 4 forgotten ranges merging the two oldest (runs 3 and 4's build) 826 175
8 slots, 4 forgotten ranges merging the two oldest (this build) 0 6
8 slots, 2 forgotten ranges merging the two oldest 57 60

Per block:

  • Run 6's 6 false answers sit in one block with 247 checks, where the current rule gives 38. They fall two each at +24, +56 and +57, all on the part described below. That is after the wash at +2 to +14, and three presents before the glow at +60.
  • The current rule's false answers sit in 9 and 7 blocks. Most come from one block in each run: in run 5, 8,866 false of 30,175 checks; in run 6, 2,410 of 15,442. This build gives 0 in both.

On the magenta:

  • Run 5 had no false stored-over answer in its whole trace, and run 6 none before +24. Both still show magenta earlier, at +17 to +20 and +2 to +14. So magenta appears without a false answer.
  • After runs 3 and 4, I tested the one part of a block where their false answers fell in the presents just before the artifacts. The title re-creates a few small images there every few presents. On runs 3 and 4's build, with a local switch, I forced stored-over answers for that part during +15 to +24 and +40 to +49. The title checked that part in three presents, all in the first window (+19, +20 and +23): 16 forced answers, and no artifact followed them. That run's magenta came before them, with no false answer in gameplay before +52: 0.24 and 0.06 at +7 and +9, 0.48 and 0.20 at +17 and +18.
  • The forced run's pass trace leads elsewhere for the first gameplay frames (+1 to +9): there a post-processing pass reads a surface that no pass has written in the frame. I'm following that up separately.
  • Whether run 6's glow at +60 is related to its false answers at +56 and +57, I can't tell from these runs. Its false answers at +24, and the forced answers on the same part, were followed by no artifact. Apart from that, I agree that these runs don't point at the rings' rule. The green block is still unattributed.

One correction to "only by extra stored-over answers, never by stale reads". In a traced run on runs 3 and 4's build, false answers on that same part in two presents made a 1×1 float image there take its guest bytes again (two more uploads), and the next dispatch that read it got 3.8e-25 where it had read 0. The difference was too small to show on screen, but an extra stored-over answer can bring back stale bytes. That's one more reason to keep them rare, which 8 slots do.

AI-assisted: yes (Claude Code)

@milescoviello

milescoviello commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

@SleglJan thank you for runs 5 and 6. With 8 slots, "8 slots, 4 forgotten ranges merging the two oldest" gives 0 and 6 false stored-over answers, against 9,131 and 2,695 for #7233's rule. It differs from the logged answer in 0 checks, so the hook now logs what the build answers. And no rule misses one. That settles the slot count for me.

Thanks too for the correction, which I accept: I was wrong that extra stored-over answers can't cause stale reads. Your traced run shows a false answer making the 1×1 float image take its guest bytes again, and the next dispatch read 3.8e-25 where it had read 0. A false stored-over answer can overwrite a GPU result with older guest bytes. That makes keeping them rare part of correctness, not only cost, and I'll say so in the PR description.

On the magenta: run 5 shows it with no false answer in its whole trace, and run 6 before its first one. Your forced answers on that part were followed by no artifact. So I agree it doesn't point at the rings. Your lead on the post-processing pass that reads a surface no pass wrote in the frame sounds like the place to look.

The follow-up PR still waits on #7233. Its head moved to ac0a545, and in your run that still fails agc_driver_native_depth_clears in DriverDispatch. Once #7233 settles, I'll rebase fix/driver-store-forget onto it and open the PR with your six 007 runs. I'll cite them as your measurements.

AI-assisted: yes (Claude Code)

@SleglJan

Copy link
Copy Markdown
Contributor Author

Closing in favour of #10333. #7233 (e424d57, @MarcZX10) carries this rule for both rings; #10333 keeps the tests and a rewritten debt line. Thanks @jotitadev and @simonbbby for the red/green runs and the interval oracle, and @milescoviello for the Ruffle case and the eviction follow-up.

AI-assisted: yes (Claude Code)

@SleglJan SleglJan closed this Oct 23, 2026
SleglJan added a commit to SleglJan/AnyPS5 that referenced this pull request Oct 23, 2026
e424d57 (MarcZX10, boykopovar#7233) made a driver store whose range contains earlier pieces of its ring take their slot and a full ring forget its oldest piece, for the exact and the possible ring. boykopovar#6868 proposed that rule for the exact ring; this keeps its tests and its debt line and drops the code. The tests cover what main's do not: repeated exact stores of a neighbour's bytes leave the other surface's share of the block unstored and keep their own coverage, a containing store answers for the contained bytes only and frees the slots of the pieces it contains, a full exact ring forgets its oldest piece and not a newer one, the forgotten piece counts for the rest of the block, an overlapping store that contains nothing replaces nothing, and a storage image written back after five such stores in its last 64 KiB block keeps every texel over watched memory and over a watched host import. The debt line records what a forgotten piece costs for both rings.

The recorder's --repeated-neighbour-stores-only mode returns 77 when no watched arm ran (no write watching); in the full suite each arm prints its own ok or not-tested line instead of one ok for both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SleglJan added a commit to SleglJan/AnyPS5 that referenced this pull request Oct 24, 2026
e424d57 (MarcZX10, boykopovar#7233) made a driver store whose range contains earlier pieces of its ring take their slot and a full ring forget its oldest piece, for the exact and the possible ring. boykopovar#6868 proposed that rule for the exact ring; this keeps its tests and its debt line and drops the code. The tests cover what main's do not: repeated exact stores of a neighbour's bytes leave the other surface's share of the block unstored and keep their own coverage, a containing store answers for the contained bytes only and frees the slots of the pieces it contains, a full exact ring forgets its oldest piece and not a newer one, the forgotten piece counts for the rest of the block, an overlapping store that contains nothing replaces nothing, and a storage image written back after five such stores in its last 64 KiB block keeps every texel over watched memory and over a watched host import. The debt line records what a forgotten piece costs for both rings.

The recorder's --repeated-neighbour-stores-only mode returns 77 when no watched arm ran (no write watching); in the full suite each arm prints its own ok or not-tested line instead of one ok for both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SleglJan added a commit to SleglJan/AnyPS5 that referenced this pull request Oct 24, 2026
e424d57 (MarcZX10, boykopovar#7233) made a driver store whose range contains earlier pieces of its ring take their slot and a full ring forget its oldest piece, for the exact and the possible ring. boykopovar#6868 proposed that rule for the exact ring; this keeps its tests and its debt line and drops the code. The tests cover what main's do not: repeated exact stores of a neighbour's bytes leave the other surface's share of the block unstored and keep their own coverage, a containing store answers for the contained bytes only and frees the slots of the pieces it contains, a full exact ring forgets its oldest piece and not a newer one, the forgotten piece counts for the rest of the block, an overlapping store that contains nothing replaces nothing, and a storage image written back after five such stores in its last 64 KiB block keeps every texel over watched memory and over a watched host import. The debt line records what a forgotten piece costs for both rings.

The recorder's --repeated-neighbour-stores-only mode returns 77 when no watched arm ran (no write watching); in the full suite each arm prints its own ok or not-tested line instead of one ok for both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SleglJan added a commit to SleglJan/AnyPS5 that referenced this pull request Oct 25, 2026
e424d57 (MarcZX10, boykopovar#7233) made a driver store whose range contains earlier pieces of its ring take their slot and a full ring forget its oldest piece, for the exact and the possible ring. boykopovar#6868 proposed that rule for the exact ring; this keeps its tests and its debt line and drops the code. The tests cover what main's do not: repeated exact stores of a neighbour's bytes leave the other surface's share of the block unstored and keep their own coverage, a containing store answers for the contained bytes only and frees the slots of the pieces it contains, a full exact ring forgets its oldest piece and not a newer one, the forgotten piece counts for the rest of the block, an overlapping store that contains nothing replaces nothing, and a storage image written back after five such stores in its last 64 KiB block keeps every texel over watched memory and over a watched host import. The debt line records what a forgotten piece costs for both rings.

The recorder's --repeated-neighbour-stores-only mode returns 77 when no watched arm ran (no write watching); in the full suite each arm prints its own ok or not-tested line instead of one ok for both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SleglJan added a commit to SleglJan/AnyPS5 that referenced this pull request Oct 29, 2026
e424d57 (MarcZX10, boykopovar#7233) made a driver store whose range contains earlier pieces of its ring take their slot and a full ring forget its oldest piece, for the exact and the possible ring. boykopovar#6868 proposed that rule for the exact ring; this keeps its tests and its debt line and drops the code. The tests cover what main's do not: repeated exact stores of a neighbour's bytes leave the other surface's share of the block unstored and keep their own coverage, a containing store answers for the contained bytes only and frees the slots of the pieces it contains, a full exact ring forgets its oldest piece and not a newer one, the forgotten piece counts for the rest of the block, an overlapping store that contains nothing replaces nothing, and a storage image written back after five such stores in its last 64 KiB block keeps every texel over watched memory and over a watched host import. The debt line records what a forgotten piece costs for both rings.

The recorder's --repeated-neighbour-stores-only mode returns 77 when no watched arm ran (no write watching); in the full suite each arm prints its own ok or not-tested line instead of one ok for both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SleglJan added a commit to SleglJan/AnyPS5 that referenced this pull request Oct 29, 2026
e424d57 (MarcZX10, boykopovar#7233) made a driver store whose range contains earlier pieces of its ring take their slot and a full ring forget its oldest piece, for the exact and the possible ring. boykopovar#6868 proposed that rule for the exact ring; this keeps its tests and its debt line and drops the code. The tests cover what main's do not: repeated exact stores of a neighbour's bytes leave the other surface's share of the block unstored and keep their own coverage, a containing store answers for the contained bytes only and frees the slots of the pieces it contains, a full exact ring forgets its oldest piece and not a newer one, the forgotten piece counts for the rest of the block, an overlapping store that contains nothing replaces nothing, and a storage image written back after five such stores in its last 64 KiB block keeps every texel over watched memory and over a watched host import. The debt line records what a forgotten piece costs for both rings.

The recorder's --repeated-neighbour-stores-only mode returns 77 when no watched arm ran (no write watching); in the full suite each arm prints its own ok or not-tested line instead of one ok for both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SleglJan added a commit to SleglJan/AnyPS5 that referenced this pull request Oct 30, 2026
e424d57 (MarcZX10, boykopovar#7233) made a driver store whose range contains earlier pieces of its ring take their slot and a full ring forget its oldest piece, for the exact and the possible ring. boykopovar#6868 proposed that rule for the exact ring; this keeps its tests and its debt line and drops the code. The tests cover what main's do not: repeated exact stores of a neighbour's bytes leave the other surface's share of the block unstored and keep their own coverage, a containing store answers for the contained bytes only and frees the slots of the pieces it contains, a full exact ring forgets its oldest piece and not a newer one, the forgotten piece counts for the rest of the block, an overlapping store that contains nothing replaces nothing, and a storage image written back after five such stores in its last 64 KiB block keeps every texel over watched memory and over a watched host import. The debt line records what a forgotten piece costs for both rings.

The recorder's --repeated-neighbour-stores-only mode returns 77 when no watched arm ran (no write watching); in the full suite each arm prints its own ok or not-tested line instead of one ok for both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SleglJan added a commit to SleglJan/AnyPS5 that referenced this pull request Oct 31, 2026
e424d57 (MarcZX10, boykopovar#7233) made a driver store whose range contains earlier pieces of its ring take their slot and a full ring forget its oldest piece, for the exact and the possible ring. boykopovar#6868 proposed that rule for the exact ring; this keeps its tests and its debt line and drops the code. The tests cover what main's do not: repeated exact stores of a neighbour's bytes leave the other surface's share of the block unstored and keep their own coverage, a containing store answers for the contained bytes only and frees the slots of the pieces it contains, a full exact ring forgets its oldest piece and not a newer one, the forgotten piece counts for the rest of the block, an overlapping store that contains nothing replaces nothing, and a storage image written back after five such stores in its last 64 KiB block keeps every texel over watched memory and over a watched host import. The debt line records what a forgotten piece costs for both rings.

The recorder's --repeated-neighbour-stores-only mode returns 77 when no watched arm ran (no write watching); in the full suite each arm prints its own ok or not-tested line instead of one ok for both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build docs needs rebase Conflicts with main, rebase needed unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants