Each frame the game waits in one nvnSyncWait (1.2.1 lr 0x7000b23dfc) for the frame it presented last. On the Switch
that work is long done by then; here it is still in the replay and on the GPU, and on Great Sky Island the wait is
about 4.6 ms of a 13 ms frame. With TOTK_SYNC_AHEAD=1 that one wait waits for the frame before it instead, once the
replay thread submitted the asked value, so the game builds the next frame while the GPU runs the last one.
The game then writes guest memory that GPU work still in flight reads. Shadow storage makes that safe: a shadow update
of a pool with a reader in flight is queued and copied by the GPU in the next part's prologue, in queue order, instead
of memcpy'd under that reader (TOTK_SHADOW_DEFER=1, the default with the switch; 0 makes the replay thread wait for
the reader instead). The guard is read under the shadow lock and the wait takes that lock once after raising it, so no
sync that read the old guard is still copying when the game writes again. The switch needs shadow storage (Linux and
Wine by default, TOTK_SHADOW=1 on native Windows) and the 1.2.1 call site; otherwise it stays off with a log line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every texture initialization got a 128-byte host copy for recordings to point at, kept for the whole run. The game
initializes its transient targets again every frame (about 29 new copies a frame on 1.4.3), so host memory grew by
about 44 MiB per minute of play, standing still too, until the commit limit ended long sessions (#670).
Each pin now counts the recording copies whose ops name it: counted in flatten_recording, uncounted when a copy or a
per-submission clone is freed. A pin with no copy left is freed NVN_PIN_AGE (1200) submits after its last use, so a
recording still in progress keeps its pins, and never while the replay holds it as a current render target, which
carries over to the next submission and is compared by pointer. TOTK_PIN_SWEEP=0 keeps every copy.
A 300 s standing-still run on the field save: host memory without VRAM grew 5 MiB, against about 220 MiB before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments named the agent task (T1-T7, M2-M5, Task 2.x) that once owned each file or block, and pointed at local
notes the repository does not carry (backlog items, milestone plans, research notes). They now say what the code
does. gfx_stubs.c held weak stand-ins for functions that every build has defined for a long time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imported pools put vertices, indices, constants and storage buffers in system memory, so discrete GPUs read them over
PCIe on every draw. A pool now gets a device-local mirror on its first use as a vertex, index, uniform, storage or
indirect buffer; bindings, buffer copies, fills and uniform updates use it at the same offsets. The first access of a
submission syncs the pages written since (write-watch snapshots shared with the texture checks, plus
nvnMemoryPoolFlushMappedRange ranges) in a prologue command buffer before the part; what the GPU writes into a mirror
goes back to guest memory at the end of the submission. TOTK_MEMORY=import|mirror|verify overrides the device class;
verify compares sampled mirror pages with guest memory at the fence. The vkexec probe gains a mirror part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At the start of every submit the dirty census queries write watch over the imported pools the replay bound as vertex,
index, uniform, storage or indirect buffers, stamps the shared page generations and logs eager, lazy and first-use
bytes per frame and per submit by pool class as mem: dirty every 60 frames, with FlushMappedRange calls and bytes and
the live and cumulative mirror size. The device class line (uma or discrete, heaps and memory types) is logged once at
device selection and the pool census also at frame 600. perf_summary.py summarises the new lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>