@camstack/addon-post-analysis 1.2.291 → 1.2.293

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (15) hide show
  1. package/dist/{clip-model-registry-CTY5co6P.js → clip-model-registry-BAuTHS_s.js} +2323 -2033
  2. package/dist/{clip-model-registry-Cudz--Ql.mjs → clip-model-registry-D2zqXamW.mjs} +2324 -2034
  3. package/dist/embedding-encoder/index.js +1 -1
  4. package/dist/embedding-encoder/index.mjs +1 -1
  5. package/dist/pipeline-analytics/_stub.js +2 -2
  6. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CBHxwCSj.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DW6-zerK.mjs} +2 -2
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Boh18P08.mjs +26 -0
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Bs5QFgmh.mjs +26 -0
  9. package/dist/pipeline-analytics/{hostInit-DSKT2dE4.mjs → hostInit-CAJh3aX8.mjs} +2 -2
  10. package/dist/pipeline-analytics/index.js +445 -45
  11. package/dist/pipeline-analytics/index.mjs +445 -45
  12. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  13. package/package.json +1 -1
  14. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-2OwTV_D2.mjs +0 -26
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DAAKQ3sC.mjs +0 -26
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_clip_model_registry = require("../clip-model-registry-CTY5co6P.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-BAuTHS_s.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_clip_model_registry.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -47054,6 +47054,42 @@ function pairRiders(detections) {
47054
47054
  return pairs;
47055
47055
  }
47056
47056
  //#endregion
47057
+ //#region src/pipeline-analytics/pipeline/tracker/low-score-continuation.ts
47058
+ var LOW_SCORE_CONTINUATION_FLOOR_DEFAULT = .35;
47059
+ var LOW_SCORE_CONTINUATION_CLASSES = new Set(["person", "animal"]);
47060
+ /** `[x1, y1, x2, y2]` (the discard trail's tuple) → the tracker's rectangle. */
47061
+ function toRect(bbox) {
47062
+ const [x1, y1, x2, y2] = bbox;
47063
+ return {
47064
+ x: x1,
47065
+ y: y1,
47066
+ w: x2 - x1,
47067
+ h: y2 - y1
47068
+ };
47069
+ }
47070
+ /**
47071
+ * The continuation candidates of one frame. `floor === null` means the tier is
47072
+ * off for this camera and nothing is selected.
47073
+ */
47074
+ function selectLowScoreContinuations(discarded, floor) {
47075
+ if (floor === null || discarded === void 0) return [];
47076
+ const out = [];
47077
+ for (const d of discarded) {
47078
+ if (d.reason !== "below-threshold") continue;
47079
+ if (d.score < floor) continue;
47080
+ if (!LOW_SCORE_CONTINUATION_CLASSES.has(d.macroClass)) continue;
47081
+ const bbox = toRect(d.bbox);
47082
+ if (bbox.w <= 0 || bbox.h <= 0) continue;
47083
+ out.push({
47084
+ class: d.macroClass,
47085
+ originalClass: d.macroClass,
47086
+ score: d.score,
47087
+ bbox
47088
+ });
47089
+ }
47090
+ return out;
47091
+ }
47092
+ //#endregion
47057
47093
  //#region src/pipeline-analytics/pipeline/track-id.ts
47058
47094
  /**
47059
47095
  * A track's identity: short, random, and mintable inside a frame.
@@ -47202,6 +47238,30 @@ function reacquireReachPx(bbox, elapsedMs) {
47202
47238
  const elapsed = Math.max(0, elapsedMs);
47203
47239
  return clampRange(diag * REACH_DIAGONALS_PER_SEC * (elapsed / 1e3), diag * REACH_MIN_DIAGONALS, diag * REACH_MAX_DIAGONALS);
47204
47240
  }
47241
+ /**
47242
+ * Resurrection reach, in box DIAGONALS per second of gap (D665).
47243
+ *
47244
+ * A corpse is reached only after `maxMissedMs` (4 s) without a box, so the
47245
+ * rescue rate above — which is at its 1.5-diagonal ceiling by 1 s — says
47246
+ * nothing useful here. A subject the detector has not boxed for four seconds
47247
+ * is, on this fleet's evidence, one that stood still or stood half-hidden,
47248
+ * not one striding across the frame (a striding person is detected): 0.25
47249
+ * diagonals per second is a slow walk for a mid-field person and a pause for
47250
+ * a near one. At the 4 s floor of the graveyard band it gives 1.0 diagonal;
47251
+ * camera 590's refused returns sat at 1.05–1.82× the old flat 0.75 reach,
47252
+ * i.e. 0.79–1.37 diagonals, which this covers from about 5.5 s of gap.
47253
+ */
47254
+ var RESURRECTION_REACH_DIAGONALS_PER_SEC = .25;
47255
+ /** Never tighter than the flat `rescueCentroidFactor` default it replaces. */
47256
+ var RESURRECTION_REACH_MIN_DIAGONALS = .75;
47257
+ /**
47258
+ * How far from a corpse's last box a detection may sit and still resurrect it
47259
+ * on the proximity clause, for a corpse last seen `gapMs` ago.
47260
+ */
47261
+ function resurrectionReachPx(bbox, gapMs) {
47262
+ const diag = Math.hypot(bbox.w, bbox.h);
47263
+ return clampRange(diag * RESURRECTION_REACH_DIAGONALS_PER_SEC * (Math.max(0, gapMs) / 1e3), diag * RESURRECTION_REACH_MIN_DIAGONALS, diag * 2);
47264
+ }
47205
47265
  //#endregion
47206
47266
  //#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
47207
47267
  /**
@@ -47350,7 +47410,10 @@ var DEFAULT_TRACKER_CONFIG = {
47350
47410
  byteTrackLowIouThreshold: .2,
47351
47411
  minSpawnAreaEnabled: true,
47352
47412
  classSpawnAreaFracs: { person: 5e-4 },
47353
- classGroupAssoc: true
47413
+ classGroupAssoc: true,
47414
+ lowScoreContinuationEnabled: true,
47415
+ lowScoreContinuationMaxMs: 8e3,
47416
+ resurrectionTimeScaled: true
47354
47417
  };
47355
47418
  /** Macro-classes the duplicate resolver may collapse across (people in
47356
47419
  * non-upright poses misdetected as animals). Any other cross-class pair is
@@ -47358,6 +47421,15 @@ var DEFAULT_TRACKER_CONFIG = {
47358
47421
  var PERSON_CLASS = "person";
47359
47422
  var ANIMAL_CLASS = "animal";
47360
47423
  var MAX_PATH_LENGTH = 300;
47424
+ function emptyLowScoreStats() {
47425
+ return {
47426
+ offered: 0,
47427
+ continued: 0,
47428
+ resumed: 0,
47429
+ sustainCapDrops: 0,
47430
+ shadowed: 0
47431
+ };
47432
+ }
47361
47433
  /** No stationary-held detections on this frame — the default for every caller
47362
47434
  * that has no gate (replay, tests, the non-pipeline planes). */
