@hyperframes/engine 0.6.111 → 0.6.113

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/config.d.ts CHANGED
@@ -42,6 +42,12 @@ export interface EngineConfig {
42
42
  expectedChromiumMajor?: number;
43
43
  /** Force screenshot capture mode (skip BeginFrame even on Linux). */
44
44
  forceScreenshot: boolean;
45
+ /**
46
+ * Static-frame dedup: reuse byte-identical frames instead of re-seeking +
47
+ * re-screenshotting (anchor-verified at init). Default ON; disable via
48
+ * `HF_STATIC_DEDUP` in {false,0,off}. Only arms in screenshot capture mode.
49
+ */
50
+ staticFrameDedup: boolean;
45
51
  /**
46
52
  * Low-memory render profile. When `true`, the orchestrator collapses the
47
53
  * pipeline to its cheapest shape on memory-constrained hosts: it skips the
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAE3B,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;IACvC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IAGpB,yDAAyD;IACzD,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,2DAA2D;IAC3D,oBAAoB,EAAE,MAAM,CAAC;IAG7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;;OAQG;IACH,cAAc,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;IACjD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qEAAqE;IACrE,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;;;;OAQG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,yBAAyB,EAAE,OAAO,CAAC;IAGnC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,iCAAiC,EAAE,MAAM,CAAC;IAG1C,+DAA+D;IAC/D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,sBAAsB,EAAE,MAAM,CAAC;IAG/B,kEAAkE;IAClE,GAAG,EAAE;QAAE,QAAQ,EAAE,KAAK,GAAG,IAAI,CAAA;KAAE,GAAG,KAAK,CAAC;IACxC,yEAAyE;IACzE,aAAa,EAAE,OAAO,CAAC;IAGvB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,sBAAsB,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,6BAA6B,EAAE,MAAM,CAAC;IAGtC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;;;;;OAQG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAG9B,kDAAkD;IAClD,aAAa,EAAE,OAAO,CAAC;IACvB,mDAAmD;IACnD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAG7B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAGzB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,qEAAqE;AACrE,eAAO,MAAM,cAAc,EAAE,YA6C5B,CAAC;AAgBF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CA+H7E"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAE3B,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;IACvC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IAGpB,yDAAyD;IACzD,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,2DAA2D;IAC3D,oBAAoB,EAAE,MAAM,CAAC;IAG7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;;OAQG;IACH,cAAc,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;IACjD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qEAAqE;IACrE,eAAe,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;;;;;;OAQG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,yBAAyB,EAAE,OAAO,CAAC;IAGnC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,iCAAiC,EAAE,MAAM,CAAC;IAG1C,+DAA+D;IAC/D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,sBAAsB,EAAE,MAAM,CAAC;IAG/B,kEAAkE;IAClE,GAAG,EAAE;QAAE,QAAQ,EAAE,KAAK,GAAG,IAAI,CAAA;KAAE,GAAG,KAAK,CAAC;IACxC,yEAAyE;IACzE,aAAa,EAAE,OAAO,CAAC;IAGvB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,sBAAsB,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,6BAA6B,EAAE,MAAM,CAAC;IAGtC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;;;;;OAQG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAG9B,kDAAkD;IAClD,aAAa,EAAE,OAAO,CAAC;IACvB,mDAAmD;IACnD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAG7B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAGzB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,qEAAqE;AACrE,eAAO,MAAM,cAAc,EAAE,YA8C5B,CAAC;AAgBF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAqI7E"}
package/dist/config.js CHANGED
@@ -22,6 +22,7 @@ export const DEFAULT_CONFIG = {
22
22
  browserTimeout: 120_000,
23
23
  protocolTimeout: 300_000,
24
24
  forceScreenshot: false,
25
+ staticFrameDedup: true,
25
26
  // Auto-detected per host in `resolveConfig`; defaults off for the raw
26
27
  // DEFAULT_CONFIG (used directly by tests and worker-sizing fallbacks).
27
28
  lowMemoryMode: false,
@@ -95,6 +96,11 @@ export function resolveConfig(overrides) {
95
96
  return false;
96
97
  return isLowMemorySystem();
97
98
  };
99
+ // Opt-OUT: default ON, disabled only by an explicit falsey value.
100
+ const resolveStaticFrameDedup = () => {
101
+ const raw = env("HF_STATIC_DEDUP")?.trim().toLowerCase();
102
+ return !(raw === "false" || raw === "off" || raw === "0");
103
+ };
98
104
  // Env-var layer (backward compat)
99
105
  const fromEnv = {
100
106
  concurrency: env("PRODUCER_MAX_WORKERS") ? Number(env("PRODUCER_MAX_WORKERS")) : undefined,
@@ -111,6 +117,7 @@ export function resolveConfig(overrides) {
111
117
  ? Number(env("PRODUCER_EXPECTED_CHROMIUM_MAJOR"))
112
118
  : undefined,
113
119
  forceScreenshot: envBool("PRODUCER_FORCE_SCREENSHOT", DEFAULT_CONFIG.forceScreenshot),
120
+ staticFrameDedup: resolveStaticFrameDedup(),
114
121
  lowMemoryMode: resolveLowMemoryMode(),
115
122
  enablePageSideCompositing: envBool("HF_PAGE_SIDE_COMPOSITING", DEFAULT_CONFIG.enablePageSideCompositing),
116
123
  enableChunkedEncode: envBool("PRODUCER_ENABLE_CHUNKED_ENCODE", DEFAULT_CONFIG.enableChunkedEncode),
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,6BAA6B,GAC9B,MAAM,4BAA4B,CAAC;AAoLpC,qEAAqE;AACrE,MAAM,CAAC,MAAM,cAAc,GAAiB;IAC1C,GAAG,EAAE,EAAE;IACP,OAAO,EAAE,UAAU;IACnB,MAAM,EAAE,MAAM;IACd,WAAW,EAAE,EAAE;IAEf,WAAW,EAAE,MAAM;IACnB,cAAc,EAAE,GAAG;IACnB,iBAAiB,EAAE,GAAG;IACtB,oBAAoB,EAAE,IAAI;IAE1B,UAAU,EAAE,KAAK;IACjB,cAAc,EAAE,UAAU;IAC1B,iBAAiB,EAAE,IAAI;IACvB,cAAc,EAAE,OAAO;IACvB,eAAe,EAAE,OAAO;IACxB,eAAe,EAAE,KAAK;IACtB,sEAAsE;IACtE,uEAAuE;IACvE,aAAa,EAAE,KAAK;IACpB,yBAAyB,EAAE,IAAI;IAE/B,mBAAmB,EAAE,KAAK;IAC1B,eAAe,EAAE,GAAG;IACpB,qBAAqB,EAAE,IAAI;IAC3B,iCAAiC,EAAE,GAAG;IAEtC,mBAAmB,EAAE,OAAO;IAC5B,oBAAoB,EAAE,OAAO;IAC7B,sBAAsB,EAAE,OAAO;IAE/B,GAAG,EAAE,KAAK;IACV,aAAa,EAAE,IAAI;IAEnB,SAAS,EAAE,CAAC;IACZ,sBAAsB,EAAE,GAAG;IAC3B,6BAA6B,EAAE,IAAI;IAEnC,kBAAkB,EAAE,MAAM;IAC1B,kBAAkB,EAAE,MAAM;IAC1B,qBAAqB,EAAE,MAAM;IAE7B,aAAa,EAAE,IAAI;IAEnB,KAAK,EAAE,KAAK;CACb,CAAC;AAEF,SAAS,wBAAwB;IAC/B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,EAAE,CAAC;IAC5B,IAAI,KAAK,IAAI,6BAA6B;QAAE,OAAO,EAAE,CAAC;IACtD,OAAO,cAAc,CAAC,sBAAsB,CAAC;AAC/C,CAAC;AAED,SAAS,0BAA0B;IACjC,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC;IAC7B,IAAI,KAAK,IAAI,6BAA6B;QAAE,OAAO,GAAG,CAAC;IACvD,OAAO,cAAc,CAAC,6BAA6B,CAAC;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiC;IAC7D,MAAM,GAAG,GAAG,CAAC,GAAW,EAAsB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAU,EAAE;QACvD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACrB,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;YAAE,OAAO,QAAQ,CAAC;QACrD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC3C,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,QAAiB,EAAW,EAAE;QAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACrB,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAC;QACvC,OAAO,GAAG,KAAK,MAAM,CAAC;IACxB,CAAC,CAAC;IACF,MAAM,iBAAiB,GAAG,GAAmC,EAAE;QAC7D,MAAM,GAAG,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC;QAC7C,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,MAAM;YAAE,OAAO,GAAG,CAAC;QAC3E,OAAO,cAAc,CAAC,cAAc,CAAC;IACvC,CAAC,CAAC;IACF,4EAA4E;IAC5E,MAAM,oBAAoB,GAAG,GAAY,EAAE;QACzC,MAAM,GAAG,GAAG,GAAG,CAAC,0BAA0B,CAAC,EAAE,WAAW,EAAE,CAAC;QAC3D,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC/D,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,KAAK,CAAC;QAClE,OAAO,iBAAiB,EAAE,CAAC;IAC7B,CAAC,CAAC;IAEF,kCAAkC;IAClC,MAAM,OAAO,GAA0B;QACrC,WAAW,EAAE,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;QAC1F,cAAc,EAAE,MAAM,CAAC,2BAA2B,EAAE,cAAc,CAAC,cAAc,CAAC;QAClF,iBAAiB,EAAE,MAAM,CAAC,8BAA8B,EAAE,cAAc,CAAC,iBAAiB,CAAC;QAC3F,oBAAoB,EAAE,MAAM,CAC1B,iCAAiC,EACjC,cAAc,CAAC,oBAAoB,CACpC;QAED,UAAU,EAAE,GAAG,CAAC,8BAA8B,CAAC;QAC/C,UAAU,EAAE,OAAO,CAAC,sBAAsB,EAAE,cAAc,CAAC,UAAU,CAAC;QACtE,cAAc,EAAE,iBAAiB,EAAE;QACnC,iBAAiB,EAAE,OAAO,CAAC,8BAA8B,EAAE,cAAc,CAAC,iBAAiB,CAAC;QAC5F,cAAc,EAAE,MAAM,CAAC,sCAAsC,EAAE,cAAc,CAAC,cAAc,CAAC;QAC7F,eAAe,EAAE,MAAM,CACrB,wCAAwC,EACxC,cAAc,CAAC,eAAe,CAC/B;QACD,qBAAqB,EAAE,GAAG,CAAC,kCAAkC,CAAC;YAC5D,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;YACjD,CAAC,CAAC,SAAS;QAEb,eAAe,EAAE,OAAO,CAAC,2BAA2B,EAAE,cAAc,CAAC,eAAe,CAAC;QACrF,aAAa,EAAE,oBAAoB,EAAE;QACrC,yBAAyB,EAAE,OAAO,CAChC,0BAA0B,EAC1B,cAAc,CAAC,yBAAyB,CACzC;QAED,mBAAmB,EAAE,OAAO,CAC1B,gCAAgC,EAChC,cAAc,CAAC,mBAAmB,CACnC;QACD,eAAe,EAAE,IAAI,CAAC,GAAG,CACvB,GAAG,EACH,MAAM,CAAC,4BAA4B,EAAE,cAAc,CAAC,eAAe,CAAC,CACrE;QACD,qBAAqB,EAAE,OAAO,CAC5B,kCAAkC,EAClC,cAAc,CAAC,qBAAqB,CACrC;QACD,iCAAiC,EAAE,IAAI,CAAC,GAAG,CACzC,CAAC,EACD,MAAM,CACJ,gDAAgD,EAChD,cAAc,CAAC,iCAAiC,CACjD,CACF;QAED,mBAAmB,EAAE,MAAM,CAAC,0BAA0B,EAAE,cAAc,CAAC,mBAAmB,CAAC;QAC3F,oBAAoB,EAAE,MAAM,CAAC,2BAA2B,EAAE,cAAc,CAAC,oBAAoB,CAAC;QAC9F,sBAAsB,EAAE,MAAM,CAC5B,6BAA6B,EAC7B,cAAc,CAAC,sBAAsB,CACtC;QAED,GAAG,EAAE,CAAC,GAAG,EAAE;YACT,MAAM,GAAG,GAAG,GAAG,CAAC,uBAAuB,CAAC,CAAC;YACzC,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI;gBAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;YAC5D,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,EAAE;QACJ,aAAa,EAAE,OAAO,CAAC,0BAA0B,EAAE,cAAc,CAAC,aAAa,CAAC;QAEhF,SAAS,EAAE,MAAM,CAAC,qBAAqB,EAAE,cAAc,CAAC,SAAS,CAAC;QAClE,sBAAsB,EAAE,IAAI,CAAC,GAAG,CAC9B,EAAE,EACF,MAAM,CAAC,qCAAqC,EAAE,wBAAwB,EAAE,CAAC,CAC1E;QACD,6BAA6B,EAAE,IAAI,CAAC,GAAG,CACrC,EAAE,EACF,MAAM,CAAC,wCAAwC,EAAE,0BAA0B,EAAE,CAAC,CAC/E;QAED,kBAAkB,EAAE,MAAM,CACxB,kCAAkC,EAClC,cAAc,CAAC,kBAAkB,CAClC;QACD,kBAAkB,EAAE,MAAM,CACxB,kCAAkC,EAClC,cAAc,CAAC,kBAAkB,CAClC;QACD,qBAAqB,EAAE,MAAM,CAC3B,qCAAqC,EACrC,cAAc,CAAC,qBAAqB,CACrC;QAED,aAAa,EAAE,GAAG,CAAC,oCAAoC,CAAC,KAAK,OAAO;QACpE,mBAAmB,EAAE,GAAG,CAAC,mCAAmC,CAAC;QAE7D,eAAe,EAAE,GAAG,CAAC,+BAA+B,CAAC;KACtD,CAAC;IAEF,0DAA0D;IAC1D,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC;IAEhG,OAAO;QACL,GAAG,cAAc;QACjB,GAAG,QAAQ;QACX,GAAG,SAAS;KACb,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,25 @@ export interface CaptureSession {
21
21
  outputDir: string;
22
22
  onBeforeCapture: BeforeCaptureHook | null;
23
23
  isInitialized: boolean;
24
+ /**
25
+ * Static-frame dedup (default-on; opt out with `HF_STATIC_DEDUP=false`): indices of frames byte-identical
26
+ * to their predecessor (no GSAP tween / clip cut active in either), predicted from
27
+ * window.__timelines and empirically anchor-verified. These reuse `lastFrameBuffer`
28
+ * instead of re-seeking + re-screenshotting. Undefined when disabled or ineligible.
29
+ */
30
+ staticFrames?: Set<number>;
31
+ /** Last non-deduped frame buffer, reused for every `staticFrames` index in its run. */
32
+ lastFrameBuffer?: Buffer;
33
+ /** Count of frames served from a reused buffer (dedup telemetry). */
34
+ staticDedupCount?: number;
35
+ /** Dedup was enabled for this render (default-on; opt out with `HF_STATIC_DEDUP=false`). */
36
+ staticDedupEnabled?: boolean;
37
+ /**
38
+ * Short machine code for WHY dedup did not arm, for a low-cardinality breakdown.
39
+ * One of: `capture_mode` | `video_injection` | `page_composite` |
40
+ * `ineligible` | `verification_failed` | `verification_budget`. Undefined when armed or disabled.
41
+ */
42
+ staticDedupSkipReason?: string;
24
43
  pageReleased?: boolean;
25
44
  browserReleased?: boolean;
26
45
  browserConsoleBuffer: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"frameCapture.d.ts","sourceRoot":"","sources":["../../src/services/frameCapture.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"frameCapture.d.ts","sourceRoot":"","sources":["../../src/services/frameCapture.ts"],"names":[],"mappings":"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"}
@@ -739,6 +739,7 @@ export async function initializeSession(session) {
739
739
  if (session.options.format === "png") {
740
740
  await initTransparentBackground(session.page);
741
741
  }
742
+ await armStaticDedup(session, session.page, logInitPhase);
742
743
  session.isInitialized = true;
743
744
  return;
744
745
  }
@@ -866,6 +867,7 @@ export async function initializeSession(session) {
866
867
  if (session.options.format === "png") {
867
868
  await initTransparentBackground(session.page);
868
869
  }
870
+ await armStaticDedup(session, session.page, logInitPhase);
869
871
  session.isInitialized = true;
870
872
  }
871
873
  async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
@@ -947,6 +949,295 @@ async function prepareFrameForCapture(session, frameIndex, time) {
947
949
  }
948
950
  return { quantizedTime, seekMs, beforeCaptureMs };
949
951
  }
952
+ // ── Static-frame dedup (default-on, opt-out HF_STATIC_DEDUP=false) ─────────────
953
+ // Skip re-seeking + re-screenshotting frames that are byte-identical to their
954
+ // predecessor. A frame is dedupable iff no GSAP tween or clip cut is active in it or
955
+ // its predecessor (predicted from window.__timelines), AND an empirical anchor-compare
956
+ // confirms it. Capture-mode-independent (works on screenshot + beginframe), lossless
957
+ // (verification disables the whole comp on any drift), default off. Pays on
958
+ // static-hold content (title cards, slideshows, data-viz pauses); a no-op on
959
+ // continuously-animated comps and disqualified by video/canvas/non-GSAP animation.
960
+ /**
961
+ * Clip-cut boundary frames (±1) from the [data-start] schedule. A hard scene swap at a
962
+ * cut changes content with no tween; treat those frames as animated so the post-cut
963
+ * frame is captured fresh and later static frames reuse the correct scene.
964
+ */
965
+ async function computeClipBoundaryFrames(page, fps) {
966
+ const schedule = await page.evaluate(() => Array.from(document.querySelectorAll("[data-start]")).map((el) => ({
967
+ start: parseFloat(el.dataset.start || ""),
968
+ dur: parseFloat(el.dataset.duration || ""),
969
+ })));
970
+ const frames = new Set();
971
+ for (const { start, dur } of schedule) {
972
+ if (Number.isNaN(start))
973
+ continue;
974
+ const edges = [Math.round(start * fps)];
975
+ if (!Number.isNaN(dur))
976
+ edges.push(Math.round((start + dur) * fps));
977
+ for (const e of edges) {
978
+ for (const f of [e - 1, e, e + 1]) {
979
+ if (f >= 0)
980
+ frames.add(f);
981
+ }
982
+ }
983
+ }
984
+ return frames;
985
+ }
986
+ /**
987
+ * Predict the dedupable (static) frame set from window.__timelines. A frame f (f>0) is
988
+ * static iff NEITHER f NOR f-1 falls inside any GSAP tween interval — content didn't
989
+ * change f-1→f, so f can reuse f-1's buffer. Requiring BOTH neighbours static under-
990
+ * claims by one frame at each tween edge (the SAFE direction). Disqualifies the whole
991
+ * comp on any signal the tween-walker can't see: video / canvas / webgl (redraw without
992
+ * a tween), zero tweens (non-GSAP animation), or a running CSS/WAAPI animation.
993
+ */
994
+ async function computeStaticFrameSet(page, fps) {
995
+ const result = await page.evaluate(() => {
996
+ const intervals = [];
997
+ let tweenCount = 0;
998
+ // totalDuration() (NOT duration()): a repeat/yoyo tween animates past one iteration;
999
+ // a repeating timeline is marked opaque over its whole span (conservative).
1000
+ function walk(tl, offset) {
1001
+ if (typeof tl.getChildren !== "function")
1002
+ return;
1003
+ for (const child of tl.getChildren(false, true, true)) {
1004
+ const start = offset + (typeof child.startTime === "function" ? child.startTime() : 0);
1005
+ const single = typeof child.duration === "function" ? child.duration() : 0;
1006
+ const total = typeof child.totalDuration === "function" ? child.totalDuration() : single;
1007
+ if (typeof child.getChildren === "function") {
1008
+ if (total > single + 1e-6)
1009
+ intervals.push({ start, end: start + total });
1010
+ else
1011
+ walk(child, start);
1012
+ }
1013
+ else {
1014
+ tweenCount++;
1015
+ intervals.push({ start, end: start + total });
1016
+ }
1017
+ }
1018
+ }
1019
+ const w = window;
1020
+ for (const tl of Object.values(w.__timelines || {})) {
1021
+ if (tl && typeof tl.getChildren === "function")
1022
+ walk(tl, 0);
1023
+ }
1024
+ const hasVideo = !!document.querySelector("video");
1025
+ const hasCanvas = !!document.querySelector("canvas");
1026
+ // A non-numeric data-start (reference expression like "intro+0.5") can't be turned
1027
+ // into a clip-cut boundary by computeClipBoundaryFrames' parseFloat, so the cut goes
1028
+ // unprotected and could be deduped into the previous scene. Disqualify the comp.
1029
+ const hasUnresolvableClipStart = Array.from(document.querySelectorAll("[data-start]")).some((el) => {
1030
+ const v = el.dataset.start;
1031
+ return v != null && v.trim() !== "" && !Number.isFinite(parseFloat(v));
1032
+ });
1033
+ // Non-GSAP animation (CSS @keyframes / transitions / WAAPI) surfaces via
1034
+ // getAnimations(); any running/paused one can change content without a tween.
1035
+ let hasNonGsapAnim = false;
1036
+ try {
1037
+ const docAnims = document.getAnimations;
1038
+ if (typeof docAnims === "function") {
1039
+ hasNonGsapAnim = docAnims.call(document).some((a) => {
1040
+ const t = a;
1041
+ return t.playState === "running" || t.playState === "paused";
1042
+ });
1043
+ }
1044
+ }
1045
+ catch {
1046
+ hasNonGsapAnim = true;
1047
+ }
1048
+ return {
1049
+ intervals,
1050
+ tweenCount,
1051
+ duration: w.__hf?.duration ?? 0,
1052
+ hasVideo,
1053
+ hasCanvas,
1054
+ hasNonGsapAnim,
1055
+ hasUnresolvableClipStart,
1056
+ };
1057
+ });
1058
+ const { intervals, tweenCount, duration, hasVideo, hasCanvas, hasNonGsapAnim, hasUnresolvableClipStart, } = result;
1059
+ const totalFrames = Math.max(1, Math.ceil(duration * fps));
1060
+ const animated = new Set();
1061
+ for (const { start, end } of intervals) {
1062
+ const lo = Math.max(0, Math.floor(start * fps));
1063
+ const hi = Math.min(totalFrames - 1, Math.ceil(end * fps));
1064
+ for (let f = lo; f <= hi; f++)
1065
+ animated.add(f);
1066
+ }
1067
+ for (const f of await computeClipBoundaryFrames(page, fps))
1068
+ animated.add(f);
1069
+ const reasons = [];
1070
+ if (!(duration > 0))
1071
+ reasons.push("unknown/zero duration");
1072
+ if (hasVideo)
1073
+ reasons.push("video");
1074
+ if (hasCanvas)
1075
+ reasons.push("canvas/webgl");
1076
+ if (tweenCount === 0)
1077
+ reasons.push("no GSAP tweens (non-GSAP animation)");
1078
+ if (hasNonGsapAnim)
1079
+ reasons.push("running CSS/WAAPI animation");
1080
+ if (hasUnresolvableClipStart)
1081
+ reasons.push("unresolvable clip start (reference expression)");
1082
+ const eligible = reasons.length === 0;
1083
+ const staticFrameSet = new Set();
1084
+ if (eligible) {
1085
+ for (let f = 1; f < totalFrames; f++) {
1086
+ if (!animated.has(f) && !animated.has(f - 1))
1087
+ staticFrameSet.add(f);
1088
+ }
1089
+ }
1090
+ return {
1091
+ totalFrames,
1092
+ staticFrameSet,
1093
+ hasVideo,
1094
+ hasCanvas,
1095
+ hasNonGsapAnim,
1096
+ tweenCount,
1097
+ eligible,
1098
+ reason: eligible ? "eligible" : reasons.join("+"),
1099
+ };
1100
+ }
1101
+ /**
1102
+ * Empirically verify the predicted-static set before trusting it. Group static frames
1103
+ * into runs; each run [a..b] reuses anchor a-1. CRITICAL: compare against the ANCHOR,
1104
+ * not the predecessor — a slow drift with sub-quantization per-frame deltas is byte-
1105
+ * identical frame-to-frame yet drifts far from the anchor by the run's end (the real
1106
+ * frozen error). Capture each run's anchor once, compare END + a midpoint to it; any
1107
+ * mismatch ⇒ the run isn't truly static ⇒ disable dedup whole-comp. Capture-mode-
1108
+ * independent (seeks + screenshots in normal DOM). Returns the first bad frame, or null.
1109
+ */
1110
+ async function verifyStaticFramesSafe(session, page, staticFrames, fps, sampleCount) {
1111
+ const frames = [...staticFrames].sort((a, b) => a - b);
1112
+ if (frames.length === 0)
1113
+ return null;
1114
+ // Runs are maximal-contiguous (adjacent frames merge), so a run's anchor a-1 is
1115
+ // guaranteed NOT static — always a freshly-captured frame.
1116
+ const runs = [];
1117
+ for (const f of frames) {
1118
+ const last = runs[runs.length - 1];
1119
+ if (last && f === last.b + 1)
1120
+ last.b = f;
1121
+ else
1122
+ runs.push({ a: f, b: f });
1123
+ }
1124
+ const seekCapture = async (frameIdx) => {
1125
+ const t = quantizeTimeToFrame(frameIdx / fps, fps);
1126
+ await page.evaluate((tt) => {
1127
+ const hf = window.__hf;
1128
+ if (hf && typeof hf.seek === "function")
1129
+ hf.seek(tt);
1130
+ }, t);
1131
+ return pageScreenshotCapture(page, session.options);
1132
+ };
1133
+ // Verify EVERY run in order (no longest-first truncation that would leave runs armed
1134
+ // but unverified). Per run, compare the FIRST reused frame `a`, the END `b` (max
1135
+ // accumulated drift), and interior points at a stride — against the anchor the run
1136
+ // actually reuses. `sampleCount` sets the interior density (points per run ~ that many
1137
+ // for a long run); a hard cap bounds pathological run counts, and hitting it DISABLES
1138
+ // dedup (conservative: never trust an unverified set).
1139
+ const perRun = Math.max(3, Math.min(sampleCount, 8));
1140
+ const hardCap = Math.max(sampleCount * 8, 400);
1141
+ let spent = 0;
1142
+ for (const { a, b } of runs) {
1143
+ const anchor = a - 1;
1144
+ if (anchor < 0)
1145
+ continue;
1146
+ const anchorBuf = await seekCapture(anchor);
1147
+ spent++;
1148
+ const span = b - a;
1149
+ const stride = span > 0 ? Math.max(1, Math.floor(span / (perRun - 1))) : 1;
1150
+ const pts = new Set();
1151
+ for (let f = a; f <= b; f += stride)
1152
+ pts.add(f);
1153
+ pts.add(b); // always include the end (max drift)
1154
+ for (const f of [...pts].sort((x, y) => x - y)) {
1155
+ const cur = await seekCapture(f);
1156
+ spent++;
1157
+ if (!anchorBuf.equals(cur))
1158
+ return { badFrame: f, budgetExhausted: false };
1159
+ }
1160
+ // Budget exhausted → can't fully verify → disarm. Reported distinctly from real
1161
+ // drift so a `verification_budget` spike in telemetry signals "tune HF_STATIC_DEDUP_SAMPLES",
1162
+ // not "compositions are non-static".
1163
+ if (spent > hardCap)
1164
+ return { badFrame: a, budgetExhausted: true };
1165
+ }
1166
+ return null;
1167
+ }
1168
+ /**
1169
+ * Arm static-frame dedup for this render (default-on; opt out with HF_STATIC_DEDUP=false).
1170
+ * Runs at init in normal DOM state so the verification screenshots are valid. Predicts
1171
+ * the static set, anchor-verifies it (skip with HF_STATIC_DEDUP_VERIFY=false — unsafe),
1172
+ * and on success stores it on the session for captureFrameCore to reuse. Sample budget
1173
+ * via HF_STATIC_DEDUP_SAMPLES (default 24).
1174
+ */
1175
+ async function armStaticDedup(session, page, logInitPhase) {
1176
+ // Default ON for everyone; opt out via HF_STATIC_DEDUP in {false,0,off} (resolved into
1177
+ // EngineConfig.staticFrameDedup by resolveConfig). Verification is the safety net at scale.
1178
+ // Default-on: only an explicit `staticFrameDedup === false` (resolved from
1179
+ // HF_STATIC_DEDUP) disables; a missing config leaves dedup enabled.
1180
+ session.staticDedupEnabled = session.config?.staticFrameDedup !== false;
1181
+ if (!session.staticDedupEnabled)
1182
+ return;
1183
+ // Conservative gates: dedup is verified against the plain screenshot path, so only arm
1184
+ // where the production capture matches what verification measures, and where reuse is
1185
+ // sound. Skip when:
1186
+ // - capture mode is not screenshot (BeginFrame advances the compositor clock per
1187
+ // frame; skipping beginFrame for static frames gaps the tick sequence, and the
1188
+ // verifier uses pageScreenshotCapture not beginFrameCapture — its proof wouldn't
1189
+ // transfer);
1190
+ // - a before-capture hook is set (per-frame video-frame injection — those frames are
1191
+ // NOT static even if the GSAP timeline is idle, and the injector is skipped on reuse);
1192
+ // - page-side compositing is active (shader transitions / drawElement composite paint
1193
+ // a frame the plain verification screenshot doesn't reproduce).
1194
+ if (session.captureMode !== "screenshot") {
1195
+ session.staticDedupSkipReason = "capture_mode";
1196
+ logInitPhase(`static-frame dedup: disabled (capture mode ${session.captureMode}, not screenshot)`);
1197
+ return;
1198
+ }
1199
+ if (session.onBeforeCapture) {
1200
+ session.staticDedupSkipReason = "video_injection";
1201
+ logInitPhase("static-frame dedup: disabled (before-capture hook / video injection active)");
1202
+ return;
1203
+ }
1204
+ const pageComposite = await page
1205
+ .evaluate(() => typeof window
1206
+ .__hf_page_composite_prepare === "function")
1207
+ .catch(() => true); // fail CLOSED: if we can't determine, assume compositing → skip dedup
1208
+ if (pageComposite) {
1209
+ session.staticDedupSkipReason = "page_composite";
1210
+ logInitPhase("static-frame dedup: disabled (page-side compositing active)");
1211
+ return;
1212
+ }
1213
+ const fps = fpsToNumber(session.options.fps);
1214
+ const stats = await computeStaticFrameSet(page, fps);
1215
+ if (!stats.eligible || stats.staticFrameSet.size === 0) {
1216
+ session.staticDedupSkipReason = "ineligible";
1217
+ logInitPhase(`static-frame dedup: disabled (${stats.reason})`);
1218
+ return;
1219
+ }
1220
+ const rawSamples = Number(process.env.HF_STATIC_DEDUP_SAMPLES ?? "24");
1221
+ const samples = Number.isFinite(rawSamples) && rawSamples >= 1 ? rawSamples : 24;
1222
+ const verdict = process.env.HF_STATIC_DEDUP_VERIFY === "false"
1223
+ ? null
1224
+ : await verifyStaticFramesSafe(session, page, stats.staticFrameSet, fps, samples);
1225
+ if (verdict !== null) {
1226
+ session.staticDedupSkipReason = verdict.budgetExhausted
1227
+ ? "verification_budget"
1228
+ : "verification_failed";
1229
+ logInitPhase(verdict.budgetExhausted
1230
+ ? `static-frame dedup: disabled (verification budget exhausted before frame ${verdict.badFrame}; ` +
1231
+ `raise HF_STATIC_DEDUP_SAMPLES to verify more)`
1232
+ : `static-frame dedup: disabled (verification failed — content drifts from anchor at ` +
1233
+ `predicted-static frame ${verdict.badFrame})`);
1234
+ return;
1235
+ }
1236
+ // armed + predicted are derived from staticFrames in getCapturePerfSummary.
1237
+ session.staticFrames = stats.staticFrameSet;
1238
+ logInitPhase(`static-frame dedup: ${stats.staticFrameSet.size}/${stats.totalFrames} frame(s) reusable ` +
1239
+ `(${Math.round((stats.staticFrameSet.size / stats.totalFrames) * 100)}%, verified)`);
1240
+ }
950
1241
  /**
951
1242
  * Internal core: prepare, screenshot, and track perf.
952
1243
  * Shared by captureFrame (disk) and captureFrameToBuffer (buffer).
@@ -955,6 +1246,29 @@ async function prepareFrameForCapture(session, frameIndex, time) {
955
1246
  async function captureFrameCore(session, frameIndex, time) {
956
1247
  const { page, options } = session;
957
1248
  const startTime = Date.now();
1249
+ // Static-frame dedup: this frame is byte-identical to its predecessor (predicted +
1250
+ // anchor-verified at init) → reuse the prior buffer, skip the seek + screenshot.
1251
+ // KEY: index by the ABSOLUTE composition frame (derived from `time`), NOT the
1252
+ // `frameIndex` arg — chunked/parallel/distributed callers pass a chunk-RELATIVE
1253
+ // frameIndex (captureStage passes the loop `i`, parallelCoordinator passes
1254
+ // `i-outputFrameOffset`) while staticFrames is keyed in absolute frames. Using `time`
1255
+ // is correct on every path (sequential, per-worker range, distributed chunk) because
1256
+ // `time` is always the absolute composition time for the frame. Each session captures
1257
+ // its range in ascending order, so lastFrameBuffer is the correct in-range anchor (and
1258
+ // since a static run is verified identical, reusing the run's first in-range capture
1259
+ // equals reusing the global anchor). Telemetry: count reuses separately; do NOT bump
1260
+ // capturePerf.frames (that would dilute the per-frame timing averages).
1261
+ // Use the SAME floor+epsilon idiom as quantizeTimeToFrame so the dedup lookup agrees
1262
+ // with the frame the seek actually lands on, even if `time` ever isn't exactly i/fps.
1263
+ const absFrameIndex = Math.floor(time * fpsToNumber(options.fps) + 1e-9);
1264
+ if (session.staticFrames?.has(absFrameIndex) && session.lastFrameBuffer) {
1265
+ session.staticDedupCount = (session.staticDedupCount ?? 0) + 1;
1266
+ return {
1267
+ buffer: session.lastFrameBuffer,
1268
+ quantizedTime: quantizeTimeToFrame(time, fpsToNumber(options.fps)),
1269
+ captureTimeMs: Date.now() - startTime,
1270
+ };
1271
+ }
958
1272
  try {
959
1273
  const { quantizedTime, seekMs, beforeCaptureMs } = await prepareFrameForCapture(session, frameIndex, time);
960
1274
  const screenshotStart = Date.now();
@@ -978,6 +1292,9 @@ async function captureFrameCore(session, frameIndex, time) {
978
1292
  session.capturePerf.beforeCaptureMs += beforeCaptureMs;
979
1293
  session.capturePerf.screenshotMs += screenshotMs;
980
1294
  session.capturePerf.totalMs += captureTimeMs;
1295
+ // Retain this freshly-captured buffer so the following static frames can reuse it.
1296
+ if (session.staticFrames)
1297
+ session.lastFrameBuffer = screenshotBuffer;
981
1298
  return { buffer: screenshotBuffer, quantizedTime, captureTimeMs };
982
1299
  }
983
1300
  catch (captureError) {
@@ -1038,18 +1355,38 @@ export async function discardWarmupCapture(session, frameIndex = 0, time = 0, in
1038
1355
  const perfBefore = { ...session.capturePerf };
1039
1356
  const hasDamageBefore = session.beginFrameHasDamageCount;
1040
1357
  const noDamageBefore = session.beginFrameNoDamageCount;
1358
+ const dedupCountBefore = session.staticDedupCount;
1359
+ const lastFrameBufferBefore = session.lastFrameBuffer;
1041
1360
  try {
1042
1361
  await innerCapture(session, frameIndex, time);
1043
1362
  }
1044
1363
  finally {
1045
1364
  // Always restore — even on error. A failed warmup capture should not
1046
- // leak inflated perf counters into the real capture summary.
1365
+ // leak inflated perf counters, a phantom dedup reuse, or a warmup-era
1366
+ // lastFrameBuffer anchor into the real capture summary/state.
1047
1367
  session.capturePerf = perfBefore;
1048
1368
  session.beginFrameHasDamageCount = hasDamageBefore;
1049
1369
  session.beginFrameNoDamageCount = noDamageBefore;
1370
+ session.staticDedupCount = dedupCountBefore;
1371
+ session.lastFrameBuffer = lastFrameBufferBefore;
1050
1372
  }
1051
1373
  }
1052
1374
  export async function closeCaptureSession(session) {
1375
+ // Realized static-dedup telemetry: how much the cache actually helped this
1376
+ // render (vs the prediction logged at arm time). Both capture paths
1377
+ // (sequential orchestrator + parallel workers) close their session here, so
1378
+ // this is the one uniform emit point. Zero the count afterward so the
1379
+ // idempotent re-close (HDR cleanup) doesn't double-log.
1380
+ const reused = session.staticDedupCount ?? 0;
1381
+ if (session.staticFrames && reused > 0) {
1382
+ const captured = session.capturePerf.frames; // excludes reuses by design
1383
+ const total = captured + reused;
1384
+ const pct = total > 0 ? Math.round((reused / total) * 100) : 0;
1385
+ const avgTotalMs = captured > 0 ? Math.round(session.capturePerf.totalMs / captured) : 0;
1386
+ console.log(`[static-dedup] reused ${reused}/${total} frame(s) (${pct}%), ` +
1387
+ `est. ~${reused * avgTotalMs}ms saved (avg ${avgTotalMs}ms/frame)`);
1388
+ session.staticDedupCount = 0;
1389
+ }
1053
1390
  // INVARIANT: closeCaptureSession is idempotent. The renderOrchestrator HDR
1054
1391
  // cleanup path tracks a `domSessionClosed` flag and may still re-call this
1055
1392
  // in the outer finally if the inner cleanup raised before the flag flipped.
@@ -1099,6 +1436,12 @@ export function prepareCaptureSessionForReuse(session, outputDir, onBeforeCaptur
1099
1436
  };
1100
1437
  session.beginFrameHasDamageCount = 0;
1101
1438
  session.beginFrameNoDamageCount = 0;
1439
+ // Reset per-render dedup state so a buffer captured by the prior render/probe can't
1440
+ // bleed into this render's first static frame. staticFrames (the armed set) is left
1441
+ // intact: it's keyed in absolute frames and stays valid for a same-composition reuse;
1442
+ // lastFrameBuffer must be re-seeded by this render's first fresh capture.
1443
+ session.lastFrameBuffer = undefined;
1444
+ session.staticDedupCount = 0;
1102
1445
  }
1103
1446
  export async function getCompositionDuration(session) {
1104
1447
  if (!session.isInitialized)
@@ -1115,6 +1458,12 @@ export function getCapturePerfSummary(session) {
1115
1458
  avgSeekMs: Math.round(session.capturePerf.seekMs / frames),
1116
1459
  avgBeforeCaptureMs: Math.round(session.capturePerf.beforeCaptureMs / frames),
1117
1460
  avgScreenshotMs: Math.round(session.capturePerf.screenshotMs / frames),
1461
+ staticDedupReused: session.staticDedupCount ?? 0,
1462
+ staticDedupEnabled: session.staticDedupEnabled ?? false,
1463
+ // armed ⟺ a non-empty static set survived verification; predicted === its size.
1464
+ staticDedupArmed: (session.staticFrames?.size ?? 0) > 0,
1465
+ staticDedupPredicted: session.staticFrames?.size ?? 0,
1466
+ staticDedupSkipReason: session.staticDedupSkipReason,
1118
1467
  };
1119
1468
  }
1120
1469
  //# sourceMappingURL=frameCapture.js.map