A user on a fresh install got "Calibration failed: only 0 of 21 dots" with
no reason. The installer never installed our tracker, so gaze used
SteamVR's, and the only way SteamVR's tracker rejects a dot is losing an
eye for most of the look. The panel just showed a red ring.
- install.sh: step 9/10 installs our tracker (gaze/tracker/install.sh)
after gaze mode, yes by default; it needs sudo, so --yes runs it only
when sudo won't prompt. If it fails, gaze keeps SteamVR's tracker.
Configs that still say GAZE_TRACKER=steam (the old template) are asked
whether to switch.
- GAZE_TRACKER=auto, the new default: ours when it's installed (the
frame grabber, its unit, and ft-eyes' Python), else SteamVR's.
ft-gazed rechecks every second, so installing it switches over. Input
Settings lists Own tracker first as recommended, and says how to
install it when it's missing (checking the host's /etc through
/run/host from the dev container).
- The calibration panel has a note line, orange over the instructions:
why a dot wasn't taken (an eye lost, a blink, the eyes disagreeing for
SteamVR's tracker, from steady_samples' new drop counts; ft-eyes' reply
for ours), what a click is still waiting for after 1.5 s, and a failed
calibration's most common reason, which the Gaze page shows too.
steady_samples keeps the same samples as before (checked on 2037
windows of recordings); a lost eye is named before a blink, since its
openness reads 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Steam's eyetracking.txt writes "HMD on" every minute or so with nobody in the
headset, and SteamVR said it was worn for 12 hours straight, so the quick check
opened about 40 times an hour at an empty headset. It now opens when SteamVR's
tracker sees eyes for 3 s after none for 3 s, and closes when they're gone 2 s.
The log reader also drops repeated "HMD on" lines and sub-second offs, so
lessons stop counting as from an older wear every minute.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frame-eyes, a separate project until now, becomes gaze/tracker:
- ft-eyegrab (was fe-bufprobe) copies the eye-camera frames, read-only, out of
SteamVR's eyetracking process. It runs as the system service
frametop-eyegrab.service, which gaze/tracker/install.sh installs to
/etc/frametop with sudo. It keeps only CAP_SYS_PTRACE, CAP_DAC_READ_SEARCH, and
CAP_CHOWN, and copies frames only while /dev/shm/frametop-eyes-want is fresh,
holding none of the tracker's buffers otherwise.
- ft-eyes (was fe-trackd) runs under ft-gazed in the dev container, with
build/venv's pinned numpy and OpenCV: while Eye tracker is Own tracker, or on
the probe's lease ("eyes SECONDS"). No sudo password or fe-live script at
run time any more.
- lab/ holds the research tools (ft-eyes-score, -e2e, -record, -replay,
-session) and findings.md. Recordings live outside the repo, in
~/.local/share/frametop/eyes/captures; .gitignore catches stray frame dumps.
Its socket is now @ft_eyes, its output /dev/shm/frametop-eyes-gaze, and its state
~/.local/state/frametop/gaze/eyes. On practice1 -> practice2 the whole live path
(ft-eyes-e2e) gives 1.30 deg median and 3.18 for the worst tenth, as before the
move (1.30, 3.21).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frametop Input Settings' Gaze page gets two settings, saved in frametop.conf and
read again by ft-gazed when the file changes: Eye tracker (GAZE_TRACKER: SteamVR's
or our own, frame-eyes' fe-trackd) and Eye bias (GAZE_EYE: auto, left, right).
ft-gazed now combines the eyes, each calibrated on its own: SteamVR's set 2 eyes
with the probe's Left eye and Right eye calibrations, or our tracker's eyes as
they come. Without per-eye calibrations, or with --source, it keeps the older
one-source path. A pointer nudge finds its look from the raw gaze the helper
echoes back; with our tracker it goes to fe-trackd as a click.
The bias leans instead of choosing (gazecal.EyeWeights): on 306 live clicks the
eyes' errors partly cancelled, both together 0.65 deg off against 0.96 and 1.11
for either alone. Left or Right counts that eye twice; auto weights each eye by
its RMS miss at its last 20 nudges, since the calibration's fit picked the wrong
eye on SteamVR's test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SteamVR's combined gaze keeps going on one eye, but it holds the lost
eye's yaw, so the gaze moves half as far sideways as the eyes do.
ft-gaze now reads each eye's tracking uncertainty and raw measurement
from eye-server.mmap. When the tracker loses an eye, ft-gazed takes the
gaze from the other one, plus the offset that eye usually shows
against both, learned while both are seen. On a recording, one eye
alone came out a median 0.8 degrees from both eyes' gaze.
Glances down at the keyboard, past every screen, aren't sent. The
pointer stays put, and eyes lost there don't count as lost.
The gaze probe gets a Headset fit mode. It shows per-eye tracking,
openness and confidence, maps where each eye gets lost, gives hints,
and has a guided check. The settings app opens it from the Gaze page
and shows how often each eye is lost. The probe can also test each eye
alone, and its side panel now collapses to a title bar so the dot
isn't hidden behind it.
Snapping to UI elements is deferred; the mouse drag is the correction.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The correction models, filters, blink filter, and SteamVR log reader move
out of the probe so the gaze service can use them too. Two additions on the
way: the log reader follows the headset going on and off (SteamVR starts its
eye model over each time it goes on), and LiveCorrection counts lessons from
before the last time it went on less (OLD_WEAR), so the first few after
relearn the offset while the shape is kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>