@cyberart-io/engine 0.0.4 → 0.0.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/README.md +102 -21
- package/dist/headless.d.ts +779 -68
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +1144 -9
- package/dist/index.js +1 -1
- package/docs/asset-resolver.md +4 -4
- package/docs/audio.md +152 -0
- package/docs/browser-harness.md +126 -0
- package/docs/capability-manifest.md +18 -7
- package/docs/compositor.md +103 -0
- package/docs/deterministic-mode.md +1 -1
- package/docs/events.md +75 -13
- package/docs/executable-modules.md +112 -0
- package/docs/headless-harness.md +44 -14
- package/docs/midi.md +128 -0
- package/docs/presentation-adapter.md +8 -8
- package/docs/presentation-cue.md +2 -2
- package/docs/replay-inspector.md +88 -0
- package/docs/runtime-group.md +9 -7
- package/docs/snapshots.md +102 -0
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Versioned snapshots
|
|
2
|
+
|
|
3
|
+
Portable save envelope for carts and embedding hosts. Schema **1** is the previous engine-owned `CartStateBundle` (`CART_STATE_BUNDLE_VERSION = 1`). Schema **2** wraps that blob in `engineState` and keeps host-owned data in `hostState` / `hostStateRef`. Cyberart migrates envelopes in memory; the host owns persistence (local storage, a database, cross-user saves). This package is not the database of record.
|
|
4
|
+
|
|
5
|
+
Related: [deterministic mode](deterministic-mode.md) (`rng` / clock), [capability manifest](capability-manifest.md) (cart id). Back to the [package README](../README.md).
|
|
6
|
+
|
|
7
|
+
## One-command reproduce (this repo)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm exec vitest run packages/engine/src/canvas/cyb-50-snapshots.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Scheme (`SNAPSHOT_SCHEMA_VERSION = 2`)
|
|
14
|
+
|
|
15
|
+
`ENGINE_SNAPSHOT_RUNTIME` is `'0.0.5'`. It is a snapshot runtime id, independent of the npm package version — bump whichever one the change actually belongs to.
|
|
16
|
+
|
|
17
|
+
| Field | Meaning |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `schemaVersion` | Envelope schema. Current **2**. |
|
|
20
|
+
| `runtimeVersion` | `ENGINE_SNAPSHOT_RUNTIME` at save. |
|
|
21
|
+
| `cart` | `{ id, version, generative? }` — exact cart version (`'0'` if unversioned). |
|
|
22
|
+
| `modules` | Optional `{ id, version }[]`. |
|
|
23
|
+
| `seed` | Deterministic token hash. |
|
|
24
|
+
| `rng` | Optional `RandomState` (sfc32 snapshot). Recorded when available; cart `importState` still does not replay the PRNG. |
|
|
25
|
+
| `clock` | `framesElapsed`, optional `elapsedSinceStart` / `now` / `frameRate`. |
|
|
26
|
+
| `engineState` | The existing `CartStateBundle` (bundle `version` remains **1**). |
|
|
27
|
+
| `hostState` | Host-owned JSON payload. Opaque to the engine. |
|
|
28
|
+
| `hostStateRef` | Host persistence pointer (slot id, URL, …). Not loaded by the engine. |
|
|
29
|
+
| `assets` | Optional `{ id, version?, ref? }[]` content version references. |
|
|
30
|
+
| `createdAt` | ISO-8601 creation time. Migrated v1 bundles use `1970-01-01T00:00:00.000Z`. |
|
|
31
|
+
| `provenance` | Optional `{ source?, integrity?: { alg, hash } }`. Hosts fill integrity; the engine does not hash. |
|
|
32
|
+
|
|
33
|
+
`cart.id` is required. `snapshotFromCartBundle` and the 1→2 migration fail closed when the legacy bundle has no `cartId` — they do not stamp `'unknown'`.
|
|
34
|
+
|
|
35
|
+
`defineSnapshot` / `parseSnapshot` / `validateSnapshot` return `{ ok: true, snapshot }` or `{ ok: false, errors }`. They do not throw. JSON round-trip: `JSON.parse(JSON.stringify(snapshot))` equals the snapshot.
|
|
36
|
+
|
|
37
|
+
Legacy cart bundles (no `schemaVersion`, have `version` / `seed` / `framesElapsed` / `state`) parse as schema **1**. `exportState` / `importState` still use that blob so existing carts keep working. `importState` also accepts a current envelope: it migrates if needed and restores `engineState` only.
|
|
38
|
+
|
|
39
|
+
## Migrations
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import {
|
|
43
|
+
applySnapshotMigrations,
|
|
44
|
+
createEngineSnapshotMigrations,
|
|
45
|
+
createSnapshotMigrationRegistry,
|
|
46
|
+
defineSnapshot,
|
|
47
|
+
parseSnapshot,
|
|
48
|
+
} from '@cyberart-io/engine';
|
|
49
|
+
|
|
50
|
+
const defined = defineSnapshot({
|
|
51
|
+
cart: { id: 'art.cart', version: '3' },
|
|
52
|
+
seed: '0x' + '50'.repeat(32),
|
|
53
|
+
clock: { framesElapsed: 12 },
|
|
54
|
+
engineState: {
|
|
55
|
+
version: 1,
|
|
56
|
+
cartId: 'art.cart',
|
|
57
|
+
seed: '0x' + '50'.repeat(32),
|
|
58
|
+
framesElapsed: 12,
|
|
59
|
+
state: { n: 12 },
|
|
60
|
+
},
|
|
61
|
+
hostState: { sceneId: 'alpha' },
|
|
62
|
+
createdAt: '2026-01-15T00:00:00.000Z',
|
|
63
|
+
});
|
|
64
|
+
if (!defined.ok) throw new Error(defined.errors.map((e) => e.detail).join('; '));
|
|
65
|
+
|
|
66
|
+
const registry = createSnapshotMigrationRegistry({
|
|
67
|
+
migrations: createEngineSnapshotMigrations(),
|
|
68
|
+
});
|
|
69
|
+
const migrated = applySnapshotMigrations(legacyBundle, registry);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`applySnapshotMigrations` clones the input, walks `from → from+1` until schema 2, and validates each step. The original object is left unchanged, including when a step is missing or a migrate function fails.
|
|
73
|
+
|
|
74
|
+
| Diagnostic `code` | When |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `invalid-json` | `parseSnapshot` received unparseable text. |
|
|
77
|
+
| `invalid-snapshot` | Root value is not a JSON object, or a legacy blob is missing. |
|
|
78
|
+
| `invalid-field` | A required string/number/object field is the wrong shape. |
|
|
79
|
+
| `invalid-schema` | `validateSnapshot` / `defineSnapshot` saw the wrong `schemaVersion`. |
|
|
80
|
+
| `invalid-engine-state` | Nested `CartStateBundle` failed `parseCartStateBundle`. |
|
|
81
|
+
| `unsupported-schema` | `schemaVersion` is newer than 2. |
|
|
82
|
+
| `missing-migration` | No registered step from the snapshot's schema to the next integer. |
|
|
83
|
+
| `invalid-migration-result` | A `migrate` function threw, produced the wrong schema, or failed validation. |
|
|
84
|
+
|
|
85
|
+
Disjoint live cart state still throws `IncompatibleCartStateError` from `importState` / `importSnapshot` after the envelope has been migrated. Missing envelope paths surface as `missing-migration` diagnostics; `importSnapshot` wraps those details in `IncompatibleCartStateError`.
|
|
86
|
+
|
|
87
|
+
The current engine table is only 1→2 (`SNAPSHOT_SCHEMA_VERSION` is 2). When a later envelope schema ships, compose host migrations after that table; each step must advance by exactly one. Duplicate `from` values throw when the registry is created.
|
|
88
|
+
|
|
89
|
+
## Runtime
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const envelope = await cart.exportSnapshot({
|
|
93
|
+
cartVersion: '3',
|
|
94
|
+
hostState: { sceneId: 'alpha' },
|
|
95
|
+
modules: [{ id: 'overlay.module', version: '1' }],
|
|
96
|
+
});
|
|
97
|
+
await cart.importSnapshot(envelope);
|
|
98
|
+
await cart.importState(legacyBundle); // still a CartStateBundle
|
|
99
|
+
await cart.importState(envelope); // envelope → engineState
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`exportSnapshot` wraps `exportState()` plus clock / rng. `importSnapshot` always migrates. Hosts restore `hostState` themselves. W/E localStorage helpers still store the cart bundle; `parseStoredCartState` migrates a stored envelope first and returns null if the path is missing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyberart-io/engine",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
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",
|