@viji-dev/core 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.3",
4
- "generatedAt": "2026-05-31T14:08:20.861Z",
3
+ "coreVersion": "0.8.5",
4
+ "generatedAt": "2026-06-03T09:18:41.915Z",
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-DF_IFS3A.js";
1
+ import { g as kn } from "./index-BQtrhfjg.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-H69T-_AQ.js.map
3377
+ //# sourceMappingURL=essentia-wasm.web-CaXce3AA.js.map
@@ -536,7 +536,7 @@ class DB {
536
536
  }
537
537
  }
538
538
  }
539
- const hB = "" + new URL("assets/viji.worker-BQ4vNMJ0.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-H69T-_AQ.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-CaXce3AA.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");
@@ -7001,7 +7001,6 @@ class K {
7001
7001
  eventSourceCore = null;
7002
7002
  frameSourceCores = [];
7003
7003
  eventSourceCleanup = null;
7004
- resizeSyncEnabled = !1;
7005
7004
  // Interaction state management
7006
7005
  currentInteractionEnabled;
7007
7006
  // Parameter system for Phase 2
@@ -7605,49 +7604,32 @@ class K {
7605
7604
  }, [A]);
7606
7605
  }
7607
7606
  /**
7608
- * Link this core to receive events from a source core
7609
- * @param syncResolution Smart default: true for headless, false for visible
7607
+ * Link this core to receive input events (mouse/keyboard/touch) from a
7608
+ * source core. Event forwarding only — resolution and frame-rate are pushed
7609
+ * mixer→sources through the frame-source relationship (see
7610
+ * {@link linkFrameSources}), not here.
7610
7611
  */
