updateGazeConfidence method

double updateGazeConfidence({
  1. required double x,
  2. required double y,
  3. required DateTime timestamp,
})

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;
}