@trillboards/edge-sdk 0.2.59 → 0.2.60

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.
@@ -163,8 +163,13 @@ export declare const STILL_FLOOR_SLOT_SHARE = 0.5;
163
163
  */
164
164
  export declare const MIN_ANALYZE_INTERVAL_MS = 30000;
165
165
  /**
166
- * Measured wall-clock cost of ONE LOOK, in ms — a real RTSP grab, not the
167
- * decode-and-detect that follows it.
166
+ * Measured wall-clock cost of ONE LOOK on the PER-GRAB capture path, in ms — a
167
+ * real RTSP grab, not the decode-and-detect that follows it.
168
+ *
169
+ * STILL LIVE, and still exactly right for the path it describes: the per-grab
170
+ * provider is what a feed runs when it cannot hold a persistent session (see
171
+ * `MEASURED_STREAM_LOOK_MS`), so this is the number `lookIntervalMsForFeedCount`
172
+ * uses in `'per_grab'` mode and nothing about it has changed.
168
173
  *
169
174
  * The cascade budget above quotes 3.6 ms for a look. That is the cost AFTER the
170
175
  * frame is in hand: pull it down to ~178x100 grey and run the detector. Getting
@@ -197,6 +202,75 @@ export declare const MIN_ANALYZE_INTERVAL_MS = 30000;
197
202
  * the connector is not running.
198
203
  */
199
204
  export declare const MEASURED_LOOK_MS = 2000;
