@cyberart-io/engine 0.0.6 → 0.0.7

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.
@@ -0,0 +1,134 @@
1
+ # Calculation carts
2
+
3
+ Carts that run `getDefaultState`, `update`, and host-channel events without a paint surface. They share a lockstep clock and router with sibling render carts. They do not create a canvas and the runtime does not call `render`, even if a no-op is supplied.
4
+
5
+ Generic names only: telemetry, world, overlay. Event types stay on `host.intent.*` / `host.state.*`. Scene ids such as `alpha` and overlay ids such as `banner` are payload fields, not product names.
6
+
7
+ Related: [runtime group](runtime-group.md), [events](events.md), [capability manifest](capability-manifest.md), [deterministic mode](deterministic-mode.md).
8
+
9
+ Back to the [package README](../README.md).
10
+
11
+ ## One-command reproduce (this repo)
12
+
13
+ ```bash
14
+ pnpm exec vitest run packages/engine/src/canvas/cyb-20-calculation-cart.repro.spec.ts
15
+ ```
16
+
17
+ ## When to use
18
+
19
+ | Surface | Import | Use |
20
+ |---|---|---|
21
+ | Production host | `createRuntime({ kind: 'calculation' })` or `createRuntimeGroup` participant `kind: 'calculation'` from `@cyberart-io/engine` | World / telemetry carts that emit and consume events for a sibling overlay (or other render) cart. |
22
+ | Vitest / jsdom | `createHeadlessMultiCartHarness` from `@cyberart-io/engine/headless` | Same group API after `installHeadlessCanvas()`. |
23
+
24
+ `kind` defaults to `'render'`. Existing carts, `/art`, and `metadata.audio: 'tone'` pieces are unchanged: they still get a canvas and a paint phase.
25
+
26
+ ## `createRuntime({ kind: 'calculation' })`
27
+
28
+ ```ts
29
+ import { createRuntime } from '@cyberart-io/engine';
30
+ import type { AnimationCart } from '@cyberart-io/engine';
31
+
32
+ type WorldState = { sceneId: string | null };
33
+
34
+ const worldCart: AnimationCart<WorldState> = {
35
+ metadata: { id: 'world', name: 'World', frameRate: 30 },
36
+ getDefaultState: () => ({ sceneId: null }),
37
+ update: (_R, _f, _p, _d, state, _k, _ptr, _gm, _t, _fs, hostChannel) => {
38
+ const events = hostChannel?.consume() ?? [];
39
+ let sceneId = state.sceneId;
40
+ for (const event of events) {
41
+ if (event.type === 'host.state.scene-changed') {
42
+ sceneId = (event.payload as { sceneId?: string } | undefined)?.sceneId ?? null;
43
+ hostChannel?.emit({
44
+ type: 'host.intent.overlay',
45
+ kind: 'intent',
46
+ payload: { overlayId: 'banner' },
47
+ });
48
+ }
49
+ }
50
+ return { sceneId };
51
+ },
52
+ // render omitted
53
+ };
54
+
55
+ const runtime = createRuntime({
56
+ container,
57
+ kind: 'calculation',
58
+ deterministic: { origin: 0 },
59
+ seed: `0x${'20'.repeat(32)}`,
60
+ });
61
+ const cart = runtime.mount(worldCart);
62
+ // cart.canvas === undefined; container has no <canvas>
63
+ ```
64
+
65
+ `runtime.kind` is `'calculation'`. `CartHandle.canvas` is `undefined`. `step` / `advance` still run `update`. A no-op `render` is not invoked.
66
+
67
+ ## Runtime group
68
+
69
+ ```ts
70
+ import { createRuntimeGroup } from '@cyberart-io/engine';
71
+
72
+ const group = createRuntimeGroup({
73
+ origin: 0,
74
+ participants: [
75
+ {
76
+ id: 'world',
77
+ cart: worldCart,
78
+ kind: 'calculation',
79
+ seed: `0x${'20'.repeat(32)}`,
80
+ emit: ['host.intent.*'],
81
+ subscribe: ['host.state.*'],
82
+ },
83
+ {
84
+ id: 'overlay',
85
+ cart: overlayCart,
86
+ kind: 'render',
87
+ subscribe: ['host.intent.*'],
88
+ },
89
+ {
90
+ id: 'telemetry',
91
+ cart: telemetryCart,
92
+ kind: 'calculation',
93
+ subscribe: ['host.state.*', 'host.intent.*'],
94
+ },
95
+ ],
96
+ });
97
+
98
+ group.publish({
99
+ type: 'host.state.scene-changed',
100
+ kind: 'state',
101
+ payload: { sceneId: 'alpha' },
102
+ });
103
+ await group.step(2);
104
+ const { participants } = await group.inspect();
105
+ // participants.world.kind === 'calculation'
106
+ // group.participant('world').cart.canvas === undefined
107
+ // overlay still paints
108
+ group.destroy();
109
+ ```
110
+
111
+ `createRuntimeGroup` passes each participant `kind` into `createRuntime`. Calculation participants keep a mount container (for the runtime) but never attach a canvas. `resize` skips missing canvases. Compositor layers that point at a calculation id are skipped (`cart.canvas` is absent).
112
+
113
+ ## Capability manifest
114
+
115
+ Optional `kind: 'calculation'` on the manifest (omitted means render). Hosts that want to enforce it pass `kinds: ['render', 'calculation']`. Existing manifests and hosts that omit `kinds` are unchanged.
116
+
117
+ ```ts
118
+ import { defineCapabilityManifest, validateCapabilityManifest } from '@cyberart-io/engine';
119
+
120
+ const defined = defineCapabilityManifest({
121
+ id: 'world.telemetry',
122
+ kind: 'calculation',
123
+ runtime: { minContractVersion: 1, features: ['router'] },
124
+ phases: ['ready'],
125
+ managers: ['hostChannel'],
126
+ assets: { kinds: [], declarations: [] },
127
+ acceptedEvents: ['host.state.*'],
128
+ emittedEvents: ['host.intent.overlay'],
129
+ permissions: { emit: ['host.intent.*'], subscribe: ['host.state.*'] },
130
+ integrations: [],
131
+ });
132
+ ```
133
+
134
+ `CAPABILITY_CART_KINDS` is `['render', 'calculation']`. Diagnostics: `invalid-kind`, `unsupported-kind`.
@@ -1,6 +1,6 @@
1
1
  # Capability manifest
