At start the game rewrites its save files with the same bytes and commits
twice; each of those commits made a new committed save and a backup, so one
start pushed every older backup out of save-backup. A Commit whose copy is
the newest save file for file (all hard links, same names) now only retries
a waiting swap.
Renaming the committed save away is retried for 200 ms instead of 2 s: a
failure no longer loses the commit, the next Commit or start retries, and the
game's next change to its save data waits for the commit thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows refuses to rename a directory while any file under it is open, so a
virus scanner, Explorer's preview or a sync tool holding a file of
save/<uid> made every Commit of a session fail after its 2 s of retries.
The game had its Commit return 0 and showed the new slots from the working
copy, and the next start deleted that copy: hours of play were gone (#260,
#263, log line "cannot move the committed save (error 5)").
A Commit now renames its finished staging copy to save-tx/ready-<n> before it
touches the committed save. That rename is the commit point. When the swap
into save/<uid> fails, ready-<n> stays the newest save: later working copies
and the next start are made from it, and the next Commit or start retries the
swap. The failure log names the programs holding files in the committed save
(Restart Manager) or says the folder refuses the rename (no DELETE access).
--probe-fs holds a committed file open across two Commits and restarts.
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.
The game's Commit returns at once. Every later change to the working copy, Unmount and the next Commit wait for the thread, so no write lands in the snapshot, and the window close, guest ExitProcess and --frames exits wait through fs_save_flush. A crash during the commit loses that commit only; the transaction and save_recover are unchanged. probe-fs waits for each commit and checks a write and an Unmount right after Commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mirror and the texture checks queried about 930 MiB of write-watched memory per frame (0.96 ms on the RX 9070
machine, about 3 ms on an 8th-gen i7), at about 1 us per MiB, and 770 MiB of it lay in 2 MiB regions nothing had
written for two seconds. In mirror mode a region left unwritten for TOTK_FREEZE_IDLE submissions (300; 0 turns it off)
and lying wholly in imported pools is now made read-only (memory.c mem_freeze), and the snapshot and change queries
skip it. The first write faults and a vectored handler thaws the region before the write retries; file reads into
guest memory, which the kernel would refuse on a read-only page, thaw their buffer first and log a refusal, as do pool
init and finalize, and commits, decommits and protection changes thaw under the same lock. A region stays thawed for
5 s after a file read. Field: 66 queries over 380 MiB, 0.56 ms per frame; 0.38 ms of it are the queries that find
writes. The perf line `mem: write-watch` gives queries, bytes, time, frozen MiB, freezes and thaws. The gfx-abi probe
covers freeze, thaw by a write and the hold after mem_thaw. TOTK_MEMORY, TOTK_MIRROR_GAP and TOTK_FREEZE_IDLE are known
settings now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>