205
+ /**
206
+ * Measured wall-clock cost of ONE LOOK on the PERSISTENT capture path, in ms.
207
+ *
208
+ * `RTSPStreamCameraProvider` holds one long-lived ffmpeg per feed decoding into
209
+ * a bounded ring, so `getFrame()` is a memory read of the newest frame rather
210
+ * than a process start plus a DESCRIBE/SETUP/PLAY plus a wait for an IDR. The
211
+ * handshake is paid ONCE, at open.
212
+ *
213
+ * MEASURED 2026-08-18 against the same rig, same clip, same degraded profile
214
+ * (640x360 / 399 kbps / GOP 2 s), driving looks at the shipped cadence so a
215
+ * fresh frame is already in hand — which is the condition the connector runs
216
+ * in and the only one where "cost of a look" means anything:
217
+ *
218
+ * per-grab p50 2,000 ms
219
+ * persistent p50 0 ms (sub-millisecond; the ring read is a pointer)
220
+ *
221
+ * Reproduce: `node scripts/measure-persistent-capture.mjs --feeds 1
222
+ * --seconds 60 --pace-ms 500`. The rig is committed — see that file's header
223
+ * for why, and for why a `file://` fixture cannot stand in for it.
224
+ *
225
+ * It is 1 rather than 0 so that `lookIntervalMsForFeedCount` can never divide
226
+ * by it, and so nobody reads a zero as "free" — a look still costs the
227
+ * detector's `MEASURED_LOOK_DETECT_MS`, which is what actually bounds the
228
+ * persistent path.
229
+ */
230
+ export declare const MEASURED_STREAM_LOOK_MS = 1;
231
+ /**
232
+ * Cost of a look AFTER the frame is in hand, in ms: pull it down to ~178x100
233
+ * grey and run `SceneChangeDetector`. Measured 3.6 ms median / 7.1 ms p95.
234
+ *
235
+ * This is the number the cascade budget has always quoted, and until the
236
+ * capture path changed it was irrelevant — a 3.6 ms stage behind a 2,000 ms
237
+ * one bounds nothing. On the persistent path it is the ONLY per-look cost
238
+ * left, so it becomes the binding term and gets named here rather than living
239
+ * in a comment.
240
+ */
241
+ export declare const MEASURED_LOOK_DETECT_MS = 3.6;
242
+ /**
243
+ * Share of ONE core the motion detector may take across every feed.
244
+ *
245
+ * The one judgement in the persistent derivation, stated rather than hidden —
246
+ * the same shape as `STILL_FLOOR_SLOT_SHARE`. Detection is the cheapest stage
247
+ * on the box and it must not become the noisy neighbour: the connector also
248
+ * runs one ffmpeg decode per feed (measured on the rig at ~2.4% of a core per
249
+ * feed at this profile) and one local VLM slot, and the VLM is the thing the
250
+ * product is actually spending money on.
251
+ *
252
+ * A quarter of a core does not bind at any realistic feed count — see
253
+ * `lookIntervalMsForFeedCount` — which is the point: it is a guard rail, not a
254
+ * throttle, and it exists so that a future 64-feed host derives a cadence it
255
+ * can serve instead of one it merely asked for.
256
+ */
257
+ export declare const LOOK_DETECT_CPU_SHARE = 0.25;
258
+ /**
259
+ * The persistent session's frame interval, in ms — the freshest a look can
260
+ * possibly be, because a look reads the newest DECODED frame.
261
+ *
262
+ * `RTSPStreamCameraProvider` defaults `streamFps` to 4, i.e. 250 ms. Restated
263
+ * here rather than imported because `edge-sdk` must not depend on a platform
264
+ * package, and asserted against the real default by `connectorCadence.test.ts`
265
+ * so the two cannot drift — the same treatment `LOOK_CAPTURE_CONCURRENCY` gets.
266
+ *
267
+ * `LOOK_INTERVAL_MS` must never be finer than this: asking for a 100 ms look
268
+ * cadence against a 250 ms stream would hand the same frame to the motion
269
+ * detector twice, which the provider refuses (it throws rather than repeat a
270
+ * frame) and which would report as a stale-stream fault on a perfectly healthy
271
+ * camera.
272
+ */
273
+ export declare const MEASURED_STREAM_FRAME_INTERVAL_MS = 250;
200
274
  /**
201
275
  * Concurrent RTSP grabs one physical recorder will take —
202
276
  * `DEFAULT_MAX_CONCURRENT_CAPTURES_PER_SOURCE` in `RTSPCameraProvider`, which
@@ -246,32 +320,59 @@ export declare const LEGACY_LOOK_INTERVAL_MS = 30000;
246
320
  * T Instant Brief Sustained State
247
321
  * 30 s 8.4% 18.6% 100% 100%
248
322
  * 5 s 50.2% 55.1% 100% 100%
249
- * 2.44 s 89.1% 71.2% 100% 100%
250
323
  * 2 s 95.5% 75.2% 100% 100%
251
324
  * 1 s 100.0% 86.6% 100% 100%
252
325
  * 0.75 s 100.0% 90.1% 100% 100%
253
- *
254
- * Two things fall out of that table and both are load bearing:
255
- *
256
- * - The Instant group — "alert me if the door opens after hours", a 2.2 s
257
- * median event — clears the 90% target at 2.4 s and is at 8.4% today. This
258
- * is the promise the 30 s cadence was silently failing.
259
- *
260
- * - The Brief group does NOT reach 90% until 0.75 s, and 0.75 s is well below
261
- * the measured cost of a single grab (`MEASURED_LOOK_MS`). So 90% on every
262
- * shape is NOT reachable by look cadence on the current capture
263
- * architecture, and this constant does not pretend otherwise. Closing that
264
- * gap needs a persistent RTSP session — one long-lived ffmpeg per feed
265
- * instead of one per grab — which is an architecture change, not a number.
266
- *
267
- * 2 000 ms is therefore the fastest cadence that is BOTH an eval output and
268
- * physically deliverable: it is the measured single-grab floor, and at it the
269
- * Instant group clears the target. Do not lower it without first lowering
270
- * `MEASURED_LOOK_MS`, and do not raise it without re-running the replay — the
271
- * `sensing-catch-rate` guard re-reads this constant and fails if it drifts from
272
- * the cadence the corpus was measured at.
326
+ * 0.5 s 100.0% 93.2% 100% 100%
327
+ *
328
+ * ── 2 000 -> 500 ms ON 2026-08-18, AND THE REASON IS THE CAPTURE PATH ──────
329
+ *
330
+ * This constant was 2 000 ms, and the comment here said why in as many words:
331
+ * "the Brief group does NOT reach 90% until 0.75 s, and 0.75 s is well below
332
+ * the measured cost of a single grab. So 90% on every shape is NOT reachable by
333
+ * look cadence on the current capture architecture… Closing that gap needs a
334
+ * persistent RTSP session — one long-lived ffmpeg per feed instead of one per
335
+ * grab — which is an architecture change, not a number."
336
+ *
337
+ * That is now built (`RTSPStreamCameraProvider`), the constraint it named is
338
+ * gone, and this is the number that moves as a result. A look is a memory read
339
+ * of the newest decoded frame — `MEASURED_STREAM_LOOK_MS`, sub-millisecond —
340
+ * so the recorder no longer bounds the look cadence at all. Brief clears the
341
+ * 90% target at 0.75 s; 500 ms is the next value that is BOTH under it and a
342
+ * whole multiple of the persistent session's 250 ms frame interval
343
+ * (`MEASURED_STREAM_FRAME_INTERVAL_MS`), so a look always finds an unseen frame
344
+ * in hand and never blocks.
345
+ *
346
+ * ── WHY STOP AT 500 ms ────────────────────────────────────────────────────
347
+ *
348
+ * The same stopping rule that picked 2 000 ms: go until the last unmet target
349
+ * is met, and no further. At 500 ms every reporting group clears 90% — the
350
+ * first cadence in the product's history where that is true. Below it the
351
+ * corpus has nothing left to buy (Instant and Brief are saturated, Sustained
352
+ * and State were never look-limited) while the cost is real and linear: each
353
+ * halving doubles both the detector's CPU (`MEASURED_LOOK_DETECT_MS` per look
354
+ * per feed) and the session's JPEG-encode rate.
355
+ *
356
+ * ── AND IT MUST NOT COST ANALYSES ─────────────────────────────────────────
357
+ *
358
+ * Looking 4x more often must not analyse 4x more often, or the change has
359
+ * failed: one 7.30 s slot is 493 analyses/hour for the whole connector and
360
+ * there is no cloud path left to absorb an overflow. Three things hold that,
361
+ * and all three are asserted rather than assumed:
362
+ * - `MIN_ANALYZE_INTERVAL_MS` (30 s) is a SEPARATE constant and is what
363
+ * `MotionGate.minAnalyzeIntervalMs` floors on.
364
+ * - `periodicIntervalMsForFeedCount` floors on the ANALYZE floor, never on
365
+ * the look cadence, so the still-scene heartbeat does not move.
366
+ * - a still scene contributes exactly ZERO motion-triggered analyses, which
367
+ * is what makes the extra looks free at the model.
368
+ * The `sensing-catch-rate` guard's volume ratchet is the mechanical version:
369
+ * `volume.idleAnalyses` may only go DOWN.
370
+ *
371
+ * Do not raise or lower this without re-running the replay — the guard re-reads
372
+ * this constant and fails if it drifts from the cadence the corpus baseline was
373
+ * measured at.
273
374
  */
