The recorder subshell slept 8 s and then started even when the game had died at 3 s, and kept
recording the headset for hours. It now starts only if the game is alive, runs in its own session,
and is stopped (SIGINT, bounded wait, TERM, KILL, pkill) on game exit and on TERM/INT/HUP, with a
log line saying which path ran.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Two launches at KARTFRAME_END_DELAY_US=4000 with KARTFRAME_VRTIMING_FRAMES=1, so a
clean and a doubled launch at the same delay can be compared by phase.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
m= is the wake-up in CLOCK_MONOTONIC ms (the end is m + e), r= the eye releases
after the wake-up (-1 on a repeat), t= the predicted display time converted with
xrConvertTimeToTimespecTimeKHR; an 'XRF clock:' line says how. Lines the app's
timeline up with vrframes.log (frame/tools/phase.py). DEV.md: lane W section.
Mock (END_DELAY 4000, MOCK_XR_TIME_OFFSET_S=5): ok, offset 5000 ms removed,
r = e - 0.01 ms; ctest 32/32.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reads vrframes.log and the XRF lines of a session folder: SteamVR's own app
timings against its vsync reference (frame ready, poses ready, margin to the
compositor's update start), and the app's wake-up, end, release and display
time minus the nearest compositor anchor through the P-line clock offset,
with 0.25 ms histograms and a per-second drift table. openvr-stub.c: a fake
libopenvr_api.so to exercise vrtiming.py's frame mode without SteamVR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One F line per compositor frame (de-duplicated by index, the newest two of each
poll held back), a P line per poll tying SteamVR's system time to CLOCK_MONOTONIC
through GetTimeSinceLastVsync, G lines for missed frames. SIGTERM now ends the
loop cleanly so both logs are flushed. KARTFRAME_STEAMVR_DIR picks the library
(tests). Checked against a stub libopenvr_api (120 Hz, no gaps over 4 s).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Default unchanged. With idle, vkQueueWaitIdle under the queue guard right before
xrReleaseSwapchainImage of the eye pairs and before xrEndFrame, so SteamVR's
client submissions there never wait behind our next set's GPU work (the probe,
clean on the headset, had nothing queued at those points). Logs the drain's
cost every 10 s. DEV.md lines for the switch and the mock's new lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The exact check's second sample is taken once an empty submission made at the
release completed, so it could not see work queued after the release. The new
strict check compares the image's bytes at its release with its bytes at its
next acquire (no queue involved). A waiter thread now times each release's
empty submission: how long a submission made inside xrReleaseSwapchainImage
(as SteamVR's client makes) waits behind the app's queued GPU work, per
swapchain and frame parity. Both only with MOCK_XR_BUSY_MS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frame/perf/LANE_S.md: 240-set windows of consecutive released eye images in the scripted race (head
still, standing with a 25 deg sway, driving), H7's zigzag rule per eye 0 % at two GPU loads while an
injected period-2 jump is flagged in 99-100 % of moving sets; no stale image, no eye difference.
frame/perf/session-h13/README.md: recorded race images through GalaxyFrame's loop (xrprobe --replay,
to write), cases for the content phase, KartFrame's pair rotation and race quad.
mockdump/dumprace.sh reproduces the recording; DEV.md lists the new mock switches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stalling dump read every image back inside xrReleaseSwapchainImage and skipped about nine
refreshes in ten, so consecutive sets never existed. With the queue every set of a 240-set window
is dumped (end after wake-up p99 1.1 ms during the windows), and index.txt carries each image's
stamp (eye, display time, scene words), decoded from the stamp rows copied beside it.
zigzag.py: H7's rule on two tracks per eye (view: columns of the upper image; scene: the
track-side strips moving apart), the scene stamp's steps, eye phase and a lagdiff.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane S tooling to test the panel zigzag on the mock's released eye images: the head's sway can be
made larger and held still until a given time, a scripted press can be held (driving) and can stop,
and MOCK_XR_DUMP writes index.txt with each image's eye and stamped display time. zigzag.py applies
H7's rule, a lagdiff and the eye phase per dump window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces lane P's one-launch plan. Mock: 0 errors, 0 of 42792 releases changed
after the GPU, stamps and end times exact, every race frame projection + quad,
validation layer clean, race timing unchanged A/B.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A frame whose only layer would be the projection gets a 16x16 transparent quad
(alpha blended, 1 cm, 2 m ahead) after it; its images are cleared once by the
bridge's own command buffer, waited for and released. Before each set's first
writes the bridge submits a barrier ALL_COMMANDS/MEMORY_WRITE to
ALL_COMMANDS/MEMORY_READ|WRITE on its images (layout kept), on Dawn's queue under
its lock, ahead of Aurora's set. XRF lines carry n=<layers>; xrmock counts the
layer lists by shape and flags quads on never-released swapchains.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The design already snapshots each set's packet once (ProvideStereoFrame copies it under
published_mutex_; every eye path uses that copy). This makes the remaining ways one eye
could differ measurable on the headset too:
- the stamp's fourth pose word: the frustum tangents each eye's fill used; xrmock compares
them with the layer's submitted fov (and the other eye's);
- a GPU uniform probe (KARTFRAME_FRAME_LOG=1 or KARTFRAME_UNIFORM_PROBE=1): after both eyes,
each eye's first perspective range is read back from g_uniformBuffer (now CopySrc) and
compared with the bytes its fill wrote, counted per 10 s;
- the XRP fields ride on the XRS line (one log call per set).
The test stamp is 64x15 pixels, one pixel a bit (was 4x4 cells), and xrmock does its
read-back bookkeeping before xrWaitFrame's sleep instead of after the wake-up it measures
from: with the larger stamp that work alone moved 'ends later than 1 ms' by 2 %.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both fill sites (the seal's eye pair and prepare_late_stereo_replay) record per eye the
packet (display time, token) and the eye transform written, and count draws filled with
another; the stamp block gains three words (rows 7-12, 256x52), an XRP line per set under
KARTFRAME_FRAME_LOG=1 and a 'VR eye poses' summary every 10 s (always). xrmock decodes the
words and compares each eye with the pose its layer submitted, and left against right.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>