@viji-dev/core 0.8.3 → 0.8.5

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/docs-api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export const docsApi = {
2
2
  "version": "1.1.0",
3
- "coreVersion": "0.8.2",
4
- "generatedAt": "2026-05-30T19:23:56.482Z",
3
+ "coreVersion": "0.8.4",
4
+ "generatedAt": "2026-06-02T11:07:17.944Z",
5
5
  "navigation": [
6
6
  {
7
7
  "id": "getting-started",
@@ -1875,7 +1875,7 @@ export const docsApi = {
1875
1875
  },
1876
1876
  {
1877
1877
  "type": "text",
1878
- "markdown": "## When to Use `viji.time` vs `viji.deltaTime`\n\n| Use Case | Property | Why |\n|----------|----------|-----|\n| Oscillation at **constant** speed | `viji.time` | Depends on absolute position in time |\n| Hue cycling at constant rate | `viji.time` | Needs a continuous, monotonic input |\n| Oscillation at **parameter-driven** speed | `viji.deltaTime` (accumulator) | Avoids phase jumps when the slider changes |\n| Position accumulation | `viji.deltaTime` | Must advance the same amount per second, regardless of FPS |\n| Physics / velocity | `viji.deltaTime` | Distance = speed × time elapsed |\n| Fading / easing | `viji.deltaTime` | Progress should be per-second, not per-frame |\n| Periodic events | `viji.time` | Trigger at fixed time intervals |\n\n**Rule of thumb:** if the speed multiplier is a **constant**, `viji.time * constant` is fine. If the speed comes from a **parameter** (slider, etc.), accumulate with `viji.deltaTime` to prevent jumps.\n\n### Accumulator Pattern\n\nWhen animation speed is driven by a user parameter, multiplying `viji.time * speed.value` recalculates the entire phase history every frame: changing the slider causes a visible jump. Instead, accumulate the phase incrementally:\n\n```javascript\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nlet phase = 0;\n\nfunction render(viji) {\n phase += speed.value * viji.deltaTime;\n const x = Math.cos(phase) * radius;\n}\n```\n\nThe slider now only affects future frames: past phase is preserved, so the animation transitions smoothly.\n\n> [!NOTE]\n> For shaders, use [`@viji-accumulator`](/shader/parameters/accumulator) instead: it performs the same integration (`speed × deltaTime`) on the host side and provides the accumulated value as a uniform.\n\n## `viji.frameCount`\n\nAn integer that starts at 0 and increments by 1 every frame. Useful for alternating patterns, sampling every N frames, or seeding frame-unique randomness:\n\n```javascript\nif (viji.frameCount % 60 === 0) {\n // runs once per ~60 frames\n}\n```\n\n## `viji.fps`: Target Frame Rate\n\n`viji.fps` is the **target** frame rate based on the host's frame rate mode and screen refresh rate: it is **not** a measured FPS value:\n\n- `frameRateMode: 'full'` → `viji.fps` equals the screen refresh rate (typically 60 or 120)\n- `frameRateMode: 'half'` → `viji.fps` equals half the screen refresh rate (typically 30 or 60)\n\nThis value is stable and does not fluctuate frame-to-frame. It's useful for informational display or calculations that need the intended rate, but **not** for driving animation: use `viji.time` or `viji.deltaTime` instead.\n\n## Frame-Rate Independence\n\n> [!NOTE]\n> Always use `viji.width` and `viji.height` for positioning and sizing, and `viji.deltaTime` for frame-rate-independent animation. Never hardcode pixel values or assume a specific frame rate.\n\nA scene running at 30 FPS should look the same as one running at 120 FPS. The only way to achieve this is to scale all per-frame accumulation by `viji.deltaTime`:\n\n```javascript\n// Bad: moves faster at higher frame rates\nposition += 5;\n\n// Good: moves at 5 units per second regardless of frame rate\nposition += 5 * viji.deltaTime;\n```\n\n## Next Steps\n\n- [Canvas & Context](/native/canvas-context): [`viji.canvas`](/native/canvas-context), [`viji.useContext()`](/native/canvas-context), dimensions\n- [Parameters](/native/parameters): sliders, colors, toggles\n- [P5 Timing](/p5/timing): timing in the P5 renderer\n- [Shader Timing](/shader/timing): `u_time`, `u_deltaTime`, `u_frame`, `u_fps`\n- [API Reference](/native/api-reference): full list of everything available"
1878
+ "markdown": "## When to Use `viji.time` vs `viji.deltaTime`\n\n| Use Case | Property | Why |\n|----------|----------|-----|\n| Oscillation at **constant** speed | `viji.time` | Depends on absolute position in time |\n| Hue cycling at constant rate | `viji.time` | Needs a continuous, monotonic input |\n| Oscillation at **parameter-driven** speed | `viji.deltaTime` (accumulator) | Avoids phase jumps when the slider changes |\n| Position accumulation | `viji.deltaTime` | Must advance the same amount per second, regardless of FPS |\n| Physics / velocity | `viji.deltaTime` | Distance = speed × time elapsed |\n| Fading / easing | `viji.deltaTime` | Progress should be per-second, not per-frame |\n| Periodic events | `viji.time` | Trigger at fixed time intervals |\n\n**Rule of thumb:** if the speed multiplier is a **constant**, `viji.time * constant` is fine. If the speed comes from a **parameter** (slider, etc.), accumulate with `viji.deltaTime` to prevent jumps.\n\n### Accumulator Pattern\n\nWhen animation speed is driven by a user parameter, multiplying `viji.time * speed.value` recalculates the entire phase history every frame: changing the slider causes a visible jump. Instead, accumulate the phase incrementally:\n\n```javascript\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nlet phase = 0;\n\nfunction render(viji) {\n phase += speed.value * viji.deltaTime;\n const x = Math.cos(phase) * radius;\n}\n```\n\nThe slider now only affects future frames: past phase is preserved, so the animation transitions smoothly.\n\n> [!NOTE]\n> For shaders, use [`@viji-accumulator`](/shader/parameters/accumulator) instead: it performs the same integration (`speed × deltaTime`) on the host side and provides the accumulated value as a uniform.\n\n## `viji.frameCount`\n\nAn integer that starts at 0 and increments by 1 every frame. Useful for alternating patterns, sampling every N frames, or seeding frame-unique randomness:\n\n```javascript\nif (viji.frameCount % 60 === 0) {\n // runs once per ~60 frames\n}\n```\n\n## `viji.fps`: Target Frame Rate\n\n`viji.fps` is the **target** frame rate based on the host's frame rate mode and screen refresh rate: it is **not** a measured FPS value:\n\n- `frameRateMode: 'full'` → `viji.fps` equals the screen refresh rate (typically 60 or 120)\n- `frameRateMode: 'half'` → `viji.fps` equals half the screen refresh rate (typically 30 or 60)\n- `frameRateMode: 'quarter'` → `viji.fps` equals a quarter of the screen refresh rate (typically 15 or 30)\n- `frameRateMode: 'eighth'` → `viji.fps` equals an eighth of the screen refresh rate (typically 7.5 or 15)\n\nThis value is stable and does not fluctuate frame-to-frame. It's useful for informational display or calculations that need the intended rate, but **not** for driving animation: use `viji.time` or `viji.deltaTime` instead.\n\n## Frame-Rate Independence\n\n> [!NOTE]\n> Always use `viji.width` and `viji.height` for positioning and sizing, and `viji.deltaTime` for frame-rate-independent animation. Never hardcode pixel values or assume a specific frame rate.\n\nA scene running at 30 FPS should look the same as one running at 120 FPS. The only way to achieve this is to scale all per-frame accumulation by `viji.deltaTime`:\n\n```javascript\n// Bad: moves faster at higher frame rates\nposition += 5;\n\n// Good: moves at 5 units per second regardless of frame rate\nposition += 5 * viji.deltaTime;\n```\n\n## Next Steps\n\n- [Canvas & Context](/native/canvas-context): [`viji.canvas`](/native/canvas-context), [`viji.useContext()`](/native/canvas-context), dimensions\n- [Parameters](/native/parameters): sliders, colors, toggles\n- [P5 Timing](/p5/timing): timing in the P5 renderer\n- [Shader Timing](/shader/timing): `u_time`, `u_deltaTime`, `u_frame`, `u_fps`\n- [API Reference](/native/api-reference): full list of everything available"
1879
1879
  }
1880
1880
  ]
1881
1881
  },
@@ -4708,7 +4708,7 @@ export const docsApi = {
4708
4708
  },
4709
4709
  {
4710
4710
  "type": "text",
4711
- "markdown": "## When to Use `viji.time` vs `viji.deltaTime`\n\n| Use Case | Property | Why |\n|----------|----------|-----|\n| `p5.sin()` / `p5.cos()` at **constant** speed | `viji.time` | Periodic functions need absolute time |\n| Hue cycling at constant rate | `viji.time` | Continuous monotonic input |\n| Oscillation at **parameter-driven** speed | `viji.deltaTime` (accumulator) | Avoids phase jumps when the slider changes |\n| Position += velocity | `viji.deltaTime` | Distance = speed × elapsed time |\n| Rotation += angular speed | `viji.deltaTime` | Angle increments must be per-second |\n| Opacity fading | `viji.deltaTime` | Fade rate is per-second |\n\n### Accumulator Pattern: Parameter-Driven Speed\n\nWhen animation speed is controlled by a user parameter, do **not** use `viji.time * speed.value`: changing the slider recalculates the full phase history and causes a visible jump. Instead, accumulate incrementally:\n\n```javascript\n// @renderer p5\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nlet phase = 0;\n\nfunction render(viji, p5) {\n phase += speed.value * viji.deltaTime;\n p5.rotate(phase);\n}\n```\n\n> [!TIP]\n> `viji.time * constant` is fine when the multiplier never changes at runtime. The accumulator is only needed when speed is a **user-controlled parameter**. See [Best Practices](/getting-started/best-practices) for the full explanation.\n\n## `viji.frameCount` vs `p5.frameCount`\n\nBoth exist but have different origins:\n\n| Property | Source | Starts At |\n|----------|--------|-----------|\n| `viji.frameCount` | Viji runtime | 0 |\n| `p5.frameCount` | P5 internal | 1 |\n\n`viji.frameCount` is the canonical frame counter across all renderers. It increments by 1 every frame and is consistent whether you're in a native, P5, or shader scene. `p5.frameCount` is maintained by P5 internally and may differ by 1. Use `viji.frameCount` for consistency.\n\n## `viji.fps`: Target Frame Rate\n\n`viji.fps` is the **target** frame rate based on the host's configuration, not a measured value:\n\n- `frameRateMode: 'full'` → screen refresh rate (typically 60 or 120)\n- `frameRateMode: 'half'` → half the screen refresh rate (typically 30 or 60)\n\nThis value is stable and does not fluctuate. Don't use it for animation timing: use `viji.time` or `viji.deltaTime` instead.\n\n> [!NOTE]\n> P5's `frameRate()` function is not available in Viji: the host controls the render loop.\n\n## Frame-Rate Independence\n\n> [!NOTE]\n> Always use `viji.width` and `viji.height` for positioning and sizing, and `viji.deltaTime` for frame-rate-independent animation. Never hardcode pixel values or assume a specific frame rate.\n\n```javascript\n// Bad: speed depends on frame rate\nangle += 0.02;\n\n// Good: same visual speed at any frame rate\nangle += 1.2 * viji.deltaTime; // 1.2 radians per second\n```\n\n## Next Steps\n\n- [Canvas & Resolution](/p5/canvas-resolution): [`viji.width`](/p5/canvas-resolution), [`viji.height`](/p5/canvas-resolution), responsive layouts\n- [Scene Structure](/p5/scene-structure): `setup()`, `render()`, lifecycle\n- [Parameters](/p5/parameters): sliders, colors, toggles\n- [Native Timing](/native/timing): timing in the native renderer\n- [Shader Timing](/shader/timing): `u_time`, `u_deltaTime`, `u_frame`, `u_fps`\n- [API Reference](/p5/api-reference): full list of everything available"
4711
+ "markdown": "## When to Use `viji.time` vs `viji.deltaTime`\n\n| Use Case | Property | Why |\n|----------|----------|-----|\n| `p5.sin()` / `p5.cos()` at **constant** speed | `viji.time` | Periodic functions need absolute time |\n| Hue cycling at constant rate | `viji.time` | Continuous monotonic input |\n| Oscillation at **parameter-driven** speed | `viji.deltaTime` (accumulator) | Avoids phase jumps when the slider changes |\n| Position += velocity | `viji.deltaTime` | Distance = speed × elapsed time |\n| Rotation += angular speed | `viji.deltaTime` | Angle increments must be per-second |\n| Opacity fading | `viji.deltaTime` | Fade rate is per-second |\n\n### Accumulator Pattern: Parameter-Driven Speed\n\nWhen animation speed is controlled by a user parameter, do **not** use `viji.time * speed.value`: changing the slider recalculates the full phase history and causes a visible jump. Instead, accumulate incrementally:\n\n```javascript\n// @renderer p5\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nlet phase = 0;\n\nfunction render(viji, p5) {\n phase += speed.value * viji.deltaTime;\n p5.rotate(phase);\n}\n```\n\n> [!TIP]\n> `viji.time * constant` is fine when the multiplier never changes at runtime. The accumulator is only needed when speed is a **user-controlled parameter**. See [Best Practices](/getting-started/best-practices) for the full explanation.\n\n## `viji.frameCount` vs `p5.frameCount`\n\nBoth exist but have different origins:\n\n| Property | Source | Starts At |\n|----------|--------|-----------|\n| `viji.frameCount` | Viji runtime | 0 |\n| `p5.frameCount` | P5 internal | 1 |\n\n`viji.frameCount` is the canonical frame counter across all renderers. It increments by 1 every frame and is consistent whether you're in a native, P5, or shader scene. `p5.frameCount` is maintained by P5 internally and may differ by 1. Use `viji.frameCount` for consistency.\n\n## `viji.fps`: Target Frame Rate\n\n`viji.fps` is the **target** frame rate based on the host's configuration, not a measured value:\n\n- `frameRateMode: 'full'` → screen refresh rate (typically 60 or 120)\n- `frameRateMode: 'half'` → half the screen refresh rate (typically 30 or 60)\n- `frameRateMode: 'quarter'` → a quarter of the screen refresh rate (typically 15 or 30)\n- `frameRateMode: 'eighth'` → an eighth of the screen refresh rate (typically 7.5 or 15)\n\nThis value is stable and does not fluctuate. Don't use it for animation timing: use `viji.time` or `viji.deltaTime` instead.\n\n> [!NOTE]\n> P5's `frameRate()` function is not available in Viji: the host controls the render loop.\n\n## Frame-Rate Independence\n\n> [!NOTE]\n> Always use `viji.width` and `viji.height` for positioning and sizing, and `viji.deltaTime` for frame-rate-independent animation. Never hardcode pixel values or assume a specific frame rate.\n\n```javascript\n// Bad: speed depends on frame rate\nangle += 0.02;\n\n// Good: same visual speed at any frame rate\nangle += 1.2 * viji.deltaTime; // 1.2 radians per second\n```\n\n## Next Steps\n\n- [Canvas & Resolution](/p5/canvas-resolution): [`viji.width`](/p5/canvas-resolution), [`viji.height`](/p5/canvas-resolution), responsive layouts\n- [Scene Structure](/p5/scene-structure): `setup()`, `render()`, lifecycle\n- [Parameters](/p5/parameters): sliders, colors, toggles\n- [Native Timing](/native/timing): timing in the native renderer\n- [Shader Timing](/shader/timing): `u_time`, `u_deltaTime`, `u_frame`, `u_fps`\n- [API Reference](/p5/api-reference): full list of everything available"
4712
4712
  }
4713
4713
  ]