7611
- linkEventSource(A, B) {
7612
+ linkEventSource(A) {
7612
7613
  if (this.eventSourceCore && this.unlinkEventSource(), this.eventSourceCore = A, A.iframeManager) {
7613
- const Q = A.iframeManager, g = (i) => {
7614
- this.injectMouseEvent(i);
7615
- }, E = (i) => {
7616
- this.injectKeyboardEvent(i);
7617
- }, C = (i) => {
7618
- this.injectTouchEvent(i);
7614
+ const B = A.iframeManager, I = (E) => {
7615
+ this.injectMouseEvent(E);
7616
+ }, Q = (E) => {
7617
+ this.injectKeyboardEvent(E);
7618
+ }, g = (E) => {
7619
+ this.injectTouchEvent(E);
7619
7620
  };
7620
- Q.onInteractionEvent("mouse-update", g), Q.onInteractionEvent("keyboard-update", E), Q.onInteractionEvent("touch-update", C), this.eventSourceCleanup = () => {
7621
- Q.offInteractionEvent("mouse-update", g), Q.offInteractionEvent("keyboard-update", E), Q.offInteractionEvent("touch-update", C);
7621
+ B.onInteractionEvent("mouse-update", I), B.onInteractionEvent("keyboard-update", Q), B.onInteractionEvent("touch-update", g), this.eventSourceCleanup = () => {
7622
+ B.offInteractionEvent("mouse-update", I), B.offInteractionEvent("keyboard-update", Q), B.offInteractionEvent("touch-update", g);
7622
7623
  };
7623
7624
  } else
7624
7625
  console.warn(`[${this.instanceId}] Cannot link event source - ${A.instanceId} has no iframeManager`);
7625
- const I = B ?? this.isHeadless;
7626
- if (this.resizeSyncEnabled = I, I && !A.isHeadless && A.iframeManager) {
7627
- const Q = A.iframeManager.getEffectiveResolution();
7628
- Q && (this.setResolution(Q), this.debugLog(`Resolution synced: ${Q.width}x${Q.height}`));
7629
- const g = () => {
7630
- if (this.resizeSyncEnabled && A.iframeManager) {
7631
- const E = A.iframeManager.getEffectiveResolution();
7632
- E && (this.debugLog(`[ResizeSync] ${A.instanceId} → ${this.instanceId}: ${E.width}x${E.height}`), this.setResolution(E));
7633
- }
7634
- };
7635
- if (A.config.hostContainer) {
7636
- const E = new ResizeObserver(g);
7637
- E.observe(A.config.hostContainer);
7638
- const C = this.eventSourceCleanup;
7639
- this.eventSourceCleanup = () => {
7640
- E.disconnect(), C && C();
7641
- };
7642
- }
7643
- }
7644
7626
  this.debugLog(`Linked event source: ${A.instanceId}`);
7645
7627
  }
7646
7628
  /**
7647
7629
  * Unlink from event source
7648
7630
  */
7649
7631
  unlinkEventSource() {
7650
- this.eventSourceCore && (this.eventSourceCleanup && (this.eventSourceCleanup(), this.eventSourceCleanup = null), this.eventSourceCore = null, this.resizeSyncEnabled = !1);
7632
+ this.eventSourceCore && (this.eventSourceCleanup && (this.eventSourceCleanup(), this.eventSourceCleanup = null), this.eventSourceCore = null);
7651
7633
  }
7652
7634
  /**
7653
7635
  * Link source cores that this core will pull frames from
@@ -7658,7 +7640,7 @@ class K {
7658
7640
  this.debugLog(`[Compositor] Mixer renderer type: ${B}`), A.forEach((I, Q) => {
7659
7641
  const g = I.sceneRenderer, E = B === "shader";
7660
7642
  this.debugLog(`[Compositor] Source ${Q}: ${g} → ${B}, flipY=${E}`), I.enableAutoCapture({ flipY: E });
7661
- }), this.debugLog(`[Compositor] Linked ${A.length} frame source(s)`);
7643
+ }), this.syncFrameSourceQuality(), this.debugLog(`[Compositor] Linked ${A.length} frame source(s)`);
7662
7644
  }
7663
7645
  /**
7664
7646
  * Unlink all frame sources
@@ -7666,6 +7648,26 @@ class K {
7666
7648
  unlinkFrameSources() {
7667
7649
  this.frameSourceCores.forEach((A) => A.disableAutoCapture()), this.frameSourceCores = [];
7668
7650
  }
7651
+ /**
7652
+ * Push this (mixer) core's effective resolution and frame-rate mode to every
7653
+ * linked frame source, so sources render at exactly the compositor's quality
7654
+ * (no wasted pixels/frames the compositor would discard). No-op unless frame
7655
+ * sources are linked, so normal single-core use is unaffected. Re-invoked on
7656
+ * link and on every `setResolution`/`setFrameRate`/`updateResolution`.
7657
+ *
7658
+ * Each source is guarded on `ready` (a not-yet-initialized or destroyed
7659
+ * source would throw from `setResolution`/`setFrameRate`). `setFrameRate` is
7660
+ * async; its promise is intentionally not awaited (fire-and-forget) with a
7661
+ * catch so a source torn down mid-call cannot raise an unhandled rejection.
7662
+ */
7663
+ syncFrameSourceQuality() {
7664
+ if (this.frameSourceCores.length === 0 || !this.iframeManager) return;
7665
+ const A = this.iframeManager.getEffectiveResolution(), B = this.config.frameRateMode;
7666
+ for (const I of this.frameSourceCores)
7667
+ I.ready && (I.setResolution({ width: A.width, height: A.height }), I.setFrameRate(B).catch(
7668
+ (Q) => this.debugLog(`[Compositor] frame-source setFrameRate failed: ${Q}`)
7669
+ ));
7670
+ }
7669
7671
  /**
7670
7672
  * Get parameter groups filtered by active capabilities
7671
7673
  */
@@ -8019,10 +8021,10 @@ class K {
8019
8021
  }
8020
8022
  /**
8021
8023
  * Sets the scene frame rate mode
8022
- * @param mode - 'full' for every animation frame, 'half' for every second frame
8024
+ * @param mode - render every animation frame (`'full'`), every 2nd (`'half'`), every 4th (`'quarter'`), or every 8th (`'eighth'`)
8023
8025
  */
8024
8026
  async setFrameRate(A) {
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})`);
8027
+ this.validateReady(), this.config.frameRateMode = A, this.workerManager && this.workerManager.postMessage("frame-rate-update", { mode: A }), this.updateFrameRateStats(), this.syncFrameSourceQuality(), this.debugLog(`Scene frame rate set to ${A} (${this.instanceId})`);
8026
8028
  }
8027
8029
  /**
8028
8030
  * Sets the CV processing frame rate mode (relative to scene frame rate)
@@ -8042,7 +8044,7 @@ class K {
8042
8044
  effectiveWidth: A.width,
8043
8045
  effectiveHeight: A.height,
8044
8046
  displayScale: this.iframeManager.getDisplayScale()
8045
- }), this.stats.resolution = A, this.stats.scale = this.iframeManager.getScale(), this.debugLog(`Resolution updated successfully (${this.instanceId})`, A);
8047
+ }), this.stats.resolution = A, this.stats.scale = this.iframeManager.getScale(), this.syncFrameSourceQuality(), this.debugLog(`Resolution updated successfully (${this.instanceId})`, A);
8046
8048
  }
8047
8049
  /**
8048
8050
  * Sets the audio stream for analysis
@@ -8498,7 +8500,7 @@ class K {
8498
8500
  effectiveWidth: B.width,
8499
8501
  effectiveHeight: B.height,
8500
8502
  displayScale: this.iframeManager.getDisplayScale()
8501
- }), this.stats.resolution = B, this.debugLog(`Resolution: ${B.width}x${B.height}`);
8503
+ }), this.stats.resolution = B, this.syncFrameSourceQuality(), this.debugLog(`Resolution: ${B.width}x${B.height}`);
8502
8504
  }
8503
8505
  /**
8504
8506
  * Detects the screen refresh rate
@@ -8727,8 +8729,8 @@ class K {
8727
8729
  );
8728
8730
  if (!A.sceneCode || typeof A.sceneCode != "string")
8729
8731
  throw new S("sceneCode must be a non-empty string", "INVALID_CONFIG");
8730
- if (A.frameRateMode !== void 0 && A.frameRateMode !== "full" && A.frameRateMode !== "half")
8731
- throw new S('frameRateMode must be either "full" or "half"', "INVALID_CONFIG");
8732
+ if (A.frameRateMode !== void 0 && !["full", "half", "quarter", "eighth"].includes(A.frameRateMode))
8733
+ throw new S('frameRateMode must be one of "full", "half", "quarter", or "eighth"', "INVALID_CONFIG");
8732
8734
  }
8733
8735
  /**
8734
8736
  * Cleans up all resources
@@ -8752,7 +8754,7 @@ function DI(h) {
8752
8754
  expectedVersion: 1
8753
8755
  } : typeof A.senderTime != "number" ? { code: "invalid-field", details: "senderTime must be a number", field: "senderTime" } : null;
8754
8756
  }
8755
- const hI = "0.8.3";
8757
+ const hI = "0.8.5";
8756
8758
  export {
8757
8759
  yA as A,
8758
8760
  hI as V,
@@ -8760,4 +8762,4 @@ export {
8760
8762
  S as b,
8761
8763
  PB as g
8762
8764
  };
8763
- //# sourceMappingURL=index-DF_IFS3A.js.map
8765
+ //# sourceMappingURL=index-BQtrhfjg.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.3";
2660
+ export declare const VERSION = "0.8.5";
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;
@@ -3048,7 +3051,6 @@ export declare class VijiCore {
3048
3051
  private eventSourceCore;
3049
3052
  private frameSourceCores;
3050
3053
  private eventSourceCleanup;
3051
- private resizeSyncEnabled;
3052
3054
  private currentInteractionEnabled;
3053
3055
  private parameterGroups;
3054
3056
  private parameterValues;
@@ -3256,10 +3258,12 @@ export declare class VijiCore {
3256
3258
  */
3257
3259
  injectFrame(bitmap: ImageBitmap, streamIndex: number): void;
3258
3260
  /**
3259
- * Link this core to receive events from a source core
3260
- * @param syncResolution Smart default: true for headless, false for visible
3261
+ * Link this core to receive input events (mouse/keyboard/touch) from a
3262
+ * source core. Event forwarding only — resolution and frame-rate are pushed
3263
+ * mixer→sources through the frame-source relationship (see
3264
+ * {@link linkFrameSources}), not here.
3261
3265
  */
3262
- linkEventSource(sourceCore: VijiCore, syncResolution?: boolean): void;
3266
+ linkEventSource(sourceCore: VijiCore): void;
3263
3267
  /**
3264
3268
  * Unlink from event source
3265
3269
  */
@@ -3272,6 +3276,19 @@ export declare class VijiCore {
3272
3276
  * Unlink all frame sources
3273
3277
  */
3274
3278
  unlinkFrameSources(): void;
3279
+ /**
3280
+ * Push this (mixer) core's effective resolution and frame-rate mode to every
3281
+ * linked frame source, so sources render at exactly the compositor's quality
3282
+ * (no wasted pixels/frames the compositor would discard). No-op unless frame
3283
+ * sources are linked, so normal single-core use is unaffected. Re-invoked on
3284
+ * link and on every `setResolution`/`setFrameRate`/`updateResolution`.
3285
+ *
3286
+ * Each source is guarded on `ready` (a not-yet-initialized or destroyed
3287
+ * source would throw from `setResolution`/`setFrameRate`). `setFrameRate` is
3288
+ * async; its promise is intentionally not awaited (fire-and-forget) with a
3289
+ * catch so a source torn down mid-call cannot raise an unhandled rejection.
3290
+ */
3291
+ private syncFrameSourceQuality;
3275
3292
  /**
3276
3293
  * Get parameter groups filtered by active capabilities
3277
3294
  */
@@ -3447,7 +3464,7 @@ export declare class VijiCore {
3447
3464
  private notifyParameterListeners;
3448
3465
  /**
3449
3466
  * Sets the scene frame rate mode
3450
- * @param mode - 'full' for every animation frame, 'half' for every second frame
3467
+ * @param mode - render every animation frame (`'full'`), every 2nd (`'half'`), every 4th (`'quarter'`), or every 8th (`'eighth'`)
3451
3468
  */
3452
3469
  setFrameRate(mode: FrameRateMode): Promise<void>;
3453
3470
  /**
@@ -3937,7 +3954,7 @@ export declare interface VijiCoreConfig {
3937
3954
  hostContainer?: HTMLElement;
3938
3955
  /** Artist scene code to execute */
3939
3956
  sceneCode: string;
3940
- /** Frame rate mode - 'full' for every animation frame, 'half' for every second frame */
3957
+ /** Frame rate mode - render every frame (`'full'`), every 2nd (`'half'`), every 4th (`'quarter'`), or every 8th (`'eighth'`). */
3941
3958
  frameRateMode?: FrameRateMode;
3942
3959
  /** Resolution: scale (number) or explicit dimensions (object). Required for headless, must be object form */
3943
3960
  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-DF_IFS3A.js";
1
+ import { A as a, V as i, a as s, b as e } from "./index-BQtrhfjg.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.4",
3
+ "version": "0.8.6",
4
4
  "description": "Universal execution engine for Viji Creative scenes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",