Spec: Implicit Gaze Calibration — Click Association, Held-Out Promotion and Topology Safety¶
| Field | Value |
|---|---|
| ID | spec-eye-implicit-calibration |
| Status | Proposed |
| Date | 2026-09-22 |
| Modules | src/yazses/gaze/implicit.py, store/runtime click observer |
| Related | ADR-v2-010, ADR-v2-149, ADR-014 |
| Issues | #397–#399 + topology/HiDPI task |
Goal¶
Finish the runtime half of implicit gaze calibration while guaranteeing that passive learning cannot silently replace a better explicit calibration or survive an incompatible desktop topology.
Existing core¶
gaze/implicit.py already provides the incremental estimator and refined_if_better held-out comparison.
This spec covers missing runtime acquisition, persistence, topology context and promotion.
Non-goals¶
- no raw click text/window content;
- no screenshots;
- no frame storage;
- no cloud learning;
- no automatic caret targeting;
- no unconditional online replacement of the baseline.
Sample association¶
A candidate sample is:
(timestamped gaze features, confidence)
+
(timestamped user pointer click x/y)
->
calibration observation
The click observer is platform-neutral at the estimator boundary.
Association rules: - choose a gaze observation within a documented look-back/association window; - reject when no fresh observation exists; - reject below confidence threshold; - reject implausible residual/outlier per existing core policy; - record only derived numeric observation needed by estimator.
The exact latency offset should be configurable/research-measurable rather than hard-coded forever.
Sample buffers¶
Separate: - training/update samples/state; - held-out validation samples.
A sample used to fit a candidate is not simultaneously evidence that the candidate improved.
Use bounded state/history consistent with existing incremental design and privacy goals.
Baseline and candidate¶
- baseline = explicit calibration currently accepted for this calibration context;
- candidate = implicit refinement.
Candidate may become active only when refined_if_better/equivalent held-out gate wins by the defined margin/sample requirement.
Persistence¶
Calibration artifact includes: - map coefficients; - source: explicit / implicitly refined; - version; - timestamp; - ADR-v2-149 context fingerprint.
Write atomically.
A failed candidate leaves the prior artifact exactly valid.
Keep enough metadata to report/rollback the previous accepted map if rollback is a supported command.
Context/topology¶
Before applying stored calibration: - compare current display/camera context to saved fingerprint; - incompatible context -> mark stale, do not route with it; - topology change during session -> suspend gaze routing until refreshed/revalidated.
Tests cover: - single monitor 1.0 scale; - 2x/HiDPI; - negative-origin second monitor; - changed arrangement/resolution; - changed camera identifier.
User control¶
Implicit refinement is opt-in.
Status reports: - baseline source; - candidate sample count; - last evaluation result; - stale/valid reason; - no raw personal data.
A user can disable refinement without deleting explicit calibration.
Acceptance criteria¶
- Low confidence click pair is rejected.
- Stale gaze sample is rejected.
- Training sample does not leak into holdout verdict.
- Worse candidate cannot replace baseline.
- Better candidate promotes atomically.
- Persistence failure leaves baseline valid.
- Incompatible topology refuses stored map.
- Same compatible topology reuses map.
- Report/export contains derived metrics only.
Tests¶
- existing
tests/test_gaze_implicit.py; - fake click observer;
- fake clock/ring buffer;
- atomic store failure;
- topology fixtures;
- rollback;
- privacy schema golden test.
Field evaluation¶
Report: - explicit baseline error; - candidate held-out error; - sample count/time; - drift over session; - dock/undock/camera change outcome.
No promotion to recommended implicit calibration without longitudinal field evidence.