updateGazeConfidence method
Implementation
double updateGazeConfidence({
required double x,
required double y,
required DateTime timestamp,
}) {
final sampleMs = timestamp.millisecondsSinceEpoch;
// 0) attention gate (tab active + visible)
final attention = (_docHasFocus && _docVisible);
if (!attention) {
_emaConf = 0.3;
_lastEpochMs = sampleMs;
return _emaConf;
}
// 1) Frame-to-frame dt
final dtMs = (_lastEpochMs == 0)
? 16
: (sampleMs - _lastEpochMs).clamp(1, 2000);
_lastEpochMs = sampleMs;
// 2) Freshness: full score if dt ≤ 150 ms, then smooth decay.
// Map: 0..150ms → 1.0, 150..600ms → 1→0
double fTime;
if (dtMs <= 150) {
fTime = 1.0;
} else if (dtMs >= 600) {
fTime = 0.0;
} else {
fTime = 1.0 - ((dtMs - 150) / (600 - 150));
}
// 3) In-bounds (soft margin). No heavy math.
final vw = (window.innerWidth
.toDouble()); //?? window.screen.width.toDouble()
final vh = (window.innerHeight
.toDouble()); //??window.screen.height.toDouble()
// Soft margin: fully good if ≥m px inside; smoothly drops to 0 by going m px outside.
const m = 32.0; // margin in px
double insideX = 0, insideY = 0;
if (x >= 0 && x <= vw) {
// how far from nearest edge, clamped at m
final dx = math.min(x, vw - x);
insideX = (dx >= m) ? 1.0 : (dx / m).clamp(0.0, 1.0);
} else {
final dxOut = (x < 0) ? -x : (x - vw);
insideX = (1.0 - (dxOut / m)).clamp(0.0, 1.0);
}
if (y >= 0 && y <= vh) {
final dy = math.min(y, vh - y);
insideY = (dy >= m) ? 1.0 : (dy / m).clamp(0.0, 1.0);
} else {
final dyOut = (y < 0) ? -y : (y - vh);
insideY = (1.0 - (dyOut / m)).clamp(0.0, 1.0);
}
// Combine X/Y with simple mean (or min if you want stricter).
final fBounds = 0.5 * (insideX + insideY);
// 4) Target = simple weighted combo. (Time is a bit more important.)
// You can set both to 0.5 for equal weight.
const wTime = 0.45;
const wBounds = 0.55;
final target = (wTime * fTime + wBounds * fBounds).clamp(
0.0,
1.0,
);
// 5) Two-rate EMA + small anti-cliff cap.
// If target >= EMA → rise quickly; else decay slowly.
const alphaUp = 0.35; // fast recovery
const alphaDn = 0.10; // slow decay
final alpha = (target >= _emaConf) ? alphaUp : alphaDn;
double next = _emaConf + alpha * (target - _emaConf);
// Prevent sudden drops per frame (e.g. due to a single bad sample).
const maxDropPerFrame = 0.05; // 5%
if (next < _emaConf - maxDropPerFrame) {
next = _emaConf - maxDropPerFrame;
}
// Clamp and store
_emaConf = next.clamp(0.0, 0.90);
return _emaConf;
}