47363
47435
  var EMPTY_INDEX_SET = /* @__PURE__ */ new Set();
@@ -47429,6 +47501,8 @@ var SortTracker = class SortTracker {
47429
47501
  trackLossSampler = new TrackLossSampler();
47430
47502
  /** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
47431
47503
  spawnBesideObserver = null;
47504
+ /** Running counts behind {@link drainLowScoreStats} (D665). */
47505
+ lowScoreStats = emptyLowScoreStats();
47432
47506
  constructor(config = {}) {
47433
47507
  this.config = {
47434
47508
  ...DEFAULT_TRACKER_CONFIG,
@@ -47489,7 +47563,7 @@ var SortTracker = class SortTracker {
47489
47563
  if (SortTracker.spawnBesideFamily(track.class) !== family) continue;
47490
47564
  const tc = bboxCentroid(track.bbox);
47491
47565
  const dist = Math.hypot(tc.x - dc.x, tc.y - dc.y);
47492
- const reach = this.reacquireReach(track, timestamp, true);
47566
+ const reach = this.reacquireReach(track, timestamp, "rescue");
47493
47567
  if (dist > reach * 2) continue;
47494
47568
  if (dist < bestDist) {
47495
47569
  best = track;
@@ -47536,7 +47610,7 @@ var SortTracker = class SortTracker {
47536
47610
  explainDecline(track, det, detections, frameArea, timestamp, centroidDist) {
47537
47611
  if (track.class !== det.class) return det.nativeRecovery !== void 0 ? "native-measured-class" : "cross-class";
47538
47612
  if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "already-claimed";
47539
- if (centroidDist <= this.reacquireReach(track, timestamp, true)) {
47613
+ if (centroidDist <= this.reacquireReach(track, timestamp, "rescue")) {
47540
47614
  if (!this.isPlausibleBirth(det, frameArea)) return "already-claimed";
47541
47615
  if (!this.config.reacquireTimeScaled) return "plausible-birth-veto";
47542
47616
  return this.isUnambiguousReach(track, det, detections) ? "already-claimed" : "crowd-guard";
@@ -47991,34 +48065,38 @@ var SortTracker = class SortTracker {
47991
48065
  * this gate crossed the group, and it is where a `person` at 0.86 took over
47992
48066
  * an animal's id (camera 615, 17:11:01).
47993
48067
  */
47994
- looseMatch(track, det, timestamp, timeScaled = true) {
48068
+ looseMatch(track, det, timestamp, pass = "rescue") {
47995
48069
  if (track.class !== det.class) return null;
47996
48070
  if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "iou";
47997
48071
  const tc = bboxCentroid(track.bbox);
47998
48072
  const dc = bboxCentroid(det.bbox);
47999
- if (Math.hypot(tc.x - dc.x, tc.y - dc.y) <= this.reacquireReach(track, timestamp, timeScaled)) return "centroid";
48073
+ if (Math.hypot(tc.x - dc.x, tc.y - dc.y) <= this.reacquireReach(track, timestamp, pass)) return "centroid";
48000
48074
  if (growthAdjacentMatch(track.bbox, det.bbox, this.growthContext(track, timestamp))) return "growth";
48001
48075
  return null;
48002
48076
  }
48003
48077
  /**
48004
48078
  * The proximity budget the `centroid` clause is measured against.
48005
48079
  *
48006
- * On the RESCUE pass (`timeScaled`) it is {@link reacquireReachPx} — a
48007
- * multiple of the track's diagonal that grows with the elapsed gap and stops
48008
- * at a ceiling, because a live coasting track's whereabouts genuinely become
48009
- * less certain the longer it has gone unseen (D399).
48010
- *
48011
- * On the RESURRECTION pass it stays the flat `rescueCentroidFactor × diagonal`
48012
- * it always was. That pass re-attaches a CORPSE, and it exists for one thing:
48013
- * "a flickering stationary object keeps its identity". Elapsed time is not the
48014
- * variable there — a stationary subject does not travel further because it
48015
- * blinked for longer — and the corpse is strictly weaker evidence than a live
48016
- * track, which is the same reasoning `resurrectionCrowdedIouThreshold` already
48017
- * encodes.
48018
- */
48019
- reacquireReach(track, timestamp, timeScaled) {
48080
+ * On the RESCUE pass it is {@link reacquireReachPx} — a multiple of the
48081
+ * track's diagonal that grows with the elapsed gap and stops at a ceiling,
48082
+ * because a live coasting track's whereabouts genuinely become less certain
48083
+ * the longer it has gone unseen (D399).
48084
+ *
48085
+ * On the RESURRECTION pass it is {@link resurrectionReachPx} (D665): the same
48086
+ * idea on the graveyard's own time scale — 0.25 diagonals per second of gap,
48087
+ * never below the flat 0.75 it replaces, never above 2. Until D665 this pass
48088
+ * kept the flat `rescueCentroidFactor × diagonal` on the argument that it
48089
+ * exists for "a flickering stationary object", and a stationary subject does
48090
+ * not travel further because it blinked for longer. True of the object it
48091
+ * was written for; not of the pass as it runs: on camera 590 (2026-09-27)
48092
+ * 38% of all re-splits were PEOPLE returning after 4–12 s, further than the
48093
+ * flat gate, with frames flowing the whole time. `resurrectionTimeScaled`
48094
+ * off restores the flat gate.
48095
+ */
48096
+ reacquireReach(track, timestamp, pass) {
48020
48097
  const diag = Math.hypot(track.bbox.w, track.bbox.h);
48021
- if (!timeScaled || !this.config.reacquireTimeScaled) return this.config.rescueCentroidFactor * diag;
48098
+ if (pass === "resurrection") return this.config.resurrectionTimeScaled ? resurrectionReachPx(track.bbox, timestamp - track.lastSeen) : this.config.rescueCentroidFactor * diag;
48099
+ if (!this.config.reacquireTimeScaled) return this.config.rescueCentroidFactor * diag;
48022
48100
  return reacquireReachPx(track.bbox, timestamp - track.lastSeen);
48023
48101
  }
48024
48102
  /**
@@ -48068,6 +48146,23 @@ var SortTracker = class SortTracker {
48068
48146
  nearestPx: nearest
48069
48147
  };
48070
48148
  }
48149
+ /**
48150
+ * The resurrection counterpart of {@link isUnambiguousReach}: no other
48151
+ * same-class subject is live or resurrectable beside `corpse`
48152
+ * ({@link sameClassPeerCount} over the SURVIVING set, which is what the crowd
48153
+ * guard counts), and `det` is the only box of that class on the frame —
48154
+ * the sub-floor `lowScore` boxes included.
48155
+ */
48156
+ isSoleSubjectResurrection(corpse, det, detections, surviving, lowScore) {
48157
+ if (this.sameClassPeerCount(corpse, surviving) > 0) return false;
48158
+ for (const other of detections) if (other !== det && other.class === corpse.class) return false;
48159
+ for (const other of lowScore) {
48160
+ if (other.class !== corpse.class) continue;
48161
+ if (iou$2(other.bbox, det.bbox) >= this.config.byteTrackLowIouThreshold) continue;
48162
+ return false;
48163
+ }
48164
+ return true;
48165
+ }
48071
48166
  isUnambiguousReach(track, det, detections) {
48072
48167
  if (this.sameClassPeerCount(track, this.tracks) > 0) return false;
48073
48168
  for (const other of detections) {
@@ -48222,6 +48317,64 @@ var SortTracker = class SortTracker {
48222
48317
  used.add(pair.detIdx);
48223
48318
  }
48224
48319
  }
48320
+ /** A CONFIDENT box fed `track` (match, resurrection): the emitted box
48321
+ * follows it again, and a hold that ends this way is an id the tier kept. */
48322
+ markConfident(track, bbox, timestamp) {
48323
+ track.confidentBbox = bbox;
48324
+ track.confidentSeenAt = timestamp;
48325
+ if (track.lowScoreHeld) {
48326
+ track.lowScoreHeld = false;
48327
+ this.lowScoreStats.resumed++;
48328
+ }
48329
+ }
48330
+ /**
48331
+ * The low-score continuation stage (D665): greedy, highest IoU first, of the
48332
+ * sub-floor boxes against the CONFIRMED tracks nothing claimed this frame,
48333
+ * same class, on the predicted box at `byteTrackLowIouThreshold`.
48334
+ *
48335
+ * A bound box moves the track's association state only — `bbox`, `lastSeen`,
48336
+ * `age` — so the next confident box meets the subject where it is. Not
48337
+ * `hits` (it confirms nothing), not `score`, `class`, the vote, the path or
48338
+ * the velocity (it describes nothing downstream may act on). A track past
48339
+ * `lowScoreContinuationMaxMs` since its last confident box is not offered.
48340
+ */
48341
+ continueOnLowScore(established, lowScore, timestamp, matchedTracks, matched) {
48342
+ const claimed = [...matched.values()].map((d) => d.bbox);
48343
+ const candidates = lowScore.filter((det) => {
48344
+ const shadow = claimed.some((b) => iou$2(b, det.bbox) >= this.config.byteTrackLowIouThreshold);
48345
+ if (shadow) this.lowScoreStats.shadowed++;
48346
+ return !shadow;
48347
+ });
48348
+ const pairs = [];
48349
+ for (const track of established) {
48350
+ if (matchedTracks.has(track)) continue;
48351
+ if (timestamp - track.confidentSeenAt > this.config.lowScoreContinuationMaxMs) continue;
48352
+ const pbox = this.predicted(track, timestamp);
48353
+ for (const det of candidates) {
48354
+ if (det.class !== track.class) continue;
48355
+ const score = iou$2(pbox, det.bbox);
48356
+ if (score >= this.config.byteTrackLowIouThreshold) pairs.push({
48357
+ track,
48358
+ det,
48359
+ score
48360
+ });
48361
+ }
48362
+ }
48363
+ pairs.sort((a, b) => b.score - a.score);
48364
+ const usedTracks = /* @__PURE__ */ new Set();
48365
+ const usedDets = /* @__PURE__ */ new Set();
48366
+ for (const pair of pairs) {
48367
+ if (usedTracks.has(pair.track) || usedDets.has(pair.det)) continue;
48368
+ usedTracks.add(pair.track);
48369
+ usedDets.add(pair.det);
48370
+ pair.track.bbox = pair.det.bbox;
48371
+ pair.track.lastSeen = timestamp;
48372
+ pair.track.lastSeenFrameSeq = this.frameSeq;
48373
+ pair.track.age = 0;
48374
+ pair.track.lowScoreHeld = true;
48375
+ this.lowScoreStats.continued++;
48376
+ }
48377
+ }
48225
48378
  update(detections, timestamp, frameContext) {
48226
48379
  this.frameSeq++;
48227
48380
  const frameArea = frameContext ? frameContext.frameWidth * frameContext.frameHeight : 0;
@@ -48279,6 +48432,9 @@ var SortTracker = class SortTracker {
48279
48432
  this.rescueAssociate(established, PROXIMITY, detections, lowSet, frameArea, timestamp, used, matchedTracks, matched, continuationOnly);
48280
48433
  this.rescueAssociate(provisional, new Set([...OVERLAP, ...PROXIMITY]), detections, lowSet, frameArea, timestamp, used, matchedTracks, matched, continuationOnly);
48281
48434
  if (this.config.byteTrackEnabled && lowIdxs.length > 0) for (const tier of [established, provisional]) this.greedyAssociate(tier, detections, lowIdxs, this.config.byteTrackLowIouThreshold, timestamp, used, matchedTracks, matched, continuationOnly);
48435
+ const lowScore = frameContext?.lowScore ?? [];
48436
+ this.lowScoreStats.offered += lowScore.length;
48437
+ if (this.config.lowScoreContinuationEnabled && lowScore.length > 0) this.continueOnLowScore(established, lowScore, timestamp, matchedTracks, matched);
48282
48438
  for (const [track, det] of matched) {
48283
48439
  if (track.class !== det.class) this.reportRefusal("cross-class-absorbed", det, {
48284
48440
  against: track,
@@ -48309,6 +48465,7 @@ var SortTracker = class SortTracker {
48309
48465
  track.hits++;
48310
48466
  track.lastSeen = timestamp;
48311
48467
  track.lastSeenFrameSeq = this.frameSeq;
48468
+ this.markConfident(track, det.bbox, timestamp);
48312
48469
  if (dtMs > 0) {
48313
48470
  track.velocity = {
48314
48471
  dx: (newCenter.x - prevCenter.x) / dtMs,
@@ -48333,10 +48490,15 @@ var SortTracker = class SortTracker {
48333
48490
  }
48334
48491
  const occluded = this.config.occlusionEnabled && occluders.some((ob) => containment(this.predicted(track, timestamp), ob.bbox) >= this.config.occlusionContainment);
48335
48492
  track.age++;
48336
- if (timestamp - track.lastSeen > (occluded ? this.config.occlusionMaxMissedMs : this.config.maxMissedMs)) {
48493
+ const coastMs = timestamp - track.lastSeen;
48494
+ const limitMs = occluded ? this.config.occlusionMaxMissedMs : this.config.maxMissedMs;
48495
+ const heldTooLong = track.lowScoreHeld && timestamp - track.confidentSeenAt > this.config.lowScoreContinuationMaxMs;
48496
+ if (heldTooLong) this.lowScoreStats.sustainCapDrops++;
48497
+ if (coastMs > limitMs || heldTooLong) {
48337
48498
  track.lost = true;
48338
48499
  track.lostAt = timestamp;
48339
48500
  track.resurrectable = true;
48501
+ track.lowScoreHeld = false;
48340
48502
  this.lostTracks.push(track);
48341
48503
  } else surviving.push(track);
48342
48504
  }
@@ -48347,15 +48509,17 @@ var SortTracker = class SortTracker {
48347
48509
  let bestScore = -1;
48348
48510
  let bestGate = "iou";
48349
48511
  let bestPeers = 0;
48512
+ let bestVetoLifted = false;
48350
48513
  let refused;
48351
48514
  let refusedGate = "iou";
48352
48515
  let refusedScore = -1;
48353
48516
  for (const lost of this.lostTracks) {
48354
48517
  if (!lost.resurrectable) continue;
48355
48518
  if (timestamp - lost.lostAt > this.config.resurrectionWindowMs) continue;
48356
- const gate = this.looseMatch(lost, det, timestamp, false);
48519
+ const gate = this.looseMatch(lost, det, timestamp, "resurrection");
48357
48520
  if (gate === null) continue;
48358
- if (gate === "centroid" && this.isPlausibleBirth(det, frameArea)) continue;
48521
+ const vetoLifted = gate === "centroid" && this.isPlausibleBirth(det, frameArea);
48522
+ if (vetoLifted && !(this.config.resurrectionTimeScaled && this.isSoleSubjectResurrection(lost, det, detections, surviving, lowScore))) continue;
48359
48523
  const score = iou$2(lost.bbox, det.bbox);
48360
48524
  const peers = this.sameClassPeerCount(lost, surviving);
48361
48525
  if (this.config.resurrectionCrowdGuard && peers > 0 && score < this.config.resurrectionCrowdedIouThreshold) {
@@ -48371,13 +48535,14 @@ var SortTracker = class SortTracker {
48371
48535
  bestScore = score;
48372
48536
  bestGate = gate;
48373
48537
  bestPeers = peers;
48538
+ bestVetoLifted = vetoLifted;
48374
48539
  }
48375
48540
  }
48376
48541
  if (!best) {
48377
48542
  if (refused !== void 0) this.reportBind("resurrection-refused", refusedGate, refused, det, timestamp);
48378
48543
  continue;
48379
48544
  }
48380
- resurrectedScene.set(best, bestPeers > 0 ? "multi-subject" : "sole-subject");
48545
+ resurrectedScene.set(best, bestPeers > 0 ? "multi-subject" : bestVetoLifted ? "sole-subject-veto-lifted" : "sole-subject");
48381
48546
  this.reportBind("resurrection", bestGate, best, det, timestamp);
48382
48547
  this.lostTracks = this.lostTracks.filter((t) => t !== best);
48383
48548
  best.bbox = det.bbox;
@@ -48391,6 +48556,7 @@ var SortTracker = class SortTracker {
48391
48556
  best.lost = false;
48392
48557
  best.lastSeen = timestamp;
48393
48558
  best.lastSeenFrameSeq = this.frameSeq;
48559
+ this.markConfident(best, det.bbox, timestamp);
48394
48560
  best.velocity = {
48395
48561
  dx: 0,
48396
48562
  dy: 0
@@ -48467,7 +48633,10 @@ var SortTracker = class SortTracker {
48467
48633
  classVotes: new Map([[det.class, det.score > 0 ? det.score : Number.EPSILON]]),
48468
48634
  emitted: false,
48469
48635
  lossReported: false,
48470
- lastNativeRecovery: det.nativeRecovery
48636
+ lastNativeRecovery: det.nativeRecovery,
48637
+ confidentBbox: det.bbox,
48638
+ confidentSeenAt: timestamp,
48639
+ lowScoreHeld: false
48471
48640
  });
48472
48641
  }
48473
48642
  this.tracks = surviving;
@@ -48475,11 +48644,12 @@ var SortTracker = class SortTracker {
48475
48644
  return this.tracks.filter((t) => this.isConfirmedForEmit(t)).map((t) => {
48476
48645
  t.emitted = true;
48477
48646
  const scene = resurrectedScene.get(t);
48647
+ const held = t.lowScoreHeld;
48478
48648
  return {
48479
48649
  class: this.resolveReportedClass(t),
48480
48650
  originalClass: t.originalClass,
48481
48651
  score: t.score,
48482
- bbox: t.bbox,
48652
+ bbox: held ? t.confidentBbox : t.bbox,
48483
48653
  trackId: t.id,
48484
48654
  trackAge: t.hits,
48485
48655
  velocity: {
@@ -48487,8 +48657,9 @@ var SortTracker = class SortTracker {
48487
48657
  dy: t.velocity.dy * t.emitIntervalMs
48488
48658
  },
48489
48659
  path: [...t.path],
48490
- matchedThisFrame: t.lastSeen === timestamp,
48491
- ...scene !== void 0 ? { resurrectionScene: scene } : {}
48660
+ matchedThisFrame: t.lastSeen === timestamp && !held,
48661
+ ...scene !== void 0 ? { resurrectionScene: scene } : {},
48662
+ ...held ? { heldByLowScore: true } : {}
48492
48663
  };
48493
48664
  });
48494
48665
  }
@@ -48502,6 +48673,16 @@ var SortTracker = class SortTracker {
48502
48673
  this.tracks = this.tracks.filter((t) => t.id !== trackId);
48503
48674
  this.lostTracks = this.lostTracks.filter((t) => t.id !== trackId);
48504
48675
  }
48676
+ /**
48677
+ * What the low-score continuation stage did since the last call, and reset
48678
+ * (D665). The tracker has no `deviceId` and no logger; its owner reads this
48679
+ * on its own cadence and logs it per camera.
48680
+ */
48681
+ drainLowScoreStats() {
48682
+ const out = this.lowScoreStats;
48683
+ this.lowScoreStats = emptyLowScoreStats();
48684
+ return out;
48685
+ }
48505
48686
  getActiveTracks() {
48506
48687
  return this.tracks;
48507
48688
  }
@@ -48876,6 +49057,8 @@ var FrameProcessor = class {
48876
49057
  detectionRules;
48877
49058
  /** See {@link setZoneMembershipMinOverlap}. 0 = any positive overlap. */
48878
49059
  zoneMembershipMinOverlap;
49060
+ /** See {@link setLowScoreContinuationFloor}. `null` = tier off. */
49061
+ lowScoreFloor = LOW_SCORE_CONTINUATION_FLOOR_DEFAULT;
48879
49062
  /** See {@link getLastZoneOverlaps}. */
48880
49063
  lastZoneOverlaps;
48881
49064
  /** See {@link getLastZoneRejections}. */
@@ -48988,6 +49171,18 @@ var FrameProcessor = class {
48988
49171
  * failure mode this whole area keeps producing. {@link lastZoneOverlaps}
48989
49172
  * exists so the distribution can be read before a number is picked.
48990
49173
  */
49174
+ /**
49175
+ * The score at or above which a box the operator's class floor dropped is
49176
+ * handed to the tracker as low-score continuation evidence (D665); `null`
49177
+ * turns the tier off for this camera. Pushed per frame, like the zones.
49178
+ */
49179
+ setLowScoreContinuationFloor(floor) {
49180
+ this.lowScoreFloor = floor !== null && Number.isFinite(floor) && floor >= 0 && floor <= 1 ? floor : null;
49181
+ }
49182
+ /** The tracker's low-score continuation counts since the last call (D665). */
49183
+ drainLowScoreStats() {
49184
+ return this.tracker.drainLowScoreStats();
49185
+ }
48991
49186
  setZoneMembershipMinOverlap(minOverlap) {
48992
49187
  this.zoneMembershipMinOverlap = Number.isFinite(minOverlap) && minOverlap >= 0 && minOverlap <= 1 ? minOverlap : 0;
48993
49188
  }
@@ -49380,10 +49575,16 @@ var FrameProcessor = class {
49380
49575
  }
49381
49576
  trackerInput.push(det);
49382
49577
  }
49578
+ const lowCandidates = selectLowScoreContinuations(frame.discarded, this.lowScoreFloor);
49579
+ const lowScore = lowCandidates.length === 0 ? lowCandidates : this.zoneEngine.filterDetections(lowCandidates.map((d) => ({
49580
+ ...d.bbox,
49581
+ detection: d
49582
+ })), this.zones, this.detectionRules, frameWidth, frameHeight, (fd) => fd.detection.class).passed.map((fd) => fd.detection);
49383
49583
  const trackedDetections = this.tracker.update(trackerInput, timestamp, {
49384
49584
  frameWidth,
49385
49585
  frameHeight,
49386
- continuationOnly
49586
+ continuationOnly,
49587
+ lowScore
49387
49588
  });
49388
49589
  const stationaryContinued = continuableBoxes.size === 0 ? 0 : trackedDetections.filter((t) => t.matchedThisFrame !== false && continuableBoxes.has(t.bbox)).length;
49389
49590
  const riderFolds = foldByVehicleBbox.size === 0 ? EMPTY_RIDER_FOLDS : [...foldByVehicleBbox].map(([bbox, fold]) => {
@@ -49625,6 +49826,19 @@ var FrameProcessor = class {
49625
49826
  }
49626
49827
  };
49627
49828
  //#endregion
49829
+ //#region src/pipeline-analytics/face-identity-retraction.ts
49830
+ /** The recognizer's step id — the attribution a face identity label carries. */
49831
+ var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
49832
+ async function retractFaceIdentityLabel(stores, trackId) {
49833
+ const meta = stores.trackStore.getActiveByTrack(trackId)?.subLabelMeta;
49834
+ if (meta === void 0) return "no-label";
49835
+ if (meta.stepId !== "face-recognizer") return "not-face-recognizer";
49836
+ await stores.eventStore.clearLabelForTrack(trackId, 2);
49837
+ await stores.trackStore.clearLabel(trackId, 2);
49838
+ await stores.recomputeImportance?.(trackId);
49839
+ return "retracted";
49840
+ }
49841
+ //#endregion
49628
49842
  //#region src/pipeline-analytics/pipeline/track-provenance.ts
49629
49843
  function trackProvenanceOf(frame) {
49630
49844
  if (!(frame.width > 0 && frame.height > 0)) return void 0;
@@ -63280,8 +63494,6 @@ function faceSizeScore(detectionFrameShortSidePx) {
63280
63494
  }
63281
63495
  //#endregion
63282
63496
  //#region src/pipeline-analytics/face-recognizer.ts
63283
- /** Attribution of an identity resolved by the in-process ArcFace matcher. */
63284
- var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
63285
63497
  /** At most one "dropping imageless track" log per this interval, per recognizer. */
63286
63498
  var FACE_IMAGELESS_LOG_THROTTLE_MS = 6e4;
63287
63499
  /** At most one 'embedding magnitude' sample line per device per this interval. */
@@ -63307,6 +63519,9 @@ var FaceRecognizer = class {
63307
63519
  gallery = [];
63308
63520
  names = /* @__PURE__ */ new Map();
63309
63521
  aggregates = /* @__PURE__ */ new Map();
63522
+ /** The aggregate a veto-lifted quarantine set aside, restored if the first
63523
+ * face proves the same person (D665). */
63524
+ stashedIdentity = /* @__PURE__ */ new Map();
63310
63525
  bestFace = /* @__PURE__ */ new Map();
63311
63526
  /** Frame instant of each live track's last source park (D644). */
63312
63527
  lastSourceParkAt = /* @__PURE__ */ new Map();
@@ -63498,9 +63713,35 @@ var FaceRecognizer = class {
63498
63713
  * • the face becomes non-promotable: {@link onTrackEnd} will not write it to
63499
63714
  * the gallery while the flag stands.
63500
63715
  *
63501
- * The aggregate is deliberately left alone — see the note in `onTrackEnd`.
63502
- */
63503
- quarantineHeldFace(deviceId, trackId) {
63716
+ * The identity AGGREGATE depends on the scene (D665):
63717
+ *
63718
+ * • `multi-subject` — a bind that needed real overlap (IoU ≥ 0.3) with the
63719
+ * corpse's last box. The aggregate is LEFT ALONE: the next face either
63720
+ * confirms (same person, name intact at no cost — the common case, a
63721
+ * seated person occluded for a few seconds) or is rejected by cosine, and
63722
+ * a rejection is a PROVEN subject change: the aggregate is deleted and the
63723
+ * written identity label retracted then.
63724
+ * • `sole-subject-veto-lifted` — a bind on proximity alone. The aggregate is
63725
+ * STASHED and reset, and the written identity label retracted at once:
63726
+ * `updateTrackAggregate` keeps an assignment through every unmatched frame,
63727
+ * so without the reset a stranger who never matched anybody would carry
63728
+ * the name. The first face-bearing frame decides — `confirmed` restores the
63729
+ * stash and rewrites the label (the same person, back immediately);
63730
+ * `rejected`, or no face before the track ends, discards it.
63731
+ */
63732
+ async quarantineHeldFace(deviceId, trackId, scene) {
63733
+ if (scene === "sole-subject-veto-lifted") {
63734
+ const aggregate = this.aggregates.get(trackId);
63735
+ if (aggregate?.assignedIdentityId != null) {
63736
+ const peak = this.bestMatchScore.get(trackId);
63737
+ this.stashedIdentity.set(trackId, {
63738
+ aggregate,
63739
+ ...peak !== void 0 ? { bestMatchScore: peak } : {}
63740
+ });
63741
+ this.aggregates.delete(trackId);
63742
+ await this.retractIdentity(deviceId, trackId, scene, aggregate.assignedIdentityId);
63743
+ } else this.aggregates.delete(trackId);
63744
+ }
63504
63745
  const held = this.bestFace.get(trackId);
63505
63746
  if (held === void 0 || held.unverified === true) return;
63506
63747
  const quarantined = {
@@ -63523,6 +63764,7 @@ var FaceRecognizer = class {
63523
63764
  trackId
63524
63765
  },
63525
63766
  meta: {
63767
+ scene,
63526
63768
  heldScore: held.score,
63527
63769
  heldAt: held.timestamp,
63528
63770
  strippedIdentityId: held.recognizedIdentityId ?? null,
@@ -63531,6 +63773,40 @@ var FaceRecognizer = class {
63531
63773
  });
63532
63774
  }
63533
63775
  /**
63776
+ * Take the identity label back from the track and its events — only when the
63777
+ * face recognizer wrote it (`retractFaceIdentityLabel`). Stated either way.
63778
+ */
63779
+ async retractIdentity(deviceId, trackId, reason, identityId) {
63780
+ const retract = this.deps.retractIdentityLabel;
63781
+ if (retract === void 0) return;
63782
+ try {
63783
+ const outcome = await retract(trackId);
63784
+ this.deps.logger.info("face: identity label retracted after an unverified re-attach", {
63785
+ tags: {
63786
+ deviceId,
63787
+ trackId
63788
+ },
63789
+ meta: {
63790
+ reason,
63791
+ identityId,
63792
+ outcome
63793
+ }
63794
+ });
63795
+ } catch (err) {
63796
+ this.deps.logger.warn("face: identity label retraction failed", {
63797
+ tags: {
63798
+ deviceId,
63799
+ trackId
63800
+ },
63801
+ meta: {
63802
+ reason,
63803
+ identityId,
63804
+ error: String(err)
63805
+ }
63806
+ });
63807
+ }
63808
+ }
63809
+ /**
63534
63810
  * Ask a FRESH face whether the quarantined held face is still about the same
63535
63811
  * person, at the same bar the gallery uses to say two vectors are one person
63536
63812
  * (`similarityThreshold` — one knob over one band, D222).
@@ -63552,7 +63828,7 @@ var FaceRecognizer = class {
63552
63828
  }
63553
63829
  async processFrame(input) {
63554
63830
  const { settings } = input;
63555
- for (const t of input.tracked) if (t.resurrectionScene === "multi-subject") this.quarantineHeldFace(input.deviceId, t.trackId);
63831
+ for (const t of input.tracked) if (t.resurrectionScene !== void 0 && t.resurrectionScene !== "sole-subject") await this.quarantineHeldFace(input.deviceId, t.trackId, t.resurrectionScene);
63556
63832
  const candidates = input.tracked.filter((t) => Array.isArray(t.embedding) && t.embedding.length > 0 && typeof t.embeddingModelId === "string" && (t.nativeFaceShortSidePx ?? (t.faceBbox ? Math.min(t.faceBbox.w, t.faceBbox.h) : void 0) ?? Number.POSITIVE_INFINITY) >= settings.minFacePx);
63557
63833
  if (candidates.length === 0) return;
63558
63834
  this.deps.logger.debug("face: frame candidates", {
@@ -63619,11 +63895,26 @@ var FaceRecognizer = class {
63619
63895
  embedding: c.embedding,
63620
63896
  embeddingModelId: c.embeddingModelId
63621
63897
  }, settings.similarityThreshold);
63622
- if (verdict === "confirmed") this.bestFace.set(c.trackId, {
63623
- ...quarantined,
63624
- unverified: false
63625
- });
63626
- else if (verdict === "rejected") {
63898
+ const stashed = this.stashedIdentity.get(c.trackId);
63899
+ this.stashedIdentity.delete(c.trackId);
63900
+ if (verdict === "confirmed") {
63901
+ this.bestFace.set(c.trackId, {
63902
+ ...quarantined,
63903
+ unverified: false
63904
+ });
63905
+ const name = stashed?.aggregate.assignedIdentityId;
63906
+ if (stashed !== void 0 && name != null) {
63907
+ this.aggregates.set(c.trackId, stashed.aggregate);
63908
+ if (stashed.bestMatchScore !== void 0) this.bestMatchScore.set(c.trackId, stashed.bestMatchScore);
63909
+ labelWork.push({
63910
+ trackId: c.trackId,
63911
+ assignedIdentityId: name,
63912
+ matchScore: stashed.bestMatchScore ?? null
63913
+ });
63914
+ }
63915
+ } else if (verdict === "rejected") {
63916
+ const named = this.aggregates.get(c.trackId)?.assignedIdentityId;
63917
+ if (named != null) await this.retractIdentity(input.deviceId, c.trackId, "rejected", named);
63627
63918
  this.bestFace.delete(c.trackId);
63628
63919
  this.bestTracker.delete(c.trackId);
63629
63920
  this.aggregates.delete(c.trackId);
@@ -63838,6 +64129,7 @@ var FaceRecognizer = class {
63838
64129
  const bestMatchScore = this.bestMatchScore.get(trackId);
63839
64130
  const bestSuggestion = this.bestSuggestion.get(trackId);
63840
64131
  this.aggregates.delete(trackId);
64132
+ this.stashedIdentity.delete(trackId);
63841
64133
  this.bestFace.delete(trackId);
63842
64134
  this.bestTracker.delete(trackId);
63843
64135
  this.bestMatchScore.delete(trackId);
@@ -67999,6 +68291,19 @@ function shouldSkipDropoutFrame(activeTrackCount, detectionCount, consecutiveSki
67999
68291
  if (activeTrackCount <= 0) return false;
68000
68292
  return consecutiveSkips < cfg.maxSkipFrames;
68001
68293
  }
68294
+ /**
68295
+ * How much evidence a frame carries for the dropout decision: its detections,
68296
+ * plus the sub-floor boxes the tracker may continue a track on (D665).
68297
+ *
68298
+ * Until D665 the skip counted `frame.detections` only, so a frame whose sole
68299
+ * box was a person at 0.5 read as a detector glitch and never reached the
68300
+ * tracker — on camera 590 more than half the frames of a re-split gap were
68301
+ * dropout-skipped. A frame with a continuable box is not empty; `floor` is the
68302
+ * camera's low-score floor, `null` when the tier is off.
68303
+ */
68304
+ function dropoutEvidenceCount(frame, floor) {
68305
+ return frame.detections.length + selectLowScoreContinuations(frame.discarded, floor).length;
68306
+ }
68002
68307
  //#endregion
68003
68308
  //#region src/pipeline-analytics/pipeline/key-event-query.ts
68004
68309
  async function rankKeyEvents(candidates, options, peakLookup) {
@@ -82952,7 +83257,21 @@ var TrackingSettingsSchema = require_clip_model_registry.object({
82952
83257
  * passes, only for a coarse box below `animalOverPersonMaxScore`, never on
82953
83258
  * the rescue / resurrection passes. Reported label stays via the class
82954
83259
  * vote. Off = exact per-frame class gating. */
82955
- classGroupAssoc: require_clip_model_registry.boolean().default(true)
83260
+ classGroupAssoc: require_clip_model_registry.boolean().default(true),
83261
+ /** Hand the boxes the per-class confidence floor dropped to the tracker as
83262
+ * CONTINUATION-ONLY evidence (D665): they may keep a confirmed track alive,
83263
+ * never open one, never produce media, events or notifications. */
83264
+ lowScoreContinuationEnabled: require_clip_model_registry.boolean().default(true),
83265
+ /** Score at/above which a floor-dropped box counts as continuation evidence
83266
+ * (D665 — justified in `low-score-continuation.ts`). */
83267
+ lowScoreContinuationFloor: require_clip_model_registry.number().min(0).max(1).default(.35),
83268
+ /** Longest a track may be held by floor-dropped boxes alone, from its last
83269
+ * confident box (ms). */
83270
+ lowScoreContinuationMaxMs: require_clip_model_registry.number().min(0).default(8e3),
83271
+ /** Graveyard reach scaled by the gap, and the plausible-birth veto lifted for
83272
+ * a sole subject (D665). Off = the flat pre-D665 resurrection gate — the
83273
+ * per-camera rollback that needs no redeploy. */
83274
+ resurrectionTimeScaled: require_clip_model_registry.boolean().default(true)
82956
83275
  });
82957
83276
  var TRACKING_DEFAULTS = TrackingSettingsSchema.parse({});
82958
83277
  /**
@@ -83007,7 +83326,11 @@ function resolveTrackingSettings(raw) {
83007
83326
  minSpawnAreaFracPerson: s.minSpawnAreaFracPerson.catch(TRACKING_DEFAULTS.minSpawnAreaFracPerson).parse(raw.minSpawnAreaFracPerson),
83008
83327
  minSpawnAreaFracAnimal: s.minSpawnAreaFracAnimal.catch(TRACKING_DEFAULTS.minSpawnAreaFracAnimal).parse(raw.minSpawnAreaFracAnimal),
83009
83328
  minSpawnAreaFracVehicle: s.minSpawnAreaFracVehicle.catch(TRACKING_DEFAULTS.minSpawnAreaFracVehicle).parse(raw.minSpawnAreaFracVehicle),
83010
- classGroupAssoc: s.classGroupAssoc.catch(TRACKING_DEFAULTS.classGroupAssoc).parse(raw.classGroupAssoc)
83329
+ classGroupAssoc: s.classGroupAssoc.catch(TRACKING_DEFAULTS.classGroupAssoc).parse(raw.classGroupAssoc),
83330
+ lowScoreContinuationEnabled: s.lowScoreContinuationEnabled.catch(TRACKING_DEFAULTS.lowScoreContinuationEnabled).parse(raw.lowScoreContinuationEnabled),
83331
+ lowScoreContinuationFloor: s.lowScoreContinuationFloor.catch(TRACKING_DEFAULTS.lowScoreContinuationFloor).parse(raw.lowScoreContinuationFloor),
83332
+ lowScoreContinuationMaxMs: s.lowScoreContinuationMaxMs.catch(TRACKING_DEFAULTS.lowScoreContinuationMaxMs).parse(raw.lowScoreContinuationMaxMs),
83333
+ resurrectionTimeScaled: s.resurrectionTimeScaled.catch(TRACKING_DEFAULTS.resurrectionTimeScaled).parse(raw.resurrectionTimeScaled)
83011
83334
  };
83012
83335
  }
83013
83336
  //#endregion
@@ -83437,6 +83760,40 @@ function buildDetectionSettingsSections() {
83437
83760
  step: .05,
83438
83761
  default: TRACKING_DEFAULTS.byteTrackLowIouThreshold
83439
83762
  },
83763
+ {
83764
+ type: "boolean",
83765
+ key: "lowScoreContinuationEnabled",
83766
+ label: "Keep tracks through low-confidence frames",
83767
+ description: "Boxes below the class confidence floor may keep an EXISTING track alive while the detector is unsure, so a person read at 0.5 for a few seconds keeps one id instead of splitting. They never start a track and never produce events, notifications or snapshots.",
83768
+ default: TRACKING_DEFAULTS.lowScoreContinuationEnabled
83769
+ },
83770
+ {
83771
+ type: "number",
83772
+ key: "lowScoreContinuationFloor",
83773
+ label: "Low-confidence continuation floor",
83774
+ description: "Score at or above which a box the class floor dropped still counts as evidence that a tracked subject is there. Only used with the option above.",
83775
+ min: 0,
83776
+ max: 1,
83777
+ step: .05,
83778
+ default: TRACKING_DEFAULTS.lowScoreContinuationFloor
83779
+ },
83780
+ {
83781
+ type: "number",
83782
+ key: "lowScoreContinuationMaxMs",
83783
+ label: "Low-confidence hold limit (ms)",
83784
+ description: "Longest a track may be kept alive by low-confidence boxes alone, counted from its last confident box.",
83785
+ min: 0,
83786
+ max: 3e4,
83787
+ step: 500,
83788
+ default: TRACKING_DEFAULTS.lowScoreContinuationMaxMs
83789
+ },
83790
+ {
83791
+ type: "boolean",
83792
+ key: "resurrectionTimeScaled",
83793
+ label: "Wider re-attach after a long gap",
83794
+ description: "Let a track that was lost for a few seconds be re-attached to a subject that reappears further away, the longer the gap (up to two box diagonals) — only when nobody else of that class is in view. A recognised face is re-checked after such a re-attach. Off = the fixed short re-attach distance.",
83795
+ default: TRACKING_DEFAULTS.resurrectionTimeScaled
83796
+ },
83440
83797
  {
83441
83798
  type: "boolean",
83442
83799
  key: "minSpawnAreaEnabled",
@@ -87937,6 +88294,8 @@ function decodeEmbeddingBase64(base64) {
87937
88294
  var PHANTOM_CELL_PX = 32;
87938
88295
  /** How long a cell remembers its closes. */
87939
88296
  var PHANTOM_CELL_WINDOW_MS = 360 * 6e4;
88297
+ /** Cadence of the per-camera low-score continuation line (D665). */
88298
+ var LOW_SCORE_REPORT_INTERVAL_MS = 6e4;
87940
88299
  var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_model_registry.BaseAddon {
87941
88300
  /** Recent SHORT+MOTIONLESS track closes per `<device>:<class>:<cell>` —
87942
88301
  * see {@link noteShortMotionlessTrack}. Measurement only; each entry is
@@ -88604,6 +88963,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
88604
88963
  packageBackground = null;
88605
88964
  /** Consecutive detector-dropout frames skipped per (deviceId, source) key. */
88606
88965
  dropoutSkipsByKey = /* @__PURE__ */ new Map();
88966
+ /** Last time each processor's low-score continuation counts were logged (D665). */
88967
+ lowScoreReportAtByKey = /* @__PURE__ */ new Map();
88607
88968
  /** The per-track close-path orchestrator (S2, refactor spec 2026-07-22):
88608
88969
  * owns the single-flight sweep latch + the FULL per-track close sequence
88609
88970
  * (zero-media policy, lastFrame promotion, recognizer ends with the
@@ -90162,6 +90523,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
90162
90523
  mediaStore: stores.mediaStore,
90163
90524
  trackStore: stores.trackStore,
90164
90525
  eventStore: stores.eventStore,
90526
+ retractIdentityLabel: (trackId) => retractFaceIdentityLabel({
90527
+ trackStore: stores.trackStore,
90528
+ eventStore: stores.eventStore,
90529
+ recomputeImportance: (id) => recomputeTrackImportance({
90530
+ trackStore: stores.trackStore,
90531
+ eventStore: stores.eventStore
90532
+ }, id)
90533
+ }, trackId),
90165
90534
  captureCrop: (frameHandle, bbox, frameWidth, frameHeight, padding, ownerId, deviceId) => this.captureScheduler.request({
90166
90535
  deviceId,
90167
90536
  trackId: ownerId,
@@ -92116,6 +92485,31 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
92116
92485
  * tracking/zone/event state never crosses between sources. Emits the
92117
92486
  * SAME canonical events, distinguished only by `source`.
92118
92487
  */
92488
+ /**
92489
+ * One line per camera per minute with what the low-score continuation tier
92490
+ * did (D665): boxes offered, tracks continued, holds that ended in a
92491
+ * confident box again (`resumed` — an id the tier kept), holds cut at the
92492
+ * cap. Never one line per box. Silent while nothing was offered.
92493
+ */
92494
+ reportLowScoreContinuation(deviceId, key, processor) {
92495
+ const now = Date.now();
92496
+ const last = this.lowScoreReportAtByKey.get(key);
92497
+ if (last === void 0) {
92498
+ this.lowScoreReportAtByKey.set(key, now);
92499
+ return;
92500
+ }
92501
+ if (now - last < LOW_SCORE_REPORT_INTERVAL_MS) return;
92502
+ this.lowScoreReportAtByKey.set(key, now);
92503
+ const stats = processor.drainLowScoreStats();
92504
+ if (stats.offered === 0 && stats.sustainCapDrops === 0) return;
92505
+ this.ctx.logger.info("tracker: low-score continuation", {
92506
+ tags: { deviceId },
92507
+ meta: {
92508
+ ...stats,
92509
+ windowMs: now - last
92510
+ }
92511
+ });
92512
+ }
92119
92513
  async processFrame(deviceId, frame, source, rawFrameHandle, detailSteps, occasion = "session") {
92120
92514
  if (this.shuttingDown) return;
92121
92515
  if (!await this.bindingCache.isActive(deviceId)) {
@@ -92140,7 +92534,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
92140
92534
  const trk = await this.resolveDeviceTrackingSettings(deviceId);
92141
92535
  const activeCount = this.lastActiveTrackIds.get(key)?.size ?? 0;
92142
92536
  const skips = this.dropoutSkipsByKey.get(key) ?? 0;
92143
- if (shouldSkipDropoutFrame(activeCount, frame.detections.length, skips, {
92537
+ const lowScoreFloor = trk.lowScoreContinuationEnabled ? trk.lowScoreContinuationFloor : null;
92538
+ if (shouldSkipDropoutFrame(activeCount, dropoutEvidenceCount(frame, lowScoreFloor), skips, {
92144
92539
  enabled: trk.dropoutSkipEnabled,
92145
92540
  maxSkipFrames: trk.dropoutMaxSkipFrames
92146
92541
  })) {
@@ -92168,6 +92563,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
92168
92563
  processor.setZones(liveZones);
92169
92564
  processor.setDetectionRules(liveRules);
92170
92565
  processor.setZoneMembershipMinOverlap(trk.zoneMembershipMinOverlap);
92566
+ processor.setLowScoreContinuationFloor(lowScoreFloor);
92171
92567
  const injection = injectSecondarySubjects(frame, this.secondarySubjects.take(key, Date.now()));
92172
92568
  for (const id of injection.covered) this.secondarySubjects.drop(key, id);
92173
92569
  const trackerFrame = injection.frame;
@@ -92183,6 +92579,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
92183
92579
  timestamp: frame.timestamp,
92184
92580
  frame: trackerFrame
92185
92581
  });
92582
+ this.reportLowScoreContinuation(deviceId, key, processor);
92186
92583
  for (const r of processor.getLastLabelWriteRefusals()) {
92187
92584
  const message = r.reason.kind === "macro-class" ? "label tier collapse refused" : "label write refused";
92188
92585
  this.ctx.logger.info(message, {
@@ -95870,7 +96267,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
95870
96267
  animal: trk.minSpawnAreaFracAnimal,
95871
96268
  vehicle: trk.minSpawnAreaFracVehicle
95872
96269
  },
95873
- classGroupAssoc: trk.classGroupAssoc
96270
+ classGroupAssoc: trk.classGroupAssoc,
96271
+ lowScoreContinuationEnabled: trk.lowScoreContinuationEnabled,
96272
+ lowScoreContinuationMaxMs: trk.lowScoreContinuationMaxMs,
96273
+ resurrectionTimeScaled: trk.resurrectionTimeScaled
95874
96274
  }, {
95875
96275
  stationaryThresholdSec,
95876
96276
  ...stillSpeedFracPerSec !== void 0 ? { stillSpeedFracPerSec } : {}