Every guest thread is a host thread, and the game makes and ends them all through play. A thread that slept through plat_wait_ns kept a waitable timer, one that read or wrote a file an event for each, and one that decoded zstd through the host a decoder context of about 100 KiB; nothing closed or freed them when the thread ended, and a detached thread's own record stayed allocated. They are now given back when the thread's function returns: the handles in win32.c thread_main, the decoder context through plat_thread_end_hook, and the record by whichever of the thread and its detach comes last.
tests/thread_end_check.py runs 416 such threads and compares the process's handle count before and after. tests/sync_wake_check.c gets the two stand-ins kernel.c has needed since the service wait log, so that check builds and passes again. The Linux file only gains the hook call and was not compiled here.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Between the lookup and the drop another thread could take the spent slot for its own stream; the drop then cleared that thread's slot, and a third thread could take it as free and reset the decoder context still in use.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Four "mem: sizes" lines give the entries and bytes of what no other line counted: the guest heap, physical maps, kernel region, lazily written ranges and the fixed kernel, setjmp, save-file, RomFS and zstd tables; pool records, mirrors, deferred destructions, upload and staging buffers, the device address table and the texture, sampler and slot caches; image views, descriptor spares, the completion queue, uniform snapshot arenas, recording copies and pins; and pipelines, layouts, stage libraries, job queues, the shader memory cache and the translator processes. Each subsystem reads its own counters under its lock; settings now count the replaced values they keep. Segment and scene lines say "-" for the world clock before it starts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A slow frame's line and the 60-frame window's line now say how long each kind took on any thread, how often, how many bytes and the longest single one, so a hitch outside the render thread's own waits has a named cause. While NX2NAVI_TRACE=perf is off each site pays one branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
That build kept the game's own decoder until its sites had been through a check run. The check has now run over a village and a field: 3,072 streams and 31.7 GiB of output compared byte for byte, none different. The game's decoder took about a fifth of the running time of the game's threads in a village and about a second of each area load. zstd_native = 0 keeps the game's decoder.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The loading threads feed each chunk they read to the game's stream decoder, which is where most of the
decompression time goes. That call now reaches a host stream that writes straight into the game's output
buffer and keeps the game's state fields and results. A stream is taken from its first chunk or left to
the game; zstd_check = 1 runs both and compares the outputs. --probe-zstd checks the host streams on
frames it makes itself.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A profile of a play session showed the game's own zstd decoder, lifted from ARM64, taking 16 to 19% of all running CPU time on the loading threads: the patch set that sends decodes to the host's libzstd had sites for 1.4.3 only. recomp/patches-v111.txt now has the three calls of ZSTD_decompressMultiFrame (main+0xeb380) of this build, checked against its main by the generator; a lift with them changes three chunks. ov_zstd.c takes the guest function's address by game version.
The sites have not been through a check run on this build, and 1.4.3 has a caller that relies on the game decoder's own result, so on 1.1.1 the host function passes every call on to the game's decoder unless zstd_native = 1 is set or zstd_check = 1 runs both and logs what differs. The loading threads' stream feed (main+0xf5c80), which decodes most of the data, is not replaced yet.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>