@camstack/addon-post-analysis 1.2.290 → 1.2.292

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.
@@ -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--sFeijO7.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-CTY5co6P.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");
@@ -39677,7 +39677,12 @@ var RebuildInputSchema = require_clip_model_registry.object({
39677
39677
  * Milliseconds between tracks; omit for the default, `0` to run flat out.
39678
39678
  * A rebuild shares hub-main's only thread with every live request.
39679
39679
  */
39680
- pacingMs: require_clip_model_registry.number().int().nonnegative().optional()
39680
+ pacingMs: require_clip_model_registry.number().int().nonnegative().optional(),
39681
+ /**
39682
+ * Re-embed ONLY tracks already in the pinned space — to redo vectors cut
39683
+ * from the wrong box (D664). Requires `maxTracks`.
39684
+ */
39685
+ force: require_clip_model_registry.boolean().optional()
39681
39686
  });
39682
39687
  var RebuildStartedSchema = require_clip_model_registry.object({
39683
39688
  started: require_clip_model_registry.boolean(),
@@ -39697,7 +39702,20 @@ var RebuildStatusSchema = require_clip_model_registry.object({
39697
39702
  scanned: require_clip_model_registry.number(),
39698
39703
  rebuilt: require_clip_model_registry.number(),
39699
39704
  missingKeyFrame: require_clip_model_registry.number(),
39705
+ /** No object event at all — its own number since D664. */
39706
+ missingEvent: require_clip_model_registry.number(),
39700
39707
  missingBbox: require_clip_model_registry.number(),
39708
+ /** Which rule placed each judged track's box (D664). */
39709
+ boxSource: require_clip_model_registry.object({
39710
+ subjectBox: require_clip_model_registry.number(),
39711
+ positions: require_clip_model_registry.number(),
39712
+ event: require_clip_model_registry.number(),
39713
+ refused: require_clip_model_registry.number()
39714
+ }),
39715
+ alreadyInSpace: require_clip_model_registry.number(),
39716
+ /** Whether the current / last pass was a `force` pass (D664). */
39717
+ force: require_clip_model_registry.boolean().nullable(),
39718
+ forcedInSpace: require_clip_model_registry.number(),
39701
39719
  /** Refused by the executing node (unreadable key frame / bad box), not broken. */
39702
39720
  notRunnable: require_clip_model_registry.number(),
39703
39721
  /** The pass STOPPED: no node in the cluster could serve the pinned model. */
@@ -40972,7 +40990,7 @@ function toPixelBox(b) {
40972
40990
  h: b.height
40973
40991
  };
40974
40992
  }
40975
- function refused(reason, detail) {
40993
+ function refused$1(reason, detail) {
40976
40994
  return {
40977
40995
  kind: "refused",
40978
40996
  reason,
@@ -41000,8 +41018,8 @@ function comparableFaces(input) {
41000
41018
  /** Refuse before choosing when nothing in the photo can become the sample. */
41001
41019
  function precondition(input) {
41002
41020
  const enrollable = input.faces.filter((f) => f.enrollable && f.embedding !== void 0);
41003
- if (enrollable.length === 0) return refused("no-enrollable-face", `${input.faces.length} face(s) found, none enrollable (${input.faces.map((f) => f.refusedReason ?? "unknown").join(", ")})`);
41004
- if (!enrollable.some((f) => isEnrollableIn(f, input.modelId))) return refused("model-mismatch", `faces were embedded by ${[...new Set(enrollable.map((f) => f.modelId))].join(", ")}, the old sample by ${input.modelId} — re-embed the gallery first`);
41021
+ if (enrollable.length === 0) return refused$1("no-enrollable-face", `${input.faces.length} face(s) found, none enrollable (${input.faces.map((f) => f.refusedReason ?? "unknown").join(", ")})`);
41022
+ if (!enrollable.some((f) => isEnrollableIn(f, input.modelId))) return refused$1("model-mismatch", `faces were embedded by ${[...new Set(enrollable.map((f) => f.modelId))].join(", ")}, the old sample by ${input.modelId} — re-embed the gallery first`);
41005
41023
  return null;
41006
41024
  }
41007
41025
  function chooseByIou(input, oldBox, scored) {
@@ -41010,13 +41028,13 @@ function chooseByIou(input, oldBox, scored) {
41010
41028
  face,
41011
41029
  iou: iou$3(box, toPixelBox(face.bbox))
41012
41030
  })).filter((c) => c.iou >= PHOTO_REENROL_MIN_IOU);
41013
- if (overlapping.length === 0) return refused("no-iou-match", `no face overlaps the old box at IoU ≥ ${PHOTO_REENROL_MIN_IOU}`);
41014
- if (overlapping.length > 1) return refused("ambiguous", `${overlapping.length} faces overlap the old box (IoU ${overlapping.map((c) => c.iou.toFixed(2)).join(", ")})`);
41031
+ if (overlapping.length === 0) return refused$1("no-iou-match", `no face overlaps the old box at IoU ≥ ${PHOTO_REENROL_MIN_IOU}`);
41032
+ if (overlapping.length > 1) return refused$1("ambiguous", `${overlapping.length} faces overlap the old box (IoU ${overlapping.map((c) => c.iou.toFixed(2)).join(", ")})`);
41015
41033
  const [only] = overlapping;
41016
- if (only === void 0) return refused("no-iou-match", "no overlapping face");
41017
- if (!isEnrollableIn(only.face, input.modelId)) return refused("no-enrollable-face", `the face on the old box is not enrollable (${only.face.refusedReason ?? `model ${only.face.modelId ?? "none"}`})`);
41034
+ if (only === void 0) return refused$1("no-iou-match", "no overlapping face");
41035
+ if (!isEnrollableIn(only.face, input.modelId)) return refused$1("no-enrollable-face", `the face on the old box is not enrollable (${only.face.refusedReason ?? `model ${only.face.modelId ?? "none"}`})`);
41018
41036
  const cosine = scored.find((s) => s.face === only.face)?.cosine;
41019
- if (cosine === void 0 || cosine < .5) return refused("below-floor", `the overlapping face scores cosine ${cosine?.toFixed(3) ?? "n/a"} < ${PHOTO_REENROL_COSINE_FLOOR}`);
41037
+ if (cosine === void 0 || cosine < .5) return refused$1("below-floor", `the overlapping face scores cosine ${cosine?.toFixed(3) ?? "n/a"} < ${PHOTO_REENROL_COSINE_FLOOR}`);
41020
41038
  return {
41021
41039
  kind: "chosen",
41022
41040
  face: only.face,
@@ -41027,10 +41045,10 @@ function chooseByIou(input, oldBox, scored) {
41027
41045
  }
41028
41046
  function chooseByCosine(input, scored) {
41029
41047
  const [best, runnerUp] = [...scored].toSorted((a, b) => b.cosine - a.cosine);
41030
- if (best === void 0) return refused("model-mismatch", "no face has a comparable vector");
41031
- if (best.cosine < .5) return refused("below-floor", `best cosine ${best.cosine.toFixed(3)} < ${PHOTO_REENROL_COSINE_FLOOR}`);
41032
- if (runnerUp !== void 0 && runnerUp.cosine >= .5 && best.cosine - runnerUp.cosine < .1) return refused("ambiguous", `two faces clear the floor within ${PHOTO_REENROL_AMBIGUITY_MARGIN}: ${best.cosine.toFixed(3)} vs ${runnerUp.cosine.toFixed(3)}`);
41033
- if (!isEnrollableIn(best.face, input.modelId)) return refused("ambiguous", `the closest face (cosine ${best.cosine.toFixed(3)}) is not enrollable (${best.face.refusedReason ?? "unknown"}) — the enrollable ones may be someone else`);
41048
+ if (best === void 0) return refused$1("model-mismatch", "no face has a comparable vector");
41049
+ if (best.cosine < .5) return refused$1("below-floor", `best cosine ${best.cosine.toFixed(3)} < ${PHOTO_REENROL_COSINE_FLOOR}`);
41050
+ if (runnerUp !== void 0 && runnerUp.cosine >= .5 && best.cosine - runnerUp.cosine < .1) return refused$1("ambiguous", `two faces clear the floor within ${PHOTO_REENROL_AMBIGUITY_MARGIN}: ${best.cosine.toFixed(3)} vs ${runnerUp.cosine.toFixed(3)}`);
41051
+ if (!isEnrollableIn(best.face, input.modelId)) return refused$1("ambiguous", `the closest face (cosine ${best.cosine.toFixed(3)}) is not enrollable (${best.face.refusedReason ?? "unknown"}) — the enrollable ones may be someone else`);
41034
41052
  return {
41035
41053
  kind: "chosen",
41036
41054
  face: best.face,
@@ -47036,6 +47054,42 @@ function pairRiders(detections) {
47036
47054
  return pairs;
47037
47055
  }
47038
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
47039
47093
  //#region src/pipeline-analytics/pipeline/track-id.ts
47040
47094
  /**
47041
47095
  * A track's identity: short, random, and mintable inside a frame.
@@ -47184,6 +47238,30 @@ function reacquireReachPx(bbox, elapsedMs) {
47184
47238
  const elapsed = Math.max(0, elapsedMs);
47185
47239
  return clampRange(diag * REACH_DIAGONALS_PER_SEC * (elapsed / 1e3), diag * REACH_MIN_DIAGONALS, diag * REACH_MAX_DIAGONALS);
47186
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
+ }
47187
47265
  //#endregion
47188
47266
  //#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
47189
47267
  /**
@@ -47332,7 +47410,10 @@ var DEFAULT_TRACKER_CONFIG = {
47332
47410
  byteTrackLowIouThreshold: .2,
47333
47411
  minSpawnAreaEnabled: true,
47334
47412
  classSpawnAreaFracs: { person: 5e-4 },
47335
- classGroupAssoc: true
47413
+ classGroupAssoc: true,
47414
+ lowScoreContinuationEnabled: true,
47415
+ lowScoreContinuationMaxMs: 8e3,
47416
+ resurrectionTimeScaled: true
47336
47417
  };
47337
47418
  /** Macro-classes the duplicate resolver may collapse across (people in
47338
47419
  * non-upright poses misdetected as animals). Any other cross-class pair is
@@ -47340,6 +47421,15 @@ var DEFAULT_TRACKER_CONFIG = {
47340
47421
  var PERSON_CLASS = "person";
47341
47422
  var ANIMAL_CLASS = "animal";
47342
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
+ }
47343
47433
  /** No stationary-held detections on this frame — the default for every caller
47344
47434
  * that has no gate (replay, tests, the non-pipeline planes). */
47345
47435
  var EMPTY_INDEX_SET = /* @__PURE__ */ new Set();
@@ -47411,6 +47501,8 @@ var SortTracker = class SortTracker {
47411
47501
  trackLossSampler = new TrackLossSampler();
47412
47502
  /** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
47413
47503
  spawnBesideObserver = null;
47504
+ /** Running counts behind {@link drainLowScoreStats} (D665). */
47505
+ lowScoreStats = emptyLowScoreStats();
47414
47506
  constructor(config = {}) {
47415
47507
  this.config = {
47416
47508
  ...DEFAULT_TRACKER_CONFIG,
@@ -47471,7 +47563,7 @@ var SortTracker = class SortTracker {
47471
47563
  if (SortTracker.spawnBesideFamily(track.class) !== family) continue;
47472
47564
  const tc = bboxCentroid(track.bbox);
47473
47565
  const dist = Math.hypot(tc.x - dc.x, tc.y - dc.y);
47474
- const reach = this.reacquireReach(track, timestamp, true);
47566
+ const reach = this.reacquireReach(track, timestamp, "rescue");
47475
47567
  if (dist > reach * 2) continue;
47476
47568
  if (dist < bestDist) {
47477
47569
  best = track;
@@ -47518,7 +47610,7 @@ var SortTracker = class SortTracker {
47518
47610
  explainDecline(track, det, detections, frameArea, timestamp, centroidDist) {
47519
47611
  if (track.class !== det.class) return det.nativeRecovery !== void 0 ? "native-measured-class" : "cross-class";
47520
47612
  if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "already-claimed";
47521
- if (centroidDist <= this.reacquireReach(track, timestamp, true)) {
47613
+ if (centroidDist <= this.reacquireReach(track, timestamp, "rescue")) {
47522
47614
  if (!this.isPlausibleBirth(det, frameArea)) return "already-claimed";
47523
47615
  if (!this.config.reacquireTimeScaled) return "plausible-birth-veto";
47524
47616
  return this.isUnambiguousReach(track, det, detections) ? "already-claimed" : "crowd-guard";
@@ -47973,34 +48065,38 @@ var SortTracker = class SortTracker {
47973
48065
  * this gate crossed the group, and it is where a `person` at 0.86 took over
47974
48066
  * an animal's id (camera 615, 17:11:01).
47975
48067
  */
47976
- looseMatch(track, det, timestamp, timeScaled = true) {
48068
+ looseMatch(track, det, timestamp, pass = "rescue") {
47977
48069
  if (track.class !== det.class) return null;
47978
48070
  if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "iou";
47979
48071
  const tc = bboxCentroid(track.bbox);
47980
48072
  const dc = bboxCentroid(det.bbox);
47981
- 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";
47982
48074
  if (growthAdjacentMatch(track.bbox, det.bbox, this.growthContext(track, timestamp))) return "growth";
47983
48075
  return null;
47984
48076
  }
47985
48077
  /**
47986
48078
  * The proximity budget the `centroid` clause is measured against.
47987
48079
  *
47988
- * On the RESCUE pass (`timeScaled`) it is {@link reacquireReachPx} — a
47989
- * multiple of the track's diagonal that grows with the elapsed gap and stops
47990
- * at a ceiling, because a live coasting track's whereabouts genuinely become
47991
- * less certain the longer it has gone unseen (D399).
47992
- *
47993
- * On the RESURRECTION pass it stays the flat `rescueCentroidFactor × diagonal`
47994
- * it always was. That pass re-attaches a CORPSE, and it exists for one thing:
47995
- * "a flickering stationary object keeps its identity". Elapsed time is not the
47996
- * variable there — a stationary subject does not travel further because it
47997
- * blinked for longer — and the corpse is strictly weaker evidence than a live
47998
- * track, which is the same reasoning `resurrectionCrowdedIouThreshold` already
47999
- * encodes.
48000
- */
48001
- 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) {
48002
48097
  const diag = Math.hypot(track.bbox.w, track.bbox.h);
48003
- 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;
48004
48100
  return reacquireReachPx(track.bbox, timestamp - track.lastSeen);
48005
48101
  }
48006
48102
  /**
@@ -48050,6 +48146,23 @@ var SortTracker = class SortTracker {
48050
48146
  nearestPx: nearest
48051
48147
  };
48052
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
+ }
48053
48166
  isUnambiguousReach(track, det, detections) {
48054
48167
  if (this.sameClassPeerCount(track, this.tracks) > 0) return false;
48055
48168
  for (const other of detections) {
@@ -48204,6 +48317,64 @@ var SortTracker = class SortTracker {
48204
48317
  used.add(pair.detIdx);
48205
48318
  }
48206
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
+ }
48207
48378
  update(detections, timestamp, frameContext) {
48208
48379
  this.frameSeq++;
48209
48380
  const frameArea = frameContext ? frameContext.frameWidth * frameContext.frameHeight : 0;
@@ -48261,6 +48432,9 @@ var SortTracker = class SortTracker {
48261
48432
  this.rescueAssociate(established, PROXIMITY, detections, lowSet, frameArea, timestamp, used, matchedTracks, matched, continuationOnly);
48262
48433
  this.rescueAssociate(provisional, new Set([...OVERLAP, ...PROXIMITY]), detections, lowSet, frameArea, timestamp, used, matchedTracks, matched, continuationOnly);
48263
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);
48264
48438
  for (const [track, det] of matched) {
48265
48439
  if (track.class !== det.class) this.reportRefusal("cross-class-absorbed", det, {
48266
48440
  against: track,
@@ -48291,6 +48465,7 @@ var SortTracker = class SortTracker {
48291
48465
  track.hits++;
48292
48466
  track.lastSeen = timestamp;
48293
48467
  track.lastSeenFrameSeq = this.frameSeq;
48468
+ this.markConfident(track, det.bbox, timestamp);
48294
48469
  if (dtMs > 0) {
48295
48470
  track.velocity = {
48296
48471
  dx: (newCenter.x - prevCenter.x) / dtMs,
@@ -48315,10 +48490,15 @@ var SortTracker = class SortTracker {
48315
48490
  }
48316
48491
  const occluded = this.config.occlusionEnabled && occluders.some((ob) => containment(this.predicted(track, timestamp), ob.bbox) >= this.config.occlusionContainment);
48317
48492
  track.age++;
48318
- 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) {
48319
48498
  track.lost = true;
48320
48499
  track.lostAt = timestamp;
48321
48500
  track.resurrectable = true;
48501
+ track.lowScoreHeld = false;
48322
48502
  this.lostTracks.push(track);
48323
48503
  } else surviving.push(track);
48324
48504
  }
@@ -48329,15 +48509,17 @@ var SortTracker = class SortTracker {
48329
48509
  let bestScore = -1;
48330
48510
  let bestGate = "iou";
48331
48511
  let bestPeers = 0;
48512
+ let bestVetoLifted = false;
48332
48513
  let refused;
48333
48514
  let refusedGate = "iou";
48334
48515
  let refusedScore = -1;
48335
48516
  for (const lost of this.lostTracks) {
48336
48517
  if (!lost.resurrectable) continue;
48337
48518
  if (timestamp - lost.lostAt > this.config.resurrectionWindowMs) continue;
48338
- const gate = this.looseMatch(lost, det, timestamp, false);
48519
+ const gate = this.looseMatch(lost, det, timestamp, "resurrection");
48339
48520
  if (gate === null) continue;
48340
- 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;
48341
48523
  const score = iou$2(lost.bbox, det.bbox);
48342
48524
  const peers = this.sameClassPeerCount(lost, surviving);
48343
48525
  if (this.config.resurrectionCrowdGuard && peers > 0 && score < this.config.resurrectionCrowdedIouThreshold) {
@@ -48353,13 +48535,14 @@ var SortTracker = class SortTracker {
48353
48535
  bestScore = score;
48354
48536
  bestGate = gate;
48355
48537
  bestPeers = peers;
48538
+ bestVetoLifted = vetoLifted;
48356
48539
  }
48357
48540
  }
48358
48541
  if (!best) {
48359
48542
  if (refused !== void 0) this.reportBind("resurrection-refused", refusedGate, refused, det, timestamp);
48360
48543
  continue;
48361
48544
  }
48362
- resurrectedScene.set(best, bestPeers > 0 ? "multi-subject" : "sole-subject");
48545
+ resurrectedScene.set(best, bestPeers > 0 ? "multi-subject" : bestVetoLifted ? "sole-subject-veto-lifted" : "sole-subject");
48363
48546
  this.reportBind("resurrection", bestGate, best, det, timestamp);
48364
48547
  this.lostTracks = this.lostTracks.filter((t) => t !== best);
48365
48548
  best.bbox = det.bbox;
@@ -48373,6 +48556,7 @@ var SortTracker = class SortTracker {
48373
48556
  best.lost = false;
48374
48557
  best.lastSeen = timestamp;
48375
48558
  best.lastSeenFrameSeq = this.frameSeq;
48559
+ this.markConfident(best, det.bbox, timestamp);
48376
48560
  best.velocity = {
48377
48561
  dx: 0,
48378
48562
  dy: 0
@@ -48449,7 +48633,10 @@ var SortTracker = class SortTracker {
48449
48633
  classVotes: new Map([[det.class, det.score > 0 ? det.score : Number.EPSILON]]),
48450
48634
  emitted: false,
48451
48635
  lossReported: false,
48452
- lastNativeRecovery: det.nativeRecovery
48636
+ lastNativeRecovery: det.nativeRecovery,
48637
+ confidentBbox: det.bbox,
48638
+ confidentSeenAt: timestamp,
48639
+ lowScoreHeld: false
48453
48640
  });
48454
48641
  }
48455
48642
  this.tracks = surviving;
@@ -48457,11 +48644,12 @@ var SortTracker = class SortTracker {
48457
48644
  return this.tracks.filter((t) => this.isConfirmedForEmit(t)).map((t) => {
48458
48645
  t.emitted = true;
48459
48646
  const scene = resurrectedScene.get(t);
48647
+ const held = t.lowScoreHeld;
48460
48648
  return {
48461
48649
  class: this.resolveReportedClass(t),
48462
48650
  originalClass: t.originalClass,
48463
48651
  score: t.score,
48464
- bbox: t.bbox,
48652
+ bbox: held ? t.confidentBbox : t.bbox,
48465
48653
  trackId: t.id,
48466
48654
  trackAge: t.hits,
48467
48655
  velocity: {
@@ -48469,8 +48657,9 @@ var SortTracker = class SortTracker {
48469
48657
  dy: t.velocity.dy * t.emitIntervalMs
48470
48658
  },
48471
48659
  path: [...t.path],
48472
- matchedThisFrame: t.lastSeen === timestamp,
48473
- ...scene !== void 0 ? { resurrectionScene: scene } : {}
48660
+ matchedThisFrame: t.lastSeen === timestamp && !held,
48661
+ ...scene !== void 0 ? { resurrectionScene: scene } : {},
48662
+ ...held ? { heldByLowScore: true } : {}
48474
48663
  };
48475
48664
  });
48476
48665
  }
@@ -48484,6 +48673,16 @@ var SortTracker = class SortTracker {
48484
48673
  this.tracks = this.tracks.filter((t) => t.id !== trackId);
48485
48674
  this.lostTracks = this.lostTracks.filter((t) => t.id !== trackId);
48486
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
+ }
48487
48686
  getActiveTracks() {
48488
48687
  return this.tracks;
48489
48688
  }
@@ -48858,6 +49057,8 @@ var FrameProcessor = class {
48858
49057
  detectionRules;
48859
49058
  /** See {@link setZoneMembershipMinOverlap}. 0 = any positive overlap. */
48860
49059
  zoneMembershipMinOverlap;
49060
+ /** See {@link setLowScoreContinuationFloor}. `null` = tier off. */
49061
+ lowScoreFloor = LOW_SCORE_CONTINUATION_FLOOR_DEFAULT;
48861
49062
  /** See {@link getLastZoneOverlaps}. */
48862
49063
  lastZoneOverlaps;
48863
49064
  /** See {@link getLastZoneRejections}. */
@@ -48970,6 +49171,18 @@ var FrameProcessor = class {
48970
49171
  * failure mode this whole area keeps producing. {@link lastZoneOverlaps}
48971
49172
  * exists so the distribution can be read before a number is picked.
48972
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
+ }
48973
49186
  setZoneMembershipMinOverlap(minOverlap) {
48974
49187
  this.zoneMembershipMinOverlap = Number.isFinite(minOverlap) && minOverlap >= 0 && minOverlap <= 1 ? minOverlap : 0;
48975
49188
  }
@@ -49362,10 +49575,16 @@ var FrameProcessor = class {
49362
49575
  }
49363
49576
  trackerInput.push(det);
49364
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);
49365
49583
  const trackedDetections = this.tracker.update(trackerInput, timestamp, {
49366
49584
  frameWidth,
49367
49585
  frameHeight,
49368
- continuationOnly
49586
+ continuationOnly,
49587
+ lowScore
49369
49588
  });
49370
49589
  const stationaryContinued = continuableBoxes.size === 0 ? 0 : trackedDetections.filter((t) => t.matchedThisFrame !== false && continuableBoxes.has(t.bbox)).length;
49371
49590
  const riderFolds = foldByVehicleBbox.size === 0 ? EMPTY_RIDER_FOLDS : [...foldByVehicleBbox].map(([bbox, fold]) => {
@@ -49607,6 +49826,19 @@ var FrameProcessor = class {
49607
49826
  }
49608
49827
  };
49609
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
49610
49842
  //#region src/pipeline-analytics/pipeline/track-provenance.ts
49611
49843
  function trackProvenanceOf(frame) {
49612
49844
  if (!(frame.width > 0 && frame.height > 0)) return void 0;
@@ -53746,11 +53978,10 @@ var EventStore = class {
53746
53978
  let entryId;
53747
53979
  for (const row of rows) {
53748
53980
  const bbox = row.data["bbox"];
53749
- if (bbox !== null && typeof bbox === "object") {
53750
- const bw = "w" in bbox && typeof bbox.w === "number" ? bbox.w : 0;
53751
- const bh = "h" in bbox && typeof bbox.h === "number" ? bbox.h : 0;
53752
- if (bw <= 0 || bh <= 0) continue;
53753
- }
53981
+ if (bbox === null || typeof bbox !== "object") continue;
53982
+ const bw = "w" in bbox && typeof bbox.w === "number" ? bbox.w : 0;
53983
+ const bh = "h" in bbox && typeof bbox.h === "number" ? bbox.h : 0;
53984
+ if (bw <= 0 || bh <= 0) continue;
53754
53985
  const conf = typeof row.data["confidence"] === "number" ? row.data["confidence"] : 0;
53755
53986
  if (conf > bestConf) {
53756
53987
  bestConf = conf;
@@ -62453,6 +62684,26 @@ var EmbeddingDispatcher = class {
62453
62684
  return this.pendingCrops.size;
62454
62685
  }
62455
62686
  };
62687
+ //#endregion
62688
+ //#region src/pipeline-analytics/embedding-rebuild.ts
62689
+ /**
62690
+ * Track sources a rebuild never lists (D664). An audio track and a synthetic
62691
+ * sensor track have no object event and no subject to embed — on the live hub
62692
+ * 6,726 + 180 of 42,254 tracks — and walking them only inflated the skip
62693
+ * counters an operator reads to judge media coverage.
62694
+ */
62695
+ var REBUILD_EXCLUDED_SOURCES = ["audio", "sensor"];
62696
+ /**
62697
+ * One page of the rebuild listing as a `queryRecentIds` query. Pure, so the
62698
+ * exclusion is assertable: dropping it would look identical in every result
62699
+ * except the skip counters.
62700
+ */
62701
+ function rebuildTrackPageQuery(window) {
62702
+ return {
62703
+ ...window,
62704
+ excludeSources: REBUILD_EXCLUDED_SOURCES
62705
+ };
62706
+ }
62456
62707
  /**
62457
62708
  * Re-embed every listed track from its key frame.
62458
62709
  *
@@ -62468,6 +62719,14 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62468
62719
  let recoveredKeyFrame = 0;
62469
62720
  let missingEvent = 0;
62470
62721
  let missingBbox = 0;
62722
+ let boxSubject = 0;
62723
+ let boxPositions = 0;
62724
+ let boxEvent = 0;
62725
+ let boxRefused = 0;
62726
+ let forcedInSpace = 0;
62727
+ let skippedOutOfSpace = 0;
62728
+ const refusalsByDevice = /* @__PURE__ */ new Map();
62729
+ const force = options.force === true;
62471
62730
  let alreadyInSpace = 0;
62472
62731
  let notRunnable = 0;
62473
62732
  let noCapableNode = 0;
@@ -62478,6 +62737,12 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62478
62737
  let budgetExhausted = false;
62479
62738
  /** Set when the cluster ran out of nodes — the pass stops, `complete` is false. */
62480
62739
  let abandoned = null;
62740
+ const boxSourceCounts = () => ({
62741
+ subjectBox: boxSubject,
62742
+ positions: boxPositions,
62743
+ event: boxEvent,
62744
+ refused: boxRefused
62745
+ });
62481
62746
  while (scanned < maxTracks && abandoned === null) {
62482
62747
  const page = await deps.listTrackIdPage(cursor);
62483
62748
  if (page.trackIds.length === 0) {
@@ -62485,9 +62750,14 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62485
62750
  break;
62486
62751
  }
62487
62752
  let pageIds = page.trackIds;
62753
+ let forcedPage = false;
62488
62754
  if (deps.filterOutOfSpace) {
62489
62755
  const split = await deps.filterOutOfSpace(page.trackIds);
62490
- if (split !== null) {
62756
+ if (split !== null && force) {
62757
+ skippedOutOfSpace += split.outOfSpace.length;
62758
+ pageIds = split.inSpace;
62759
+ forcedPage = true;
62760
+ } else if (split !== null) {
62491
62761
  alreadyInSpace += split.alreadyInSpace;
62492
62762
  pageIds = split.outOfSpace;
62493
62763
  }
@@ -62503,6 +62773,7 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62503
62773
  const room = maxTracks - scanned;
62504
62774
  const batch = pageIds.slice(0, Number.isFinite(room) ? room : void 0);
62505
62775
  if (batch.length < pageIds.length) budgetExhausted = true;
62776
+ if (forcedPage) forcedInSpace += batch.length;
62506
62777
  for (const trackId of batch) {
62507
62778
  if (!deps.canDispatch()) {
62508
62779
  abandoned = "no node left in the rotation can serve the pinned model — stopped before loading any further media";
@@ -62521,6 +62792,25 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62521
62792
  missingEvent += 1;
62522
62793
  return;
62523
62794
  }
62795
+ if (source.bbox === null || source.bbox.w <= 0 || source.bbox.h <= 0) {
62796
+ missingBbox += 1;
62797
+ boxRefused += 1;
62798
+ const reason = source.boxSource.kind === "refused" ? source.boxSource.reason : "degenerate-box";
62799
+ const n = (refusalsByDevice.get(source.deviceId) ?? 0) + 1;
62800
+ refusalsByDevice.set(source.deviceId, n);
62801
+ if (n === 1 || n % 100 === 0) deps.logger.warn("embedding rebuild: no box measured on the key frame — track refused (D303)", {
62802
+ tags: { deviceId: source.deviceId },
62803
+ meta: {
62804
+ trackId,
62805
+ reason,
62806
+ refusedOnThisCamera: n
62807
+ }
62808
+ });
62809
+ return;
62810
+ }
62811
+ if (source.boxSource.kind === "subjectBox") boxSubject += 1;
62812
+ else if (source.boxSource.kind === "positions") boxPositions += 1;
62813
+ else boxEvent += 1;
62524
62814
  let keyFrame = source.keyFrame;
62525
62815
  let recovered = false;
62526
62816
  if (keyFrame === null && deps.recoverKeyFrame) {
@@ -62532,10 +62822,6 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62532
62822
  return;
62533
62823
  }
62534
62824
  if (recovered) recoveredKeyFrame += 1;
62535
- if (source.bbox === null || source.bbox.w <= 0 || source.bbox.h <= 0) {
62536
- missingBbox += 1;
62537
- return;
62538
- }
62539
62825
  const outcome = await deps.embed(recovered ? {
62540
62826
  ...source,
62541
62827
  keyFrame
@@ -62603,6 +62889,9 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62603
62889
  recoveredKeyFrame,
62604
62890
  missingEvent,
62605
62891
  missingBbox,
62892
+ boxSource: boxSourceCounts(),
62893
+ forcedInSpace,
62894
+ skippedOutOfSpace,
62606
62895
  alreadyInSpace,
62607
62896
  notRunnable,
62608
62897
  noCapableNode,
@@ -62626,6 +62915,10 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62626
62915
  recoveredKeyFrame,
62627
62916
  missingEvent,
62628
62917
  missingBbox,
62918
+ boxSource: boxSourceCounts(),
62919
+ force,
62920
+ forcedInSpace,
62921
+ skippedOutOfSpace,
62629
62922
  alreadyInSpace,
62630
62923
  notRunnable,
62631
62924
  noCapableNode,
@@ -62640,6 +62933,9 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62640
62933
  recoveredKeyFrame,
62641
62934
  missingEvent,
62642
62935
  missingBbox,
62936
+ boxSource: boxSourceCounts(),
62937
+ forcedInSpace,
62938
+ skippedOutOfSpace,
62643
62939
  alreadyInSpace,
62644
62940
  notRunnable,
62645
62941
  noCapableNode,
@@ -63198,8 +63494,6 @@ function faceSizeScore(detectionFrameShortSidePx) {
63198
63494
  }
63199
63495
  //#endregion
63200
63496
  //#region src/pipeline-analytics/face-recognizer.ts
63201
- /** Attribution of an identity resolved by the in-process ArcFace matcher. */
63202
- var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
63203
63497
  /** At most one "dropping imageless track" log per this interval, per recognizer. */
63204
63498
  var FACE_IMAGELESS_LOG_THROTTLE_MS = 6e4;
63205
63499
  /** At most one 'embedding magnitude' sample line per device per this interval. */
@@ -63225,6 +63519,9 @@ var FaceRecognizer = class {
63225
63519
  gallery = [];
63226
63520
  names = /* @__PURE__ */ new Map();
63227
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();
63228
63525
  bestFace = /* @__PURE__ */ new Map();
63229
63526
  /** Frame instant of each live track's last source park (D644). */
63230
63527
  lastSourceParkAt = /* @__PURE__ */ new Map();
@@ -63416,9 +63713,35 @@ var FaceRecognizer = class {
63416
63713
  * • the face becomes non-promotable: {@link onTrackEnd} will not write it to
63417
63714
  * the gallery while the flag stands.
63418
63715
  *
63419
- * The aggregate is deliberately left alone — see the note in `onTrackEnd`.
63420
- */
63421
- 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
+ }
63422
63745
  const held = this.bestFace.get(trackId);
63423
63746
  if (held === void 0 || held.unverified === true) return;
63424
63747
  const quarantined = {
@@ -63441,6 +63764,7 @@ var FaceRecognizer = class {
63441
63764
  trackId
63442
63765
  },
63443
63766
  meta: {
63767
+ scene,
63444
63768
  heldScore: held.score,
63445
63769
  heldAt: held.timestamp,
63446
63770
  strippedIdentityId: held.recognizedIdentityId ?? null,
@@ -63449,6 +63773,40 @@ var FaceRecognizer = class {
63449
63773
  });
63450
63774
  }
63451
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
+ /**
63452
63810
  * Ask a FRESH face whether the quarantined held face is still about the same
63453
63811
  * person, at the same bar the gallery uses to say two vectors are one person
63454
63812
  * (`similarityThreshold` — one knob over one band, D222).
@@ -63470,7 +63828,7 @@ var FaceRecognizer = class {
63470
63828
  }
63471
63829
  async processFrame(input) {
63472
63830
  const { settings } = input;
63473
- 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);
63474
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);
63475
63833
  if (candidates.length === 0) return;
63476
63834
  this.deps.logger.debug("face: frame candidates", {
@@ -63537,11 +63895,26 @@ var FaceRecognizer = class {
63537
63895
  embedding: c.embedding,
63538
63896
  embeddingModelId: c.embeddingModelId
63539
63897
  }, settings.similarityThreshold);
63540
- if (verdict === "confirmed") this.bestFace.set(c.trackId, {
63541
- ...quarantined,
63542
- unverified: false
63543
- });
63544
- 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);
63545
63918
  this.bestFace.delete(c.trackId);
63546
63919
  this.bestTracker.delete(c.trackId);
63547
63920
  this.aggregates.delete(c.trackId);
@@ -63756,6 +64129,7 @@ var FaceRecognizer = class {
63756
64129
  const bestMatchScore = this.bestMatchScore.get(trackId);
63757
64130
  const bestSuggestion = this.bestSuggestion.get(trackId);
63758
64131
  this.aggregates.delete(trackId);
64132
+ this.stashedIdentity.delete(trackId);
63759
64133
  this.bestFace.delete(trackId);
63760
64134
  this.bestTracker.delete(trackId);
63761
64135
  this.bestMatchScore.delete(trackId);
@@ -67917,6 +68291,19 @@ function shouldSkipDropoutFrame(activeTrackCount, detectionCount, consecutiveSki
67917
68291
  if (activeTrackCount <= 0) return false;
67918
68292
  return consecutiveSkips < cfg.maxSkipFrames;
67919
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
+ }
67920
68307
  //#endregion
67921
68308
  //#region src/pipeline-analytics/pipeline/key-event-query.ts
67922
68309
  async function rankKeyEvents(candidates, options, peakLookup) {
@@ -73038,6 +73425,58 @@ function hasKeyFrame(source) {
73038
73425
  }
73039
73426
  //#endregion
73040
73427
  //#region src/pipeline-analytics/rebuild-source-loader.ts
73428
+ function refused(reason) {
73429
+ return {
73430
+ bbox: null,
73431
+ boxSource: {
73432
+ kind: "refused",
73433
+ reason
73434
+ }
73435
+ };
73436
+ }
73437
+ function normaliseMeasured(box) {
73438
+ if (box.frameWidth <= 0 || box.frameHeight <= 0 || box.w <= 0 || box.h <= 0) return null;
73439
+ return {
73440
+ x: box.x / box.frameWidth,
73441
+ y: box.y / box.frameHeight,
73442
+ w: box.w / box.frameWidth,
73443
+ h: box.h / box.frameHeight
73444
+ };
73445
+ }
73446
+ /**
73447
+ * The box measured on THIS key frame: its own `subjectBox`, else the track
73448
+ * position at exactly its instant, else a named refusal. Never another
73449
+ * frame's box (D303).
73450
+ */
73451
+ async function keyFrameBox(deps, trackId, keyFrame) {
73452
+ const own = (await deps.media.listSubjectBoxesByOwner("track", trackId)).find((row) => row.mediaId === keyFrame.mediaId);
73453
+ if (own !== void 0) {
73454
+ const bbox = normaliseMeasured(own.subjectBox);
73455
+ if (bbox !== null) return {
73456
+ bbox,
73457
+ boxSource: { kind: "subjectBox" }
73458
+ };
73459
+ }
73460
+ const track = await deps.tracks.getPersistedByTrackId(trackId);
73461
+ if (track === null) return refused("track-row-missing");
73462
+ const { frameWidth, frameHeight } = track;
73463
+ if (frameWidth === void 0 || frameHeight === void 0) return refused("track-frame-size-unknown");
73464
+ const at = track.positions.find((p) => p.timestamp === keyFrame.timestamp);
73465
+ if (at === void 0) return refused("no-position-at-key-frame");
73466
+ const bbox = normaliseMeasured({
73467
+ ...at.bbox,
73468
+ frameWidth,
73469
+ frameHeight
73470
+ });
73471
+ if (bbox === null) return refused("no-position-at-key-frame");
73472
+ return {
73473
+ bbox,
73474
+ boxSource: { kind: "positions" }
73475
+ };
73476
+ }
73477
+ function hasBox(event) {
73478
+ return event.bbox !== void 0 && event.bbox.w > 0 && event.bbox.h > 0;
73479
+ }
73041
73480
  /**
73042
73481
  * Assemble one track's rebuild inputs, reading at most ONE blob.
73043
73482
  *
@@ -73045,7 +73484,8 @@ function hasKeyFrame(source) {
73045
73484
  * to describe, and the caller counts it. A track WITH an event but WITHOUT a
73046
73485
  * key frame still returns a source (with `keyFrame: null`) so the caller's
73047
73486
  * `missingKeyFrame` counter stays the one place that decision is made; no blob
73048
- * is read in that case either.
73487
+ * is read in that case either. A key frame with no box measured on it returns
73488
+ * `bbox: null` and a named refusal, also without reading the blob.
73049
73489
  */
73050
73490
  async function loadRebuildSource(deps, trackId) {
73051
73491
  const media = await deps.media.listInfoByOwner("track", trackId);
@@ -73053,26 +73493,44 @@ async function loadRebuildSource(deps, trackId) {
73053
73493
  const cropInfo = media.find((m) => m.kind === "thumbnail");
73054
73494
  const peak = await deps.events.peakForTrack(trackId);
73055
73495
  const events = await deps.events.queryObjectByTrackIds([trackId]);
73056
- const best = events.find((e) => String(e.id) === peak.bestEventId) ?? events[0];
73496
+ const best = events.find((e) => String(e.id) === peak.bestEventId) ?? events.find(hasBox) ?? events[0];
73057
73497
  if (best === void 0) return null;
73058
73498
  const common = {
73059
73499
  trackId,
73060
73500
  deviceId: best.deviceId,
73061
- timestamp: best.timestamp,
73062
73501
  className: best.className,
73063
73502
  confidence: best.confidence ?? 0,
73064
- bbox: normaliseEventBbox(best),
73065
73503
  ...cropInfo !== void 0 ? { mediaId: cropInfo.mediaId } : {}
73066
73504
  };
73067
- if (keyFrameInfo === void 0) return {
73505
+ if (keyFrameInfo === void 0) {
73506
+ const bbox = normaliseEventBbox(best);
73507
+ return {
73508
+ ...common,
73509
+ timestamp: best.timestamp,
73510
+ bbox,
73511
+ boxSource: bbox !== null ? { kind: "event" } : {
73512
+ kind: "refused",
73513
+ reason: "event-without-box"
73514
+ },
73515
+ keyFrame: null
73516
+ };
73517
+ }
73518
+ const box = await keyFrameBox(deps, trackId, keyFrameInfo);
73519
+ const keyed = {
73068
73520
  ...common,
73521
+ timestamp: keyFrameInfo.timestamp,
73522
+ bbox: box.bbox,
73523
+ boxSource: box.boxSource,
73524
+ keyFrameMediaId: keyFrameInfo.mediaId
73525
+ };
73526
+ if (box.bbox === null) return {
73527
+ ...keyed,
73069
73528
  keyFrame: null
73070
73529
  };
73071
73530
  const keyFrameFile = await deps.media.getById(keyFrameInfo.mediaId);
73072
73531
  return {
73073
- ...common,
73074
- keyFrame: keyFrameFile ? Buffer.from(keyFrameFile.base64, "base64") : null,
73075
- keyFrameMediaId: keyFrameInfo.mediaId
73532
+ ...keyed,
73533
+ keyFrame: keyFrameFile ? Buffer.from(keyFrameFile.base64, "base64") : null
73076
73534
  };
73077
73535
  }
73078
73536
  //#endregion
@@ -82799,7 +83257,21 @@ var TrackingSettingsSchema = require_clip_model_registry.object({
82799
83257
  * passes, only for a coarse box below `animalOverPersonMaxScore`, never on
82800
83258
  * the rescue / resurrection passes. Reported label stays via the class
82801
83259
  * vote. Off = exact per-frame class gating. */
82802
- 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)
82803
83275
  });
82804
83276
  var TRACKING_DEFAULTS = TrackingSettingsSchema.parse({});
82805
83277
  /**
@@ -82854,7 +83326,11 @@ function resolveTrackingSettings(raw) {
82854
83326
  minSpawnAreaFracPerson: s.minSpawnAreaFracPerson.catch(TRACKING_DEFAULTS.minSpawnAreaFracPerson).parse(raw.minSpawnAreaFracPerson),
82855
83327
  minSpawnAreaFracAnimal: s.minSpawnAreaFracAnimal.catch(TRACKING_DEFAULTS.minSpawnAreaFracAnimal).parse(raw.minSpawnAreaFracAnimal),
82856
83328
  minSpawnAreaFracVehicle: s.minSpawnAreaFracVehicle.catch(TRACKING_DEFAULTS.minSpawnAreaFracVehicle).parse(raw.minSpawnAreaFracVehicle),
82857
- 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)
82858
83334
  };
82859
83335
  }
82860
83336
  //#endregion
@@ -83284,6 +83760,40 @@ function buildDetectionSettingsSections() {
83284
83760
  step: .05,
83285
83761
  default: TRACKING_DEFAULTS.byteTrackLowIouThreshold
83286
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
+ },
83287
83797
  {
83288
83798
  type: "boolean",
83289
83799
  key: "minSpawnAreaEnabled",
@@ -83621,7 +84131,7 @@ function buildGlobalSettingsSchema(cap) {
83621
84131
  addonId: "pipeline-analytics",
83622
84132
  action: "embedding.rebuildStatus",
83623
84133
  buttonLabel: "Check progress",
83624
- successMessage: "{modelId} → {index}: indexed {indexed} · scanned {scanned} · rebuilt {rebuilt} · no key frame {missingKeyFrame} · not runnable {notRunnable} · failed {failed}"
84134
+ successMessage: "{modelId} → {index}: indexed {indexed} · scanned {scanned} · rebuilt {rebuilt} · no key frame {missingKeyFrame} · no box {missingBbox} · no event {missingEvent} · not runnable {notRunnable} · failed {failed}"
83625
84135
  },
83626
84136
  {
83627
84137
  type: "addon-action-button",
@@ -87784,6 +88294,8 @@ function decodeEmbeddingBase64(base64) {
87784
88294
  var PHANTOM_CELL_PX = 32;
87785
88295
  /** How long a cell remembers its closes. */
87786
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;
87787
88299
  var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_model_registry.BaseAddon {
87788
88300
  /** Recent SHORT+MOTIONLESS track closes per `<device>:<class>:<cell>` —
87789
88301
  * see {@link noteShortMotionlessTrack}. Measurement only; each entry is
@@ -88451,6 +88963,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
88451
88963
  packageBackground = null;
88452
88964
  /** Consecutive detector-dropout frames skipped per (deviceId, source) key. */
88453
88965
  dropoutSkipsByKey = /* @__PURE__ */ new Map();
88966
+ /** Last time each processor's low-score continuation counts were logged (D665). */
88967
+ lowScoreReportAtByKey = /* @__PURE__ */ new Map();
88454
88968
  /** The per-track close-path orchestrator (S2, refactor spec 2026-07-22):
88455
88969
  * owns the single-flight sweep latch + the FULL per-track close sequence
88456
88970
  * (zero-media policy, lastFrame promotion, recognizer ends with the
@@ -90009,6 +90523,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
90009
90523
  mediaStore: stores.mediaStore,
90010
90524
  trackStore: stores.trackStore,
90011
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),
90012
90534
  captureCrop: (frameHandle, bbox, frameWidth, frameHeight, padding, ownerId, deviceId) => this.captureScheduler.request({
90013
90535
  deviceId,
90014
90536
  trackId: ownerId,
@@ -91963,6 +92485,31 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
91963
92485
  * tracking/zone/event state never crosses between sources. Emits the
91964
92486
  * SAME canonical events, distinguished only by `source`.
91965
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
+ }
91966
92513
  async processFrame(deviceId, frame, source, rawFrameHandle, detailSteps, occasion = "session") {
91967
92514
  if (this.shuttingDown) return;
91968
92515
  if (!await this.bindingCache.isActive(deviceId)) {
@@ -91987,7 +92534,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
91987
92534
  const trk = await this.resolveDeviceTrackingSettings(deviceId);
91988
92535
  const activeCount = this.lastActiveTrackIds.get(key)?.size ?? 0;
91989
92536
  const skips = this.dropoutSkipsByKey.get(key) ?? 0;
91990
- if (shouldSkipDropoutFrame(activeCount, frame.detections.length, skips, {
92537
+ const lowScoreFloor = trk.lowScoreContinuationEnabled ? trk.lowScoreContinuationFloor : null;
92538
+ if (shouldSkipDropoutFrame(activeCount, dropoutEvidenceCount(frame, lowScoreFloor), skips, {
91991
92539
  enabled: trk.dropoutSkipEnabled,
91992
92540
  maxSkipFrames: trk.dropoutMaxSkipFrames
91993
92541
  })) {
@@ -92015,6 +92563,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
92015
92563
  processor.setZones(liveZones);
92016
92564
  processor.setDetectionRules(liveRules);
92017
92565
  processor.setZoneMembershipMinOverlap(trk.zoneMembershipMinOverlap);
92566
+ processor.setLowScoreContinuationFloor(lowScoreFloor);
92018
92567
  const injection = injectSecondarySubjects(frame, this.secondarySubjects.take(key, Date.now()));
92019
92568
  for (const id of injection.covered) this.secondarySubjects.drop(key, id);
92020
92569
  const trackerFrame = injection.frame;
@@ -92030,6 +92579,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
92030
92579
  timestamp: frame.timestamp,
92031
92580
  frame: trackerFrame
92032
92581
  });
92582
+ this.reportLowScoreContinuation(deviceId, key, processor);
92033
92583
  for (const r of processor.getLastLabelWriteRefusals()) {
92034
92584
  const message = r.reason.kind === "macro-class" ? "label tier collapse refused" : "label write refused";
92035
92585
  this.ctx.logger.info(message, {
@@ -95717,7 +96267,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
95717
96267
  animal: trk.minSpawnAreaFracAnimal,
95718
96268
  vehicle: trk.minSpawnAreaFracVehicle
95719
96269
  },
95720
- classGroupAssoc: trk.classGroupAssoc
96270
+ classGroupAssoc: trk.classGroupAssoc,
96271
+ lowScoreContinuationEnabled: trk.lowScoreContinuationEnabled,
96272
+ lowScoreContinuationMaxMs: trk.lowScoreContinuationMaxMs,
96273
+ resurrectionTimeScaled: trk.resurrectionTimeScaled
95721
96274
  }, {
95722
96275
  stationaryThresholdSec,
95723
96276
  ...stillSpeedFracPerSec !== void 0 ? { stillSpeedFracPerSec } : {}
@@ -97208,6 +97761,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97208
97761
  refusal: "stores-unavailable"
97209
97762
  };
97210
97763
  }
97764
+ const force = input.force === true;
97765
+ if (force && input.maxTracks === void 0) {
97766
+ this.ctx.logger.warn("rebuildObjectEmbeddings: refusing a force pass with no maxTracks", { meta: { refusal: "force-requires-max-tracks" } });
97767
+ return {
97768
+ started: false,
97769
+ alreadyRunning: false,
97770
+ refusal: "force-requires-max-tracks"
97771
+ };
97772
+ }
97211
97773
  const confirmation = await this.getClusterPinConfirmer(api, "clip-rebuild").confirm();
97212
97774
  if (confirmation.kind === "refused") {
97213
97775
  this.ctx.logger.warn("rebuildObjectEmbeddings: the cluster CLIP model is not confirmed — refusing to start a pass that cannot pin one", { meta: { refusal: confirmation.reason } });
@@ -97251,16 +97813,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97251
97813
  logger: this.ctx.logger
97252
97814
  }));
97253
97815
  const deps = {
97254
- listTrackIdPage: async (cursor) => trackStore.queryRecentIds({
97816
+ listTrackIdPage: async (cursor) => trackStore.queryRecentIds(rebuildTrackPageQuery({
97255
97817
  deviceIds,
97256
97818
  ...input.since !== void 0 ? { since: input.since } : {},
97257
97819
  ...input.until !== void 0 ? { until: input.until } : {},
97258
97820
  ...cursor !== null ? { cursor } : {},
97259
97821
  limit: REBUILD_PAGE_SIZE
97260
- }),
97822
+ })),
97261
97823
  loadSource: (trackId) => loadRebuildSource({
97262
97824
  media: mediaStore,
97263
- events: eventStore
97825
+ events: eventStore,
97826
+ tracks: trackStore
97264
97827
  }, trackId),
97265
97828
  recoverKeyFrame: async (source) => {
97266
97829
  const recovered = await recordedStills.recover(source.deviceId, source.timestamp, RECOVERED_KEY_FRAME_MAX_WIDTH);
@@ -97286,7 +97849,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97286
97849
  if (split === null) return null;
97287
97850
  return {
97288
97851
  outOfSpace: split.outOfSpaceIds,
97289
- alreadyInSpace: split.inSpace
97852
+ alreadyInSpace: split.inSpace,
97853
+ inSpace: split.inSpaceIds
97290
97854
  };
97291
97855
  },
97292
97856
  logger: {
@@ -97298,6 +97862,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97298
97862
  running: true,
97299
97863
  modelId,
97300
97864
  index,
97865
+ force,
97301
97866
  startedAtMs: Date.now(),
97302
97867
  progress: {
97303
97868
  scanned: 0,
@@ -97306,6 +97871,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97306
97871
  recoveredKeyFrame: 0,
97307
97872
  missingEvent: 0,
97308
97873
  missingBbox: 0,
97874
+ boxSource: {
97875
+ subjectBox: 0,
97876
+ positions: 0,
97877
+ event: 0,
97878
+ refused: 0
97879
+ },
97880
+ forcedInSpace: 0,
97881
+ skippedOutOfSpace: 0,
97309
97882
  alreadyInSpace: 0,
97310
97883
  notRunnable: 0,
97311
97884
  noCapableNode: 0,
@@ -97313,6 +97886,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97313
97886
  }
97314
97887
  };
97315
97888
  rebuildObjectEmbeddings(deps, {
97889
+ force,
97316
97890
  ...input.maxTracks !== void 0 ? { maxTracks: input.maxTracks } : {},
97317
97891
  ...input.pacingMs !== void 0 ? { pacingMs: input.pacingMs } : {},
97318
97892
  onProgress: (progress) => {
@@ -97324,6 +97898,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97324
97898
  running: false,
97325
97899
  modelId,
97326
97900
  index,
97901
+ force,
97327
97902
  startedAtMs: this.rebuildState.startedAtMs ?? Date.now(),
97328
97903
  finishedAtMs: Date.now(),
97329
97904
  progress: outcome,
@@ -97339,6 +97914,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97339
97914
  running: false,
97340
97915
  modelId,
97341
97916
  index,
97917
+ force,
97342
97918
  startedAtMs: this.rebuildState.startedAtMs ?? Date.now(),
97343
97919
  finishedAtMs: Date.now(),
97344
97920
  error: String(err)
@@ -97353,7 +97929,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97353
97929
  modelId,
97354
97930
  index,
97355
97931
  deviceId: input.deviceId ?? "all",
97356
- maxTracks: input.maxTracks ?? "unbounded"
97932
+ maxTracks: input.maxTracks ?? "unbounded",
97933
+ force
97357
97934
  } });
97358
97935
  return {
97359
97936
  started: true,
@@ -98355,8 +98932,18 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
98355
98932
  running: state.running,
98356
98933
  scanned: p?.scanned ?? 0,
98357
98934
  rebuilt: p?.rebuilt ?? 0,
98358
- missingKeyFrame: (p?.missingKeyFrame ?? 0) + (p?.missingEvent ?? 0),
98935
+ missingKeyFrame: p?.missingKeyFrame ?? 0,
98936
+ missingEvent: p?.missingEvent ?? 0,
98359
98937
  missingBbox: p?.missingBbox ?? 0,
98938
+ boxSource: p?.boxSource ?? {
98939
+ subjectBox: 0,
98940
+ positions: 0,
98941
+ event: 0,
98942
+ refused: 0
98943
+ },
98944
+ alreadyInSpace: p?.alreadyInSpace ?? 0,
98945
+ force: state.force ?? null,
98946
+ forcedInSpace: p?.forcedInSpace ?? 0,
98360
98947
  notRunnable: p?.notRunnable ?? 0,
98361
98948
  noCapableNode: p?.noCapableNode ?? 0,
98362
98949
  failed: p?.failed ?? 0,