@camstack/addon-ai 0.2.18 → 0.2.19

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.
package/dist/addon.js CHANGED
@@ -23717,13 +23717,24 @@ method(object({
23717
23717
  /** Playback-speed multiplier for the render (1 = realtime). */
23718
23718
  var ExportSpeedSchema = number().min(.25).max(32);
23719
23719
  /**
23720
- * One dense interval, in SECONDS FROM THE EXPORT'S OWN `fromMs`.
23720
+ * One dense interval, in WALL-CLOCK SECONDS FROM THE EXPORT'S OWN `fromMs`.
23721
23721
  *
23722
- * Relative and not absolute epoch on purpose: the renderer's frame-select
23723
- * expression sees ffmpeg's `t`, which starts at 0 for the export's source
23724
- * playlist. Handing it absolute epochs would make every call site responsible
23725
- * for the same subtraction, and the one that forgot would emit a filter that
23726
- * selects nothing — silently, as a uniform timelapse.
23722
+ * **Wall clock, not ffmpeg's `t`** — and the recorder translates. A caller
23723
+ * derives these bounds from things that happened at a TIME (a track's
23724
+ * `firstSeen`), while `t` runs over the source playlist: the concatenation of
23725
+ * every segment present for the range, with each recording GAP removed. The
23726
+ * two agree only on a window that recorded without one interruption, and only
23727
+ * the render side knows the segments, so the translation lives there
23728
+ * (`export-dense-map.ts`, addon-pipeline).
23729
+ *
23730
+ * It was not always so. These seconds were fed to `between(t,…)` verbatim, and
23731
+ * on a 10 h window holding 29,393 s of footage every range landed late by the
23732
+ * gap accumulated before it — up to 6,607 s, well past EOF. Nothing matched,
23733
+ * the video was a uniform timelapse, and the log line reported the five ranges
23734
+ * that had been ASKED for (2026-08-13, export `57d14363`, camera 615).
23735
+ *
23736
+ * Relative and not absolute epoch, because an absolute epoch would make every
23737
+ * call site responsible for the same subtraction.
23727
23738
  */
23728
23739
  var ExportDenseRangeSchema = object({
23729
23740
  fromSec: number().nonnegative(),
@@ -30853,25 +30864,32 @@ object({
30853
30864
  var NativeLeaseAdmissionSchema = _enum(["all", "inferred"]);
30854
30865
  object({
30855
30866
  /**
30856
- * How long a retained native frame is served before it counts as a miss.
30867
+ * How many delivered frames the worker HOLDS at once, waiting for each one's
30868
+ * detection result.
30857
30869
  *
30858
- * Must cover the FULL late-crop horizon: detection inference + the
30859
- * cross-process inference-result hop to hub post-analysis + tracking + the
30860
- * tRPC crop round-trip back. Below ~500 ms the busiest cameras' subject crops
30861
- * outrun it and fall back to the ≤640 detection frame; above ~3 s the resident
30862
- * RAM per busy camera grows linearly with no measured hit-rate gain.
30870
+ * This replaced a TTL on 2026-08-13, and the replacement is the whole point:
30871
+ * a time window was never related to the event the pixels were waiting for.
30872
+ * A held frame now lives from delivery until the runner has its `FrameResult`
30873
+ * — at which moment the runner cuts the subject tiles it actually wanted and
30874
+ * releases the frame. The bound exists only so a runner that stops answering
30875
+ * cannot pin RAM: above it the OLDEST held frame is dropped and counted.
30876
+ *
30877
+ * Sizing: the steady state is `inferenceLatency × deliveredFps`, measured at
30878
+ * 40-160 ms × ≤25 fps = 1-4 frames. The default leaves headroom for a hiccup
30879
+ * without ever approaching the old resident set (43 frames × 24.9 MB at 4K).
30880
+ * Raising it does not buy hit rate — it buys tolerance for a slow runner, and
30881
+ * `holdOverflow` on the metrics line is what says you need it.
30863
30882
  */
30864
- ttlMs: number().int().min(250).max(1e4),
30883
+ holdFrames: number().int().min(1).max(64),
30865
30884
  /**
30866
30885
  * Hard per-decode-worker RAM ceiling for retained native frames, in MB.
30867
30886
  *
30868
- * Intended as a SAFETY ceiling with the TTL as the effective cap — but check
30869
- * which one is actually binding before reasoning from that. At the shipped
30870
- * 1024 MB and a 2 800 ms TTL, a 4K camera hits the CEILING first (~43 frames
30871
- * at ~24 MB each) and the TTL never gets to expire anything; `leaseMb` /
30872
- * `leaseFrames` on the metrics line say which. When the ceiling binds, a
30873
- * change that admits fewer frames buys retention WINDOW at constant RAM
30874
- * rather than giving RAM back — lower this knob if RAM is what you wanted.
30887
+ * Since 2026-08-13 this is a SAFETY ceiling and nothing else: `holdFrames`
30888
+ * is what decides how much is held, and the ceiling is the number above which
30889
+ * something is wrong. Before that it was the effective cap — at 1024 MB with
30890
+ * a 2 800 ms TTL a 4K camera sat pinned at `leaseMb:1020, leaseFrames:43`
30891
+ * with the TTL expiring nothing, which is exactly the confusion the hold
30892
+ * removes. `leaseMb` / `leaseFrames` still say what is resident.
30875
30893
  * `0` DISABLES the lease entirely and falls the worker back to the tiny
30876
30894
  * leak-prone GPU surface ring (~85% crop miss; that is what the lease exists
30877
30895
  * to replace).
@@ -30897,22 +30915,45 @@ object({
30897
30915
  * there is the signal that some caller names frames outside the inference set
30898
30916
  * and that this must go back to `all`.
30899
30917
  */
30900
- admission: NativeLeaseAdmissionSchema
30918
+ admission: NativeLeaseAdmissionSchema,
30919
+ /**
30920
+ * RAM ceiling per decode worker, in MB, for the SUBJECT TILES — the
30921
+ * compressed native crops the worker cuts at the moment a frame's detection
30922
+ * result arrives, and keeps long after the frame itself is freed.
30923
+ *
30924
+ * This is the knob that replaced the old retention window, and it buys about
30925
+ * three orders of magnitude more of it: a tile is one subject at native
30926
+ * resolution, JPEG-encoded (~60-120 KB on a 4K person), against ~24.9 MB for
30927
+ * the frame it was cut from. A frame on which nothing was detected costs
30928
+ * nothing at all, which is the real change — the old lease paid per FRAME and
30929
+ * was interrogated per SUBJECT.
30930
+ *
30931
+ * `0` DISABLES tiles, leaving only the hold window and the ≤640 RAM
30932
+ * fallback — i.e. the pre-2026-08-13 miss profile. Set it there only to
30933
+ * reproduce that.
30934
+ */
30935
+ tileBudgetMb: number().int().min(0).max(1024)
30901
30936
  });
30902
30937
  /**
30903
- * The values in force when the operator has set nothing — byte-for-byte the
30904
- * constants the decode worker shipped with as env-var defaults, so making these
30905
- * settings changed no behaviour on the day it landed.
30938
+ * The values in force when the operator has set nothing.
30939
+ *
30940
+ * `budgetMb` stays at 1024 on the day the hold landed, deliberately: it stopped
30941
+ * being the retention window and became the OOM ceiling, and lowering a ceiling
30942
+ * in the same change that redefines it would make a regression and a retune
30943
+ * indistinguishable. Cut it once `tileHits` / `holdOverflow` have been read on
30944
+ * live traffic.
30906
30945
  */
30907
30946
  var DEFAULT_NATIVE_LEASE_SETTINGS = {
30908
- ttlMs: 1200,
30947
+ holdFrames: 8,
30909
30948
  budgetMb: 1024,
30910
30949
  activityMs: 15e3,
30950
+ tileBudgetMb: 64,
30911
30951
  admission: "inferred"
30912
30952
  };
30913
- DEFAULT_NATIVE_LEASE_SETTINGS.ttlMs;
30953
+ DEFAULT_NATIVE_LEASE_SETTINGS.holdFrames;
30914
30954
  DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
30915
30955
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
30956
+ DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
30916
30957
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
30917
30958
  //#endregion
30918
30959
  //#region src/adapters/adapter.ts
package/dist/addon.mjs CHANGED
@@ -23679,13 +23679,24 @@ method(object({
23679
23679
  /** Playback-speed multiplier for the render (1 = realtime). */
23680
23680
  var ExportSpeedSchema = number().min(.25).max(32);
23681
23681
  /**
23682
- * One dense interval, in SECONDS FROM THE EXPORT'S OWN `fromMs`.
23682
+ * One dense interval, in WALL-CLOCK SECONDS FROM THE EXPORT'S OWN `fromMs`.
23683
23683
  *
23684
- * Relative and not absolute epoch on purpose: the renderer's frame-select
23685
- * expression sees ffmpeg's `t`, which starts at 0 for the export's source
23686
- * playlist. Handing it absolute epochs would make every call site responsible
23687
- * for the same subtraction, and the one that forgot would emit a filter that
23688
- * selects nothing — silently, as a uniform timelapse.
23684
+ * **Wall clock, not ffmpeg's `t`** — and the recorder translates. A caller
23685
+ * derives these bounds from things that happened at a TIME (a track's
23686
+ * `firstSeen`), while `t` runs over the source playlist: the concatenation of
23687
+ * every segment present for the range, with each recording GAP removed. The
23688
+ * two agree only on a window that recorded without one interruption, and only
23689
+ * the render side knows the segments, so the translation lives there
23690
+ * (`export-dense-map.ts`, addon-pipeline).
23691
+ *
23692
+ * It was not always so. These seconds were fed to `between(t,…)` verbatim, and
23693
+ * on a 10 h window holding 29,393 s of footage every range landed late by the
23694
+ * gap accumulated before it — up to 6,607 s, well past EOF. Nothing matched,
23695
+ * the video was a uniform timelapse, and the log line reported the five ranges
23696
+ * that had been ASKED for (2026-08-13, export `57d14363`, camera 615).
23697
+ *
23698
+ * Relative and not absolute epoch, because an absolute epoch would make every
23699
+ * call site responsible for the same subtraction.
23689
23700
  */
23690
23701
  var ExportDenseRangeSchema = object({
23691
23702
  fromSec: number().nonnegative(),
@@ -30815,25 +30826,32 @@ object({
30815
30826
  var NativeLeaseAdmissionSchema = _enum(["all", "inferred"]);
30816
30827
  object({
30817
30828
  /**
30818
- * How long a retained native frame is served before it counts as a miss.
30829
+ * How many delivered frames the worker HOLDS at once, waiting for each one's
30830
+ * detection result.
30819
30831
  *
30820
- * Must cover the FULL late-crop horizon: detection inference + the
30821
- * cross-process inference-result hop to hub post-analysis + tracking + the
30822
- * tRPC crop round-trip back. Below ~500 ms the busiest cameras' subject crops
30823
- * outrun it and fall back to the ≤640 detection frame; above ~3 s the resident
30824
- * RAM per busy camera grows linearly with no measured hit-rate gain.
30832
+ * This replaced a TTL on 2026-08-13, and the replacement is the whole point:
30833
+ * a time window was never related to the event the pixels were waiting for.
30834
+ * A held frame now lives from delivery until the runner has its `FrameResult`
30835
+ * — at which moment the runner cuts the subject tiles it actually wanted and
30836
+ * releases the frame. The bound exists only so a runner that stops answering
30837
+ * cannot pin RAM: above it the OLDEST held frame is dropped and counted.
30838
+ *
30839
+ * Sizing: the steady state is `inferenceLatency × deliveredFps`, measured at
30840
+ * 40-160 ms × ≤25 fps = 1-4 frames. The default leaves headroom for a hiccup
30841
+ * without ever approaching the old resident set (43 frames × 24.9 MB at 4K).
30842
+ * Raising it does not buy hit rate — it buys tolerance for a slow runner, and
30843
+ * `holdOverflow` on the metrics line is what says you need it.
30825
30844
  */
30826
- ttlMs: number().int().min(250).max(1e4),
30845
+ holdFrames: number().int().min(1).max(64),
30827
30846
  /**
30828
30847
  * Hard per-decode-worker RAM ceiling for retained native frames, in MB.
30829
30848
  *
30830
- * Intended as a SAFETY ceiling with the TTL as the effective cap — but check
30831
- * which one is actually binding before reasoning from that. At the shipped
30832
- * 1024 MB and a 2 800 ms TTL, a 4K camera hits the CEILING first (~43 frames
30833
- * at ~24 MB each) and the TTL never gets to expire anything; `leaseMb` /
30834
- * `leaseFrames` on the metrics line say which. When the ceiling binds, a
30835
- * change that admits fewer frames buys retention WINDOW at constant RAM
30836
- * rather than giving RAM back — lower this knob if RAM is what you wanted.
30849
+ * Since 2026-08-13 this is a SAFETY ceiling and nothing else: `holdFrames`
30850
+ * is what decides how much is held, and the ceiling is the number above which
30851
+ * something is wrong. Before that it was the effective cap — at 1024 MB with
30852
+ * a 2 800 ms TTL a 4K camera sat pinned at `leaseMb:1020, leaseFrames:43`
30853
+ * with the TTL expiring nothing, which is exactly the confusion the hold
30854
+ * removes. `leaseMb` / `leaseFrames` still say what is resident.
30837
30855
  * `0` DISABLES the lease entirely and falls the worker back to the tiny
30838
30856
  * leak-prone GPU surface ring (~85% crop miss; that is what the lease exists
30839
30857
  * to replace).
@@ -30859,22 +30877,45 @@ object({
30859
30877
  * there is the signal that some caller names frames outside the inference set
30860
30878
  * and that this must go back to `all`.
30861
30879
  */
30862
- admission: NativeLeaseAdmissionSchema
30880
+ admission: NativeLeaseAdmissionSchema,
30881
+ /**
30882
+ * RAM ceiling per decode worker, in MB, for the SUBJECT TILES — the
30883
+ * compressed native crops the worker cuts at the moment a frame's detection
30884
+ * result arrives, and keeps long after the frame itself is freed.
30885
+ *
30886
+ * This is the knob that replaced the old retention window, and it buys about
30887
+ * three orders of magnitude more of it: a tile is one subject at native
30888
+ * resolution, JPEG-encoded (~60-120 KB on a 4K person), against ~24.9 MB for
30889
+ * the frame it was cut from. A frame on which nothing was detected costs
30890
+ * nothing at all, which is the real change — the old lease paid per FRAME and
30891
+ * was interrogated per SUBJECT.
30892
+ *
30893
+ * `0` DISABLES tiles, leaving only the hold window and the ≤640 RAM
30894
+ * fallback — i.e. the pre-2026-08-13 miss profile. Set it there only to
30895
+ * reproduce that.
30896
+ */
30897
+ tileBudgetMb: number().int().min(0).max(1024)
30863
30898
  });
30864
30899
  /**
30865
- * The values in force when the operator has set nothing — byte-for-byte the
30866
- * constants the decode worker shipped with as env-var defaults, so making these
30867
- * settings changed no behaviour on the day it landed.
30900
+ * The values in force when the operator has set nothing.
30901
+ *
30902
+ * `budgetMb` stays at 1024 on the day the hold landed, deliberately: it stopped
30903
+ * being the retention window and became the OOM ceiling, and lowering a ceiling
30904
+ * in the same change that redefines it would make a regression and a retune
30905
+ * indistinguishable. Cut it once `tileHits` / `holdOverflow` have been read on
30906
+ * live traffic.
30868
30907
  */
30869
30908
  var DEFAULT_NATIVE_LEASE_SETTINGS = {
30870
- ttlMs: 1200,
30909
+ holdFrames: 8,
30871
30910
  budgetMb: 1024,
30872
30911
  activityMs: 15e3,
30912
+ tileBudgetMb: 64,
30873
30913
  admission: "inferred"
30874
30914
  };
30875
- DEFAULT_NATIVE_LEASE_SETTINGS.ttlMs;
30915
+ DEFAULT_NATIVE_LEASE_SETTINGS.holdFrames;
30876
30916
  DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
30877
30917
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
30918
+ DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
30878
30919
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
30879
30920
  //#endregion
30880
30921
  //#region src/adapters/adapter.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
4
4
  "description": "AI addon for CamStack — the `llm` collection provider (cloud, LAN, and camstack-managed local llama.cpp profiles) plus the per-node `llm-runtime` managed executor.",
5
5
  "keywords": [
6
6
  "camstack",