Crash logs ended in fatal errors when the system's commit ran out for a moment: a staging buffer (-2), a guest commit
with the system at 24442 of 24447 MB, image creation (-2). Windows grows a system-managed page file and some drivers
charge device memory to the process commit, so the same request often succeeds a moment later.
oom.c holds the wait nvn_api_cmd.c realloc_wait already used: it requests a release, retries every 10 ms for up to
30 s, logs the first 8 with the commit numbers. rt_oom_request sets a flag that vk_texture_new_submit takes (one atomic
load per submit): it drops up to 256 released textures of the two kinds sweep_pressure drops (overwritten, clean
mirrored), never GPU-written content, and frees the headroom blocks. vk_oom_release does at once what needs no submit:
headroom blocks, finished upload batches and deferred objects.
- memory.c commit: waits and retries without frz_lock between tries; every caller waits, boot ones too.
- vk_staging and the draw-data chunks (vk_exec_draw.c scratch): release, then wait; the open upload batch is not
submitted, its caller still records into it.
- create_tex: release and one retry; no wait under the texture lock, the texture is asked for again at its next use.
- the texture write-back buffer: release, then wait.
- the headroom thread keeps no spare block for 30 s after a failure.
TOTK_TEST_OOM=<site>:<n> (commit, staging, image, chunk) fails the first n attempts; probe-kernel forces 3 refused
commits, probe-vkexec 2 staging buffers, 1 image and 1 chunk, and both pass through them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three game threads flatten recordings, inline them and add control memory at once, and every large copy came
from a fresh VirtualAlloc, so each one paid zero-page faults, and release_copies freed them under the exclusive
copy lock. Copies are now freed after the lock through a per-thread dead list, blocks of 256 KiB to 16 MiB come
from a small best-fit cache, and each copy keeps its pin list so a free or clone no longer walks the records again.
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>
1.4.3 initializes and finalizes command buffers about 2000 times a second while a scene loads and several times a
frame in play. Finalize freed the recording buffer, so each new recording grew again from 64 KiB through up to five
reallocations; the two large per-frame recordings ended in fresh 1-2 MiB blocks, new commit twice a frame. On a
16 GB machine at render scale 200 one of those 1 MiB growths failed during a Depths load and the game ended with
"nvn: command recording buffer of 1048576 bytes" (#94).
Finalize now hands the buffer to a pool of 16 (largest kept, up to 4 MiB each) and the next recording takes the
largest one: a 60 s island run made 22 growths instead of about 110000. A failed recording buffer or command copy
allocation frees the pool and retries every 10 ms for up to 30 s before the fatal, so a short commit shortage (Windows
growing a system-managed page file) stalls the game instead of closing it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A player's game ended with "nvn: command recording buffer of 1048576 bytes" during a dive into the Depths (#94):
realloc refused 1 MiB with commit to spare. That leaves a buffer pointer that is no block of ours, from a command
buffer object the game overwrote or initialized again without Finalize. The fatal line now gives the object, its
magic, the old buffer and its size, the bytes used and asked, and Initialize logs (eight times at most) when it
replaces an object that was not finalized.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shared runtime code now goes through runtime/platform/platform.h, implemented by platform/win32.c and a new
platform/linux.c: futex locks, a userfaultfd and PAGEMAP_SCAN write watch, signal-based async preemption, our own
fiber switch, and a SIGSEGV handler for the thaw and crash hooks. The window, input, menu and software keyboard get
SDL3 back ends beside the Win32 ones, with the swapchain (gfx_present.c) and input merging (input.c) shared. SHA-256
replaces BCrypt, ELF builds get their own override sections, and the build tools gain Linux branches.
On Linux the selftest and the gfx-abi, astc, nvn and window probes pass. probe-vkmem still fails: imported guest
memory cannot be write-watched on Linux, which the shadow buffers in vk_memory.c will address.
Comments cited codes from local notes (port-lessons P1, backlog), task labels (T6c1-T6c3, M4), bare upstream commit
hashes and issue numbers, a file that no longer exists (nvn_api.c, now nvn_api_*.c), a generated file (main_0103.c)
and line numbers into upstream files that refs/ has moved past, so they pointed at unrelated code. Those now name the
file, function or project and say what the cited code does. Attribution of copied code keeps its pinned commits.
Corrected facts: TOTK_REPLAY_ASYNC defaults to 0, and the shader cache, pipeline cache and pipeline keys live next to
totk.exe, not in build/v121.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>