2
2
 
3
- Versioned JSON for what a cart needs from the host: runtime contract, lifecycle phases, managers, asset kinds, event patterns, permissions, and integrations. Related: [events](events.md) (emit / subscribe patterns), [asset resolver](asset-resolver.md) (kinds only — this file does not resolve URLs).
3
+ Versioned JSON for what a cart needs from the host: runtime contract, lifecycle phases, managers, asset kinds, event patterns, permissions, and integrations. Related: [events](events.md) (emit / subscribe patterns), [asset resolver](asset-resolver.md) (kinds only — this file does not resolve URLs), [calculation carts](calculation-carts.md) (`kind`).
4
4
 
5
5
  Back to the [package README](../README.md).
6
6
 
@@ -26,6 +26,7 @@ pnpm exec vitest run packages/engine/src/canvas/cyb-25-capability-manifest.repro
26
26
  | `acceptedEvents` / `emittedEvents` | Dotted type patterns (`*` = one segment). |
27
27
  | `permissions` | `emit` / `subscribe` patterns; optional `authoritative`. |
28
28
  | `integrations` | Required host libraries: `tone` \| `midi`. |
29
+ | `kind` | Optional. `'render'` (omitted) or `'calculation'`. |
29
30
 
30
31
  Emitted types must be covered by `permissions.emit`; accepted types by `permissions.subscribe`.
31
32
 
@@ -84,12 +85,14 @@ const parsed = parseCapabilityManifest(JSON.stringify(defined.manifest));
84
85
  | `unknown-permission` | Cart emit/subscribe permission is not on the host allowlist (only if host listed `emit` / `subscribe`). |
85
86
  | `invalid-surface` / `invalid-clear-policy` / `invalid-blend` | Cart or host `surface` / `layers` values are the wrong shape or not in the allowlist. |
86
87
  | `unsupported-surface` / `unsupported-layer` | Host omitted `surface.alpha` / `clearPolicy` or compositor blend modes the cart requires. |
88
+ | `invalid-kind` / `unsupported-kind` | Manifest `kind` is not `render` / `calculation`, or the host `kinds` list does not include it. |
87
89
 
88
90
  Optional additive keys (omitted on existing carts):