4714
4714
  },
@@ -7422,7 +7422,7 @@ export const docsApi = {
7422
7422
  },
7423
7423
  {
7424
7424
  "type": "text",
7425
- "markdown": "## `u_deltaTime`: Frame Delta\n\n`u_deltaTime` is the time between the current and previous frame. In shaders, it's primarily useful for:\n\n- Display purposes (showing timing info)\n- Custom accumulation in combination with the `backbuffer`\n- Understanding frame rate behavior\n\nFor most shader animation, `u_time` is the right choice because GLSL is stateless: each pixel is computed independently every frame with no per-pixel accumulation.\n\n## `u_frame`: Frame Counter\n\nAn integer that increments by 1 each frame. Useful for:\n\n- Alternating patterns: `mod(float(u_frame), 2.0)`\n- Noise seeding that changes per frame\n- Periodic events: `mod(float(u_frame), 60.0) < 1.0`\n\n## `u_fps`: Target Frame Rate\n\n`u_fps` is the **target** frame rate based on the host configuration, not a measured value:\n\n- `frameRateMode: 'full'` → screen refresh rate (typically 60.0 or 120.0)\n- `frameRateMode: 'half'` → half the screen refresh rate (typically 30.0 or 60.0)\n\nThis value is stable and does not fluctuate. Use it for informational display, not for animation timing.\n\n## Animation Patterns\n\n### Oscillation\n\n```glsl\nfloat pulse = sin(u_time * 3.14159) * 0.5 + 0.5; // 0..1 pulse, period ~2s\nfloat blink = step(0.5, fract(u_time * 2.0)); // on/off blink, 2 Hz\n```\n\n### Smooth Cycling\n\n```glsl\nfloat t = fract(u_time * 0.25); // 0..1 over 4 seconds, then repeats\nvec3 color = mix(colorA, colorB, t);\n```\n\n### Rotation\n\n```glsl\nfloat angle = u_time * 1.5;\nmat2 rot = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));\nvec2 rotated = rot * uv;\n```\n\n### Speed-Controlled Animation: The Accumulator\n\nWhen you write `u_time * speed`, changing the `speed` slider causes a visible jump because the entire phase is recalculated. The [`@viji-accumulator`](/shader/parameters/accumulator) directive solves this:\n\n```glsl\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\n// @viji-accumulator:phase rate:speed\n\nvoid main() {\n // Use `phase` instead of `u_time * speed`\n float wave = sin(phase * 6.28);\n // ...\n}\n```\n\nThe accumulator adds `speed × deltaTime` to `phase` each frame on the CPU side. Changing `speed` only affects future growth: no backward jumps."
7425
+ "markdown": "## `u_deltaTime`: Frame Delta\n\n`u_deltaTime` is the time between the current and previous frame. In shaders, it's primarily useful for:\n\n- Display purposes (showing timing info)\n- Custom accumulation in combination with the `backbuffer`\n- Understanding frame rate behavior\n\nFor most shader animation, `u_time` is the right choice because GLSL is stateless: each pixel is computed independently every frame with no per-pixel accumulation.\n\n## `u_frame`: Frame Counter\n\nAn integer that increments by 1 each frame. Useful for:\n\n- Alternating patterns: `mod(float(u_frame), 2.0)`\n- Noise seeding that changes per frame\n- Periodic events: `mod(float(u_frame), 60.0) < 1.0`\n\n## `u_fps`: Target Frame Rate\n\n`u_fps` is the **target** frame rate based on the host configuration, not a measured value:\n\n- `frameRateMode: 'full'` → screen refresh rate (typically 60.0 or 120.0)\n- `frameRateMode: 'half'` → half the screen refresh rate (typically 30.0 or 60.0)\n- `frameRateMode: 'quarter'` → a quarter of the screen refresh rate (typically 15.0 or 30.0)\n- `frameRateMode: 'eighth'` → an eighth of the screen refresh rate (typically 7.5 or 15.0)\n\nThis value is stable and does not fluctuate. Use it for informational display, not for animation timing.\n\n## Animation Patterns\n\n### Oscillation\n\n```glsl\nfloat pulse = sin(u_time * 3.14159) * 0.5 + 0.5; // 0..1 pulse, period ~2s\nfloat blink = step(0.5, fract(u_time * 2.0)); // on/off blink, 2 Hz\n```\n\n### Smooth Cycling\n\n```glsl\nfloat t = fract(u_time * 0.25); // 0..1 over 4 seconds, then repeats\nvec3 color = mix(colorA, colorB, t);\n```\n\n### Rotation\n\n```glsl\nfloat angle = u_time * 1.5;\nmat2 rot = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));\nvec2 rotated = rot * uv;\n```\n\n### Speed-Controlled Animation: The Accumulator\n\nWhen you write `u_time * speed`, changing the `speed` slider causes a visible jump because the entire phase is recalculated. The [`@viji-accumulator`](/shader/parameters/accumulator) directive solves this:\n\n```glsl\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\n// @viji-accumulator:phase rate:speed\n\nvoid main() {\n // Use `phase` instead of `u_time * speed`\n float wave = sin(phase * 6.28);\n // ...\n}\n```\n\nThe accumulator adds `speed × deltaTime` to `phase` each frame on the CPU side. Changing `speed` only affects future growth: no backward jumps."
7426
7426
  },
