@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 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`, jsdom canvas, inspect / screenshot
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('adventure.intent.exit-requested', {
291
+ const exit = defineIntent('host.intent.exit-requested', {
286
292
  version: 1,
287
293
  fields: { exitId: { type: 'string' } },
288
294
  });
289
- const room = defineStateEvent('adventure.state.room-changed', {
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 (checkmarks, ripples, room fades). No `setTimeout` / rAF. Drive `step` from the same clock as deterministic `cart.step`.
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: 'Joiner Brook', regions: [] },
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: 'room-bg', ref: 'moltazine:post/porch-1#primary-image', type: 'image' as const },
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/` (event / deterministic / harness / presentation-adapter / asset-resolver / presentation-cue API), and `package.json`. Site code that still imports `src/ui/lib/...` hits thin re-export shims so those paths keep working.
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