89
91
 
90
92
  ```ts
91
93
  surface: { alpha: true, clearPolicy: 'transparent' }
92
94
  layers: { compositor: true, blend: ['source-over', 'screen'] }
95
+ kind: 'calculation'
93
96
  ```
94
97
 
95
- See [compositor](compositor.md).
98
+ `host.kinds` is optional. When omitted, `unsupported-kind` is not checked. See [calculation carts](calculation-carts.md).
@@ -48,7 +48,7 @@ const frame = compositor.captureComposedFrame();
48
48
  compositor.destroy();
49
49
  ```
50
50
 
51
- Typical stack: host image, then opaque overlay carts (`source-over`), then additive layers (`screen` so RGB `0,0,0` backing is identity unless `clearPolicy: 'transparent'` knocks it out). Later `order` values draw on top.
51
+ Typical stack: host image, then opaque overlay carts (`source-over`), then additive layers (`screen` so RGB `0,0,0` backing is identity unless `clearPolicy: 'transparent'` knocks it out). Later `order` values draw on top. Versioned overlay swaps (`v1` → `v2`) belong on [`createVisualLayerController`](visual-layers.md), which calls `setLayer` / `addLayer` with `source: { kind: 'image', image }` so a failed asset never becomes the live frame.
52
52
 
53
53
  ### Options
54
54
 
@@ -65,7 +65,7 @@ Typical stack: host image, then opaque overlay carts (`source-over`), then addit
65
65
 
66
66
  | Field | Default | Meaning |
67
67
  |---|---|---|
68
- | `id` | required | Runtime-group participant id. |
68
+ | `id` | required | Runtime-group participant id, or a visual-layer slot with an `image` / `canvas` source. |
69
69
  | `order` | required | Lower draws first. |
70
70
  | `visible` | `true` | Skip compose and pointer hits when false. |
71
71
  | `opacity` | `1` | 0..1. |
@@ -73,6 +73,11 @@ Typical stack: host image, then opaque overlay carts (`source-over`), then addit
73
73
  | `clip` | none | Pixel rect; compose and pointer hits are clipped. |
74
74
  | `pointerEvents` | `auto` | `none` skips hit testing and sets `canvas.style.pointerEvents`. |
75
75
  | `clearPolicy` | compositor default | `transparent` knocks out black backing. |
76
+ | `source` | `{ kind: 'participant' }` | `participant` reads the group canvas. `image` clones `ImageData`. `canvas` rereads that canvas each compose. |
77
+
78
+ `setLayer` applies a patch, including an atomic `source` swap. `addLayer` appends a declaration (ImageData slots do not need a group participant). `unmountLayer` detaches a participant when one exists; ImageData-only layers are removed from the compose list.
79
+
80
+ `inspect().layers[].sourceKind` is `participant` | `image` | `canvas`.
76
81
 
77
82
  ### Handle
78
83
 
@@ -81,7 +86,7 @@ Typical stack: host image, then opaque overlay carts (`source-over`), then addit
81
86
  | `compose()` | Blend host + visible layers; returns `ImageData`. |
82
87
  | `captureComposedFrame()` | `{ imageData, pngDataUrl, declaredOrder, width, height }`. |
83
88
  | `resize(w, h, dpr?)` | Shared viewport; calls `group.resize` on the buffer size. Order/blend survive `group.step`. Carts that cache `DimensionContext` should `group.reset()` after a size change. |
84
- | `setLayer` / `layer` / `layers` | Mutate / inspect declarative settings. |
89
+ | `setLayer` / `addLayer` / `layer` / `layers` | Mutate / inspect declarative settings. `setLayer` can swap `source`. |
85
90
  | `pointerTarget(x, y)` | Top-most visible layer with `pointerEvents: 'auto'` under the pixel. |
86
91
  | `unmountLayer(id)` | `group.detach(id)` without clearing sibling canvases. |
87
92
  | `inspect()` | Viewport, clear policy, layers, `declaredOrder` ids. |
@@ -4,7 +4,7 @@ Host helper that mounts several production `createRuntime` carts, attaches each
4
4
 
5
5
  Capability-manifest integration is optional and structural (`capability?: { emit, subscribe, authoritative }`). This module does not import the manifest.
6
6
 
7
- Related: [events](events.md), [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md), [compositor](compositor.md), [replay inspector](replay-inspector.md).
7
+ Related: [events](events.md), [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md), [compositor](compositor.md), [replay inspector](replay-inspector.md), [calculation carts](calculation-carts.md).
8
8
 
9
9
  Back to the [package README](../README.md).
10
10
 
@@ -82,7 +82,7 @@ Example mapping: an accepted `host.state.accepted` is consumed by the effects ca
82
82
  |---|---|
83
83
  | `id` | Router participant id. Reserved: `host`, `router`. |
84
84
  | `cart` | `AnimationCart` mounted through `createRuntime`. |
85
- | `kind` | `'render'` (default) or `'calculation'` (no-op/minimal render; still a real cart). |
85
+ | `kind` | `'render'` (default) or `'calculation'` (no canvas / paint; `update` and host-channel events still run). See [calculation carts](calculation-carts.md). |
86
86
  | `seed` | `0x` + 64 hex, or any seed `createRuntime` accepts. Default: derived from `id`. |
87
87
  | `container` | Injected mount node. If omitted, the group creates and owns a sized `div`. |
88
88
  | `emit` / `subscribe` / `authoritative` | Passed to `router.attach`. |
@@ -117,6 +117,6 @@ await harness.step(2);
117
117
  harness.destroy();
118
118
  ```
