@camstack/addon-post-analysis 1.2.4 → 1.2.6

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_dist = require("../dist-Ck79MtSp.js");
5
+ const require_dist = require("../dist-BvEJmXbR.js");
6
6
  let node_crypto = require("node:crypto");
7
7
  let sharp = require("sharp");
8
8
  sharp = require_dist.__toESM(sharp);
@@ -2091,6 +2091,7 @@ var FrameProcessor = class {
2091
2091
  const firstLevelBboxById = /* @__PURE__ */ new Map();
2092
2092
  const sourceIdByBbox = /* @__PURE__ */ new Map();
2093
2093
  const faceBboxByBbox = /* @__PURE__ */ new Map();
2094
+ const nativeFaceSizeByBbox = /* @__PURE__ */ new Map();
2094
2095
  const faceAlignedCropByBbox = /* @__PURE__ */ new Map();
2095
2096
  const plateByBbox = /* @__PURE__ */ new Map();
2096
2097
  const maskByBbox = /* @__PURE__ */ new Map();
@@ -2136,6 +2137,7 @@ var FrameProcessor = class {
2136
2137
  h: det.bbox.height
2137
2138
  });
2138
2139
  if (det.faceAlignedCrop !== void 0) faceAlignedCropByBbox.set(parentBbox, det.faceAlignedCrop);