274
- export declare const LOOK_INTERVAL_MS = 2000;
375
+ export declare const LOOK_INTERVAL_MS = 500;
275
376
  /** The DOOH audience default (`DEFAULT_PERIODIC_MS`). The slow end of the clamp. */
276
377
  export declare const DOOH_PERIODIC_MS = 300000;
277
378
  /**
@@ -286,27 +387,86 @@ export declare const DOOH_PERIODIC_MS = 300000;
286
387
  */
287
388
  export declare function periodicIntervalMsForFeedCount(feedCount: unknown): number | null;
288
389
  /**
289
- * The fastest look cadence a connector running `feedCount` feeds against ONE
290
- * recorder can actually SUSTAIN, in ms.
390
+ * How a feed's frames are acquired. The look cadence a connector can SUSTAIN
391
+ * depends on this and on nothing else about the box.
392
+ *
393
+ * 'persistent' one long-lived ffmpeg per feed into a bounded ring
394
+ * (`RTSPStreamCameraProvider`). A look is a memory read.
395
+ * 'per_grab' one ffmpeg per grab (`RTSPCameraProvider`). A look is an RTSP
396
+ * handshake, and the RECORDER is the bound.
397
+ */
398
+ export type CaptureMode = 'persistent' | 'per_grab';
399
+ /**
400
+ * The fastest look cadence a connector running `feedCount` feeds can actually
401
+ * SUSTAIN, in ms.
291
402
  *
292
403
  * Same shape as `periodicIntervalMsForFeedCount` and for the same reason: a
293
404
  * cadence is only real if the machine can deliver it. That function divides one
294
- * inference slot among N feeds; this one divides one recorder's capture
295
- * capacity among N feeds.
405
+ * inference slot among N feeds; this one divides whatever the capture path's
406
+ * scarce resource is — and the whole point of the persistent session is that it
407
+ * CHANGES WHICH RESOURCE THAT IS.
408
+ *
409
+ * ── 'per_grab' — the RECORDER is the bound (unchanged) ────────────────────
296
410
  *
297
411
  * N feeds x (1 look / T) <= LOOK_CAPTURE_CONCURRENCY / MEASURED_LOOK_MS
298
412
  * T >= N x MEASURED_LOOK_MS / LOOK_CAPTURE_CONCURRENCY
299
413
  *
300
414
  * Measured against that formula at 16 feeds on one recorder, GOP 2 s: predicted
301
- * 16.0 s/feed, observed 16.0 s/feed.
415
+ * 16.0 s/feed, observed 16.0 s/feed. This is still exactly right for a feed that
416
+ * could not hold a persistent session and degraded — see
417
+ * `RTSPStreamCameraProvider`'s degrade path — so the branch stays live rather
418
+ * than being deleted as legacy.
419
+ *
420
+ * ── 'persistent' — the recorder drops out and the DETECTOR is the bound ───
421
+ *
422
+ * A look no longer touches the recorder, so `LOOK_CAPTURE_CONCURRENCY` and
423
+ * `MEASURED_LOOK_MS` have nothing to say about it. What is left is the cost
424
+ * AFTER the frame is in hand:
425
+ *
426
+ * N feeds x (1 look / T) x MEASURED_LOOK_DETECT_MS <= LOOK_DETECT_CPU_SHARE
427
+ * T >= N x 3.6 / 0.25 ms
428
+ *
429
+ * That is 230 ms at 16 feeds and 461 ms at 32 — both BELOW `LOOK_INTERVAL_MS`,
430
+ * so the clamp wins and a realistic connector simply gets the eval target. It
431
+ * begins to bind past ~34 feeds, which is the honest place for it to bind: a
432
+ * host with 34 cameras has other problems (its still-scene heartbeat pinned at
433
+ * `DOOH_PERIODIC_MS` since 21 feeds) and `getSensingHealth()` reports the
434
+ * effective interval either way.
302
435
  *
303
436
  * Never faster than `LOOK_INTERVAL_MS` (the eval target — looking finer than
304
437
  * that buys nothing the corpus can see) and never slower than
305
438
  * `LEGACY_LOOK_INTERVAL_MS`, so this can only make a connector look MORE often
306
- * than it did, never less. Returns `null` on an uncountable feed count so a caller can
307
- * omit the key rather than derive a cadence from nothing.
439
+ * than it did, never less. Returns `null` on an uncountable feed count so a
440
+ * caller can omit the key rather than derive a cadence from nothing.
441
+ */
442
+ export declare function lookIntervalMsForFeedCount(feedCount: unknown, captureMode?: CaptureMode): number | null;
443
+ /**
444
+ * Decoded frames a persistent session must produce per LOOK.
445
+ *
446
+ * TWO, and the second one is not waste. `RTSPStreamCameraProvider.getFrame()`
447
+ * refuses to hand back a frame it has already served — that refusal is the
448
+ * whole reason a frozen camera cannot report as healthy — so a look that
449
+ * arrives before the next frame does BLOCKS, and if it blocks past the
450
+ * staleness deadline it throws on a camera that is working perfectly.
451
+ *
452
+ * Running the stream at exactly the look rate makes that a coin flip on every
453
+ * tick, because neither clock is locked to the other: the session's frame
454
+ * cadence follows the CAMERA's timestamps and the look cadence follows a Node
455
+ * timer, and they drift. One frame of headroom removes the race for the cost of
456
+ * one extra scale+encode per look — the cheapest stage in the pipeline, and one
457
+ * that never touches the recorder or the model.
458
+ */
459
+ export declare const STREAM_LOOK_HEADROOM = 2;
460
+ /**
461
+ * Frames per second a persistent session should decode to serve a given look
462
+ * cadence. Derived, so the two can never be set independently and drift into
463
+ * the blocking case above.
464
+ *
465
+ * At the shipped 500 ms look cadence this is 4 fps — which is also the
466
+ * provider's own default (`MEASURED_STREAM_FRAME_INTERVAL_MS`), and
467
+ * `connectorCadence.test.ts` asserts the agreement rather than trusting it.
308
468
  */