119
119
 
120
- Calls `installHeadlessCanvas()` then `createRuntimeGroup`. Same handle. Three-cart CI fixture: effects + ambience + a calculation telemetry cart.
120
+ Calls `installHeadlessCanvas()` then `createRuntimeGroup`. Same handle. Three-cart CI fixture: effects + ambience + a calculation telemetry cart (no canvas).
121
121
 
122
122
  Cleanup: `afterEach(() => group.destroy())` so a failed assertion does not leak nodes. After `destroy`, `step` throws and owned containers are gone from `document.body`.
@@ -0,0 +1,151 @@
1
+ # Visual layers
2
+
3
+ Versioned overlay / mask / sprite declarations with deterministic show, hide, replace, and crossfade. Presentation cues own timing. The compositor swaps `ImageData` / canvas sources in one `setLayer` so a partial or failed asset never becomes the live frame.
4
+
5
+ Hosts persist `controller.snapshot()` JSON inside envelope `hostState`. This module does not edit the snapshot envelope.
6
+
7
+ Related: [presentation cue](presentation-cue.md), [compositor](compositor.md), [asset resolver](asset-resolver.md), [snapshots](snapshots.md). Back to the [package README](../README.md).
8
+
9
+ ## One-command reproduce (this repo)
10
+
11
+ ```bash
12
+ pnpm exec vitest run packages/engine/src/canvas/cyb-66-visual-layers.repro.spec.ts
13
+ ```
14
+
15
+ ## When to use
16
+
17
+ | Surface | Import | Use |
18
+ |---|---|---|
19
+ | Production host | `createVisualLayerController` from `@cyberart-io/engine` | Bind `host.state.accepted` to a preloaded overlay version. |
20
+ | Vitest / jsdom | same controller after `installHeadlessCanvas()` | `captureVisualLayers` / `captureComposedFrame` / `inspect`. |
21
+
22
+ Generic ids only: overlay `overlay`, versions `v1` / `v2`, `sceneId: 'alpha'`. Cyberart owns presentation; the host owns which version is authoritative.
23
+
24
+ ## `createVisualLayerController(options)`
25
+
26
+ ```ts
27
+ import {
28
+ ASSET_READY_EVENT,
29
+ HOST_STATE_ACCEPTED_EVENT,
30
+ createCompositor,
31
+ createRuntimeGroup,
32
+ createVisualLayerController,
33
+ } from '@cyberart-io/engine';
34
+
35
+ const group = createRuntimeGroup({
36
+ participants: [{ id: 'base', cart: baseCart }],
37
+ });
38
+ const compositor = createCompositor({
39
+ group,
40
+ host: { color: '#000080' },
41
+ layers: [
42
+ { id: 'base', order: 0 },
43
+ { id: 'overlay', order: 1, visible: false },
44
+ ],
45
+ });
46
+ const layers = createVisualLayerController({
47
+ compositor,
48
+ sceneId: 'alpha',
49
+ originFrame: 0,
50
+ layers: [
51
+ {
52
+ id: 'overlay',
53
+ kind: 'layer', // also 'mask' | 'sprite'
54
+ order: 1,
55
+ initialVersion: 'v1',
56
+ fallback: 'keep-prior',
57
+ versions: [
58
+ { id: 'v1', assetId: 'overlay.v1', provenance: { source: 'fixture', version: 'v1' } },
59
+ { id: 'v2', assetId: 'overlay.v2', provenance: { source: 'fixture', version: 'v2' } },
60
+ ],
61
+ },
62
+ ],
63
+ onAccepted: [{ layerId: 'overlay', toVersion: 'v2', kind: 'replace', durationFrames: 2 }],
64
+ });
65
+
66
+ layers.registerSource('overlay.v1', imageV1);
67
+ layers.registerSource('overlay.v2', imageV2);
68
+ layers.handleHostEvent({ type: ASSET_READY_EVENT, kind: 'state', payload: { id: 'overlay.v1' } });
69
+ layers.handleHostEvent({ type: ASSET_READY_EVENT, kind: 'state', payload: { id: 'overlay.v2' } });
70
+ layers.handleHostEvent({
71
+ type: HOST_STATE_ACCEPTED_EVENT,
72
+ kind: 'state',
73
+ payload: { sceneId: 'alpha' },
74
+ });
75
+ layers.step(2);
76
+ const capture = layers.captureComposedFrame();
77
+ // capture.imageData is v2; inspect().activeVersion is 'v2'
78
+ ```
79
+
80
+ `kind: 'mask'` defaults `pointerEvents` to `none`. Incoming crossfade pixels use compositor layer `overlay:incoming` (`visualIncomingLayerId('overlay')`).
81
+
82
+ ### Options
83
+
84
+ | Option | Default | Meaning |
85
+ |---|---|---|
86
+ | `compositor` | required | `createCompositor` handle. The controller drives `setLayer` / `addLayer`. |
87
+ | `layers` | required | Versioned declarations with stable ids. |
88
+ | `preloader` | none | Optional `createAssetPreloader`. `get(id)` counts as ready when no event has been recorded yet. |
89
+ | `originFrame` | `0` | Passed to `createPresentationTimeline`. |
90
+ | `reducedMotion` | `false` | Host flag. Instant-complete cues still wait for a ready source before revealing. |
91
+ | `sceneId` | none | When set, `host.state.accepted` with a different `sceneId` is ignored. |
92
+ | `fallback` | `'keep-prior'` | Default when a version asset fails: keep the last valid frame, `'hide'`, or `{ version: 'v1' }`. |
93
+ | `onAccepted` | none | Transitions to play when `host.state.accepted` has no `overlayId`/`version`. |
94
+ | `dispatch` | none | Optional mailbox for `visual.layer.*` events. Failed events use `kind: 'diagnostic'`. |
95
+
96
+ Payload `{ overlayId, version }` (or `layerId`) on `host.state.accepted` plays a replace of that version (duration 2). `ASSET_READY_EVENT` / `ASSET_FAILED_EVENT` are keyed by payload `id` (`overlay.v2`).
97
+
98
+ ### Handle
99
+
100
+ | Member | Meaning |
101
+ |---|---|
102
+ | `registerSource(assetId, imageData \| canvas)` | Pixel buffer for that asset. Cloned for `ImageData`. Required before a version can become visible. |
103
+ | `handleHostEvent(event)` | `host.state.accepted`, `ASSET_READY_EVENT`, `ASSET_FAILED_EVENT`. |
104
+ | `play(spec)` | `CueSpec` plus `layerId`, `kind` (`show` / `hide` / `replace` / `crossfade`), `toVersion`. |
105
+ | `step(frames?)` | Advance the presentation timeline (default 1). Returns new `visual.layer.*` events. |
106
+ | `override(layerId, version \| null)` | Host override. Ready versions swap immediately; missing assets keep the prior frame. |
107
+ | `snapshot()` | JSON-serializable visibility, versions, transition position, provenance, assets, events, diagnostics. |
108
+ | `restore(json)` | `{ ok: true, snapshot }` or `{ ok: false, errors }`. Pixels are not in the JSON — register sources first. |
109
+ | `inspect()` | Per-layer `activeVersion`, `pendingVersion`, `committedVersion`, transition progress. |
110
+ | `captureComposedFrame()` | Delegates to the compositor (headless `ImageData` + `declaredOrder`). |
111
+ | `destroy()` | Drops cues. Does not destroy the compositor. |
112
+
113
+ `captureVisualLayers(controller)` returns `{ frame, layers }` (also from `@cyberart-io/engine/headless`).
114
+
115
+ ## Atomic swaps
116
+
117
+ Replace does not paint `v2` until the cue completes **and** `overlay.v2` is ready with a registered source. Mid-cue pixels stay on the last valid frame (`v1`). Crossfade paints `overlay:incoming` only after that same gate; opacity follows cue progress (outgoing `1 - t`, incoming `t`). If the cue ends before the asset is ready, the pending version waits for `ASSET_READY_EVENT` and then swaps in one `setLayer`. `ASSET_FAILED_EVENT` keeps the prior frame and records a diagnostic (`code: 'asset-failed'`).
118
+
119
+ ## Snapshot / restore
120
+
121
+ `snapshot()` survives `JSON.parse(JSON.stringify(snapshot))`. Hosts store it in envelope `hostState` (for example `{ sceneId: 'alpha', visualLayers: snapshot }`). Restore:
122
+
123
+ | When | Result |
124
+ |---|---|
125
+ | Mid-transition | Same `frame`, `committedVersion`, and cue `progress`. Further `step` continues the fade. |
126
+ | After complete | `activeVersion` is the committed version; `transition` is `null`. |
127
+
128
+ Restore does not replay host events. It reapplies version ids onto sources already registered on that controller. `schemaVersion` is `VISUAL_LAYER_SNAPSHOT_SCHEMA_VERSION` (`1`).
129
+
130
+ ## Public exports
131
+
132
+ | Export | Meaning |
133
+ |---|---|
134
+ | `createVisualLayerController` | Constructor. |
135
+ | `captureVisualLayers` | `{ frame, layers }` helper. |
136
+ | `visualIncomingLayerId` / `VISUAL_LAYER_INCOMING_SUFFIX` | Incoming compositor id (`overlay:incoming`). |
137
+ | `VISUAL_LAYER_KINDS` / `isVisualLayerKind` | `'layer'` \| `'mask'` \| `'sprite'`. |
138
+ | `VISUAL_LAYER_TRANSITIONS` / `isVisualLayerTransitionKind` | `'show'` \| `'hide'` \| `'replace'` \| `'crossfade'`. |
139
+ | `VISUAL_LAYER_EVENTS` / `isVisualLayerEventType` | Lifecycle type names. |
140
+ | `VISUAL_LAYER_REVEALED_EVENT` | `'visual.layer.revealed'` |
141
+ | `VISUAL_LAYER_HIDDEN_EVENT` | `'visual.layer.hidden'` |
142
+ | `VISUAL_LAYER_TRANSITION_STARTED_EVENT` | `'visual.layer.transition-started'` |
143
+ | `VISUAL_LAYER_TRANSITION_COMPLETED_EVENT` | `'visual.layer.transition-completed'` |
144
+ | `VISUAL_LAYER_FAILED_EVENT` | `'visual.layer.failed'` |
145
+ | `VISUAL_LAYER_FAILURE_CODES` | `'asset-failed'` \| `'missing-source'` \| `'unknown-layer'` \| `'unknown-version'` \| `'invalid-snapshot'` |
146
+ | `VISUAL_LAYER_SNAPSHOT_SCHEMA_VERSION` | `1` |
147
+ | `HOST_STATE_ACCEPTED_EVENT` | `'host.state.accepted'` |
148
+
149
+ Types: `VisualLayerDeclaration`, `VisualLayerVersionDeclaration`, `VisualLayerCueSpec`, `VisualLayerController`, `VisualLayerControllerSnapshot`, `VisualLayerInspect`, `VisualLayerEvent`, `VisualLayerDiagnostic`, `VisualLayerFallbackPolicy`, `VisualLayerAcceptedBinding`, `PlayVisualLayerResult`, `RestoreVisualLayerResult`, `CreateVisualLayerControllerOptions`, `VisualLayerCapture`, and related aliases.
150
+
151
+ Do not `await` wall-clock loads inside cart `update` / `render`. Drive `step` from the same clock as deterministic `cart.step`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberart-io/engine",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "CyberArt host engine: mount carts, events, capability manifests, geometry, runtime groups, and a Node/jsdom headless entry.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",