7427
7427
  {
7428
7428
  "type": "live-example",
@@ -1,4 +1,4 @@
1
- import { g as kn } from "./index-BzW31blh.js";
1
+ import { g as kn } from "./index-DbnjzNSY.js";
2
2
  function jn(Oe, ot) {
3
3
  for (var ve = 0; ve < ot.length; ve++) {
4
4
  const Z = ot[ve];
@@ -3374,4 +3374,4 @@ const xn = /* @__PURE__ */ kn(yi), Ln = /* @__PURE__ */ jn({
3374
3374
  export {
3375
3375
  Ln as e
3376
3376
  };
3377
- //# sourceMappingURL=essentia-wasm.web-BATCANag.js.map
3377
+ //# sourceMappingURL=essentia-wasm.web-DwSQMYtc.js.map
@@ -536,7 +536,7 @@ class DB {
536
536
  }
537
537
  }
538
538
  }
539
- const hB = "" + new URL("assets/viji.worker-Ds0fC23y.js", import.meta.url).href, RB = new URL("assets/wasm/vision_wasm_internal.js", import.meta.url).href, wB = new URL("assets/wasm/vision_wasm_internal.wasm", import.meta.url).href, sB = new URL("assets/wasm/vision_wasm_nosimd_internal.js", import.meta.url).href, FB = new URL("assets/wasm/vision_wasm_nosimd_internal.wasm", import.meta.url).href;
539
+ const hB = "" + new URL("assets/viji.worker-CydIeq-k.js", import.meta.url).href, RB = new URL("assets/wasm/vision_wasm_internal.js", import.meta.url).href, wB = new URL("assets/wasm/vision_wasm_internal.wasm", import.meta.url).href, sB = new URL("assets/wasm/vision_wasm_nosimd_internal.js", import.meta.url).href, FB = new URL("assets/wasm/vision_wasm_nosimd_internal.wasm", import.meta.url).href;
540
540
  class kB {
541
541
  constructor(A, B, I) {
542
542
  this.iframeManager = A, this.sceneCode = B, this.init = I;
@@ -1520,7 +1520,7 @@ class lB {
1520
1520
  if (!this.isInitialized)
1521
1521
  return this.initPromise ? this.initPromise : (this.initPromise = (async () => {
1522
1522
  try {
1523
- const A = await import("./essentia.js-core.es-CC_3Ap1i.js"), B = await import("./essentia-wasm.web-BATCANag.js").then((g) => g.e), I = A.Essentia || A.default?.Essentia || A.default;
1523
+ const A = await import("./essentia.js-core.es-CC_3Ap1i.js"), B = await import("./essentia-wasm.web-DwSQMYtc.js").then((g) => g.e), I = A.Essentia || A.default?.Essentia || A.default;
1524
1524
  let Q = B.default || B.EssentiaWASM || B.default?.EssentiaWASM;
1525
1525
  if (!Q)
1526
1526
  throw new Error("WASM module not found - check essentia-wasm.web.js export");
@@ -7051,6 +7051,8 @@ class K {
7051
7051
  performanceSnapshotListeners = /* @__PURE__ */ new Set();
7052
7052
  capabilityTransitionListeners = /* @__PURE__ */ new Set();
7053
7053
  capabilityReportingEnabled = !1;
7054
+ // Render-loop pause state (host-driven; see pauseRenderLoop()).
7055
+ renderLoopPaused = !1;
7054
7056
  constructor(A) {
7055
7057
  this.validateConfig(A), this.instanceId = `viji-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, this.isHeadless = !A.hostContainer, this.config = {
7056
7058
  ...A,
@@ -7181,7 +7183,7 @@ class K {
7181
7183
  }
7182
7184
  ), this.setupCommunication(), await this.workerManager.createWorker(), this.isDestroyed)
7183
7185
  throw new S("Initialization cancelled", "INITIALIZATION_CANCELLED");
7184
- this.capabilityReportingEnabled && this.workerManager.postMessage("enable-capability-reporting", { enabled: !0 });
7186
+ this.capabilityReportingEnabled && this.workerManager.postMessage("enable-capability-reporting", { enabled: !0 }), this.renderLoopPaused && this.workerManager.postMessage("set-render-loop-paused", { paused: !0 });
7185
7187
  try {
7186
7188
  this.config.onRenderStart?.();
7187
7189
  } catch (B) {
@@ -8017,7 +8019,7 @@ class K {
8017
8019
  }
8018
8020
  /**
8019
8021
  * Sets the scene frame rate mode
8020
- * @param mode - 'full' for every animation frame, 'half' for every second frame
8022
+ * @param mode - render every animation frame (`'full'`), every 2nd (`'half'`), every 4th (`'quarter'`), or every 8th (`'eighth'`)
8021
8023
  */
8022
8024
  async setFrameRate(A) {
8023
8025
  this.validateReady(), this.config.frameRateMode = A, this.workerManager && this.workerManager.postMessage("frame-rate-update", { mode: A }), this.updateFrameRateStats(), this.debugLog(`Scene frame rate set to ${A} (${this.instanceId})`);
@@ -8532,6 +8534,39 @@ class K {
8532
8534
  throw new S("IFrame manager not available", "MANAGER_NOT_READY");
8533
8535
  return this.stats;
8534
8536
  }
8537
+ /**
8538
+ * Pauses the scene render loop — stops the worker from rendering. Intended to
8539
+ * be driven from the host on `document.hidden`, so a hidden tab doesn't keep
8540
+ * rasterizing a heavy scene (which otherwise pegs the GPU and causes a freeze
8541
+ * on return).
8542
+ *
8543
+ * Only the rendering stops. The scene clock keeps running on real wall-clock,
8544
+ * and audio analysis, video/audio streams, CV, parameters and all scene state
8545
+ * keep running too — so audio you can still hear is still analyzed. On
8546
+ * {@link resumeRenderLoop}, `viji.time` reflects the **real** elapsed time
8547
+ * (including the hidden duration) and the first frame's `viji.deltaTime` is the
8548
+ * **real** time-away gap. (That gap can be large; the scene sees true time, not
8549
+ * a frozen clock.) Idempotent.
8550
+ *
8551
+ * For a live compositor *source* feeding a visible mixer, do NOT pause it
8552
+ * while its own tab is hidden — it must keep rendering. Pause is per-core and
8553
+ * host-controlled.
8554
+ */
8555
+ pauseRenderLoop() {
8556
+ this.renderLoopPaused || (this.renderLoopPaused = !0, this.workerManager?.postMessage("set-render-loop-paused", { paused: !0 }));
8557
+ }
8558
+ /**
8559
+ * Resumes the scene render loop after {@link pauseRenderLoop} (e.g. on the tab
8560
+ * becoming visible). The scene resumes on real wall-clock time, so the first
8561
+ * frame's `viji.deltaTime` is the elapsed time-away gap. Idempotent.
8562
+ */
8563
+ resumeRenderLoop() {
8564
+ this.renderLoopPaused && (this.renderLoopPaused = !1, this.workerManager?.postMessage("set-render-loop-paused", { paused: !1 }));
8565
+ }
8566
+ /** Whether the render loop is currently paused (see {@link pauseRenderLoop}). */
8567
+ isRenderLoopPaused() {
8568
+ return this.renderLoopPaused;
8569
+ }
8535
8570
  /**
8536
8571
  * Runs an on-demand fenced benchmark: the worker renders `frameCount` frames,
8537
8572
  * fencing each to capture fence-accurate CPU + GPU time per frame, and
@@ -8692,8 +8727,8 @@ class K {
8692
8727
  );
8693
8728
  if (!A.sceneCode || typeof A.sceneCode != "string")
8694
8729
  throw new S("sceneCode must be a non-empty string", "INVALID_CONFIG");
8695
- if (A.frameRateMode !== void 0 && A.frameRateMode !== "full" && A.frameRateMode !== "half")
8696
- throw new S('frameRateMode must be either "full" or "half"', "INVALID_CONFIG");
8730
+ if (A.frameRateMode !== void 0 && !["full", "half", "quarter", "eighth"].includes(A.frameRateMode))
8731
+ throw new S('frameRateMode must be one of "full", "half", "quarter", or "eighth"', "INVALID_CONFIG");
8697
8732
  }
8698
8733
  /**
8699
8734
  * Cleans up all resources
@@ -8717,7 +8752,7 @@ function DI(h) {
8717
8752
  expectedVersion: 1
8718
8753
  } : typeof A.senderTime != "number" ? { code: "invalid-field", details: "senderTime must be a number", field: "senderTime" } : null;
8719
8754
  }
8720
- const hI = "0.8.2";
8755
+ const hI = "0.8.4";
8721
8756
  export {
8722
8757
  yA as A,
8723
8758
  hI as V,
@@ -8725,4 +8760,4 @@ export {
8725
8760
  S as b,
8726
8761
  PB as g
8727
8762
  };
8728
- //# sourceMappingURL=index-BzW31blh.js.map
8763
+ //# sourceMappingURL=index-DbnjzNSY.js.map
package/dist/index.d.ts CHANGED
@@ -946,9 +946,10 @@ export declare interface CVActivity {
946
946
  declare type CVFeature = 'faceDetection' | 'faceMesh' | 'handTracking' | 'poseDetection' | 'bodySegmentation' | 'emotionDetection';
947
947
 
948
948
  /**
949
- * Computer-vision processing rate relative to the scene's render rate.
950
- * `'full'` runs CV every frame, `'half'` every other frame, `'quarter'` every
951
- * fourth, `'eighth'` every eighth. Lower rates reduce CPU/GPU cost.
949
+ * Computer-vision processing rate relative to the input video stream rate.
950
+ * `'full'` runs CV on every delivered video frame, `'half'` every other frame,
951
+ * `'quarter'` every fourth, `'eighth'` every eighth. Lower rates reduce CPU/GPU
952
+ * cost. (Throttles the camera/stream frame cadence, independent of scene FPS.)
952
953
  */
953
954
  declare type CVFrameRateMode = 'full' | 'half' | 'quarter' | 'eighth';
954
955
 
@@ -1286,11 +1287,13 @@ declare interface FrameRateInfo {
1286
1287
  }
1287
1288
 
1288
1289
  /**
1289
- * Render-loop pacing mode. `'full'` runs `render()` on every animation frame
1290
- * (~60 FPS). `'half'` runs every second frame (~30 FPS), saving CPU/GPU on
1291
- * lower-cost scenes.
1290
+ * Render-loop pacing mode. `'full'` runs `render()` on every animation frame,
1291
+ * `'half'` every 2nd frame, `'quarter'` every 4th, `'eighth'` every 8th. Lower
1292
+ * rates save CPU/GPU on lower-cost scenes. The resulting FPS is the screen
1293
+ * refresh rate divided by the mode divisor (1/2/4/8), so on a 60 Hz display:
1294
+ * `'full'` ~60, `'half'` ~30, `'quarter'` ~15, `'eighth'` ~7.5.
1292
1295
  */
1293
- declare type FrameRateMode = 'full' | 'half';
1296
+ declare type FrameRateMode = 'full' | 'half' | 'quarter' | 'eighth';
1294
1297
 
1295
1298
  /**
1296
1299
  * One named frequency band reported by the audio analyzer
@@ -2654,7 +2657,7 @@ declare type TrackingState = 'TRACKING' | 'LOCKED' | 'BREAKDOWN' | 'LOST';
2654
2657
  /** Returned by `on*` listener registration calls; invoke to unsubscribe. */
2655
2658
  export declare type Unsubscribe = () => void;
2656
2659
 
2657
- export declare const VERSION = "0.8.2";
2660
+ export declare const VERSION = "0.8.4";
2658
2661
 
2659
2662
  /**
2660
2663
  * Real-time video API: drawable frame, dimensions, and the source-side
@@ -2870,7 +2873,7 @@ export declare interface VijiAPI {
2870
2873
  deltaTime: number;
2871
2874
  /** Integer frame counter starting at `0` and incrementing by `1` each frame. */
2872
2875
  frameCount: number;
2873
- /** Target frames-per-second for the host's current frame-rate mode (`60` for `'full'`, `30` for `'half'`). */
2876
+ /** Target frames-per-second for the host's current frame-rate mode (screen refresh rate divided by the mode divisor 1/2/4/8). */
2874
2877
  fps: number;
2875
2878
  /** Real-time analysis of the main audio stream: volume, frequency bands, beat detection, spectral features. Fields are zeroed when no audio source is connected. */
2876
2879
  audio: AudioAPI;
@@ -3067,6 +3070,7 @@ export declare class VijiCore {
3067
3070
  private performanceSnapshotListeners;
3068
3071
  private capabilityTransitionListeners;
3069
3072
  private capabilityReportingEnabled;
3073
+ private renderLoopPaused;
3070
3074
  constructor(config: VijiCoreConfig);
3071
3075
  /**
3072
3076
  * Capture current scene frame
@@ -3446,7 +3450,7 @@ export declare class VijiCore {
3446
3450
  private notifyParameterListeners;
3447
3451
  /**
3448
3452
  * Sets the scene frame rate mode
3449
- * @param mode - 'full' for every animation frame, 'half' for every second frame
3453
+ * @param mode - render every animation frame (`'full'`), every 2nd (`'half'`), every 4th (`'quarter'`), or every 8th (`'eighth'`)
3450
3454
  */
3451
3455
  setFrameRate(mode: FrameRateMode): Promise<void>;
3452
3456
  /**
@@ -3811,6 +3815,33 @@ export declare class VijiCore {
3811
3815
  * Gets current performance statistics
3812
3816
  */
3813
3817
  getStats(): PerformanceStats;
3818
+ /**
3819
+ * Pauses the scene render loop — stops the worker from rendering. Intended to
3820
+ * be driven from the host on `document.hidden`, so a hidden tab doesn't keep
3821
+ * rasterizing a heavy scene (which otherwise pegs the GPU and causes a freeze
3822
+ * on return).
3823
+ *
3824
+ * Only the rendering stops. The scene clock keeps running on real wall-clock,
3825
+ * and audio analysis, video/audio streams, CV, parameters and all scene state
3826
+ * keep running too — so audio you can still hear is still analyzed. On
3827
+ * {@link resumeRenderLoop}, `viji.time` reflects the **real** elapsed time
3828
+ * (including the hidden duration) and the first frame's `viji.deltaTime` is the
3829
+ * **real** time-away gap. (That gap can be large; the scene sees true time, not
3830
+ * a frozen clock.) Idempotent.
3831
+ *
3832
+ * For a live compositor *source* feeding a visible mixer, do NOT pause it
3833
+ * while its own tab is hidden — it must keep rendering. Pause is per-core and
3834
+ * host-controlled.
3835
+ */
3836
+ pauseRenderLoop(): void;
3837
+ /**
3838
+ * Resumes the scene render loop after {@link pauseRenderLoop} (e.g. on the tab
3839
+ * becoming visible). The scene resumes on real wall-clock time, so the first
3840
+ * frame's `viji.deltaTime` is the elapsed time-away gap. Idempotent.
3841
+ */
3842
+ resumeRenderLoop(): void;
3843
+ /** Whether the render loop is currently paused (see {@link pauseRenderLoop}). */
3844
+ isRenderLoopPaused(): boolean;
3814
3845
  /**
3815
3846
  * Runs an on-demand fenced benchmark: the worker renders `frameCount` frames,
3816
3847
  * fencing each to capture fence-accurate CPU + GPU time per frame, and
@@ -3909,7 +3940,7 @@ export declare interface VijiCoreConfig {
3909
3940
  hostContainer?: HTMLElement;
3910
3941
  /** Artist scene code to execute */
3911
3942
  sceneCode: string;
3912
- /** Frame rate mode - 'full' for every animation frame, 'half' for every second frame */
3943
+ /** Frame rate mode - render every frame (`'full'`), every 2nd (`'half'`), every 4th (`'quarter'`), or every 8th (`'eighth'`). */
3913
3944
  frameRateMode?: FrameRateMode;
3914
3945
  /** Resolution: scale (number) or explicit dimensions (object). Required for headless, must be object form */
3915
3946
  resolution?: number | {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as a, V as i, a as s, b as e } from "./index-BzW31blh.js";
1
+ import { A as a, V as i, a as s, b as e } from "./index-DbnjzNSY.js";
2
2
  export {
3
3
  a as AudioSystem,
4
4
  i as VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viji-dev/core",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Universal execution engine for Viji Creative scenes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",