@cyberart-io/engine 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -12
- package/dist/headless.d.ts +602 -4
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +835 -7
- package/dist/index.js +1 -1
- package/docs/asset-resolver.md +4 -4
- package/docs/browser-harness.md +126 -0
- package/docs/capability-manifest.md +95 -0
- package/docs/compositor.md +103 -0
- package/docs/events.md +27 -22
- package/docs/headless-harness.md +46 -16
- package/docs/normalized-geometry.md +88 -0
- package/docs/presentation-adapter.md +10 -10
- package/docs/presentation-cue.md +1 -1
- package/docs/replay-inspector.md +88 -0
- package/docs/runtime-group.md +122 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -89,10 +89,16 @@ Full API for the event router, deterministic replay, and CI harness (so agents c
|
|
|
89
89
|
|
|
90
90
|
- [Events and router](docs/events.md) — mailbox, envelope, typed contracts, `createEventRouter`, hops, idempotency, rejections
|
|
91
91
|
- [Deterministic mode](docs/deterministic-mode.md) — `step` / `schedule`, clocks, `ScriptedAction`, replay diffs
|
|
92
|
-
- [Headless harness](docs/headless-harness.md) — `createHeadlessHarness` from `@cyberart-io/engine/headless`,
|
|
92
|
+
- [Headless harness](docs/headless-harness.md) — `createHeadlessHarness` from `@cyberart-io/engine/headless`, software Canvas2D, `compareImageData` / `assertPixelsEqual`, inspect / screenshot
|
|
93
93
|
- [Presentation adapter](docs/presentation-adapter.md) — host-owned render model, intents, loading / error / unsupported
|
|
94
94
|
- [Asset resolver](docs/asset-resolver.md) — host-pluggable images/audio/fonts/spritesheets, cache, preload, fallbacks
|
|
95
95
|
- [Presentation cue](docs/presentation-cue.md) — deterministic `createPresentationTimeline`, duplicate policy, reduced-motion, lifecycle events
|
|
96
|
+
- [Capability manifest](docs/capability-manifest.md) — versioned JSON for runtime features, phases, managers, assets, events, permissions, integrations
|
|
97
|
+
- [Normalized geometry](docs/normalized-geometry.md) — coordinate spaces, contain/cover/crop layout, landmarks, hit regions, debug overlay
|
|
98
|
+
- [Runtime group](docs/runtime-group.md) — `createRuntimeGroup`, shared router attach, lockstep clock; `createHeadlessMultiCartHarness` from `@cyberart-io/engine/headless`
|
|
99
|
+
- [Compositor](docs/compositor.md) — `createCompositor`, transparent stacking, `screen` blend, `writeComposedFrame`
|
|
100
|
+
- [Browser harness](docs/browser-harness.md) — `createBrowserHarness`, DOM clicks, viewport/DPR, composed screenshots
|
|
101
|
+
- [Replay inspector](docs/replay-inspector.md) — `createReplayInspector`, causation trees, redacted export/import, headless replay
|
|
96
102
|
|
|
97
103
|
## Write a cart
|
|
98
104
|
|
|
@@ -170,7 +176,7 @@ const cart = runtime.mount(artProject, {
|
|
|
170
176
|
|
|
171
177
|
`runtime.destroy()` tears down the runtime. `runtime.unlockAudio()` is for an early click before `mount`. `runtime.onError` receives frame errors (`phase`, `consecutive`, `stopped`).
|
|
172
178
|
|
|
173
|
-
One cart per runtime. A second `mount` unloads the first. Several pieces on a page means several `createRuntime()` calls.
|
|
179
|
+
One cart per runtime. A second `mount` unloads the first. Several pieces on a page means several `createRuntime()` calls, or one `createRuntimeGroup()` that attaches each mailbox to a shared router and locksteps a deterministic clock.
|
|
174
180
|
|
|
175
181
|
## Audio
|
|
176
182
|
|
|
@@ -239,11 +245,11 @@ const replay = await cart.getReplayMetadata();
|
|
|
239
245
|
CI and agents should drive the **same** `createRuntime({ deterministic })` path. Import the harness from **`@cyberart-io/engine/headless`** so browser production builds never walk `node:fs/promises`.
|
|
240
246
|
|
|
241
247
|
- Carts and production hosts: `import { createRuntime } from '@cyberart-io/engine'`
|
|
242
|
-
- Tests, CI, and frame capture: `import { createHeadlessHarness } from '@cyberart-io/engine/headless'`
|
|
248
|
+
- Tests, CI, and frame capture: `import { createHeadlessHarness, createHeadlessMultiCartHarness } from '@cyberart-io/engine/headless'`
|
|
243
249
|
|
|
244
|
-
`@cyberart-io/engine` does not re-export the harness. `installHeadlessCanvas` is the documented jsdom install (test-only). `createHeadlessHarness` sizes a container, mounts, and wraps step / input / inspect / snapshot.
|
|
250
|
+
`@cyberart-io/engine` does not re-export the harness. `installHeadlessCanvas` is the documented jsdom install (test-only). It attaches a deterministic software Canvas2D (paths, arcs, atlas text, transforms, clip, alpha, gradients, `drawImage`). Unsupported ops throw `HeadlessUnsupportedOperationError`. `createHeadlessHarness` sizes a container, mounts, and wraps step / input / inspect / snapshot. Pixel compare: `compareImageData`, `assertPixelsEqual`, `writeVisualArtifacts` (`CYBERART_UPDATE_GOLDEN=1` to refresh goldens).
|
|
245
251
|
|
|
246
|
-
Full options, `click` clock rule, Node-only `captureFrame`, remount, and the reproduce command: [headless harness](docs/headless-harness.md).
|
|
252
|
+
Full options, `click` clock rule, Node-only `captureFrame`, remount, visual asserts, and the reproduce command: [headless harness](docs/headless-harness.md).
|
|
247
253
|
|
|
248
254
|
```ts
|
|
249
255
|
import { createHeadlessHarness } from '@cyberart-io/engine/headless';
|
|
@@ -258,7 +264,7 @@ harness.destroy();
|
|
|
258
264
|
|
|
259
265
|
## Events
|
|
260
266
|
|
|
261
|
-
Default path: per-runtime **mailbox** (`dispatch` / `consume` / `emit`). Multi-cart hosts attach each `HostChannel` to `createEventRouter` — carts never see the router.
|
|
267
|
+
Default path: per-runtime **mailbox** (`dispatch` / `consume` / `emit`). Multi-cart hosts should prefer `createRuntimeGroup()` (it calls `router.attach` for each mailbox). You can still attach each `HostChannel` to `createEventRouter` yourself — carts never see the router.
|
|
262
268
|
|
|
263
269
|
```ts
|
|
264
270
|
const cart = runtime.mount(artProject, {
|
|
@@ -282,11 +288,11 @@ import {
|
|
|
282
288
|
createEventRouter,
|
|
283
289
|
} from '@cyberart-io/engine';
|
|
284
290
|
|
|
285
|
-
const exit = defineIntent('
|
|
291
|
+
const exit = defineIntent('host.intent.exit-requested', {
|
|
286
292
|
version: 1,
|
|
287
293
|
fields: { exitId: { type: 'string' } },
|
|
288
294
|
});
|
|
289
|
-
const room = defineStateEvent('
|
|
295
|
+
const room = defineStateEvent('host.state.room-changed', {
|
|
290
296
|
version: 1,
|
|
291
297
|
fields: { roomId: { type: 'string' } },
|
|
292
298
|
});
|
|
@@ -301,7 +307,7 @@ router.attach('presentation', runtime.hostChannel, deriveAttachOptions(contracts
|
|
|
301
307
|
|
|
302
308
|
## Presentation cue
|
|
303
309
|
|
|
304
|
-
Frame-stepped effects (
|
|
310
|
+
Frame-stepped effects (overlays, pulses, scene fades). No `setTimeout` / rAF. Drive `step` from the same clock as deterministic `cart.step`.
|
|
305
311
|
|
|
306
312
|
```ts
|
|
307
313
|
import { createPresentationTimeline } from '@cyberart-io/engine';
|
|
@@ -319,6 +325,84 @@ timeline.step(90);
|
|
|
319
325
|
|
|
320
326
|
Lifecycle names `cue.started` / `cue.completed` / `cue.cancelled` / `cue.replaced` are local to the timeline. They are not router envelopes unless you define a contract. Duplicate policy, late-play catch-up, and reduced-motion: [presentation cue](docs/presentation-cue.md).
|
|
321
327
|
|
|
328
|
+
## Capability manifest
|
|
329
|
+
|
|
330
|
+
JSON for what a cart needs from the host. `validateCapabilityManifest` returns structured diagnostics instead of throwing.
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
import {
|
|
334
|
+
defineCapabilityManifest,
|
|
335
|
+
validateCapabilityManifest,
|
|
336
|
+
} from '@cyberart-io/engine';
|
|
337
|
+
|
|
338
|
+
const defined = defineCapabilityManifest({
|
|
339
|
+
id: 'host.presentation',
|
|
340
|
+
runtime: { minContractVersion: 1, features: ['router'] },
|
|
341
|
+
phases: ['loading', 'ready', 'error', 'unsupported'],
|
|
342
|
+
managers: ['pointer', 'hostChannel'],
|
|
343
|
+
assets: { kinds: ['image'], declarations: [{ id: 'backdrop', kind: 'image' }] },
|
|
344
|
+
acceptedEvents: ['host.state.*'],
|
|
345
|
+
emittedEvents: ['host.intent.exit-requested'],
|
|
346
|
+
permissions: { emit: ['host.intent.*'], subscribe: ['host.state.*'] },
|
|
347
|
+
integrations: ['tone'],
|
|
348
|
+
});
|
|
349
|
+
if (!defined.ok) throw new Error(defined.errors.map((e) => e.detail).join('; '));
|
|
350
|
+
validateCapabilityManifest(defined.manifest, {
|
|
351
|
+
contractVersion: 1,
|
|
352
|
+
features: ['router'],
|
|
353
|
+
integrations: ['tone'],
|
|
354
|
+
emit: ['host.intent.*'],
|
|
355
|
+
subscribe: ['host.state.*'],
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Fields, host allowlists, and diagnostic codes: [capability manifest](docs/capability-manifest.md).
|
|
360
|
+
|
|
361
|
+
## Runtime group
|
|
362
|
+
|
|
363
|
+
Several production carts, one router, one lockstep clock. Use this instead of intercepting each `onEvent` and republishing by hand.
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
import { createRuntimeGroup } from '@cyberart-io/engine';
|
|
367
|
+
|
|
368
|
+
const group = createRuntimeGroup({
|
|
369
|
+
origin: 0,
|
|
370
|
+
participants: [
|
|
371
|
+
{ id: 'effects', cart: effectsCart, emit: ['ambience.intent.*'], subscribe: ['host.state.*'] },
|
|
372
|
+
{ id: 'ambience', cart: ambienceCart, subscribe: ['ambience.intent.*'] },
|
|
373
|
+
],
|
|
374
|
+
});
|
|
375
|
+
group.publish({ type: 'host.state.accepted', kind: 'state', payload: { id: 'north' } });
|
|
376
|
+
await group.step(2);
|
|
377
|
+
const { trace } = await group.inspect();
|
|
378
|
+
group.destroy();
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Headless / CI: `createHeadlessMultiCartHarness` from `@cyberart-io/engine/headless` is the same handle after `installHeadlessCanvas()`. Full options: [runtime group](docs/runtime-group.md). Causation trees, redaction, and tape replay: [replay inspector](docs/replay-inspector.md).
|
|
382
|
+
|
|
383
|
+
## Normalized geometry
|
|
384
|
+
|
|
385
|
+
Shared 0–1 content-box coordinates, landmarks, and hit regions. Pixel `PresentationRegion` on the presentation adapter is unchanged.
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
import {
|
|
389
|
+
createPresentationLayout,
|
|
390
|
+
pointerToRegion,
|
|
391
|
+
ROUND_TRIP_TOLERANCE,
|
|
392
|
+
} from '@cyberart-io/engine';
|
|
393
|
+
|
|
394
|
+
const layout = createPresentationLayout({
|
|
395
|
+
contentWidth: 1920,
|
|
396
|
+
contentHeight: 1080,
|
|
397
|
+
viewportWidth: 1280,
|
|
398
|
+
viewportHeight: 800,
|
|
399
|
+
mode: 'contain',
|
|
400
|
+
});
|
|
401
|
+
const hit = pointerToRegion({ x: canvasX, y: canvasY }, layout, doc);
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Spaces, contain/cover/crop, debug overlay, and the reproduce command: [normalized geometry](docs/normalized-geometry.md).
|
|
405
|
+
|
|
322
406
|
## Presentation adapter
|
|
323
407
|
|
|
324
408
|
Host-owned render model in, interaction intents out. Cyberart does not mutate the canonical world. `gameManager` is not this contract.
|
|
@@ -339,7 +423,7 @@ const adapter = attachPresentationAdapter(harness);
|
|
|
339
423
|
adapter.present({
|
|
340
424
|
contractVersion: 1,
|
|
341
425
|
phase: 'ready',
|
|
342
|
-
view: { title: '
|
|
426
|
+
view: { title: 'Overlook', regions: [] },
|
|
343
427
|
});
|
|
344
428
|
```
|
|
345
429
|
|
|
@@ -357,7 +441,7 @@ import {
|
|
|
357
441
|
} from '@cyberart-io/engine';
|
|
358
442
|
|
|
359
443
|
const roomAssets = [
|
|
360
|
-
{ id: '
|
|
444
|
+
{ id: 'backdrop', ref: 'moltazine:post/porch-1#primary-image', type: 'image' as const },
|
|
361
445
|
{
|
|
362
446
|
id: 'ambience',
|
|
363
447
|
ref: 'world:asset/stream-loop',
|
|
@@ -463,7 +547,7 @@ A canvas the host adopted is left in place on destroy; a canvas the engine creat
|
|
|
463
547
|
|
|
464
548
|
## Publishing this package (maintainers)
|
|
465
549
|
|
|
466
|
-
Not part of writing a cart. Engine source lives in `packages/engine/src/` (not mixed into the site). The npm tarball is built from `packages/engine/src/index.ts` and `packages/engine/src/headless.ts` and contains minified `dist/index.js` + `dist/headless.js`, rolled-up `.d.ts` for both, `LICENSE`, `README.md`, `docs/` (
|
|
550
|
+
Not part of writing a cart. Engine source lives in `packages/engine/src/` (not mixed into the site). The npm tarball is built from `packages/engine/src/index.ts` and `packages/engine/src/headless.ts` and contains minified `dist/index.js` + `dist/headless.js`, rolled-up `.d.ts` for both, `LICENSE`, `README.md`, `docs/` (including compositor, browser harness, and replay inspector), and `package.json`. Site code that still imports `src/ui/lib/...` hits thin re-export shims so those paths keep working.
|
|
467
551
|
|
|
468
552
|
```bash
|
|
469
553
|
pnpm run pack:engine
|