309
- export declare function lookIntervalMsForFeedCount(feedCount: unknown): number | null;
469
+ export declare function streamFpsForLookIntervalMs(lookIntervalMs: unknown): number;
310
470
  /**
311
471
  * The still-scene analyses/hour a whole connector demands at a heartbeat, and
312
472
  * what fraction of the single slot that is. Exposed so the derivation is
@@ -1 +1 @@
1
- {"version":3,"file":"connectorCadence.d.ts","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0GG;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,OAAQ,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AAEtC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AAEtC,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAExC;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CA4BhF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK5E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,OAAO,EAClB,kBAAkB,EAAE,OAAO,GAC1B;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAOhD"}
1
+ {"version":3,"file":"connectorCadence.d.ts","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0GG;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,OAAQ,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAExC;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CA4BhF;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,UAAU,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,OAAO,EAClB,WAAW,GAAE,WAAwB,GACpC,MAAM,GAAG,IAAI,CA0Bf;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,OAAO,GAAG,MAAM,CAI1E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,OAAO,EAClB,kBAAkB,EAAE,OAAO,GAC1B;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAOhD"}
@@ -107,9 +107,10 @@
107
107
  * what the operator wants; this says what the machine can deliver.
108
108
  */
109
109
  Object.defineProperty(exports, "__esModule", { value: true });
110
- exports.DOOH_PERIODIC_MS = exports.LOOK_INTERVAL_MS = exports.LEGACY_LOOK_INTERVAL_MS = exports.LOOK_CAPTURE_CONCURRENCY = exports.MEASURED_LOOK_MS = exports.MIN_ANALYZE_INTERVAL_MS = exports.STILL_FLOOR_SLOT_SHARE = exports.MEASURED_LOCAL_ANALYZE_MS = void 0;
110
+ exports.STREAM_LOOK_HEADROOM = exports.DOOH_PERIODIC_MS = exports.LOOK_INTERVAL_MS = exports.LEGACY_LOOK_INTERVAL_MS = exports.LOOK_CAPTURE_CONCURRENCY = exports.MEASURED_STREAM_FRAME_INTERVAL_MS = exports.LOOK_DETECT_CPU_SHARE = exports.MEASURED_LOOK_DETECT_MS = exports.MEASURED_STREAM_LOOK_MS = exports.MEASURED_LOOK_MS = exports.MIN_ANALYZE_INTERVAL_MS = exports.STILL_FLOOR_SLOT_SHARE = exports.MEASURED_LOCAL_ANALYZE_MS = void 0;
111
111
  exports.periodicIntervalMsForFeedCount = periodicIntervalMsForFeedCount;
112
112
  exports.lookIntervalMsForFeedCount = lookIntervalMsForFeedCount;
113
+ exports.streamFpsForLookIntervalMs = streamFpsForLookIntervalMs;
113
114
  exports.stillSceneLoad = stillSceneLoad;
114
115
  /**
115
116
  * Measured median seconds per on-device analysis, in ms. See the header for the
@@ -169,8 +170,13 @@ exports.STILL_FLOOR_SLOT_SHARE = 0.5;
169
170
  */
170
171
  exports.MIN_ANALYZE_INTERVAL_MS = 30000;
