A zero or negative XRT_COMPOSITOR_SCALE_PERCENTAGE produced a huge
uint32 render target after the float-to-unsigned conversion. Clamp to
the default instead.
NotifyButton runs on each controller's RunFrame thread and shared the
rising-edge state unlocked, so simultaneous presses could double-toggle
or tear the menu double-press timestamp. Hold the camera mutex.
Cleanup freed only the HMD driver and left the controllers leaking with
nulled xdev pointers, and a Cleanup without prior Deactivate left pose
threads running into xrt teardown. Deactivate (now idempotent) then
delete everything before destroying the system.
A failed status read decoded as stamp 0 and flipped a localized room
(universe 2) to the session universe (3) until the next poll, yanking
the chaperone mid-session. Ignore unreadable polls instead.
m_poseUpdating is written on the SteamVR thread and read on the pose
threads, g_bExiting the same across the watchdog thread. Plain bools
are a data race; use std::atomic.
Deactivate joined the pose thread but left the flag false and the
pointer dangling, so a re-Activate spawned a thread that exited
immediately (frozen poses) and leaked the old thread; a second
Deactivate joined twice and aborted. Reset the flag on Activate,
join/delete/null on Deactivate, and clear stale haptic outputs.
m_ulPropertyContainer and m_skeletal_input_control were read before
Activate assigns them (haptic routing compares the container, RunFrame
tests the skeleton handle). Give them deterministic invalid values.
project_led_points() and project_bounding_points() write into
caller stack arrays sized MAX_OBJECT_LEDS (64) but looped over the
model's uint8_t counts unchecked. Reject oversized models instead
of overflowing the stack.
joint_p3p_fallback() calloc'd ~300KB (24 full results) on every
invocation. Only count/overflow are read before written, so malloc
plus explicit init is enough.
joint_p3p_candidate() malloc/free'd a ~12KB result per geometry
verified root. Verify into a stack object and copy only survivors
into the bounded set.
t_constellation_tracker_add_device() guarded the devices[] write with
assert() only, so a release build adding a fifth device would
overflow the array. Return NULL when full instead.
check_led_against_model_subset() used printf on the per-frame search
path, bypassing log levels and spamming stdout whenever lambdatwist
returns an inconsistent root. Log at debug level instead.
sample_free() pushes observations back to the blobwatch queue from
both the fast and the long thread, while process() pops under
bw_lock. Take bw_lock around the release so concurrent free/process
pairs cannot corrupt the queue head/tail and hand one observation
buffer to two samples.
Unchecked enumerate_modes left modes/count undefined on failure and
an unchecked stream_start returned a camera that never delivers
frames. Fail loudly and free the mode list on the error path.
A missing CameraCalibration array returned false (== success) and
leaked the JSON root, and a short list left zeroed cameras that the
tracker indexes unconditionally. Require all five.
The completion path below asserts ret > 3, but a 2-3 byte read fell
through and was consumed with an uninitialised busy flag. Bail on
short reads to match.