2140
+ if (det.nativeFaceShortSidePx !== void 0) nativeFaceSizeByBbox.set(parentBbox, det.nativeFaceShortSidePx);
2139
2141
  if (det.embedding !== void 0 && !embeddingByBbox.has(parentBbox)) embeddingByBbox.set(parentBbox, {
2140
2142
  embedding: det.embedding,
2141
2143
  ...det.embeddingModelId !== void 0 ? { embeddingModelId: det.embeddingModelId } : {}
@@ -2191,6 +2193,7 @@ var FrameProcessor = class {
2191
2193
  });
2192
2194
  const emb = embeddingByBbox.get(td.bbox);
2193
2195
  const faceBbox = faceBboxByBbox.get(td.bbox);
2196
+ const nativeFaceShortSidePx = nativeFaceSizeByBbox.get(td.bbox);
2194
2197
  const faceAlignedCrop = faceAlignedCropByBbox.get(td.bbox);
2195
2198
  const plate = plateByBbox.get(td.bbox);
2196
2199
  const sourceDetectionId = sourceIdByBbox.get(td.bbox);
@@ -2209,6 +2212,7 @@ var FrameProcessor = class {
2209
2212
  ...emb.embeddingModelId !== void 0 ? { embeddingModelId: emb.embeddingModelId } : {}
2210
2213
  } : {},
2211
2214
  ...faceBbox !== void 0 ? { faceBbox } : {},
2215
+ ...nativeFaceShortSidePx !== void 0 ? { nativeFaceShortSidePx } : {},
2212
2216
  ...faceAlignedCrop !== void 0 ? { faceAlignedCrop } : {},
2213
2217
  ...plate !== void 0 ? {
2214
2218
  plateText: plate.text,
@@ -2345,6 +2349,10 @@ function buildTrackLifecyclePayload(input) {
2345
2349
  ...hasMedia ? { media } : {}
2346
2350
  };
2347
2351
  }
2352
+ //#endregion
2353
+ //#region src/pipeline-analytics/pipeline/edge-clear.ts
2354
+ /** Default border tolerance — 1% of each dimension. */
2355
+ var DEFAULT_EDGE_TOLERANCE = .01;
2348
2356
  /**
2349
2357
  * True when `bbox` sits fully inside the frame — no side within the tolerance
2350
2358
  * band of any border. Degenerate/unknown frame dims (≤ 0) return true so the
@@ -2390,6 +2398,104 @@ function bboxCenterScore(bbox, frameWidth, frameHeight) {
2390
2398
  const dist = Math.hypot(dx, dy) / Math.SQRT2;
2391
2399
  return Math.max(0, Math.min(1, 1 - dist));
2392
2400
  }
2401
+ /** Maximum fraction of a candidate's score a SINGLE fully-spanning clipped side
2402
+ * can subtract (the edge-containment penalty). A clipped side that spans the
2403
+ * whole of its border removes up to this much; a side that only grazes the
2404
+ * border (small perpendicular span) removes proportionally less. 0.5 = a
2405
+ * subject flush against — and filling — one border loses half its effective
2406
+ * score, so a fully-contained near-equal detection outranks it, while a
2407
+ * DECISIVELY higher-confidence clipped detection still wins. */
2408
+ var CONTAINMENT_MAX_SIDE_PENALTY = .5;
2409
+ /** Floor on the containment factor — a subject clipped on several borders never
2410
+ * drops to zero (which would annihilate its score and could leave a track with
2411
+ * NO best frame at all). */
2412
+ var CONTAINMENT_MIN = .1;
2413
+ /**
2414
+ * Edge-CONTAINMENT factor for a (clamped) bbox: an estimate in
2415
+ * `[CONTAINMENT_MIN, 1]` of the fraction of the subject that is actually in
2416
+ * frame. 1 = fully contained (no border clipping); lower = the subject is
2417
+ * flush against one or more borders and is probably truncated (half of a person
2418
+ * walking out of view).
2419
+ *
2420
+ * Because detection bboxes are already CLAMPED to the frame, the true off-screen
2421
+ * extent is unknown, so this is a purely-geometric proxy: for each border the
2422
+ * bbox touches (within the {@link DEFAULT_EDGE_TOLERANCE} band), the penalty is
2423
+ * scaled by how much of that border the bbox spans on the perpendicular axis —
2424
+ * i.e. the subject's aspect against that edge. A wide bbox flush to the
2425
+ * top/bottom (a wide subject cut off top/bottom) or a tall bbox flush to the
2426
+ * left/right (a tall subject cut off at the side) is heavily penalised, whereas
2427
+ * a narrow subject whose feet merely graze the bottom border keeps most of its
2428
+ * score. Degenerate/unknown dims (≤ 0) return 1 (neutral — matches
2429
+ * {@link isEdgeClear}).
2430
+ */
2431
+ function bboxContainment(bbox, frameWidth, frameHeight, tolerance = DEFAULT_EDGE_TOLERANCE) {
2432
+ if (frameWidth <= 0 || frameHeight <= 0) return 1;
2433
+ const tolX = tolerance * frameWidth;
2434
+ const tolY = tolerance * frameHeight;
2435
+ const left = bbox.x;
2436
+ const top = bbox.y;
2437
+ const right = bbox.x + bbox.w;
2438
+ const bottom = bbox.y + bbox.h;
2439
+ const spanX = Math.max(0, Math.min(1, bbox.w / frameWidth));
2440
+ const spanY = Math.max(0, Math.min(1, bbox.h / frameHeight));
2441
+ let containment = 1;
2442
+ const penalize = (span) => {
2443
+ containment *= 1 - CONTAINMENT_MAX_SIDE_PENALTY * span;
2444
+ };
2445
+ if (left <= tolX) penalize(spanY);
2446
+ if (right >= frameWidth - tolX) penalize(spanY);
2447
+ if (top <= tolY) penalize(spanX);
2448
+ if (bottom >= frameHeight - tolY) penalize(spanX);
2449
+ return Math.max(CONTAINMENT_MIN, Math.min(1, containment));
2450
+ }
2451
+ /** Fraction of the frame area at which a subject's size score saturates to 1.
2452
+ * 10% of the frame is already a large, close subject; anything bigger gains
2453
+ * no further preference (and the plausibility gate rejects exploded boxes). */
2454
+ var SIZE_SCORE_SATURATION_AREA_FRAC = .1;
2455
+ /** A candidate whose effective score beats the held peak by at least this
2456
+ * RATIO is a DECISIVE improvement: it bypasses the capture rate limit
2457
+ * (`minGapMs`) the way a tier upgrade does. Drive-through subjects hit their
2458
+ * best framing 1–3s after birth — exactly inside the rate-limit window — and
2459
+ * the "better shot" otherwise never gets captured (2026-07-22 audit:
2460
+ * BEST_SHOT_STALE on 9/63 tracks with 1.5–3.8× larger in-frame views). */
2461
+ var DECISIVE_IMPROVEMENT_RATIO = 1.25;
2462
+ /**
2463
+ * Subject-size score for the best-frame ranking: sqrt of the bbox's frame-area
2464
+ * fraction, saturating at {@link SIZE_SCORE_SATURATION_AREA_FRAC}. A bigger,
2465
+ * closer subject is a better SHOT even at equal detector confidence — raw
2466
+ * confidence does not correlate with human-perceived crop quality (a distant
2467
+ * 20px person can out-score a full-frame close-up). sqrt softens the term so
2468
+ * confidence still matters between similar sizes. Degenerate dims → 1
2469
+ * (neutral), matching {@link isEdgeClear}.
2470
+ */
2471
+ function bboxSizeScore(bbox, frameWidth, frameHeight) {
2472
+ if (frameWidth <= 0 || frameHeight <= 0) return 1;
2473
+ const areaFrac = bbox.w * bbox.h / (frameWidth * frameHeight);
2474
+ return Math.sqrt(Math.max(0, Math.min(1, areaFrac / SIZE_SCORE_SATURATION_AREA_FRAC)));
2475
+ }
2476
+ /** Floor of the size factor's influence: the raw sizeScore (0..1) is mapped to
2477
+ * `[SIZE_FACTOR_FLOOR, 1]` before scaling the confidence, so a tiny subject
2478
+ * halves its effective score at most. Keeps the effective scale comparable to
2479
+ * raw confidence — the ABSOLUTE hysteresis margin stays meaningful — while a
2480
+ * bigger view still earns up to a 2× relative preference. */
2481
+ var SIZE_FACTOR_FLOOR = .5;
2482
+ /** The effective within-tier score: detector confidence scaled by the
2483
+ * edge-containment and (floored) subject-size factors — each neutral at 1
2484
+ * when absent. */
2485
+ function effectiveScore(c) {
2486
+ const sizeFactor = c.sizeScore === void 0 ? 1 : SIZE_FACTOR_FLOOR + (1 - SIZE_FACTOR_FLOOR) * c.sizeScore;
2487
+ return c.confidence * (c.containment ?? 1) * sizeFactor;
2488
+ }
2489
+ /**
2490
+ * True when `candidate` beats `current` DECISIVELY — same-or-better edge tier
2491
+ * AND an effective score at least {@link DECISIVE_IMPROVEMENT_RATIO}× the held
2492
+ * peak's. Used by `BestDetectionTracker` to bypass the capture rate limit
2493
+ * (`minGapMs`) for genuinely better framings that land inside the rate window.
2494
+ */
2495
+ function isDecisiveImprovement(current, candidate) {
2496
+ if (!candidate.edgeClear && current.edgeClear) return false;
2497
+ return effectiveScore(candidate) >= effectiveScore(current) * DECISIVE_IMPROVEMENT_RATIO;
2498
+ }
2393
2499
  /**
2394
2500
  * Edge-aware "is `candidate` a new best over `current`?" comparator.
2395
2501
  *
@@ -2398,8 +2504,18 @@ function bboxCenterScore(bbox, frameWidth, frameHeight) {
2398
2504
  * confidence past the `hysteresis` margin wins. The tier upgrade
2399
2505
  * (touching → clear) bypasses hysteresis — the first clear frame is always taken.
2400
2506
  *
2507
+ * EDGE-CONTAINMENT (#edge-clip): within the same tier the comparison is on the
2508
+ * EFFECTIVE score `confidence × containment` (see {@link bboxContainment}), not
2509
+ * raw confidence — so a fully-contained detection outranks a larger/higher-
2510
+ * confidence but border-CLIPPED one unless the confidence margin is decisive.
2511
+ * The binary edge-clear tier still gates first (a whole subject beats a clipped
2512
+ * one regardless), and within the CLEAR tier both frames have containment 1, so
2513
+ * this is a no-op there; it discriminates WITHIN the touching tier (feet merely
2514
+ * grazing the bottom vs half the body out the side). `containment` defaults to
2515
+ * 1 when omitted (legacy face / object-embedding callers) → identical behaviour.
2516
+ *
2401
2517
  * CENTERING TIE-BREAK (#27-D): within the same tier, when neither frame clearly
2402
- * wins on confidence (the two are within the `hysteresis` band) but the
2518
+ * wins on the effective score (the two are within the `hysteresis` band) but the
2403
2519
  * candidate is meaningfully better CENTRED ({@link CENTER_TIE_BREAK_MARGIN}), the
2404
2520
  * candidate wins. This only engages when both sides carry a `centerScore` (the
2405
2521
  * best-frame path), so low-importance short tracks stop keeping an edge-of-frame
@@ -2412,9 +2528,11 @@ function bboxCenterScore(bbox, frameWidth, frameHeight) {
2412
2528
  function isEdgeAwareNewBest(current, candidate, hysteresis) {
2413
2529
  if (candidate.edgeClear && !current.edgeClear) return true;
2414
2530
  if (!candidate.edgeClear && current.edgeClear) return false;
2415
- if (candidate.confidence > current.confidence + hysteresis) return true;
2531
+ const curEffective = effectiveScore(current);
2532
+ const candEffective = effectiveScore(candidate);
2533
+ if (candEffective > curEffective + hysteresis) return true;
2416
2534
  if (candidate.centerScore !== void 0 && current.centerScore !== void 0) {
2417
- if (Math.abs(candidate.confidence - current.confidence) <= hysteresis && candidate.centerScore > current.centerScore + .1) return true;
2535
+ if (Math.abs(candEffective - curEffective) <= hysteresis && candidate.centerScore > current.centerScore + .1) return true;
2418
2536
  }
2419
2537
  return false;
2420
2538
  }
@@ -2456,6 +2574,14 @@ var BestDetectionTracker = class {
2456
2574
  * does not supply centering (face / object-embedding paths) → the centering
2457
2575
  * tie-break is disabled and the legacy confidence policy applies. */
2458
2576
  centerScore = /* @__PURE__ */ new Map();
2577
+ /** Held peak's edge-containment factor (0..1), PARALLEL to `best`. Absent =
2578
+ * the caller does not supply containment (face / object-embedding paths) →
2579
+ * the within-tier comparison stays on raw confidence (containment treated as
2580
+ * 1). */
2581
+ containment = /* @__PURE__ */ new Map();
2582
+ /** Held peak's subject-size score (0..1), PARALLEL to `best`. Absent = the
2583
+ * caller does not supply size (face / object-embedding paths) → neutral. */
2584
+ sizeScore = /* @__PURE__ */ new Map();
2459
2585
  constructor(options = {}) {
2460
2586
  this.hysteresis = options.hysteresis ?? 0;
2461
2587
  this.minGapMs = options.minGapMs ?? 0;
@@ -2472,7 +2598,7 @@ var BestDetectionTracker = class {
2472
2598
  * the classic policy holds: a confidence past the `hysteresis` margin that also
2473
2599
  * respects `minGapMs` wins. On acceptance the held peak advances.
2474
2600
  */
2475
- observe(trackId, confidence, timestamp, edgeClear, centerScore) {
2601
+ observe(trackId, confidence, timestamp, edgeClear, centerScore, containment, sizeScore) {
2476
2602
  const cur = this.best.get(trackId);
2477
2603
  if (cur === void 0) {
2478
2604
  this.best.set(trackId, {
@@ -2481,20 +2607,32 @@ var BestDetectionTracker = class {
2481
2607
  });
2482
2608
  if (edgeClear !== void 0) this.edgeClear.set(trackId, edgeClear);
2483
2609
  if (centerScore !== void 0) this.centerScore.set(trackId, centerScore);
2610
+ if (containment !== void 0) this.containment.set(trackId, containment);
2611
+ if (sizeScore !== void 0) this.sizeScore.set(trackId, sizeScore);
2484
2612
  return true;
2485
2613
  }
2486
2614
  const curClear = this.edgeClear.get(trackId) ?? true;
2487
2615
  const candClear = edgeClear ?? true;
2488
2616
  const curCenter = this.centerScore.get(trackId);
2489
- const isNewBest = candClear && !curClear ? true : isEdgeAwareNewBest({
2617
+ const curContainment = this.containment.get(trackId);
2618
+ const curSize = this.sizeScore.get(trackId);
2619
+ const held = {
2490
2620
  confidence: cur.confidence,
2491
2621
  edgeClear: curClear,
2492
- ...curCenter !== void 0 ? { centerScore: curCenter } : {}
2493
- }, {
2622
+ ...curCenter !== void 0 ? { centerScore: curCenter } : {},
2623
+ ...curContainment !== void 0 ? { containment: curContainment } : {},
2624
+ ...curSize !== void 0 ? { sizeScore: curSize } : {}
2625
+ };
2626
+ const cand = {
2494
2627
  confidence,
2495
2628
  edgeClear: candClear,
2496
- ...centerScore !== void 0 ? { centerScore } : {}
2497
- }, this.hysteresis) && timestamp - cur.atMs >= this.minGapMs;
2629
+ ...centerScore !== void 0 ? { centerScore } : {},
2630
+ ...containment !== void 0 ? { containment } : {},
2631
+ ...sizeScore !== void 0 ? { sizeScore } : {}
2632
+ };
2633
+ const tierUpgrade = candClear && !curClear;
2634
+ const gapOk = timestamp - cur.atMs >= this.minGapMs || sizeScore !== void 0 && isDecisiveImprovement(held, cand);
2635
+ const isNewBest = tierUpgrade ? true : isEdgeAwareNewBest(held, cand, this.hysteresis) && gapOk;
2498
2636
  if (isNewBest) {
2499
2637
  this.best.set(trackId, {
2500
2638
  confidence,
@@ -2502,6 +2640,8 @@ var BestDetectionTracker = class {
2502
2640
  });
2503
2641
  if (edgeClear !== void 0) this.edgeClear.set(trackId, edgeClear);
2504
2642
  if (centerScore !== void 0) this.centerScore.set(trackId, centerScore);
2643
+ if (containment !== void 0) this.containment.set(trackId, containment);
2644
+ if (sizeScore !== void 0) this.sizeScore.set(trackId, sizeScore);
2505
2645
  }
2506
2646
  return isNewBest;
2507
2647
  }
@@ -2514,14 +2654,44 @@ var BestDetectionTracker = class {
2514
2654
  this.best.delete(trackId);
2515
2655
  this.edgeClear.delete(trackId);
2516
2656
  this.centerScore.delete(trackId);
2657
+ this.containment.delete(trackId);
2658
+ this.sizeScore.delete(trackId);
2517
2659
  }
2518
2660
  clear() {
2519
2661
  this.best.clear();
2520
2662
  this.edgeClear.clear();
2521
2663
  this.centerScore.clear();
2664
+ this.containment.clear();
2665
+ this.sizeScore.clear();
2522
2666
  }
2523
2667
  };
2524
2668
  //#endregion
2669
+ //#region src/pipeline-analytics/pipeline/last-frame-promotion.ts
2670
+ /**
2671
+ * Decide whether a closing track's newest `snapshot` should be promoted to be
2672
+ * its `lastFrame`. Pure — see the module header for the contract.
2673
+ *
2674
+ * Promote when there is at least one `snapshot` AND either there is no
2675
+ * `lastFrame` yet, or the newest snapshot is strictly newer than the held
2676
+ * `lastFrame`. Otherwise keep the current behaviour (no promotion).
2677
+ */
2678
+ function decideLastFramePromotion(media) {
2679
+ let newestSnapshot;
2680
+ let lastFrame;
2681
+ for (const m of media) if (m.kind === "snapshot") {
2682
+ if (newestSnapshot === void 0 || m.timestamp > newestSnapshot.timestamp) newestSnapshot = m;
2683
+ } else if (m.kind === "lastFrame") {
2684
+ if (lastFrame === void 0 || m.timestamp > lastFrame.timestamp) lastFrame = m;
2685
+ }
2686
+ if (newestSnapshot === void 0) return { promote: false };
2687
+ if (lastFrame !== void 0 && newestSnapshot.timestamp <= lastFrame.timestamp) return { promote: false };
2688
+ return {
2689
+ promote: true,
2690
+ snapshotKey: newestSnapshot.key,
2691
+ snapshotTimestamp: newestSnapshot.timestamp
2692
+ };
2693
+ }
2694
+ //#endregion
2525
2695
  //#region src/pipeline-analytics/pipeline/track-best-detection.ts
2526
2696
  /**
2527
2697
  * `TrackBestSelector` — the ONE unified "best detection per track" primitive.
@@ -4686,6 +4856,31 @@ var MediaStore = class {
4686
4856
  return newKey;
4687
4857
  }
4688
4858
  /**
4859
+ * Promote an existing `snapshot` blob to be the track's single `lastFrame`
4860
+ * (operator "genuinely-last view" policy, see `last-frame-promotion.ts`).
4861
+ *
4862
+ * Both kinds share the SAME 960-boxed rendering, so promotion re-writes the
4863
+ * snapshot's bytes into the single-instance `lastFrame` slot (`putReplacing`,
4864
+ * which overwrites any held `lastFrame` blob + row) and then removes the
4865
+ * promoted `snapshot` row + blob — so there is exactly ONE genuinely-last view
4866
+ * and no duplicate snapshot/lastFrame pair. The `lastFrame` write lands BEFORE
4867
+ * the snapshot delete, so the view is never momentarily absent. Returns the new
4868
+ * `lastFrame` key.
4869
+ */
4870
+ async promoteToLastFrame(input) {
4871
+ const data = Buffer.from(input.snapshot.base64, "base64");
4872
+ const newKey = await this.putReplacing({
4873
+ deviceId: input.deviceId,
4874
+ ownerKind: "track",
4875
+ ownerId: input.trackId,
4876
+ kind: "lastFrame",
4877
+ timestamp: input.snapshot.timestamp,
4878
+ data
4879
+ });
4880
+ await this.deleteByKey(input.snapshot.key);
4881
+ return newKey;
4882
+ }
4883
+ /**
4689
4884
  * Fetch one media entry by its key (id). Returns null if the key is not
4690
4885
  * found in the index or if the blob is missing from storage.
4691
4886
  */
@@ -6563,37 +6758,121 @@ function squareSubjectCropRegion(bbox, frame) {
6563
6758
  };
6564
6759
  }
6565
6760
  /**
6566
- * The same SQUARE region as {@link squareSubjectCropRegion}, expressed in
6567
- * NORMALIZED [0,1]×[0,1] coordinates. A normalized box maps DIRECTLY onto a
6568
- * native-resolution surface of the SAME aspect ratio, so the region computed
6569
- * from the detection-frame dimensions addresses the exact same ROI on the
6570
- * runner's retained native frame. Reuses the pixel geometry verbatim (single
6571
- * source of truth) and divides by the frame dimensions.
6761
+ * Compute the 16:9 central-square window layout for a subject bbox.
6762
+ *
6763
+ * Algorithm:
6764
+ * 1. central square `C = squareSubjectCropRegion(bbox, frame)` the
6765
+ * subject-containing, frame-clamped square of side `c` (its contract also
6766
+ * handles the "subject bigger than the short edge / scale the window up"
6767
+ * cases). `C` becomes the middle square of the output.
6768
+ * 2. canvas: `canvasW = round(c × 16/9)`, `canvasH = c`.
6769
+ * 3. anchor: place the canvas so `C` is its horizontal middle →
6770
+ * `frameOriginX = C.x − (canvasW − c)/2` (frame-x mapping to canvas-x 0).
6771
+ * 4. in-frame slab: intersect `[frameOriginX, frameOriginX + canvasW]` with
6772
+ * `[0, W]`; the slab is that intersection at full height `c` (C ⊆ frame ⇒
6773
+ * the vertical extent is always in-frame). Its canvas offset is
6774
+ * `slabOffsetX = fxa − frameOriginX ≥ 0`; anything outside is padding.
6572
6775
  */
6573
- function squareSubjectCropRegionNormalized(bbox, frame) {
6574
- const region = squareSubjectCropRegion(bbox, frame);
6776
+ function wideCentralSquareLayout(bbox, frame) {
6777
+ const central = squareSubjectCropRegion(bbox, frame);
6778
+ const c = central.w;
6779
+ const canvasW = Math.round(c * 16 / 9);
6780
+ const centralX0 = Math.round((canvasW - c) / 2);
6781
+ let frameOriginX = central.x - (canvasW - c) / 2;
6782
+ if (canvasW <= frame.W) {
6783
+ const slideRightMax = Math.max(0, bbox.x - central.x);
6784
+ const slideLeftMax = Math.max(0, central.x + c - (bbox.x + bbox.w));
6785
+ if (frameOriginX < 0) frameOriginX += Math.min(-frameOriginX, slideRightMax);
6786
+ const overRight = frameOriginX + canvasW - frame.W;
6787
+ if (overRight > 0) frameOriginX -= Math.min(overRight, slideLeftMax);
6788
+ } else frameOriginX = (frame.W - canvasW) / 2;
6789
+ const fxa = Math.max(0, frameOriginX);
6790
+ const slabW = Math.min(frame.W, frameOriginX + canvasW) - fxa;
6791
+ const slabOffsetX = fxa - frameOriginX;
6575
6792
  return {
6576
- x: region.x / frame.W,
6577
- y: region.y / frame.H,
6578
- w: region.w / frame.W,
6579
- h: region.h / frame.H
6793
+ fetch: {
6794
+ x: fxa / frame.W,
6795
+ y: central.y / frame.H,
6796
+ w: slabW / frame.W,
6797
+ h: c / frame.H
6798
+ },
6799
+ canvasW,
6800
+ canvasH: c,
6801
+ slabOffsetX,
6802
+ slabW,
6803
+ slabH: c,
6804
+ centralX0,
6805
+ centralSide: c,
6806
+ frameOriginX
6580
6807
  };
6581
6808
  }
6809
+ //#endregion
6810
+ //#region src/shared/frame/subject-crop-variants.ts
6811
+ /**
6812
+ * Best-shot subject-crop variants (best-crop fast-load, 2026-07-21; 16:9
6813
+ * central-square reframe, 2026-07-22).
6814
+ *
6815
+ * ONE native fetch (the 16:9 central-square WINDOW — see
6816
+ * {@link wide-central-square-crop}) backs both persisted variants:
6817
+ * - `thumbnail` — the native uncapped 16:9 window JPEG (subject in the
6818
+ * central square, lateral scene context, never upscaled).
6819
+ * - `thumbnailSmall` — the SAME window downscaled to {@link WIDE_THUMBNAIL_SMALL_MAX_WIDTH}
6820
+ * long side (854×480), the reel / lists / grid fast-load
6821
+ * representative.
6822
+ *
6823
+ * The small variant is DERIVED from the already-composed native window — never a
6824
+ * second native round-trip and NEVER upscaled: if the window is already ≤ 854 on
6825
+ * its long side it is stored AS-IS (byte-identical). The window is composed from
6826
+ * the in-frame slab plus lateral letterbox padding for the (rare) part of the
6827
+ * 16:9 window that falls outside the frame at a horizontal edge.
6828
+ */
6829
+ /** Gaussian sigma for the blurred-scene letterbox fill. */
6830
+ var LETTERBOX_BLUR_SIGMA = 25;
6831
+ /** Brightness factor for the blurred fill — dimmed so the real slab reads as
6832
+ * the subject surface and the fill as ambience, never as sharp scene. */
6833
+ var LETTERBOX_BLUR_BRIGHTNESS = .55;
6834
+ /**
6835
+ * Compose the native-resolution 16:9 central-square WINDOW (`thumbnail`) from the
6836
+ * in-frame slab fetched for `layout`. The slab is placed at its computed offset
6837
+ * on a 16:9 canvas; any lateral part of the window outside the frame (a subject
6838
+ * flush against a frame edge — the geometry already slides the window in-frame
6839
+ * wherever the subject has slack) is filled with a BLURRED, dimmed stretch of
6840
+ * the slab itself instead of dead black bars (operator triage 2026-07-22).
6841
+ * When the whole window is in-frame (`slab` already spans the full canvas) the
6842
+ * slab is returned VERBATIM (no re-encode). NEVER upscales the real image —
6843
+ * the canvas is at the slab's native scale; only the out-of-frame ambience fill
6844
+ * is synthesized.
6845
+ *
6846
+ * `slab` must be the JPEG of `layout.fetch`; its native pixel dimensions set the
6847
+ * canvas scale (native px per source px = `slabNativeHeight / layout.slabH`).
6848
+ */
6849
+ async function composeWideCentralSquareThumbnail(slab, layout) {
6850
+ const meta = await (0, sharp.default)(slab).metadata();
6851
+ const nativeW = meta.width ?? 0;
6852
+ const nativeH = meta.height ?? 0;
6853
+ if (nativeW <= 0 || nativeH <= 0 || layout.slabH <= 0) return slab;
6854
+ const scale = nativeH / layout.slabH;
6855
+ const canvasNativeW = Math.max(Math.round(layout.canvasW * scale), nativeW);
6856
+ const leftPad = Math.max(0, Math.round(layout.slabOffsetX * scale));
6857
+ const rightPad = Math.max(0, canvasNativeW - nativeW - leftPad);
6858
+ if (leftPad === 0 && rightPad === 0) return slab;
6859
+ return (0, sharp.default)(await (0, sharp.default)(slab).resize(canvasNativeW, nativeH, { fit: "fill" }).blur(LETTERBOX_BLUR_SIGMA).modulate({ brightness: LETTERBOX_BLUR_BRIGHTNESS }).toBuffer()).composite([{
6860
+ input: slab,
6861
+ left: leftPad,
6862
+ top: 0
6863
+ }]).jpeg({ quality: 88 }).toBuffer();
6864
+ }
6582
6865
  /**
6583
- * Derive the `thumbnailSmall` JPEG from an already-encoded native subject-crop
6584
- * JPEG. Downscales to at most {@link THUMBNAIL_SMALL_MAX_WIDTH} on the long side
6585
- * (aspect preserved). If the native crop's long side is already ≤ the cap, the
6586
- * ORIGINAL buffer is returned unchanged (store as-is, never upscale, no wasted
6587
- * re-encode).
6866
+ * Derive the `thumbnailSmall` JPEG from the composed native 16:9 window.
6867
+ * Downscales to at most {@link WIDE_THUMBNAIL_SMALL_MAX_WIDTH} (854) on the long
6868
+ * side (aspect preserved → ~854×480). If the window's long side is already ≤ the
6869
+ * cap, the ORIGINAL buffer is returned unchanged (store as-is, never upscale).
6588
6870
  */
6589
- async function deriveThumbnailSmall(nativeJpeg) {
6590
- const meta = await (0, sharp.default)(nativeJpeg).metadata();
6871
+ async function deriveThumbnailSmall(nativeWideJpeg) {
6872
+ const meta = await (0, sharp.default)(nativeWideJpeg).metadata();
6591
6873
  const longSide = Math.max(meta.width ?? 0, meta.height ?? 0);
6592
- if (longSide > 0 && longSide <= 480) return nativeJpeg;
6593
- return (0, sharp.default)(nativeJpeg).resize(480, 480, {
6594
- fit: "inside",
6595
- withoutEnlargement: true
6596
- }).jpeg({ quality: 88 }).toBuffer();
6874
+ if (longSide > 0 && longSide <= 854) return nativeWideJpeg;
6875
+ return (0, sharp.default)(nativeWideJpeg).resize(854, null, { withoutEnlargement: true }).jpeg({ quality: 88 }).toBuffer();
6597
6876
  }
6598
6877
  //#endregion
6599
6878
  //#region src/shared/frame/box-drawer.ts
@@ -6676,6 +6955,9 @@ async function drawBoxedFrame(frameData, frameWidth, frameHeight, boxes, opts =
6676
6955
  * upscale).
6677
6956
  */
6678
6957
  var SNAPSHOT_MAX_WIDTH = 960;
6958
+ /** Bound the once-per-owner display-fallback memo so a long-lived process never
6959
+ * leaks it. */
6960
+ var MAX_LOGGED_DISPLAY_FALLBACKS = 5e3;
6679
6961
  /**
6680
6962
  * Map a detection-frame pixel box (`fromW`×`fromH`, the ≤640 raster the tracker
6681
6963
  * ran on) onto the native full frame (`toW`×`toH`, the 960-downscaled native
@@ -6755,6 +7037,9 @@ var EventMediaDispatcher = class {
6755
7037
  * {@link SNAPSHOT_MAX_WIDTH} here (the only dispatcher use of the fetch).
6756
7038
  */
6757
7039
  sharedNativeFullFrame;
7040
+ /** Once-per-owner (`${eventId}:${kind}`) memo for the DISPLAY-crop fallback
7041
+ * info log — see {@link logDisplayFallbackOnce}. */
7042
+ loggedDisplayFallbacks = /* @__PURE__ */ new Set();
6758
7043
  constructor(deps) {
6759
7044
  this.deps = deps;
6760
7045
  this.sharedNativeFullFrame = createSharedFrameResolver((handle) => this.deps.getNativeFullFrameRgb(handle, SNAPSHOT_MAX_WIDTH));
@@ -6765,9 +7050,12 @@ var EventMediaDispatcher = class {
6765
7050
  const empty = {
6766
7051
  storedSnapshots: [],
6767
7052
  thumbnailTrackIds: [],
7053
+ firstFrameTrackIds: [],
7054
+ lastFrameTrackIds: [],
6768
7055
  rasterFallbacks: []
6769
7056
  };
6770
- if (events.length === 0 && trackFrames.length === 0 && snapshots.length === 0) return empty;
7057
+ const extraCandidateCount = input.rasterFallbackCandidates?.length ?? 0;
7058
+ if (events.length === 0 && trackFrames.length === 0 && snapshots.length === 0 && extraCandidateCount === 0) return empty;
6771
7059
  let decoded;
6772
7060
  try {
6773
7061
  decoded = await resolveFrame(frameHandle, { getRemoteFrame: this.deps.getRemoteFrame });
@@ -6817,44 +7105,59 @@ var EventMediaDispatcher = class {
6817
7105
  });
6818
7106
  return empty;
6819
7107
  }
6820
- const rasterFallbacks = await this.collectRasterFallbacks(frameData, fw, fh, trackFrames, snapshots, input.cropPadding, input.rasterFallbackWantedTrackIds);
6821
- for (const ev of events) await this.writeEventMedia(deviceId, frameHandle, fw, fh, ev, input.cropPadding);
6822
- for (const tf of trackFrames) await this.writeTrackFrame(deviceId, frameHandle, frameData, fw, fh, tf);
7108
+ const rasterFallbacks = await this.collectRasterFallbacks(frameData, fw, fh, trackFrames, snapshots, input.cropPadding, input.rasterFallbackWantedTrackIds, input.rasterFallbackCandidates);
7109
+ for (const ev of events) await this.writeEventMedia(deviceId, frameHandle, frameData, fw, fh, ev, input.cropPadding);
7110
+ const firstFrameTrackIds = [];
7111
+ for (const tf of trackFrames) if (await this.writeTrackFrame(deviceId, frameHandle, frameData, fw, fh, tf)) firstFrameTrackIds.push(tf.trackId);
6823
7112
  const storedSnapshots = [];
6824
7113
  const thumbnailTrackIds = [];
7114
+ const lastFrameTrackIds = [];
6825
7115
  for (const sn of snapshots) {
6826
7116
  const res = await this.writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn);
6827
7117
  if (res.storedSnapshot) storedSnapshots.push(res.storedSnapshot);
6828
7118
  if (res.thumbnailWritten) thumbnailTrackIds.push(sn.trackId);
7119
+ if (res.lastFrameWritten) lastFrameTrackIds.push(sn.trackId);
6829
7120
  }
6830
7121
  return {
6831
7122
  storedSnapshots,
6832
7123
  thumbnailTrackIds,
7124
+ firstFrameTrackIds,
7125
+ lastFrameTrackIds,
6833
7126
  rasterFallbacks
6834
7127
  };
6835
7128
  }
6836
7129
  /**
6837
- * Cut ONE clean detection-raster subject crop per WANTED track that has a
6838
- * target (firstFrame or snapshot) in this frame the "first available
6839
- * detection-raster frame" of the zero-media fallback. Cropped from the
6840
- * already-resolved `frameData` at its REAL resolution via {@link extractCrop}
6841
- * (extract-only NEVER upscaled). Deduped per trackId (first target wins).
6842
- * A per-track encode failure is skipped (logged) a missing fallback simply
7130
+ * Cut ONE clean detection-raster subject crop per WANTED track observed this
7131
+ * frame the "first available detection-raster frame" of the zero-media
7132
+ * fallback. Candidate bboxes come from this frame's firstFrame/snapshot targets
7133
+ * AND (widened) the explicit `extraCandidates` (confirmed tracks with no such
7134
+ * target). Cropped from the already-resolved `frameData` at its REAL resolution
7135
+ * via {@link extractCrop} (extract-only NEVER upscaled). Deduped per trackId
7136
+ * (first candidate wins; target-derived candidates precede the extras). A
7137
+ * per-track encode failure is skipped (logged) — a missing fallback simply
6843
7138
  * leaves the track with no last-resort preview, never an error.
6844
7139
  */
6845
- async collectRasterFallbacks(frameData, fw, fh, trackFrames, snapshots, cropPadding, wanted) {
7140
+ async collectRasterFallbacks(frameData, fw, fh, trackFrames, snapshots, cropPadding, wanted, extraCandidates) {
6846
7141
  if (!wanted || wanted.size === 0) return [];
6847
7142
  const seen = /* @__PURE__ */ new Set();
6848
7143
  const out = [];
6849
- const candidates = [...trackFrames.map((t) => ({
6850
- trackId: t.trackId,
6851
- timestamp: t.timestamp,
6852
- bbox: t.bbox
6853
- })), ...snapshots.map((s) => ({
6854
- trackId: s.trackId,
6855
- timestamp: s.timestamp,
6856
- bbox: s.bbox
6857
- }))];
7144
+ const candidates = [
7145
+ ...trackFrames.map((t) => ({
7146
+ trackId: t.trackId,
7147
+ timestamp: t.timestamp,
7148
+ bbox: t.bbox
7149
+ })),
7150
+ ...snapshots.map((s) => ({
7151
+ trackId: s.trackId,
7152
+ timestamp: s.timestamp,
7153
+ bbox: s.bbox
7154
+ })),
7155
+ ...(extraCandidates ?? []).map((c) => ({
7156
+ trackId: c.trackId,
7157
+ timestamp: c.timestamp,
7158
+ bbox: c.bbox
7159
+ }))
7160
+ ];
6858
7161
  for (const c of candidates) {
6859
7162
  if (!wanted.has(c.trackId) || seen.has(c.trackId)) continue;
6860
7163
  seen.add(c.trackId);
@@ -6888,11 +7191,15 @@ var EventMediaDispatcher = class {
6888
7191
  * TrackStore wiring (null when `appendSnapshot` is false or the encode
6889
7192
  * failed). `thumbnailWritten` reports whether a best `thumbnail` actually
6890
7193
  * landed this frame (#27-A) so the caller can stop forcing retries.
7194
+ * `lastFrameWritten` reports whether the rolling `lastFrame` actually landed
7195
+ * (DEFECT B) so the caller advances its `lastFrameAt` clock ONLY on a real
7196
+ * write — a dropped roll leaves the clock put and retries next frame.
6891
7197
  */
6892
7198
  async writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn) {
6893
7199
  if (!sn.appendSnapshot && !sn.rollingLastFrame && !sn.bestThumbnail) return {
6894
7200
  storedSnapshot: null,
6895
- thumbnailWritten: false
7201
+ thumbnailWritten: false,
7202
+ lastFrameWritten: false
6896
7203
  };
6897
7204
  let boxed = null;
6898
7205
  if (sn.appendSnapshot || sn.rollingLastFrame) boxed = await this.boxedTimelineFrame(deviceId, frameHandle, frameData, fw, fh, sn.trackId, sn.bbox, sn.label);
@@ -6913,7 +7220,8 @@ var EventMediaDispatcher = class {
6913
7220
  bbox: sn.bbox
6914
7221
  };
6915
7222
  } catch {}
6916
- if (sn.rollingLastFrame && boxed) await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
7223
+ let lastFrameWritten = false;
7224
+ if (sn.rollingLastFrame && boxed) lastFrameWritten = await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
6917
7225
  let thumbnailWritten = false;
6918
7226
  if (sn.bestThumbnail) {
6919
7227
  const variants = await this.cropSubjectVariants(frameHandle, fw, fh, sn.bbox);
@@ -6924,60 +7232,94 @@ var EventMediaDispatcher = class {
6924
7232
  }
6925
7233
  return {
6926
7234
  storedSnapshot: stored,
6927
- thumbnailWritten
7235
+ thumbnailWritten,
7236
+ lastFrameWritten
6928
7237
  };
6929
7238
  }
6930
7239
  /**
6931
- * Clean subject-centered crop of `bbox` the shared output contract of the
6932
- * object-event `crop` kind, the child `faceCrop`/`plateCrop`, and the track
6933
- * `thumbnail`: the square-safe 16:9 region around the bbox with NO box drawn.
7240
+ * Clean subject-centered crop of `bbox` for the DISPLAY child media
7241
+ * (`faceCrop`/`plateCrop`): the square-safe 16:9 region around the bbox with NO
7242
+ * box drawn.
6934
7243
  *
6935
- * NATIVE-OR-NOTHING: the region is requested from the runner's retained native
6936
- * surface (normalized [0,1] coords map directly onto it) with NO `maxWidth` —
6937
- * the crop at TRUE native resolution (decision #3). On any miss/error (or a
6938
- * runner without the method) it returns `null` after a loud `logger.warn`; the
6939
- * caller SKIPS the write and the per-frame retry lands a real native crop
6940
- * later. It is NEVER upscaled — an upscaled ≤640 tile is a blurred lie
6941
- * (case-study lapVar 5–7), so no local resize fallback exists. Runs inside the
7244
+ * These are operator gallery images, NOT model inputs (the spec's native-source
7245
+ * hard rule governs model inputs, which stay native-strict). So a native-ROI
7246
+ * miss degrades gracefully always honest, NEVER upscaled:
7247
+ * 1. native ROI crop (the runner's retained native surface) best quality;
7248
+ * 2. the retained full frame (keyframe-native or the runner's ≤640 RAM tier)
7249
+ * cropped to the SAME region;
7250
+ * 3. the already-resolved ≤640 detection raster (`frameData`, in hand) cropped
7251
+ * to the region — the last-resort honest sub-native preview.
7252
+ * Only a total miss (all three unavailable) returns `null`. A fallback is
7253
+ * logged ONCE per owner (`${eventId}:${kind}`) at info level. Runs inside the
6942
7254
  * live-handle window opened by `captureForFrame`.
6943
7255
  */
6944
- async cropSubjectRegion(frameHandle, fw, fh, bbox, cropPadding) {
6945
- if (!this.deps.getNativeCropJpeg) {
6946
- this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
7256
+ async cropSubjectRegion(frameHandle, frameData, fw, fh, bbox, cropPadding, ownerId) {
7257
+ const norm = squareSafeCropRegionNormalized(bbox, {
7258
+ W: fw,
7259
+ H: fh
7260
+ }, cropPadding);
7261
+ if (this.deps.getNativeCropJpeg) try {
7262
+ const native = await this.deps.getNativeCropJpeg(frameHandle, norm);
7263
+ if (native) return native;
7264
+ } catch (err) {
7265
+ this.deps.logger.debug("display child crop: native ROI fetch threw", { meta: {
6947
7266
  shmId: frameHandle.shmId,
6948
- reason: "no-native-cap"
7267
+ error: err instanceof Error ? err.message : String(err)
6949
7268
  } });
6950
- return null;
6951
7269
  }
6952
7270
  try {
6953
- const norm = squareSafeCropRegionNormalized(bbox, {
6954
- W: fw,
6955
- H: fh
6956
- }, cropPadding);
6957
- const native = await this.deps.getNativeCropJpeg(frameHandle, norm);
6958
- if (native) return native;
6959
- this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
7271
+ const full = await this.sharedNativeFullFrame(frameHandle);
7272
+ if (full && full.format === "rgb" && full.width > 0 && full.height > 0) {
7273
+ const { crop } = await extractCrop(full.data, full.width, full.height, norm);
7274
+ this.logDisplayFallbackOnce(ownerId, "native-full-frame");
7275
+ return crop;
7276
+ }
7277
+ } catch (err) {
7278
+ this.deps.logger.debug("display child crop: full-frame fallback threw", { meta: {
6960
7279
  shmId: frameHandle.shmId,
6961
- reason: "native-miss"
7280
+ error: err instanceof Error ? err.message : String(err)
6962
7281
  } });
6963
- return null;
7282
+ }
7283
+ try {
7284
+ const { crop } = await extractCrop(frameData, fw, fh, norm);
7285
+ this.logDisplayFallbackOnce(ownerId, "detection-raster");
7286
+ return crop;
6964
7287
  } catch (err) {
6965
- this.deps.logger.warn("native subject crop miss will retry, no upscale", { meta: {
7288
+ this.deps.logger.warn("display child crop: all sources missed", { meta: {
6966
7289
  shmId: frameHandle.shmId,
6967
7290
  error: err instanceof Error ? err.message : String(err)
6968
7291
  } });
6969
7292
  return null;
6970
7293
  }
6971
7294
  }
7295
+ /** Info-log a DISPLAY-crop fallback ONCE per owner (`${eventId}:${kind}`) so a
7296
+ * short native window is visible without flooding the log every frame. */
7297
+ logDisplayFallbackOnce(ownerId, reason) {
7298
+ if (this.loggedDisplayFallbacks.has(ownerId)) return;
7299
+ if (this.loggedDisplayFallbacks.size >= MAX_LOGGED_DISPLAY_FALLBACKS) this.loggedDisplayFallbacks.clear();
7300
+ this.loggedDisplayFallbacks.add(ownerId);
7301
+ this.deps.logger.info("display child crop fallback used (honest sub-native, not upscaled)", { meta: {
7302
+ ownerId,
7303
+ reason
7304
+ } });
7305
+ }
6972
7306
  /**
6973
7307
  * The best-shot subject crop as its TWO persisted variants (best-crop
6974
- * fast-load, 2026-07-21). ONE native fetch: the SQUARE-framed subject ROI
6975
- * (side = max(w,h)×1.2, clamped to the frame {@link squareSubjectCropRegionNormalized})
6976
- * is requested from the runner's retained native surface with NO `maxWidth`
6977
- * (uncapped TRUE native, decision #3) the `thumbnail`. The `thumbnailSmall`
6978
- * is DERIVED by downscaling that SAME JPEG to the 480 long-side cap never a
6979
- * second `getNativeCropJpeg`, never upscaled ({@link deriveThumbnailSmall}
6980
- * returns the native buffer as-is when it is already 480).
7308
+ * fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22). ONE native
7309
+ * fetch: the in-frame slab of the 16:9 central-square WINDOW
7310
+ * ({@link wideCentralSquareLayout} a `side × 16/9` window whose middle square
7311
+ * of side `max(w,h)×1.2` fully contains the subject, clamped/anchored so the
7312
+ * subject stays in that central square at every frame edge) is requested from
7313
+ * the runner's retained native surface with NO `maxWidth` (uncapped TRUE
7314
+ * native). It is composed into the full 16:9 window (lateral letterbox only for
7315
+ * the part outside the frame) → the `thumbnail`. The `thumbnailSmall` is
7316
+ * DERIVED by downscaling that SAME window to the 854 long-side cap (854×480) —
7317
+ * never a second `getNativeCropJpeg`, never upscaled ({@link deriveThumbnailSmall}
7318
+ * returns the window as-is when it is already ≤ 854).
7319
+ *
7320
+ * Both variants share the SAME framing + central-square containment guarantee:
7321
+ * wide consumers use the 16:9 image as-is (subject centered, no cut); square
7322
+ * consumers center-crop the middle square (subject always inside it).
6981
7323
  *
6982
7324
  * NATIVE-OR-NOTHING: any miss/error (or a runner without the method) returns
6983
7325
  * `null` after a loud `logger.warn`; the caller SKIPS the write and the
@@ -6993,21 +7335,22 @@ var EventMediaDispatcher = class {
6993
7335
  return null;
6994
7336
  }
6995
7337
  try {
6996
- const norm = squareSubjectCropRegionNormalized(bbox, {
7338
+ const layout = wideCentralSquareLayout(bbox, {
6997
7339
  W: fw,
6998
7340
  H: fh
6999
7341
  });
7000
- const native = await this.deps.getNativeCropJpeg(frameHandle, norm);
7001
- if (!native) {
7342
+ const slab = await this.deps.getNativeCropJpeg(frameHandle, layout.fetch);
7343
+ if (!slab) {
7002
7344
  this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
7003
7345
  shmId: frameHandle.shmId,
7004
7346
  reason: "native-miss"
7005
7347
  } });
7006
7348
  return null;
7007
7349
  }
7350
+ const thumbnail = await composeWideCentralSquareThumbnail(slab, layout);
7008
7351
  return {
7009
- thumbnail: native,
7010
- thumbnailSmall: await deriveThumbnailSmall(native)
7352
+ thumbnail,
7353
+ thumbnailSmall: await deriveThumbnailSmall(thumbnail)
7011
7354
  };
7012
7355
  } catch (err) {
7013
7356
  this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
@@ -7096,9 +7439,9 @@ var EventMediaDispatcher = class {
7096
7439
  return false;
7097
7440
  }
7098
7441
  }
7099
- async writeEventMedia(deviceId, frameHandle, fw, fh, ev, cropPadding) {
7442
+ async writeEventMedia(deviceId, frameHandle, frameData, fw, fh, ev, cropPadding) {
7100
7443
  if (ev.childCrops) for (const child of ev.childCrops) try {
7101
- const childCropData = await this.cropSubjectRegion(frameHandle, fw, fh, child.bbox, cropPadding);
7444
+ const childCropData = await this.cropSubjectRegion(frameHandle, frameData, fw, fh, child.bbox, cropPadding, `${ev.eventId}:${child.kind}`);
7102
7445
  if (!childCropData) continue;
7103
7446
  await this.deps.mediaStore.put({
7104
7447
  deviceId,
@@ -7121,7 +7464,7 @@ var EventMediaDispatcher = class {
7121
7464
  }
7122
7465
  async writeTrackFrame(deviceId, frameHandle, frameData, fw, fh, tf) {
7123
7466
  const boxed = await this.boxedTimelineFrame(deviceId, frameHandle, frameData, fw, fh, tf.trackId, tf.bbox, tf.label);
7124
- if (!boxed) return;
7467
+ if (!boxed) return false;
7125
7468
  try {
7126
7469
  await this.deps.mediaStore.put({
7127
7470
  deviceId,
@@ -7131,6 +7474,7 @@ var EventMediaDispatcher = class {
7131
7474
  timestamp: tf.timestamp,
7132
7475
  data: boxed
7133
7476
  });
7477
+ return true;
7134
7478
  } catch (err) {
7135
7479
  this.deps.logger.warn("event media: track frame failed", {
7136
7480
  tags: { deviceId },
@@ -7140,6 +7484,7 @@ var EventMediaDispatcher = class {
7140
7484
  error: err instanceof Error ? err.message : String(err)
7141
7485
  }
7142
7486
  });
7487
+ return false;
7143
7488
  }
7144
7489
  }
7145
7490
  };
@@ -8924,7 +9269,8 @@ function evaluatePeriodicSnapshot(input) {
8924
9269
  */
8925
9270
  function planPeriodicMedia(input) {
8926
9271
  const appendSnapshot = input.dueSnapshot;
8927
- const rollingLastFrame = input.saveThumbnails && input.now - input.lastFrameAt >= input.intervalMs && !appendSnapshot;
9272
+ const lastFrameInterval = input.lastFrameIntervalMs ?? input.intervalMs;
9273
+ const rollingLastFrame = input.saveThumbnails && input.now - input.lastFrameAt >= lastFrameInterval && !appendSnapshot;
8928
9274
  const thumbnailLanded = input.thumbnailLanded ?? true;
8929
9275
  return {
8930
9276
  appendSnapshot,
@@ -8932,7 +9278,10 @@ function planPeriodicMedia(input) {
8932
9278
  bestThumbnail: input.isNewBest || !thumbnailLanded
8933
9279
  };
8934
9280
  }
8935
- var DEFAULT_ZERO_MEDIA_POLICY_CONFIG = { suppressMaxDurationMs: 1e3 };
9281
+ var DEFAULT_ZERO_MEDIA_POLICY_CONFIG = {
9282
+ suppressMaxDurationMs: 1e3,
9283
+ nothingToShowMaxDurationMs: 1500
9284
+ };
8936
9285
  /**
8937
9286
  * Classify a closing track's persistence outcome. Pure — see the module header
8938
9287
  * for the full contract.
@@ -8940,6 +9289,7 @@ var DEFAULT_ZERO_MEDIA_POLICY_CONFIG = { suppressMaxDurationMs: 1e3 };
8940
9289
  function decideZeroMediaPolicy(input, config = DEFAULT_ZERO_MEDIA_POLICY_CONFIG) {
8941
9290
  if (input.hasMedia) return "persist";
8942
9291
  if (input.durationMs < config.suppressMaxDurationMs && !input.confirmed) return "suppress";
9292
+ if (!input.hasRasterFallback && input.durationMs < config.nothingToShowMaxDurationMs) return "suppress";
8943
9293
  return input.hasRasterFallback ? "raster-fallback" : "persist";
8944
9294
  }
8945
9295
  //#endregion
@@ -10115,6 +10465,7 @@ var FaceRecognizer = class {
10115
10465
  embedding: input.embedding,
10116
10466
  embeddingModelId: modelId,
10117
10467
  ...input.faceBbox !== void 0 ? { faceBbox: input.faceBbox } : {},
10468
+ ...input.nativeFaceShortSidePx !== void 0 ? { nativeFaceShortSidePx: input.nativeFaceShortSidePx } : {},
10118
10469
  ...input.alignedCropJpeg !== void 0 ? { faceAlignedCrop: input.alignedCropJpeg } : {}
10119
10470
  };
10120
10471
  await this.processFrame({
@@ -10130,7 +10481,7 @@ var FaceRecognizer = class {
10130
10481
  }
10131
10482
  async processFrame(input) {
10132
10483
  const { settings } = input;
10133
- const candidates = input.tracked.filter((t) => Array.isArray(t.embedding) && t.embedding.length > 0 && typeof t.embeddingModelId === "string" && t.confidence >= settings.minFaceConfidence && (t.faceBbox === void 0 || Math.min(t.faceBbox.w, t.faceBbox.h) >= settings.minFacePx));
10484
+ const candidates = input.tracked.filter((t) => Array.isArray(t.embedding) && t.embedding.length > 0 && typeof t.embeddingModelId === "string" && t.confidence >= settings.minFaceConfidence && (t.nativeFaceShortSidePx ?? (t.faceBbox ? Math.min(t.faceBbox.w, t.faceBbox.h) : void 0) ?? Number.POSITIVE_INFINITY) >= settings.minFacePx);
10134
10485
  if (candidates.length === 0) return;
10135
10486
  this.deps.logger.debug("face: frame candidates", {
10136
10487
  tags: { deviceId: input.deviceId },
@@ -10168,7 +10519,7 @@ var FaceRecognizer = class {
10168
10519
  let crop;
10169
10520
  if (c.faceAlignedCrop !== void 0) crop = Buffer.from(c.faceAlignedCrop, "base64");
10170
10521
  else if (c.faceBbox !== void 0 && input.frameHandle !== void 0) try {
10171
- crop = await this.deps.captureCrop(input.frameHandle, c.faceBbox, input.frameWidth, input.frameHeight, input.cropPadding) ?? void 0;
10522
+ crop = await this.deps.captureCrop(input.frameHandle, c.faceBbox, input.frameWidth, input.frameHeight, input.cropPadding, c.trackId) ?? void 0;
10172
10523
  } catch (err) {
10173
10524
  this.deps.logger.debug("FaceRecognizer crop capture failed", {
10174
10525
  tags: { deviceId: input.deviceId },
@@ -11699,7 +12050,7 @@ var PlateRecognizer = class {
11699
12050
  if (held !== void 0 && input.score <= held.score) return;
11700
12051
  let crop;
11701
12052
  if (input.frameHandle !== void 0) try {
11702
- crop = await this.deps.captureCrop(input.frameHandle, input.bbox, input.frameWidth, input.frameHeight, input.cropPadding) ?? void 0;
12053
+ crop = await this.deps.captureCrop(input.frameHandle, input.bbox, input.frameWidth, input.frameHeight, input.cropPadding, input.trackId) ?? void 0;
11703
12054
  } catch (err) {
11704
12055
  this.deps.logger.debug("PlateRecognizer crop capture failed", {
11705
12056
  tags: { deviceId: input.deviceId },
@@ -11714,6 +12065,9 @@ var PlateRecognizer = class {
11714
12065
  score: input.score,
11715
12066
  bbox: input.bbox,
11716
12067
  timestamp: input.timestamp,
12068
+ frameWidth: input.frameWidth,
12069
+ frameHeight: input.frameHeight,
12070
+ cropPadding: input.cropPadding,
11717
12071
  ...crop !== void 0 ? { crop } : {}
11718
12072
  });
11719
12073
  }
@@ -11724,15 +12078,48 @@ var PlateRecognizer = class {
11724
12078
  this.bestPlate.delete(trackId);
11725
12079
  if (held === void 0) return;
11726
12080
  const plateId = `plate-${trackId}`;
12081
+ const keyFrameMediaKey = this.deps.getKeyFrameMediaKey?.(trackId);
12082
+ let cropData = held.crop;
12083
+ if (cropData === void 0 && keyFrameMediaKey !== void 0 && this.deps.deriveCropFromKeyFrame) try {
12084
+ const derived = await this.deps.deriveCropFromKeyFrame({
12085
+ mediaKey: keyFrameMediaKey,
12086
+ bbox: held.bbox,
12087
+ frameWidth: held.frameWidth,
12088
+ frameHeight: held.frameHeight,
12089
+ padding: held.cropPadding,
12090
+ timestamp: held.timestamp
12091
+ });
12092
+ if (derived) {
12093
+ cropData = derived.jpeg;
12094
+ this.deps.logger.info("plate crop recovered from keyFrame (live capture missed)", {
12095
+ tags: {
12096
+ deviceId,
12097
+ trackId
12098
+ },
12099
+ meta: {
12100
+ plateId,
12101
+ skewMs: derived.skewMs
12102
+ }
12103
+ });
12104
+ }
12105
+ } catch (err) {
12106
+ this.deps.logger.debug("PlateRecognizer keyFrame crop derive failed", {
12107
+ tags: { deviceId },
12108
+ meta: {
12109
+ plateId,
12110
+ error: String(err)
12111
+ }
12112
+ });
12113
+ }
11727
12114
  let mediaKey;
11728
- if (held.crop !== void 0) try {
12115
+ if (cropData !== void 0) try {
11729
12116
  mediaKey = await this.deps.mediaStore.put({
11730
12117
  deviceId,
11731
12118
  ownerKind: "plate",
11732
12119
  ownerId: plateId,
11733
12120
  kind: "crop",
11734
12121
  timestamp: held.timestamp,
11735
- data: held.crop
12122
+ data: cropData
11736
12123
  });
11737
12124
  } catch (err) {
11738
12125
  this.deps.logger.warn("PlateRecognizer plate crop put failed", {
@@ -11744,7 +12131,6 @@ var PlateRecognizer = class {
11744
12131
  });
11745
12132
  }
11746
12133
  const match = this.matchVehicle(held.text, held.score);
11747
- const keyFrameMediaKey = this.deps.getKeyFrameMediaKey?.(trackId);
11748
12134
  try {
11749
12135
  await this.deps.plateStore.insert({
11750
12136
  id: plateId,
@@ -12000,6 +12386,86 @@ function createCaptureCrop(deps) {
12000
12386
  };
12001
12387
  }
12002
12388
  //#endregion
12389
+ //#region src/pipeline-analytics/pipeline/display-crop.ts
12390
+ /** Bound the once-per-owner memo so a long-lived process never leaks it. */
12391
+ var MAX_LOGGED_OWNERS = 5e3;
12392
+ function createDisplayCrop(deps) {
12393
+ const loggedOwners = /* @__PURE__ */ new Set();
12394
+ const logFallbackOnce = (ownerId, reason) => {
12395
+ if (loggedOwners.has(ownerId)) return;
12396
+ if (loggedOwners.size >= MAX_LOGGED_OWNERS) loggedOwners.clear();
12397
+ loggedOwners.add(ownerId);
12398
+ deps.logger.info("display crop fallback used (honest sub-native, not upscaled)", { meta: {
12399
+ ownerId,
12400
+ reason
12401
+ } });
12402
+ };
12403
+ return async (frameHandle, bbox, frameWidth, frameHeight, padding, ownerId, maxWidth) => {
12404
+ const norm = padBbox({
12405
+ x: bbox.x / frameWidth,
12406
+ y: bbox.y / frameHeight,
12407
+ w: bbox.w / frameWidth,
12408
+ h: bbox.h / frameHeight
12409
+ }, padding);
12410
+ const native = await deps.fetchNativeRoiRgb(frameHandle, norm, maxWidth);
12411
+ if (native) {
12412
+ const jpeg = await deps.encodeRgb(native.bytes, native.width, native.height);
12413
+ if (jpeg) return jpeg;
12414
+ }
12415
+ const fb = await deps.fetchFallbackFrame(frameHandle);
12416
+ if (fb) try {
12417
+ const jpeg = await deps.cropRegionToJpeg(fb.frame.bytes, fb.frame.width, fb.frame.height, norm);
12418
+ logFallbackOnce(ownerId, fb.reason);
12419
+ return jpeg;
12420
+ } catch (err) {
12421
+ deps.logger.debug("display crop fallback crop failed", { meta: {
12422
+ ownerId,
12423
+ error: err instanceof Error ? err.message : String(err)
12424
+ } });
12425
+ }
12426
+ logFallbackOnce(ownerId, "unavailable");
12427
+ return null;
12428
+ };
12429
+ }
12430
+ //#endregion
12431
+ //#region src/pipeline-analytics/pipeline/keyframe-crop.ts
12432
+ function createKeyFrameCrop(deps) {
12433
+ return async (input) => {
12434
+ if (input.frameWidth <= 0 || input.frameHeight <= 0) return null;
12435
+ const media = await deps.getMedia(input.mediaKey);
12436
+ if (!media) return null;
12437
+ const skewMs = Math.abs(media.timestamp - input.timestamp);
12438
+ if (skewMs > deps.maxSkewMs) {
12439
+ deps.logger.debug("keyframe crop rejected — temporal skew too large", { meta: {
12440
+ mediaKey: input.mediaKey,
12441
+ skewMs,
12442
+ maxSkewMs: deps.maxSkewMs
12443
+ } });
12444
+ return null;
12445
+ }
12446
+ try {
12447
+ const rgb = await deps.decodeJpegToRgb(media.base64);
12448
+ if (rgb.width <= 0 || rgb.height <= 0) return null;
12449
+ const norm = padBbox({
12450
+ x: input.bbox.x / input.frameWidth,
12451
+ y: input.bbox.y / input.frameHeight,
12452
+ w: input.bbox.w / input.frameWidth,
12453
+ h: input.bbox.h / input.frameHeight
12454
+ }, input.padding);
12455
+ return {
12456
+ jpeg: await deps.cropRegionToJpeg(Buffer.from(rgb.bytes), rgb.width, rgb.height, norm),
12457
+ skewMs
12458
+ };
12459
+ } catch (err) {
12460
+ deps.logger.debug("keyframe crop derive failed", { meta: {
12461
+ mediaKey: input.mediaKey,
12462
+ error: err instanceof Error ? err.message : String(err)
12463
+ } });
12464
+ return null;
12465
+ }
12466
+ };
12467
+ }
12468
+ //#endregion
12003
12469
  //#region src/pipeline-analytics/pipeline/dropout-skip.ts
12004
12470
  function shouldSkipDropoutFrame(activeTrackCount, detectionCount, consecutiveSkips, cfg) {
12005
12471
  if (!cfg.enabled) return false;
@@ -12246,6 +12712,16 @@ var TTL_SWEEP_INTERVAL_MS = 5e3;
12246
12712
  /** Padding applied to the parent bbox for the `cropJpeg` retry fallback when a
12247
12713
  * scheduled detail call's frameHandle lease is already gone. */
12248
12714
  var DETAIL_FALLBACK_CROP_PADDING = .15;
12715
+ /** Long-side cap for the DISPLAY-crop full-frame fallback (plate/face gallery
12716
+ * tiles) when the native ROI missed. A generous native cap so a keyframe-native
12717
+ * tier still yields a legible plate/face crop; the runner's ≤640 RAM tier is
12718
+ * returned as-is (honest sub-native). Never triggers an upscale. */
12719
+ var DISPLAY_FALLBACK_MAX_WIDTH = 1920;
12720
+ /** Max |keyFrame − plate-read| skew (ms) accepted for the DURABLE keyFrame-
12721
+ * derived plate-crop last resort. A readable plate implies a slow/stopping
12722
+ * vehicle, so within this window the subject barely moves off its bbox;
12723
+ * beyond it the crop would show empty scene, so it is rejected (no image). */
12724
+ var KEYFRAME_CROP_MAX_SKEW_MS = 1500;
12249
12725
  /** How long the active CLIP model id (from the embedding-encoder) is cached
12250
12726
  * before re-reading. */
12251
12727
  var CLIP_MODEL_ID_CACHE_TTL_MS = 6e4;
@@ -12267,6 +12743,13 @@ var ZONE_SLICE_RECONCILE_MS = 3e4;
12267
12743
  /** §5 best-frame: a track's `thumbnail` is overwritten only when the current
12268
12744
  * detection confidence beats the held best by at least this margin (hysteresis
12269
12745
  * so jitter around a plateau doesn't churn the write). */
12746
+ /** Rolling `lastFrame` cadence — deliberately DENSER than the 5s snapshot
12747
+ * interval: most real tracks live 3–9s, so on the snapshot cadence they
12748
+ * closed with NO `lastFrame` at all (29/68 in the 2026-07-22 4h audit) and
12749
+ * the close-time promotion had nothing to absorb. 1.5s bounds the "Ultimo"
12750
+ * tile's staleness at ~1.5s + the coast window, at one 960-boxed re-encode
12751
+ * per 1.5s per active track (putReplacing keeps a single row). */
12752
+ var LAST_FRAME_INTERVAL_MS = 1500;
12270
12753
  var BEST_FRAME_HYSTERESIS = .05;
12271
12754
  /** §5 best-frame: at most one best-thumbnail capture per this interval per track. */
12272
12755
  var BEST_FRAME_MIN_GAP_MS = 2e3;
@@ -12521,6 +13004,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
12521
13004
  packageDropDetector = null;
12522
13005
  /** Consecutive detector-dropout frames skipped per (deviceId, source) key. */
12523
13006
  dropoutSkipsByKey = /* @__PURE__ */ new Map();
13007
+ /** Reentrancy latch for `sweepExpiredTracks` — one sweep at a time (a stalled
13008
+ * sweep + queued interval ticks used to process the same close ~20×). */
13009
+ sweepInFlight = false;
12524
13010
  /** Best (highest-confidence) frame per track — drives the single overwrite
12525
13011
  * `thumbnail` (§5 best-frame). Shares the ONE best-detection policy with the
12526
13012
  * face path (`face-recognizer.ts`); rate-limited here since each best-frame
@@ -12549,6 +13035,32 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
12549
13035
  * where no `snapshot` is appended, so it is never byte-identical to a stored
12550
13036
  * `snapshot` (kills the end-of-track duplicate). Cleared on track end. */
12551
13037
  lastFrameAtByTrack = /* @__PURE__ */ new Map();
13038
+ /** Tracks with a rolling-`lastFrame` capture CURRENTLY in flight (RC-1,
13039
+ * DEFECT B). The `lastFrameAt` clock now advances ONLY when the write lands
13040
+ * (dispatcher completion), not synchronously at plan time — so a dropped roll
13041
+ * (recycled/blank frame) leaves the clock put and re-rolls next frame. Without
13042
+ * an in-flight guard that re-roll would fire EVERY frame while the first
13043
+ * capture is still resolving (0.1–3s under the native path), stacking N
13044
+ * overlapping captures. While a track sits here `buildSnapshotTargets`
13045
+ * suppresses a new rolling-`lastFrame` request; the pending dispatch clears it
13046
+ * (and, if it landed, advances `lastFrameAtByTrack`). Cleared on track end +
13047
+ * reset. */
13048
+ lastFrameInFlight = /* @__PURE__ */ new Set();
13049
+ /** Confirmed-birth tracks whose `firstFrame` has NOT yet actually persisted
13050
+ * (DEFECT A). Seeded on a confirmed birth (alongside the birth firstFrame
13051
+ * target) and removed the moment the write lands. While a track sits here and
13052
+ * is matched this frame, `collectFirstFrameRetries` re-schedules a firstFrame
13053
+ * target so a birth capture dropped by a recycled/blank live frame is retried
13054
+ * on a later frame (earliest available view still beats none). Scoped to
13055
+ * confirmed births ONLY — a suppressed false-positive birth or resurrection
13056
+ * never enters, so it never gets a retro firstFrame. Cleared on track end +
13057
+ * reset. */
13058
+ firstFramePendingTracks = /* @__PURE__ */ new Set();
13059
+ /** Tracks with a `firstFrame` capture CURRENTLY in flight (RC-1, DEFECT A).
13060
+ * Mirrors `thumbnailInFlight`: while a firstFrame capture is resolving the
13061
+ * per-frame retry is suppressed so at most one capture is outstanding per
13062
+ * track. Cleared on dispatch settle (+ track end / reset). */
13063
+ firstFrameInFlight = /* @__PURE__ */ new Set();
12552
13064
  /** Track ids whose best `thumbnail` has ACTUALLY been persisted (#27-A). A
12553
13065
  * track absent here keeps forcing a best-thumbnail capture every frame until
12554
13066
  * one lands, so a short / high-churn track whose first capture was dropped
@@ -12639,7 +13151,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
12639
13151
  let storage = this.ctx.kernel.storage;
12640
13152
  const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
12641
13153
  if (mediaRoot) {
12642
- const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-C4bKtLou.js"));
13154
+ const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-CgyUW8_U.js"));
12643
13155
  storage = new FilesystemStorageProvider(mediaRoot);
12644
13156
  logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
12645
13157
  }
@@ -12917,13 +13429,31 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
12917
13429
  logger: logger.child("CaptureCrop")
12918
13430
  });
12919
13431
  this.captureCrop = captureCrop;
13432
+ const captureDisplayCrop = createDisplayCrop({
13433
+ fetchNativeRoiRgb: (handle, norm, maxWidth) => fetchNativeCropRgb(handle, norm, maxWidth),
13434
+ fetchFallbackFrame: async (handle) => {
13435
+ const tiered = await fetchNativeFullFrameTiered(handle, DISPLAY_FALLBACK_MAX_WIDTH);
13436
+ if (!tiered || tiered.frame.format !== "rgb") return null;
13437
+ return {
13438
+ frame: {
13439
+ bytes: Buffer.from(tiered.frame.data),
13440
+ width: tiered.frame.width,
13441
+ height: tiered.frame.height
13442
+ },
13443
+ reason: tiered.tier === "ram-fullframe" ? "detection-raster" : "keyframe-native"
13444
+ };
13445
+ },
13446
+ encodeRgb: (bytes, w, h) => encodeRgbCropToJpeg(bytes, w, h),
13447
+ cropRegionToJpeg: async (bytes, w, h, norm) => (await extractCrop(bytes, w, h, norm)).crop,
13448
+ logger: logger.child("DisplayCrop")
13449
+ });
12920
13450
  this.faceRecognizer = new FaceRecognizer({
12921
13451
  identityStore: this.identityStore,
12922
13452
  faceStore: this.faceStore,
12923
13453
  mediaStore: this.mediaStore,
12924
13454
  trackStore: this.trackStore,
12925
13455
  eventStore: this.eventStore,
12926
- captureCrop,
13456
+ captureCrop: captureDisplayCrop,
12927
13457
  recomputeImportance: (trackId) => {
12928
13458
  const trackStore = this.trackStore;
12929
13459
  const eventStore = this.eventStore;
@@ -12938,11 +13468,26 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
12938
13468
  logger: logger.child("FaceRecognizer")
12939
13469
  });
12940
13470
  this.faceRecognizer.refreshGallery();
13471
+ const plateMediaStore = this.mediaStore;
13472
+ const deriveCropFromKeyFrame = createKeyFrameCrop({
13473
+ getMedia: async (mediaKey) => {
13474
+ const m = await plateMediaStore.getByKey(mediaKey);
13475
+ return m ? {
13476
+ base64: m.base64,
13477
+ timestamp: m.timestamp
13478
+ } : null;
13479
+ },
13480
+ decodeJpegToRgb: (base64) => decodeJpegToRgb(base64),
13481
+ cropRegionToJpeg: async (bytes, w, h, norm) => (await extractCrop(bytes, w, h, norm)).crop,
13482
+ maxSkewMs: KEYFRAME_CROP_MAX_SKEW_MS,
13483
+ logger: logger.child("KeyFrameCrop")
13484
+ });
12941
13485
  this.plateRecognizer = new PlateRecognizer({
12942
13486
  plateStore: this.plateStore,
12943
13487
  vehicleStore: this.vehicleStore,
12944
13488
  mediaStore: this.mediaStore,
12945
- captureCrop,
13489
+ captureCrop: captureDisplayCrop,
13490
+ deriveCropFromKeyFrame,
12946
13491
  getKeyFrameMediaKey: (trackId) => this.keyFrameKeyByTrackId.get(trackId),
12947
13492
  emitPlateGalleryChanged: (payload) => this.emitPlateGalleryChanged(payload),
12948
13493
  logger: logger.child("PlateRecognizer")
@@ -13443,6 +13988,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
13443
13988
  this.dropoutSkipsByKey.clear();
13444
13989
  this.bestFrameTracker.clear();
13445
13990
  this.lastFrameAtByTrack.clear();
13991
+ this.lastFrameInFlight.clear();
13992
+ this.firstFramePendingTracks.clear();
13993
+ this.firstFrameInFlight.clear();
13446
13994
  this.thumbnailLandedTracks.clear();
13447
13995
  this.thumbnailInFlight.clear();
13448
13996
  this.keyFrameInFlight.clear();
@@ -13589,6 +14137,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
13589
14137
  source,
13590
14138
  resurrected: true
13591
14139
  } });
14140
+ if (this.eventMediaDispatcher) this.lastFrameAtByTrack.set(id, result.timestamp);
13592
14141
  continue;
13593
14142
  }
13594
14143
  bornCandidates.push({
@@ -13614,15 +14163,16 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
13614
14163
  className: t.className,
13615
14164
  source
13616
14165
  } });
13617
- if (this.eventMediaDispatcher && frameHandle) {
13618
- firstFrameTargets.push({
14166
+ if (this.eventMediaDispatcher) {
14167
+ this.firstFramePendingTracks.add(id);
14168
+ this.trackStore.seedSnapshotClock(id, result.timestamp, t.bbox);
14169
+ this.lastFrameAtByTrack.set(id, result.timestamp);
14170
+ if (frameHandle) firstFrameTargets.push({
13619
14171
  trackId: id,
13620
14172
  timestamp: result.timestamp,
13621
14173
  bbox: { ...t.bbox },
13622
14174
  ...t.label ? { label: t.label } : {}
13623
14175
  });
13624
- this.trackStore.seedSnapshotClock(id, result.timestamp, t.bbox);
13625
- this.lastFrameAtByTrack.set(id, result.timestamp);
13626
14176
  }
13627
14177
  this.ctx.eventBus.emit({
13628
14178
  id: `pa-${(0, node_crypto.randomUUID)()}`,
@@ -13771,9 +14321,26 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
13771
14321
  for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
13772
14322
  else plateCrops += 1;
13773
14323
  const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
14324
+ for (const retry of this.collectFirstFrameRetries(result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
13774
14325
  const keyFrameTrackIds = selectKeyFrameTrackIds(snapshotTargets);
13775
14326
  if (keyFrameTrackIds.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameTrackIds, frameHandle);
13776
- if (eventTargets.length > 0 || firstFrameTargets.length > 0 || snapshotTargets.length > 0) {
14327
+ const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
14328
+ const rasterFallbackCandidates = [];
14329
+ const widenedRasterWantedIds = /* @__PURE__ */ new Set();
14330
+ for (const t of result.tracked) {
14331
+ if (t.matchedThisFrame === false) continue;
14332
+ const closure = this.trackClosureState.get(t.trackId);
14333
+ if (!closure?.confirmed) continue;
14334
+ if (closure.rasterFallback) continue;
14335
+ if (targetedThisFrame.has(t.trackId)) continue;
14336
+ widenedRasterWantedIds.add(t.trackId);
14337
+ rasterFallbackCandidates.push({
14338
+ trackId: t.trackId,
14339
+ timestamp: result.timestamp,
14340
+ bbox: { ...t.bbox }
14341
+ });
14342
+ }
14343
+ if (eventTargets.length > 0 || firstFrameTargets.length > 0 || snapshotTargets.length > 0 || widenedRasterWantedIds.size > 0) {
13777
14344
  const captureCounts = {
13778
14345
  events: eventTargets.length,
13779
14346
  trackFrames: firstFrameTargets.length,
@@ -13794,7 +14361,12 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
13794
14361
  } });
13795
14362
  const thumbInFlightTrackIds = snapshotTargets.filter((t) => t.bestThumbnail).map((t) => t.trackId);
13796
14363
  for (const trackId of thumbInFlightTrackIds) this.thumbnailInFlight.add(trackId);
13797
- const rasterFallbackWantedTrackIds = /* @__PURE__ */ new Set();
14364
+ const firstFrameInFlightTrackIds = firstFrameTargets.map((t) => t.trackId);
14365
+ for (const trackId of firstFrameInFlightTrackIds) this.firstFrameInFlight.add(trackId);
14366
+ const lastFrameInFlightTrackIds = snapshotTargets.filter((t) => t.rollingLastFrame).map((t) => t.trackId);
14367
+ for (const trackId of lastFrameInFlightTrackIds) this.lastFrameInFlight.add(trackId);
14368
+ const dispatchTimestamp = result.timestamp;
14369
+ const rasterFallbackWantedTrackIds = new Set(widenedRasterWantedIds);
13798
14370
  for (const t of firstFrameTargets) if (!this.trackClosureState.get(t.trackId)?.rasterFallback) rasterFallbackWantedTrackIds.add(t.trackId);
13799
14371
  for (const s of snapshotTargets) if (!this.trackClosureState.get(s.trackId)?.rasterFallback) rasterFallbackWantedTrackIds.add(s.trackId);
13800
14372
  this.eventMediaDispatcher.captureForFrame({
@@ -13804,7 +14376,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
13804
14376
  trackFrames: firstFrameTargets,
13805
14377
  snapshots: snapshotTargets,
13806
14378
  cropPadding: mediaSettings.cropPadding,
13807
- rasterFallbackWantedTrackIds
14379
+ rasterFallbackWantedTrackIds,
14380
+ rasterFallbackCandidates
13808
14381
  }).then((res) => {
13809
14382
  for (const rf of res.rasterFallbacks) {
13810
14383
  const st = this.ensureTrackClosureState(deviceId, rf.trackId);
@@ -13824,8 +14397,12 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
13824
14397
  mediaKey: s.mediaKey
13825
14398
  });
13826
14399
  for (const trackId of res.thumbnailTrackIds) this.thumbnailLandedTracks.add(trackId);
14400
+ for (const trackId of res.firstFrameTrackIds) this.firstFramePendingTracks.delete(trackId);
14401
+ for (const trackId of res.lastFrameTrackIds) this.lastFrameAtByTrack.set(trackId, dispatchTimestamp);
13827
14402
  }).catch(() => {}).finally(() => {
13828
14403
  for (const trackId of thumbInFlightTrackIds) this.thumbnailInFlight.delete(trackId);
14404
+ for (const trackId of firstFrameInFlightTrackIds) this.firstFrameInFlight.delete(trackId);
14405
+ for (const trackId of lastFrameInFlightTrackIds) this.lastFrameInFlight.delete(trackId);
13829
14406
  });
13830
14407
  }
13831
14408
  }
@@ -13889,6 +14466,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
13889
14466
  }
13890
14467
  overlayDetections = frame.detections;
13891
14468
  }
14469
+ const hasMovingTrack = result.tracked.some((t) => t.state === "moving" || t.state === "entered" || t.state === "left");
13892
14470
  this.ctx.eventBus.emit({
13893
14471
  id: `pa-${(0, node_crypto.randomUUID)()}`,
13894
14472
  timestamp: new Date(result.timestamp),
@@ -13903,7 +14481,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
13903
14481
  timestamp: result.timestamp,
13904
14482
  frameWidth: result.frameWidth,
13905
14483
  frameHeight: result.frameHeight,
13906
- detections: overlayDetections
14484
+ detections: overlayDetections,
14485
+ hasMovingTrack
13907
14486
  }
13908
14487
  });
13909
14488
  }
@@ -14231,7 +14810,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
14231
14810
  if (!this.faceRecognizer || detail.embedding === void 0) return;
14232
14811
  if (!await this.resolveGlobalFaceEnabled()) return;
14233
14812
  const [settings, media] = await Promise.all([this.resolveDeviceFaceSettings(deviceId), this.resolveDeviceMediaSettings(deviceId)]);
14234
- if (detail.bbox !== void 0 && Math.min(detail.bbox.w, detail.bbox.h) < settings.minFacePx) return;
14813
+ const faceShortSidePx = detail.nativeFaceShortSidePx ?? (detail.bbox !== void 0 ? Math.min(detail.bbox.w, detail.bbox.h) : void 0);
14814
+ if (faceShortSidePx !== void 0 && faceShortSidePx < settings.minFacePx) return;
14235
14815
  await this.faceRecognizer.ingestFaceDetail({
14236
14816
  deviceId,
14237
14817
  trackId,
@@ -14527,6 +15107,34 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
14527
15107
  });
14528
15108
  this.emitTrackLifecycle(payload, timestamp);
14529
15109
  }
15110
+ /**
15111
+ * DEFECT A: build `firstFrame` RETRY targets for confirmed-birth tracks whose
15112
+ * birth capture never landed. A track qualifies when it is still pending
15113
+ * (`firstFramePendingTracks`), is OBSERVED this frame (`matchedThisFrame` — a
15114
+ * coasted/stale box would crop the empty scene), is NOT already targeted this
15115
+ * frame (its birth target), and has no capture in flight (RC-1). Each target is
15116
+ * stamped with the CURRENT frame timestamp so the media carries the real
15117
+ * capture instant, not the birth ts — the earliest AVAILABLE view still beats
15118
+ * no firstFrame at all.
15119
+ */
15120
+ collectFirstFrameRetries(tracked, timestamp, alreadyTargeted) {
15121
+ if (this.firstFramePendingTracks.size === 0) return [];
15122
+ const bornThisFrame = new Set(alreadyTargeted.map((t) => t.trackId));
15123
+ const retries = [];
15124
+ for (const t of tracked) {
15125
+ if (t.matchedThisFrame === false) continue;
15126
+ if (!this.firstFramePendingTracks.has(t.trackId)) continue;
15127
+ if (bornThisFrame.has(t.trackId)) continue;
15128
+ if (this.firstFrameInFlight.has(t.trackId)) continue;
15129
+ retries.push({
15130
+ trackId: t.trackId,
15131
+ timestamp,
15132
+ bbox: { ...t.bbox },
15133
+ ...t.label ? { label: t.label } : {}
15134
+ });
15135
+ }
15136
+ return retries;
15137
+ }
14530
15138
  buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
14531
15139
  const targets = [];
14532
15140
  for (const t of tracked) {
@@ -14549,7 +15157,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
14549
15157
  frameHeight
14550
15158
  });
14551
15159
  const centerScore = bboxCenterScore(t.bbox, frameWidth, frameHeight);
14552
- const isNewBest = this.bestFrameTracker.observe(t.trackId, t.confidence, timestamp, edgeClear, centerScore);
15160
+ const containment = bboxContainment(t.bbox, frameWidth, frameHeight);
15161
+ const sizeScore = bboxSizeScore(t.bbox, frameWidth, frameHeight);
15162
+ const isNewBest = this.bestFrameTracker.observe(t.trackId, t.confidence, timestamp, edgeClear, centerScore, containment, sizeScore);
14553
15163
  this.maybeEmitTrackLifecycleUpdate(deviceId, t, timestamp, isNewBest);
14554
15164
  const plan = planPeriodicMedia({
14555
15165
  saveThumbnails: media.saveThumbnails,
@@ -14558,21 +15168,22 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
14558
15168
  thumbnailLanded: this.thumbnailLandedTracks.has(t.trackId),
14559
15169
  lastFrameAt: this.lastFrameAtByTrack.get(t.trackId) ?? 0,
14560
15170
  now: timestamp,
14561
- intervalMs: media.snapshotIntervalMs
15171
+ intervalMs: media.snapshotIntervalMs,
15172
+ lastFrameIntervalMs: Math.min(LAST_FRAME_INTERVAL_MS, media.snapshotIntervalMs)
14562
15173
  });
14563
- if (plan.rollingLastFrame) this.lastFrameAtByTrack.set(t.trackId, timestamp);
15174
+ const rollingLastFrame = plan.rollingLastFrame && !this.lastFrameInFlight.has(t.trackId);
14564
15175
  if (plan.appendSnapshot) this.trackStore.markSnapshotPending(t.trackId, timestamp, t.bbox);
14565
15176
  const plausibleBox = isPlausibleThumbnailBox(t.bbox, frameWidth, frameHeight);
14566
15177
  const bestThumbnail = plan.bestThumbnail && plausibleBox && !this.thumbnailInFlight.has(t.trackId);
14567
15178
  const keyFrame = isNewBest && plausibleBox;
14568
- if (!plan.appendSnapshot && !plan.rollingLastFrame && !bestThumbnail && !keyFrame) continue;
15179
+ if (!plan.appendSnapshot && !rollingLastFrame && !bestThumbnail && !keyFrame) continue;
14569
15180
  targets.push({
14570
15181
  trackId: t.trackId,
14571
15182
  timestamp,
14572
15183
  bbox: { ...t.bbox },
14573
15184
  ...t.label ? { label: t.label } : {},
14574
15185
  appendSnapshot: plan.appendSnapshot,
14575
- rollingLastFrame: plan.rollingLastFrame,
15186
+ rollingLastFrame,
14576
15187
  bestThumbnail,
14577
15188
  keyFrame
14578
15189
  });
@@ -14819,14 +15430,17 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
14819
15430
  }
14820
15431
  async sweepExpiredTracks() {
14821
15432
  if (this.shuttingDown || !this.trackStore) return;
15433
+ if (this.sweepInFlight) return;
15434
+ this.sweepInFlight = true;
14822
15435
  try {
14823
15436
  const expired = await this.trackStore.expireStale(Date.now());
14824
15437
  for (const t of expired) {
14825
15438
  const duration = t.lastSeen - t.firstSeen;
14826
15439
  const closure = this.trackClosureState.get(t.trackId);
15440
+ const ownedMedia = await this.mediaStore?.listByOwner("track", t.trackId) ?? [];
14827
15441
  const outcome = decideZeroMediaPolicy({
14828
15442
  durationMs: duration,
14829
- hasMedia: (await this.mediaStore?.listByOwner("track", t.trackId) ?? []).length > 0,
15443
+ hasMedia: ownedMedia.length > 0,
14830
15444
  confirmed: closure?.confirmed ?? false,
14831
15445
  hasRasterFallback: closure?.rasterFallback !== void 0
14832
15446
  });
@@ -14861,6 +15475,25 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
14861
15475
  }
14862
15476
  });
14863
15477
  }
15478
+ const promotion = decideLastFramePromotion(ownedMedia);
15479
+ if (promotion.promote) {
15480
+ const snapshot = ownedMedia.find((m) => m.key === promotion.snapshotKey);
15481
+ if (snapshot) try {
15482
+ await this.mediaStore?.promoteToLastFrame({
15483
+ deviceId: t.deviceId,
15484
+ trackId: t.trackId,
15485
+ snapshot
15486
+ });
15487
+ } catch (err) {
15488
+ this.ctx.logger.debug("lastFrame promotion failed", {
15489
+ tags: { deviceId: t.deviceId },
15490
+ meta: {
15491
+ trackId: t.trackId,
15492
+ error: String(err)
15493
+ }
15494
+ });
15495
+ }
15496
+ }
14864
15497
  this.ctx.logger.info("track ended", {
14865
15498
  tags: { deviceId: t.deviceId },
14866
15499
  meta: {
@@ -14918,6 +15551,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
14918
15551
  this.bestFrameTracker.delete(t.trackId);
14919
15552
  this.objectEmbeddingBestSelector.delete(t.trackId);
14920
15553
  this.lastFrameAtByTrack.delete(t.trackId);
15554
+ this.lastFrameInFlight.delete(t.trackId);
15555
+ this.firstFramePendingTracks.delete(t.trackId);
15556
+ this.firstFrameInFlight.delete(t.trackId);
14921
15557
  this.thumbnailLandedTracks.delete(t.trackId);
14922
15558
  this.thumbnailInFlight.delete(t.trackId);
14923
15559
  this.keyFrameInFlight.delete(t.trackId);
@@ -14976,6 +15612,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
14976
15612
  } catch (err) {
14977
15613
  if (this.shuttingDown) return;
14978
15614
  this.ctx.logger.debug("sweepExpiredTracks failed", { meta: { error: String(err) } });
15615
+ } finally {
15616
+ this.sweepInFlight = false;
14979
15617
  }
14980
15618
  }
14981
15619
  /**
@@ -14995,6 +15633,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
14995
15633
  this.bestFrameTracker.delete(trackId);
14996
15634
  this.objectEmbeddingBestSelector.delete(trackId);
14997
15635
  this.lastFrameAtByTrack.delete(trackId);
15636
+ this.lastFrameInFlight.delete(trackId);
15637
+ this.firstFramePendingTracks.delete(trackId);
15638
+ this.firstFrameInFlight.delete(trackId);
14998
15639
  this.thumbnailLandedTracks.delete(trackId);
14999
15640
  this.thumbnailInFlight.delete(trackId);
15000
15641
  this.keyFrameInFlight.delete(trackId);
@@ -15223,6 +15864,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
15223
15864
  this.bestFrameTracker.delete(track.trackId);
15224
15865
  this.objectEmbeddingBestSelector.delete(track.trackId);
15225
15866
  this.lastFrameAtByTrack.delete(track.trackId);
15867
+ this.lastFrameInFlight.delete(track.trackId);
15868
+ this.firstFramePendingTracks.delete(track.trackId);
15869
+ this.firstFrameInFlight.delete(track.trackId);
15226
15870
  this.thumbnailLandedTracks.delete(track.trackId);
15227
15871
  this.thumbnailInFlight.delete(track.trackId);
15228
15872
  this.keyFrameInFlight.delete(track.trackId);
@@ -15582,7 +16226,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
15582
16226
  return input.kind ? all.filter((m) => m.kind === input.kind) : all;
15583
16227
  }
15584
16228
  async getTrackMedia(input) {
15585
- return this.mediaStore?.listByOwner("track", input.trackId) ?? [];
16229
+ const all = await (this.mediaStore?.listByOwner("track", input.trackId) ?? Promise.resolve([]));
16230
+ const kinds = input.kinds;
16231
+ return kinds && kinds.length > 0 ? all.filter((m) => kinds.includes(m.kind)) : all;
15586
16232
  }
15587
16233
  /**
15588
16234
  * Search object events by text using CLIP cosine similarity.