171
172
  /**
172
- * Measured wall-clock cost of ONE LOOK, in ms — a real RTSP grab, not the
173
- * decode-and-detect that follows it.
173
+ * Measured wall-clock cost of ONE LOOK on the PER-GRAB capture path, in ms — a
174
+ * real RTSP grab, not the decode-and-detect that follows it.
175
+ *
176
+ * STILL LIVE, and still exactly right for the path it describes: the per-grab
177
+ * provider is what a feed runs when it cannot hold a persistent session (see
178
+ * `MEASURED_STREAM_LOOK_MS`), so this is the number `lookIntervalMsForFeedCount`
179
+ * uses in `'per_grab'` mode and nothing about it has changed.
174
180
  *
175
181
  * The cascade budget above quotes 3.6 ms for a look. That is the cost AFTER the
176
182
  * frame is in hand: pull it down to ~178x100 grey and run the detector. Getting
@@ -203,6 +209,75 @@ exports.MIN_ANALYZE_INTERVAL_MS = 30000;
203
209
  * the connector is not running.
204
210
  */
205
211
  exports.MEASURED_LOOK_MS = 2000;
212
+ /**
213
+ * Measured wall-clock cost of ONE LOOK on the PERSISTENT capture path, in ms.
214
+ *
215
+ * `RTSPStreamCameraProvider` holds one long-lived ffmpeg per feed decoding into
216
+ * a bounded ring, so `getFrame()` is a memory read of the newest frame rather
217
+ * than a process start plus a DESCRIBE/SETUP/PLAY plus a wait for an IDR. The
218
+ * handshake is paid ONCE, at open.
219
+ *
220
+ * MEASURED 2026-08-18 against the same rig, same clip, same degraded profile
221
+ * (640x360 / 399 kbps / GOP 2 s), driving looks at the shipped cadence so a
222
+ * fresh frame is already in hand — which is the condition the connector runs
223
+ * in and the only one where "cost of a look" means anything:
224
+ *
225
+ * per-grab p50 2,000 ms
226
+ * persistent p50 0 ms (sub-millisecond; the ring read is a pointer)
227
+ *
228
+ * Reproduce: `node scripts/measure-persistent-capture.mjs --feeds 1
229
+ * --seconds 60 --pace-ms 500`. The rig is committed — see that file's header
230
+ * for why, and for why a `file://` fixture cannot stand in for it.
231
+ *
232
+ * It is 1 rather than 0 so that `lookIntervalMsForFeedCount` can never divide
233
+ * by it, and so nobody reads a zero as "free" — a look still costs the
234
+ * detector's `MEASURED_LOOK_DETECT_MS`, which is what actually bounds the
235
+ * persistent path.
236
+ */
237
+ exports.MEASURED_STREAM_LOOK_MS = 1;
238
+ /**
239
+ * Cost of a look AFTER the frame is in hand, in ms: pull it down to ~178x100
240
+ * grey and run `SceneChangeDetector`. Measured 3.6 ms median / 7.1 ms p95.
241
+ *
242
+ * This is the number the cascade budget has always quoted, and until the
243
+ * capture path changed it was irrelevant — a 3.6 ms stage behind a 2,000 ms
244
+ * one bounds nothing. On the persistent path it is the ONLY per-look cost
245
+ * left, so it becomes the binding term and gets named here rather than living
246
+ * in a comment.
247
+ */
248
+ exports.MEASURED_LOOK_DETECT_MS = 3.6;
249
+ /**
250
+ * Share of ONE core the motion detector may take across every feed.
251
+ *
252
+ * The one judgement in the persistent derivation, stated rather than hidden —
253
+ * the same shape as `STILL_FLOOR_SLOT_SHARE`. Detection is the cheapest stage
254
+ * on the box and it must not become the noisy neighbour: the connector also
255
+ * runs one ffmpeg decode per feed (measured on the rig at ~2.4% of a core per
256
+ * feed at this profile) and one local VLM slot, and the VLM is the thing the
257
+ * product is actually spending money on.
258
+ *
259
+ * A quarter of a core does not bind at any realistic feed count — see
260
+ * `lookIntervalMsForFeedCount` — which is the point: it is a guard rail, not a
261
+ * throttle, and it exists so that a future 64-feed host derives a cadence it
262
+ * can serve instead of one it merely asked for.
263
+ */
264
+ exports.LOOK_DETECT_CPU_SHARE = 0.25;
265
+ /**
266
+ * The persistent session's frame interval, in ms — the freshest a look can
267
+ * possibly be, because a look reads the newest DECODED frame.
268
+ *
269
+ * `RTSPStreamCameraProvider` defaults `streamFps` to 4, i.e. 250 ms. Restated
270
+ * here rather than imported because `edge-sdk` must not depend on a platform
271
+ * package, and asserted against the real default by `connectorCadence.test.ts`
272
+ * so the two cannot drift — the same treatment `LOOK_CAPTURE_CONCURRENCY` gets.
273
+ *
274
+ * `LOOK_INTERVAL_MS` must never be finer than this: asking for a 100 ms look
275
+ * cadence against a 250 ms stream would hand the same frame to the motion
276
+ * detector twice, which the provider refuses (it throws rather than repeat a
277
+ * frame) and which would report as a stale-stream fault on a perfectly healthy
278
+ * camera.
279
+ */
280
+ exports.MEASURED_STREAM_FRAME_INTERVAL_MS = 250;
206
281
  /**
207
282
  * Concurrent RTSP grabs one physical recorder will take —
208
283
  * `DEFAULT_MAX_CONCURRENT_CAPTURES_PER_SOURCE` in `RTSPCameraProvider`, which
@@ -252,32 +327,59 @@ exports.LEGACY_LOOK_INTERVAL_MS = 30000;
252
327
  * T Instant Brief Sustained State
253
328
  * 30 s 8.4% 18.6% 100% 100%
254
329
  * 5 s 50.2% 55.1% 100% 100%
255
- * 2.44 s 89.1% 71.2% 100% 100%
256
330
  * 2 s 95.5% 75.2% 100% 100%
257
331
  * 1 s 100.0% 86.6% 100% 100%
258
332
  * 0.75 s 100.0% 90.1% 100% 100%
259
- *
260
- * Two things fall out of that table and both are load bearing:
261
- *
262
- * - The Instant group — "alert me if the door opens after hours", a 2.2 s
263
- * median event — clears the 90% target at 2.4 s and is at 8.4% today. This
264
- * is the promise the 30 s cadence was silently failing.
265
- *
266
- * - The Brief group does NOT reach 90% until 0.75 s, and 0.75 s is well below
267
- * the measured cost of a single grab (`MEASURED_LOOK_MS`). So 90% on every
268
- * shape is NOT reachable by look cadence on the current capture
269
- * architecture, and this constant does not pretend otherwise. Closing that
270
- * gap needs a persistent RTSP session — one long-lived ffmpeg per feed
271
- * instead of one per grab — which is an architecture change, not a number.
272
- *
273
- * 2 000 ms is therefore the fastest cadence that is BOTH an eval output and
274
- * physically deliverable: it is the measured single-grab floor, and at it the
275
- * Instant group clears the target. Do not lower it without first lowering
276
- * `MEASURED_LOOK_MS`, and do not raise it without re-running the replay — the
277
- * `sensing-catch-rate` guard re-reads this constant and fails if it drifts from
278
- * the cadence the corpus was measured at.
333
+ * 0.5 s 100.0% 93.2% 100% 100%
334
+ *
335
+ * ── 2 000 -> 500 ms ON 2026-08-18, AND THE REASON IS THE CAPTURE PATH ──────
336
+ *
337
+ * This constant was 2 000 ms, and the comment here said why in as many words:
338
+ * "the Brief group does NOT reach 90% until 0.75 s, and 0.75 s is well below
339
+ * the measured cost of a single grab. So 90% on every shape is NOT reachable by
340
+ * look cadence on the current capture architecture… Closing that gap needs a
341
+ * persistent RTSP session — one long-lived ffmpeg per feed instead of one per
342
+ * grab — which is an architecture change, not a number."
343
+ *
344
+ * That is now built (`RTSPStreamCameraProvider`), the constraint it named is
345
+ * gone, and this is the number that moves as a result. A look is a memory read
346
+ * of the newest decoded frame — `MEASURED_STREAM_LOOK_MS`, sub-millisecond —
347
+ * so the recorder no longer bounds the look cadence at all. Brief clears the
348
+ * 90% target at 0.75 s; 500 ms is the next value that is BOTH under it and a
349
+ * whole multiple of the persistent session's 250 ms frame interval
350
+ * (`MEASURED_STREAM_FRAME_INTERVAL_MS`), so a look always finds an unseen frame
351
+ * in hand and never blocks.
352
+ *
353
+ * ── WHY STOP AT 500 ms ────────────────────────────────────────────────────
354
+ *
355
+ * The same stopping rule that picked 2 000 ms: go until the last unmet target
356
+ * is met, and no further. At 500 ms every reporting group clears 90% — the
357
+ * first cadence in the product's history where that is true. Below it the
358
+ * corpus has nothing left to buy (Instant and Brief are saturated, Sustained
359
+ * and State were never look-limited) while the cost is real and linear: each
360
+ * halving doubles both the detector's CPU (`MEASURED_LOOK_DETECT_MS` per look
361
+ * per feed) and the session's JPEG-encode rate.
362
+ *
363
+ * ── AND IT MUST NOT COST ANALYSES ─────────────────────────────────────────
364
+ *
365
+ * Looking 4x more often must not analyse 4x more often, or the change has
366
+ * failed: one 7.30 s slot is 493 analyses/hour for the whole connector and
367
+ * there is no cloud path left to absorb an overflow. Three things hold that,
368
+ * and all three are asserted rather than assumed:
369
+ * - `MIN_ANALYZE_INTERVAL_MS` (30 s) is a SEPARATE constant and is what
370
+ * `MotionGate.minAnalyzeIntervalMs` floors on.
371
+ * - `periodicIntervalMsForFeedCount` floors on the ANALYZE floor, never on
372
+ * the look cadence, so the still-scene heartbeat does not move.
373
+ * - a still scene contributes exactly ZERO motion-triggered analyses, which
374
+ * is what makes the extra looks free at the model.
375
+ * The `sensing-catch-rate` guard's volume ratchet is the mechanical version:
376
+ * `volume.idleAnalyses` may only go DOWN.
377
+ *
378
+ * Do not raise or lower this without re-running the replay — the guard re-reads
379
+ * this constant and fails if it drifts from the cadence the corpus baseline was
380
+ * measured at.
279
381
  */
