@cyberart-io/engine 0.0.7 → 0.0.9
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/CHANGELOG.md +37 -0
- package/README.md +308 -1
- package/dist/headless.d.ts +2584 -61
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +2585 -244
- package/dist/index.js +1 -1
- package/docs/asset-resolver.md +1 -1
- package/docs/browser-harness.md +1 -1
- package/docs/capability-manifest.md +1 -1
- package/docs/content-revision.md +64 -0
- package/docs/events.md +1 -1
- package/docs/frame-benchmark.md +107 -0
- package/docs/headless-harness.md +6 -2
- package/docs/job-orchestration.md +55 -0
- package/docs/portal-lifecycle.md +50 -0
- package/docs/presentation-bindings.md +93 -0
- package/docs/presentation-sequences.md +150 -0
- package/docs/production-scenario.md +81 -0
- package/docs/remote-cart-manifest.md +66 -0
- package/docs/replay-inspector.md +1 -1
- package/docs/runtime-group.md +1 -1
- package/docs/selection-trace.md +60 -0
- package/docs/semantic-layers.md +72 -0
- package/docs/snapshots.md +1 -1
- package/docs/visual-layers.md +1 -1
- package/docs/world-graph.md +39 -0
- package/docs/world-patch.md +61 -0
- package/package.json +3 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Production scenario
|
|
2
|
+
|
|
3
|
+
End-to-end production composition harness. One scenario definition mounts the real host adapter, reducer, runtime group, production carts, DOM layers, compositor, visual/semantic layers, bindings, sequences, asset path, and headless audio. Isolated effect tests are not a substitute: omitting a required cart or compositor layer fails closed.
|
|
4
|
+
|
|
5
|
+
Related: [Browser harness](browser-harness.md), [Compositor](compositor.md), [Visual layers](visual-layers.md), [Semantic layers](semantic-layers.md), [Presentation sequences](presentation-sequences.md), [Presentation bindings](presentation-bindings.md), [Audio](audio.md), [Replay inspector](replay-inspector.md), [Snapshots](snapshots.md), [Runtime group](runtime-group.md), [Asset resolver](asset-resolver.md). 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-82-production-scenario.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `defineProductionScenario` / `createProductionScenarioRunner` from `@cyberart-io/engine` | Prove the shipped composition, not a second runtime. Persist `save()` envelopes in hostState. |
|
|
18
|
+
| Vitest / jsdom | same, or `@cyberart-io/engine/headless` | `installHeadlessCanvas()` first. CI and local must pass the **same** scenario definition object. |
|
|
19
|
+
|
|
20
|
+
This module composes existing harnesses. It does not reimplement Canvas2D, routing, or playback. Audio is the headless adapter: invocation and provenance only, never physical speakers.
|
|
21
|
+
|
|
22
|
+
Generic fixture ids only: `whistle`, `ripple`, `room`, `npc`.
|
|
23
|
+
|
|
24
|
+
## Contract
|
|
25
|
+
|
|
26
|
+
1. **Definition.** `schemaVersion` `1`, stable `id`, `seed`, `required.participants`, `required.layers`. JSON only. Callbacks and incomplete steps (a `click` without `selector` or `x`/`y`, a `key` without `key`) fail closed at define time. Invalid sequence or binding manifests throw when the runner is created, never silently dropped.
|
|
27
|
+
2. **Required composition.** After mount, every required participant and compositor layer must be present. Omitting the cart under test is a failure, not a skipped assertion. Missing participant localizes to `routing`; missing layer localizes to `pixels`.
|
|
28
|
+
3. **Input.** Pointer and keyboard go through the browser harness (DOM coordinates and a11y ids), then the host reducer, then the group router.
|
|
29
|
+
4. **Observation.** Evidence records reducer accept/reject, routed envelopes, selected bindings, cue sequences, composed pixels, captions, audio invocation, focus, and cleanup.
|
|
30
|
+
5. **Save / reload / replay.** `save()` writes a snapshot envelope. `reload` restores host + presentation sidecars. `replay` re-runs the inspector tape on a fresh composition and compares traces.
|
|
31
|
+
6. **Matrix.** Desktop/mobile viewport, DPR, reduced-motion, mute, failed-asset ids, and audio fallback are fields on the same definition. Not every cell needs a unique golden.
|
|
32
|
+
7. **Evidence bundle.** `ProductionScenarioEvidenceBundle` carries pixel/semantic/a11y/trace/snapshot/asset hashes, revisions, traces, screenshot metadata, and `firstBrokenBoundary` localization: input → reducer → routing → binding → cue → pixels/caption/audio → save-replay. The optional `selectionTrace` field holds a [selection trace](selection-trace.md) export (`attachSelectionTrace(bundle, exported)`); the runner never populates it itself.
|
|
33
|
+
8. **CI = local.** `run(steps)` executes the definition’s steps (or a shared step list). Do not fork scenario JSON for CI.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {
|
|
37
|
+
createProductionScenarioRunner,
|
|
38
|
+
defineProductionScenario,
|
|
39
|
+
} from '@cyberart-io/engine';
|
|
40
|
+
import { installHeadlessCanvas } from '@cyberart-io/engine/headless';
|
|
41
|
+
|
|
42
|
+
installHeadlessCanvas();
|
|
43
|
+
|
|
44
|
+
const defined = defineProductionScenario({
|
|
45
|
+
id: 'room-whistle',
|
|
46
|
+
schemaVersion: 1,
|
|
47
|
+
seed: `0x${'82'.repeat(32)}`,
|
|
48
|
+
required: { participants: ['room', 'ripple'], layers: ['room', 'ripple'] },
|
|
49
|
+
matrix: { viewport: 'desktop', dpr: 1 },
|
|
50
|
+
steps: [
|
|
51
|
+
{ type: 'click', selector: '#whistle' },
|
|
52
|
+
{ type: 'step', frames: 2 },
|
|
53
|
+
{ type: 'save' },
|
|
54
|
+
{ type: 'replay' },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
if (!defined.ok) throw new Error(defined.errors.map((e) => e.detail).join('; '));
|
|
58
|
+
|
|
59
|
+
const runner = createProductionScenarioRunner({
|
|
60
|
+
scenario: defined.scenario,
|
|
61
|
+
participants,
|
|
62
|
+
compositorLayers,
|
|
63
|
+
visualLayers,
|
|
64
|
+
semanticRegions,
|
|
65
|
+
sequences,
|
|
66
|
+
bindings,
|
|
67
|
+
geometry,
|
|
68
|
+
host,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const evidence = await runner.run();
|
|
72
|
+
runner.destroy();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
| Event | When |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `production.scenario.state.observed` | Evidence captured with no broken boundary |
|
|
78
|
+
| `production.scenario.state.failed` | Required composition missing, or an expectation failed |
|
|
79
|
+
| `production.scenario.diagnostic.lifecycle` | Machine-readable rejection detail (`code`, `boundary`); emitted alongside every `state.failed` |
|
|
80
|
+
|
|
81
|
+
Contracts: `productionScenarioEventContracts()`.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Remote cart manifests
|
|
2
|
+
|
|
3
|
+
Signed, versioned cart catalogs for third-party worlds. HMAC signatures, declared asset hashes, and deny-by-default host grants. Isolation is in-process (trusted factories), not `eval` / `Function`.
|
|
4
|
+
|
|
5
|
+
Related: [capability manifest](capability-manifest.md) (optional nested `capabilities` + `grants`), [asset resolver](asset-resolver.md) (`createDeclaredAssetResolver`), [executable modules](executable-modules.md), [snapshots](snapshots.md) (`provenance`). Built on this: [content revision](content-revision.md) (`createRemoteContentAdapter` verifies the same signed JSON), [portal lifecycle](portal-lifecycle.md) (`HostGrantSet` transfer to the child cart). 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-74-remote-cart-manifest.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Load path
|
|
14
|
+
|
|
15
|
+
1. Parse a signed JSON envelope `{ body, signature }`.
|
|
16
|
+
2. Verify `hmac-sha256` over the canonical body (sorted keys) with a host key id.
|
|
17
|
+
3. Check `cartVersion` when the host asked for an exact version.
|
|
18
|
+
4. List `requestedGrants` (`audio`, `controller`, `network`, `persistence`, `fullscreen`).
|
|
19
|
+
5. Host applies a subset via `createHostGrantSet` (inspectable, revocable, restore fail-closed).
|
|
20
|
+
6. `createDeclaredAssetResolver` may fetch **only** declared URLs; hash mismatch or missing bytes refuse load.
|
|
21
|
+
7. Capability bag APIs throw `RemoteCartCapabilityError` (`capability-denied`) when ungranted. Granted `network.fetch` still cannot reach undeclared URLs, and returns a copy of signed bytes.
|
|
22
|
+
8. `createRemoteCartSandbox` takes the same `keys` map and re-verifies HMAC on `loadAssets`. Nested `capabilities.grants` are checked against the **host** grant set, not the cart's own list.
|
|
23
|
+
|
|
24
|
+
Tests inject a registry map or `fetch` adapter. There is no ambient network.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import {
|
|
28
|
+
createRemoteCartRegistry,
|
|
29
|
+
createRemoteCartSandbox,
|
|
30
|
+
loadRemoteCart,
|
|
31
|
+
signRemoteCartManifest,
|
|
32
|
+
} from '@cyberart-io/engine';
|
|
33
|
+
|
|
34
|
+
const signed = await signRemoteCartManifest(body, secret, 'cyb-74-test');
|
|
35
|
+
if (!signed.ok) throw new Error(signed.errors.map((e) => e.detail).join('; '));
|
|
36
|
+
|
|
37
|
+
const loaded = await loadRemoteCart({
|
|
38
|
+
source: { kind: 'registry', id: 'cyb-74.remote' },
|
|
39
|
+
keys: { 'cyb-74-test': secret },
|
|
40
|
+
registry: createRemoteCartRegistry({ 'cyb-74.remote': signed.manifest }),
|
|
41
|
+
expectedVersion: '1.0.0',
|
|
42
|
+
});
|
|
43
|
+
if (!loaded.ok) throw new Error(loaded.errors.map((e) => e.detail).join('; '));
|
|
44
|
+
|
|
45
|
+
const sandbox = createRemoteCartSandbox({
|
|
46
|
+
manifest: loaded.manifest,
|
|
47
|
+
keys: { 'cyb-74-test': secret },
|
|
48
|
+
grants: ['audio'],
|
|
49
|
+
bytesByUrl: { 'https://registry.test/cyb-74/sprite.bin': bytes },
|
|
50
|
+
});
|
|
51
|
+
sandbox.listRequestedGrants(); // before run
|
|
52
|
+
await sandbox.loadAssets(); // re-verifies HMAC; forged envelopes refuse here
|
|
53
|
+
sandbox.inspect(); // publisher, version, signature id/alg, grant set
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`snapshotProvenance()` fills envelope `provenance` (`publisher`, `version`, `signature`, `grants`) for `defineSnapshot`.
|
|
57
|
+
|
|
58
|
+
| Diagnostic `code` | When |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `invalid-json` / `invalid-manifest` | Envelope is not parseable JSON or the wrong shape. |
|
|
61
|
+
| `invalid-version` | Body version or expected `cartVersion` mismatch. |
|
|
62
|
+
| `invalid-signature` / `unknown-key` | HMAC missing, wrong, or key id unknown. |
|
|
63
|
+
| `undeclared-asset` | Resolver or granted `network.fetch` used a URL not on the manifest. |
|
|
64
|
+
| `hash-mismatch` / `unsigned-bytes` | Declared hash does not match bytes, or bytes/hash missing. |
|
|
65
|
+
| `capability-denied` | Cart called an API the host did not grant. |
|
|
66
|
+
| `destroyed` | Sandbox was torn down. |
|
package/docs/replay-inspector.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Embeddable debug API for multi-cart routing. It records router decisions, builds a causation tree, redacts configured payload keys, bounds retention, and exports a JSON tape that the headless harness can replay. There is no Player UI.
|
|
4
4
|
|
|
5
|
-
Related: [events](events.md), [runtime group](runtime-group.md), [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md).
|
|
5
|
+
Related: [events](events.md), [runtime group](runtime-group.md), [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md). Built on this: [selection trace](selection-trace.md) (reuses correlation ids, `DEFAULT_SENSITIVE_KEYS` / `REDACTED_VALUE`), [production scenario](production-scenario.md) (inspector records in the evidence bundle), [job orchestration](job-orchestration.md) and [world patch](world-patch.md) (redacted trace export).
|
|
6
6
|
|
|
7
7
|
Back to the [package README](../README.md).
|
|
8
8
|
|
package/docs/runtime-group.md
CHANGED
|
@@ -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), [calculation carts](calculation-carts.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). Built on this: [portal lifecycle](portal-lifecycle.md) (per-participant suspend while a child cart is entered), [production scenario](production-scenario.md) (required participants are checked against this group).
|
|
8
8
|
|
|
9
9
|
Back to the [package README](../README.md).
|
|
10
10
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Selection trace
|
|
2
|
+
|
|
3
|
+
Content-selection and presentation-decision diagnostics. Hosts record which projection revision, binding, content hash, sequence step, layer, caption, and fallback actually won. The same query/export API is browser-safe (`@cyberart-io/engine`) and headless. There is no Player UI.
|
|
4
|
+
|
|
5
|
+
Related: [Replay inspector](replay-inspector.md) (CYB-65 causation ids, `DEFAULT_SENSITIVE_KEYS` / `REDACTED_VALUE`, bounded export), [Production scenario](production-scenario.md) (CYB-82 evidence bundle `selectionTrace` field), [Content revision](content-revision.md) (CYB-79), [Presentation bindings](presentation-bindings.md) (CYB-83), [Presentation sequences](presentation-sequences.md) (CYB-78). 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-85-selection-trace.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `createSelectionTrace` from `@cyberart-io/engine` | Call `recordSelectionDecision` as bindings, revisions, assets, and sequences resolve. Export JSON. |
|
|
18
|
+
| Vitest / jsdom | same, or `@cyberart-io/engine/headless` | Query/export/import the same records. `attachSelectionTrace` hangs the export on a CYB-82 evidence bundle. |
|
|
19
|
+
|
|
20
|
+
Compose with `createReplayInspector` — import it, do not replace it. Link records with `correlationId` / `causationId` / `envelopeId`.
|
|
21
|
+
|
|
22
|
+
Generic fixture ids only: `whistle`, `ripple`, `room`, `npc`, revision `8` vs `9`.
|
|
23
|
+
|
|
24
|
+
## Contract
|
|
25
|
+
|
|
26
|
+
1. **Decision record.** One JSON object covers state-projection revision, each binding predicate/priority/winner, requested vs activated content identity (revision, manifest version, publisher/source, hashes), staging/cache/supersession/rejection/rollback/last-known-good, resolved asset ref/hash/readiness/fallback, sequence invocation/step/track/interrupt/skip/replay, layer/region/caption/audio intent, and snapshot/replay provenance.
|
|
27
|
+
2. **Reason codes.** `binding-conflict`, `asset-failure`, `revision-rollback`, `sequence-interruption`, and `text-only-fallback` are distinct. Hosts may add more strings.
|
|
28
|
+
3. **Correlation.** Records carry `correlationId` / `causationId` / `envelopeId` plus `initiatingEventType`. Export includes matching CYB-65 inspector records when an inspector is attached or imported.
|
|
29
|
+
4. **Redaction.** Credentials, private prompts, signed URLs, and non-whitelisted host-projection fields become `REDACTED_VALUE`. Hosts whitelist safe projection keys; an empty whitelist redacts every projection field. Array projections are whitelisted per element; a bare primitive projection is redacted. Signed URLs pasted into prose (`summary`, `staging.detail`, `rejectedReason`) are replaced in place.
|
|
30
|
+
5. **Bounds.** `maxRecords` (default 512) drops oldest rows. `sampleRate` may drop noise; the five distinct failure codes always record.
|
|
31
|
+
6. **Export / import.** JSON-serializable. Import restores records, inspector linkage, and snapshot revision/seed. Same query API in browser and headless.
|
|
32
|
+
7. **Evidence.** `attachSelectionTrace(bundle, export)` sets `ProductionScenarioEvidenceBundle.selectionTrace`. The scenario runner is unchanged.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import {
|
|
36
|
+
attachSelectionTrace,
|
|
37
|
+
createReplayInspector,
|
|
38
|
+
createSelectionTrace,
|
|
39
|
+
} from '@cyberart-io/engine';
|
|
40
|
+
|
|
41
|
+
const inspector = createReplayInspector();
|
|
42
|
+
const trace = createSelectionTrace({
|
|
43
|
+
inspector,
|
|
44
|
+
projectionWhitelist: ['inventory', 'roomId', 'lineId'],
|
|
45
|
+
});
|
|
46
|
+
trace.recordSelectionDecision({
|
|
47
|
+
kind: 'asset',
|
|
48
|
+
reason: 'text-only-fallback',
|
|
49
|
+
summary: 'npc audio not ready; caption selected',
|
|
50
|
+
correlationId: envelope.correlationId,
|
|
51
|
+
envelopeId: envelope.id,
|
|
52
|
+
initiatingEventType: 'host.state.accepted',
|
|
53
|
+
asset: { logicalRef: 'npc.line.audio', ready: false, fallback: 'npc.line.caption' },
|
|
54
|
+
});
|
|
55
|
+
trace.setSnapshotProvenance({ revision: 8, schemaVersion: 2, seed });
|
|
56
|
+
const exported = trace.exportTrace({ correlationId: envelope.correlationId });
|
|
57
|
+
const evidence = attachSelectionTrace(bundle, exported);
|
|
58
|
+
inspector.destroy();
|
|
59
|
+
trace.destroy();
|
|
60
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Semantic layers
|
|
2
|
+
|
|
3
|
+
Named visual regions published by the cart: mask, polygon, and/or depth in the shared [normalized](normalized-geometry.md) coordinate system. The host drives hover / focus / selected and supplies accessibility names and roles. Geometry, blend, and hit-test policy stay with the cart.
|
|
4
|
+
|
|
5
|
+
Related: [visual layers](visual-layers.md), [compositor](compositor.md), [normalized geometry](normalized-geometry.md), [browser harness](browser-harness.md). Built on this: [presentation sequences](presentation-sequences.md) (focus track), [presentation bindings](presentation-bindings.md) (`setRegionState`, hit-test policy via `restore`), [production scenario](production-scenario.md) (semantic hash in the evidence bundle). 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-73-semantic-layers.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `createSemanticLayerController` from `@cyberart-io/engine` | Enumerate published regions, `setRegionState`, `setRegionA11y`, `hitTest`. |
|
|
18
|
+
| Vitest / jsdom | same, after `installHeadlessCanvas()` | `inspectPublished()` — no DOM overlay. |
|
|
19
|
+
| Browser harness | `semanticLayers` on `createBrowserHarness` | A11y snapshot lists published ids / names / roles. |
|
|
20
|
+
|
|
21
|
+
Generic ids only: `cabinet`, `plaque`, `stage`. The cart authors geometry; the host owns the a11y tree and pointer focus.
|
|
22
|
+
|
|
23
|
+
## Hit-test policies
|
|
24
|
+
|
|
25
|
+
| Policy | Behavior |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `pass-through` | Published for SAM / a11y, never wins `hitTest`. |
|
|
28
|
+
| `absorb` | First absorbing hit (after exclusive/depth sort) claims the pointer. |
|
|
29
|
+
| `exclusive` | Exclusive hits beat non-exclusive overlapping regions. |
|
|
30
|
+
| `depth-ordered` | Among remaining hits, higher `geometry.depth` wins, then `order`. |
|
|
31
|
+
|
|
32
|
+
## `createSemanticLayerController(options)`
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { createSemanticLayerController } from '@cyberart-io/engine';
|
|
36
|
+
|
|
37
|
+
const regions = createSemanticLayerController({
|
|
38
|
+
compositor,
|
|
39
|
+
visualLayers,
|
|
40
|
+
regions: [
|
|
41
|
+
{
|
|
42
|
+
id: 'cabinet',
|
|
43
|
+
hitTest: 'absorb',
|
|
44
|
+
blend: 'source-over',
|
|
45
|
+
compositorLayerId: 'hotspot',
|
|
46
|
+
visualLayerId: 'hotspot',
|
|
47
|
+
geometry: {
|
|
48
|
+
polygon: [
|
|
49
|
+
{ x: 0.2, y: 0.2 },
|
|
50
|
+
{ x: 0.5, y: 0.2 },
|
|
51
|
+
{ x: 0.5, y: 0.5 },
|
|
52
|
+
{ x: 0.2, y: 0.5 },
|
|
53
|
+
],
|
|
54
|
+
depth: 0.4,
|
|
55
|
+
},
|
|
56
|
+
visuals: {
|
|
57
|
+
idle: { version: 'idle', blend: 'source-over' },
|
|
58
|
+
hover: { version: 'hover', blend: 'screen' },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
regions.setRegionA11y('cabinet', { name: 'Arcade cabinet', role: 'button' });
|
|
65
|
+
regions.setRegionState('cabinet', { hover: true });
|
|
66
|
+
const hit = regions.hitTest({ x: 0.3, y: 0.3 });
|
|
67
|
+
const sam = regions.inspectPublished();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`inspectPublished()` returns `{ id, geometryKinds, a11y }` so a Segment Anything–style client can label cart-authored regions instead of reverse-engineering pixels. `geometryKinds` lists `mask`, `polygon`, `rect`, and/or `depth` as declared — a box is `rect`, not a full-frame depth field.
|
|
71
|
+
|
|
72
|
+
Hosts persist `regions.snapshot()` JSON inside envelope `hostState`. Restore validates the full region set (ids, geometry, mask, blend, hit-test policy) before mutating. Tampered or unknown-id JSON returns `{ ok: false }` and leaves live state unchanged. Unknown visual-layer versions fail restore or throw from `setRegionState`; a missing compositor layer is skipped until the host creates it.
|
package/docs/snapshots.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
-
Related: [deterministic mode](deterministic-mode.md) (`rng` / clock), [capability manifest](capability-manifest.md) (cart id). Back to the [package README](../README.md).
|
|
5
|
+
Related: [deterministic mode](deterministic-mode.md) (`rng` / clock), [capability manifest](capability-manifest.md) (cart id). Modules that persist their own `snapshot()` JSON inside envelope `hostState` and validate before restore: [semantic layers](semantic-layers.md), [presentation bindings](presentation-bindings.md), [portal lifecycle](portal-lifecycle.md), [job orchestration](job-orchestration.md), [world graph](world-graph.md), [world patch](world-patch.md), [content revision](content-revision.md). Envelope `provenance` comes from [remote cart manifests](remote-cart-manifest.md). Back to the [package README](../README.md).
|
|
6
6
|
|
|
7
7
|
## One-command reproduce (this repo)
|
|
8
8
|
|
package/docs/visual-layers.md
CHANGED
|
@@ -4,7 +4,7 @@ Versioned overlay / mask / sprite declarations with deterministic show, hide, re
|
|
|
4
4
|
|
|
5
5
|
Hosts persist `controller.snapshot()` JSON inside envelope `hostState`. This module does not edit the snapshot envelope.
|
|
6
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).
|
|
7
|
+
Related: [presentation cue](presentation-cue.md), [compositor](compositor.md), [asset resolver](asset-resolver.md), [snapshots](snapshots.md). Built on this: [semantic layers](semantic-layers.md) (named regions over these layers), [presentation sequences](presentation-sequences.md) (visual track), [presentation bindings](presentation-bindings.md) (`visual-layer` targets, one accepted revision is one layer set). Back to the [package README](../README.md).
|
|
8
8
|
|
|
9
9
|
## One-command reproduce (this repo)
|
|
10
10
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# World graph
|
|
2
|
+
|
|
3
|
+
Optional, headless-capable spatial graph for rooms, regions, frontiers, discovery, and entity transit. Hosts supply cost/policy functions and semantic time. The module does not render maps, run an economy, or encode combat, quest, or narrative rules.
|
|
4
|
+
|
|
5
|
+
Related: [Events and router](events.md) (CYB-59 contracts), [Snapshots](snapshots.md), [Job orchestration](job-orchestration.md) (generated frontier *work*, not graph identity). Built on this: [World patch](world-patch.md) (optional graph composition; a patch's node/edge ops are applied here atomically with the world revision). 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-81-world-graph.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `createWorldGraph` from `@cyberart-io/engine` | Persist `snapshot()` in envelope `hostState`. Call `findPath` / `reachable` with host policy. |
|
|
18
|
+
| Vitest / jsdom | same, or `@cyberart-io/engine/headless` | Same API. `inspect()` is enough for calculation carts. Projections are data, not pixels. |
|
|
19
|
+
|
|
20
|
+
## Contract
|
|
21
|
+
|
|
22
|
+
1. **Stable IDs.** Nodes and edges keep host-authored ids and integer `version`s. Add/remove does not rewrite unrelated identities.
|
|
23
|
+
2. **Canonical vs known.** `projectCanonical()` is the full graph. `projectKnown(observerId)` is only discovered nodes/edges plus unresolved frontiers adjacent to known edges. `visibility: 'canonical'` edges are always known.
|
|
24
|
+
3. **Access.** Edges are `open`, `locked`, or `disabled`. Lock requirements are opaque JSON for the host policy. Traversal never inspects real time.
|
|
25
|
+
4. **Semantic time.** `departedAt`, `expectedArrival`, and query `semanticTime` are host numbers. Completing transit is an explicit `completeTransit` call.
|
|
26
|
+
5. **Patches.** `applyPatch` adds nodes/edges atomically; failure rolls back. Generated Room Eight is a patch, not a rename.
|
|
27
|
+
6. **Restore** validates JSON, schema version, and referential integrity before mutate. Tampered snapshots return `{ ok: false }` and leave the graph unchanged. Pass `migrations` (`SnapshotMigration`, n → n+1) to lift older snapshots.
|
|
28
|
+
7. **Events.** Typed `world.graph.state.*` / `world.graph.diagnostic.*` contracts from `worldGraphEventContracts()`.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { createWorldGraph } from '@cyberart-io/engine/headless';
|
|
32
|
+
|
|
33
|
+
const graph = createWorldGraph({ graphId: 'adventure' });
|
|
34
|
+
graph.applyPatch({ nodes, edges });
|
|
35
|
+
graph.discover('walker', { nodeIds: ['lodge', 'room-1'], edgeIds: ['lodge-room-1'] });
|
|
36
|
+
const minimap = graph.projectLocal('walker', 'room-1');
|
|
37
|
+
const bigMap = graph.projectRegional('walker');
|
|
38
|
+
const path = graph.findPath('lodge', 'station', hostPolicy, { semanticTime: day });
|
|
39
|
+
```
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# World patch
|
|
2
|
+
|
|
3
|
+
Transactional validation and atomic application of a generated world patch. Rooms, exits, characters, assets, dialogue, graph edges, and presentation bindings become one world revision or none. This is not a generic database transaction and does not make Cyberart the host's database of record.
|
|
4
|
+
|
|
5
|
+
Related: [Job orchestration](job-orchestration.md) (typed job result, host-owned apply), [World graph](world-graph.md) (optional `applyPatch` composition), [Content revision](content-revision.md) and [Presentation bindings](presentation-bindings.md) (availability checks, not activation), [Replay inspector](replay-inspector.md) (`DEFAULT_SENSITIVE_KEYS` / `REDACTED_VALUE`), [Snapshots](snapshots.md). 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-84-world-patch.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `createWorldPatchApplier` from `@cyberart-io/engine` | Inject a persistence adapter. Persist `snapshot()` in envelope `hostState`. Call `commit` only under host policy. |
|
|
18
|
+
| Vitest / jsdom | same, or `@cyberart-io/engine/headless` | `createMemoryWorldPersistence` is enough. Tests may supply a copy-on-write host adapter. |
|
|
19
|
+
|
|
20
|
+
The host owns domain policy, authorization, persistence, and when a ready patch may become canon. Job completion time does not leak into canonical game time: `dryRun` a validated patch and `commit` later.
|
|
21
|
+
|
|
22
|
+
## Contract
|
|
23
|
+
|
|
24
|
+
1. **Envelope.** `patchId`, `schemaVersion`, `baseRevision`, `idempotencyKey`, typed operations, producer/job provenance. Unknown fields and unknown ops fail closed.
|
|
25
|
+
2. **Dry-run.** `dryRun(patch)` validates schema, referential integrity, preconditions, cycles, asset/content availability, binding targets, and host domain validators. `inspect()` is unchanged.
|
|
26
|
+
3. **Atomic commit.** `commit` dry-runs, then swaps one complete world revision. Observers never see a half-applied multi-entity patch.
|
|
27
|
+
4. **Stale base.** A patch whose `baseRevision` is not the current revision is rejected with `stale-base` and structured expected/actual revisions. There is no silent rebase.
|
|
28
|
+
5. **Idempotency.** Re-submitting an accepted `patchId` returns the same revision and does not emit again. A new patch may `supersedes` an accepted id.
|
|
29
|
+
6. **Persistence.** Hosts inject `begin` / `applyWorldRevision` / `commit` / `rollback`, plus optional `publish`. `createMemoryWorldPersistence` is the in-memory reference; `rollback` restores the `begin()` snapshot. If post-commit publication throws, the previous revision is restored via `persistWorld`, not rollback-after-commit.
|
|
30
|
+
7. **Ordering.** Operations sort by explicit `order`, then `(op, kind, id)` — never `Object.keys` order.
|
|
31
|
+
8. **Events.** Exactly one `world.patch.state.accepted` per newly accepted revision: patch id, old/new revision, changed identities, producer/content provenance. Rejected and superseded patches are audited.
|
|
32
|
+
9. **Restore.** `restore` validates snapshot schema before mutate. It reloads the accepted world revision and, when present, the optional sidecar `graph` snapshot; it does not rerun the producing job. Failed graph restore reverts the world to the pre-restore revision.
|
|
33
|
+
10. **Limits.** Operation, entity, byte, and diagnostic counts are bounded.
|
|
34
|
+
11. **Graph composition.** Optional `WorldGraph` is applied by restoring a computed snapshot: `add` sets missing nodes/edges, `replace`/`revise` overwrite, `link` overwrites edges (including `from`/`to`), and `remove`/`tombstone`/`unlink` delete. Failure is `graph-apply-failed` and leaves canon unchanged.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import {
|
|
38
|
+
createMemoryWorldPersistence,
|
|
39
|
+
createWorldPatchApplier,
|
|
40
|
+
} from '@cyberart-io/engine';
|
|
41
|
+
|
|
42
|
+
const world = createWorldPatchApplier({
|
|
43
|
+
persistence: createMemoryWorldPersistence(),
|
|
44
|
+
content: { available: [{ revision: '8' }] },
|
|
45
|
+
bindings: { listedIds: ['lobby-room'] },
|
|
46
|
+
now: () => hostClock,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
world.dryRun(patch); // diagnostics only
|
|
50
|
+
world.commit(patch); // one revision, or the previous world unchanged
|
|
51
|
+
world.snapshot(); // exact revision for hostState, plus optional graph
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Event | When |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `world.patch.state.accepted` | Atomic commit of one complete revision: patch id, old/new revision, producer, jobId, contentRevisions |
|
|
57
|
+
| `world.patch.state.rejected` | Validation, stale-base, graph, or publish failure; canon unchanged |
|
|
58
|
+
| `world.patch.state.superseded` | An accepted patch id was replaced |
|
|
59
|
+
| `world.patch.diagnostic.lifecycle` | Machine-readable rejection detail |
|
|
60
|
+
|
|
61
|
+
Contracts: `worldPatchEventContracts()`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyberart-io/engine",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
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",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"dist/index.d.ts",
|
|
10
10
|
"dist/headless.js",
|
|
11
11
|
"dist/headless.d.ts",
|
|
12
|
-
"docs"
|
|
12
|
+
"docs",
|
|
13
|
+
"CHANGELOG.md"
|
|
13
14
|
],
|
|
14
15
|
"main": "./dist/index.js",
|
|
15
16
|
"module": "./dist/index.js",
|