From 7d985d13bcb59493ea77d9755bc9edeaa14fa7a0 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Mon, 12 Oct 2026 16:58:30 -0700 Subject: [PATCH] Document frame ring schema and limits of hitch attribution --- docs/HOST_WALL_TIME_RINGS.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/HOST_WALL_TIME_RINGS.md b/docs/HOST_WALL_TIME_RINGS.md index efb0305a..f1be8b8b 100644 --- a/docs/HOST_WALL_TIME_RINGS.md +++ b/docs/HOST_WALL_TIME_RINGS.md @@ -1,25 +1,33 @@ # Always-on host wall-time rings (hitch attribution) -A gameplay hitch is wall time the emulation thread did not spend running the guest. These rings -record where it went, continuously from process start in every build, so a probe never arms +A gameplay hitch is an unusually long frame, whether from guest execution, host work, or waiting. +These rings record where the time went, continuously from process start in every build, so a probe never arms anything: it queries a window that already happened. | Ring | TCP command | Holds | |------|-------------|-------| -| frame stamps | `frame_wall_ring {"from":SEQ,"n":N}` | one `[guest frame, wall us, interpreter insns (low 32 bits)]` per guest frame, 131072 frames (~36 min at 60 Hz) | +| frame stamps | `frame_wall_ring {"from":SEQ,"n":N}` | flat `f` array with `stride: 6`: `[guest frame, wall us, interpreter insns, static checks, process page faults, process read KiB]`; counters use their low 32 bits; 131072 frames (~36 min at 60 Hz) | | spans | `host_span_ring {"from":SEQ,"n":N}` | `[seq, kind, t0_us, dur_us, frame, emu, arg]` for every named blocking-capable operation of 50 us or more, 65536 spans | | host CPU sampler (debug-tools builds) | `host_profile {"frame_lo":A,"frame_hi":B}` | 1 kHz instruction-pointer samples of the emulation thread, 1 Mi samples (~17 min), with per-module totals for samples outside the exe | Wall time is microseconds since the first ring call. `emu` is 1 for the emulation thread, 0 for a worker (compiler child, publication preparer). +Readers and writers use nonblocking slot ownership. A contended sample may be dropped or skipped; +readers only return fully published records from the requested ring generation. A missing frame +stamp leaves an interval spanning multiple frames: do not report that interval as one long frame. +Legacy frame captures had three or four columns and no `stride`; consumers must identify their +layout before interpreting them. Process counters include worker threads, and page faults include +soft faults, so neither counter alone proves a blocking disk read. + Span kinds (`runtime/include/host_span_ring.h`): `autocapture_tick`, `provider_poll`, `publish_commit`, `loader_rescan`, `dll_register`, `lazy_load` (a synchronous first map of a shard at a dispatch miss), `capture_on_dma`, `capture_before_dma`, `ac_request` (spawning the compiler), -`ac_done_join`, `ac_prepare_image` (worker), `ac_child` (compiler lifetime, worker). +`ac_done_join`, `ac_prepare_image` (worker), `ac_child` (compiler lifetime, worker), +`overlay_dispatch`, `mod_work`, `frame_present`, and `disc_read` (cooked or raw sector read). -Reading a hitch: per frame, `wall delta - sum(emulation-thread spans overlapping the frame)` is -the time no span explains; then ask the host sampler for that frame window. A title's report tool +Reading a hitch: per frame, subtract the union of overlapping emulation-thread spans from the wall +delta to find the time no span explains; nested spans must not be counted twice. Then ask the host sampler for that frame window. A title's report tool joins them (`LegendOfDragoonRecomp/tools/lod_hitch_report.py`). Findings the rings produced (LoD, 2026-10-12): a toolchain staged from another emitter than the