280
- exports.LOOK_INTERVAL_MS = 2000;
382
+ exports.LOOK_INTERVAL_MS = 500;
281
383
  /** The DOOH audience default (`DEFAULT_PERIODIC_MS`). The slow end of the clamp. */
282
384
  exports.DOOH_PERIODIC_MS = 300000;
283
385
  /**
@@ -321,32 +423,107 @@ function periodicIntervalMsForFeedCount(feedCount) {
321
423
  return Math.min(exports.DOOH_PERIODIC_MS, Math.max(exports.MIN_ANALYZE_INTERVAL_MS, required));
322
424
  }
323
425
  /**
324
- * The fastest look cadence a connector running `feedCount` feeds against ONE
325
- * recorder can actually SUSTAIN, in ms.
426
+ * The fastest look cadence a connector running `feedCount` feeds can actually
427
+ * SUSTAIN, in ms.
326
428
  *
327
429
  * Same shape as `periodicIntervalMsForFeedCount` and for the same reason: a
328
430
  * cadence is only real if the machine can deliver it. That function divides one
329
- * inference slot among N feeds; this one divides one recorder's capture
330
- * capacity among N feeds.
431
+ * inference slot among N feeds; this one divides whatever the capture path's
432
+ * scarce resource is — and the whole point of the persistent session is that it
433
+ * CHANGES WHICH RESOURCE THAT IS.
434
+ *
435
+ * ── 'per_grab' — the RECORDER is the bound (unchanged) ────────────────────
331
436
  *
332
437
  * N feeds x (1 look / T) <= LOOK_CAPTURE_CONCURRENCY / MEASURED_LOOK_MS
333
438
  * T >= N x MEASURED_LOOK_MS / LOOK_CAPTURE_CONCURRENCY
334
439
  *
335
440
  * Measured against that formula at 16 feeds on one recorder, GOP 2 s: predicted
336
- * 16.0 s/feed, observed 16.0 s/feed.
441
+ * 16.0 s/feed, observed 16.0 s/feed. This is still exactly right for a feed that
442
+ * could not hold a persistent session and degraded — see
443
+ * `RTSPStreamCameraProvider`'s degrade path — so the branch stays live rather
444
+ * than being deleted as legacy.
445
+ *
446
+ * ── 'persistent' — the recorder drops out and the DETECTOR is the bound ───
447
+ *
448
+ * A look no longer touches the recorder, so `LOOK_CAPTURE_CONCURRENCY` and
449
+ * `MEASURED_LOOK_MS` have nothing to say about it. What is left is the cost
450
+ * AFTER the frame is in hand:
451
+ *
452
+ * N feeds x (1 look / T) x MEASURED_LOOK_DETECT_MS <= LOOK_DETECT_CPU_SHARE
453
+ * T >= N x 3.6 / 0.25 ms
454
+ *
455
+ * That is 230 ms at 16 feeds and 461 ms at 32 — both BELOW `LOOK_INTERVAL_MS`,
456
+ * so the clamp wins and a realistic connector simply gets the eval target. It
457
+ * begins to bind past ~34 feeds, which is the honest place for it to bind: a
458
+ * host with 34 cameras has other problems (its still-scene heartbeat pinned at
459
+ * `DOOH_PERIODIC_MS` since 21 feeds) and `getSensingHealth()` reports the
460
+ * effective interval either way.
337
461
  *
338
462
  * Never faster than `LOOK_INTERVAL_MS` (the eval target — looking finer than
339
463
  * that buys nothing the corpus can see) and never slower than
340
464
  * `LEGACY_LOOK_INTERVAL_MS`, so this can only make a connector look MORE often
341
- * than it did, never less. Returns `null` on an uncountable feed count so a caller can
342
- * omit the key rather than derive a cadence from nothing.
465
+ * than it did, never less. Returns `null` on an uncountable feed count so a
466
+ * caller can omit the key rather than derive a cadence from nothing.
343
467
  */
344
- function lookIntervalMsForFeedCount(feedCount) {
468
+ function lookIntervalMsForFeedCount(feedCount, captureMode = 'per_grab') {
345
469
  const n = Number(feedCount);
346
470
  if (!Number.isFinite(n) || n < 1)
347
471
  return null;
348
- const sustainable = Math.ceil((Math.trunc(n) * exports.MEASURED_LOOK_MS) / exports.LOOK_CAPTURE_CONCURRENCY);
349
- return Math.min(exports.LEGACY_LOOK_INTERVAL_MS, Math.max(exports.LOOK_INTERVAL_MS, sustainable));
472
+ const feeds = Math.trunc(n);
473
+ if (captureMode === 'persistent') {
474
+ const sustainable = Math.ceil((feeds * exports.MEASURED_LOOK_DETECT_MS) / exports.LOOK_DETECT_CPU_SHARE);
475
+ return Math.min(exports.LEGACY_LOOK_INTERVAL_MS, Math.max(exports.LOOK_INTERVAL_MS, sustainable));
476
+ }
477
+ // The per-grab branch keeps its OWN floor at `MEASURED_LOOK_MS`, not at
478
+ // `LOOK_INTERVAL_MS`, and that is deliberate rather than tidy.
479
+ //
480
+ // Those were the same 2 000 ms until this function learned about capture mode,
481
+ // so the distinction did not exist and the eval target did the flooring. Now
482
+ // that `LOOK_INTERVAL_MS` is 500 ms, flooring here would ask a 1-feed per-grab
483
+ // connector for a look every 1 000 ms instead of every 2 000 ms — DOUBLE the
484
+ // RTSP session churn on a recorder, derived from a constant that describes a
485
+ // capture path this branch is not on. And a feed reaches this branch by
486
+ // DEGRADING (`RTSPStreamCameraProvider` could not hold a persistent session),
487
+ // i.e. because that recorder has already refused something; doubling its
488
+ // session rate is precisely the wrong response.
489
+ //
490
+ // So: this change moves the per-grab cadence by zero milliseconds at every
491
+ // feed count, which `connectorCadence.test.ts` asserts against the values that
492
+ // shipped.
493
+ const sustainable = Math.ceil((feeds * exports.MEASURED_LOOK_MS) / exports.LOOK_CAPTURE_CONCURRENCY);
494
+ return Math.min(exports.LEGACY_LOOK_INTERVAL_MS, Math.max(exports.MEASURED_LOOK_MS, sustainable));
495
+ }
496
+ /**
497
+ * Decoded frames a persistent session must produce per LOOK.
498
+ *
499
+ * TWO, and the second one is not waste. `RTSPStreamCameraProvider.getFrame()`
500
+ * refuses to hand back a frame it has already served — that refusal is the
501
+ * whole reason a frozen camera cannot report as healthy — so a look that
502
+ * arrives before the next frame does BLOCKS, and if it blocks past the
503
+ * staleness deadline it throws on a camera that is working perfectly.
504
+ *
505
+ * Running the stream at exactly the look rate makes that a coin flip on every
506
+ * tick, because neither clock is locked to the other: the session's frame
507
+ * cadence follows the CAMERA's timestamps and the look cadence follows a Node
508
+ * timer, and they drift. One frame of headroom removes the race for the cost of
509
+ * one extra scale+encode per look — the cheapest stage in the pipeline, and one
510
+ * that never touches the recorder or the model.
511
+ */
512
+ exports.STREAM_LOOK_HEADROOM = 2;
513
+ /**
514
+ * Frames per second a persistent session should decode to serve a given look
515
+ * cadence. Derived, so the two can never be set independently and drift into
516
+ * the blocking case above.
517
+ *
518
+ * At the shipped 500 ms look cadence this is 4 fps — which is also the
519
+ * provider's own default (`MEASURED_STREAM_FRAME_INTERVAL_MS`), and
520
+ * `connectorCadence.test.ts` asserts the agreement rather than trusting it.
521
+ */
522
+ function streamFpsForLookIntervalMs(lookIntervalMs) {
523
+ const t = Number(lookIntervalMs);
524
+ if (!Number.isFinite(t) || t <= 0)
525
+ return 1000 / exports.MEASURED_STREAM_FRAME_INTERVAL_MS;
526
+ return (exports.STREAM_LOOK_HEADROOM * 1000) / t;
350
527
  }
351
528
  /**
352
529
  * The still-scene analyses/hour a whole connector demands at a heartbeat, and
@@ -1 +1 @@
1
- {"version":3,"file":"connectorCadence.js","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connectorCadence.js","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":"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"}
@@ -31,7 +31,7 @@ export interface DiagnosticReport {
31
31
  skipped: number;
32
32
  };
33
33
  }
34
- declare const SDK_VERSION = "0.2.59";
34
+ declare const SDK_VERSION = "0.2.60";
35
35
  declare function checkNodeVersion(): CheckResult;
36
36
  declare function checkPlatform(): CheckResult;
37
37
  declare function checkMemory(): CheckResult;
package/dist/diagnose.js CHANGED
@@ -63,7 +63,7 @@ const localNetworkAccess_1 = require("./localNetworkAccess");
63
63
  // ---------------------------------------------------------------------------
64
64
  // SDK version — mirrors package.json; kept in sync by CI
65
65
  // ---------------------------------------------------------------------------
66
- const SDK_VERSION = '0.2.59';
66
+ const SDK_VERSION = '0.2.60';
67
67
  exports._SDK_VERSION = SDK_VERSION;
68
68
  // ---------------------------------------------------------------------------
69
69
  // ANSI helpers (no external deps)
@@ -1 +1 @@
1
- {"version":3,"file":"localSensing.d.ts","sourceRoot":"","sources":["../src/localSensing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"localSensing.d.ts","sourceRoot":"","sources":["../src/localSensing.ts"],"names":[],"mappings":"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"}