@cyberart-io/engine 0.0.2 → 0.0.3

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
@@ -24,7 +24,7 @@ This guide is the path from `npm i` to a piece on screen, then a short map of th
24
24
  npm i @cyberart-io/engine
25
25
  ```
26
26
 
27
- The package is minified JS plus a rolled-up `.d.ts`. It does not include animation carts. Tone.js is an optional peer — skip it until a cart declares `audio: 'tone'`.
27
+ The package is minified JS plus rolled-up `.d.ts` for two entrypoints: the browser runtime (`.`) and Node/jsdom helpers (`./headless`). It does not include animation carts. Tone.js is an optional peer — skip it until a cart declares `audio: 'tone'`.
28
28
 
29
29
  ## Quick start
30
30
 
@@ -87,9 +87,12 @@ window.addEventListener('pagehide', () => {
87
87
 
88
88
  Full API for the event router, deterministic replay, and CI harness (so agents can use every export):
89
89
 
90
- - [Events and router](docs/events.md) — mailbox, envelope, `createEventRouter`, hops, idempotency, rejections
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`, jsdom canvas, inspect / screenshot
92
+ - [Headless harness](docs/headless-harness.md) — `createHeadlessHarness` from `@cyberart-io/engine/headless`, jsdom canvas, inspect / screenshot
93
+ - [Presentation adapter](docs/presentation-adapter.md) — host-owned render model, intents, loading / error / unsupported
94
+ - [Asset resolver](docs/asset-resolver.md) — host-pluggable images/audio/fonts/spritesheets, cache, preload, fallbacks
95
+ - [Presentation cue](docs/presentation-cue.md) — deterministic `createPresentationTimeline`, duplicate policy, reduced-motion, lifecycle events
93
96
 
94
97
  ## Write a cart
95
98
 
@@ -130,6 +133,10 @@ const cart = runtime.mount(artProject, {
130
133
  });
131
134
  ```
132
135
 
136
+ `runtime.hostChannel` is the mailbox for this instance. Attach it to `createEventRouter` from the host; carts never receive the router.
137
+
138
+ `runtime.assets` is the preloader when `createRuntime({ assets: { resolver } })` was set. Logical refs and fallbacks: [asset resolver](docs/asset-resolver.md).
139
+
133
140
  `createRuntime` options:
134
141
 
135
142
  | Option | Default | Meaning |
@@ -139,6 +146,7 @@ const cart = runtime.mount(artProject, {
139
146
  | `captureKeyboard` | `false` | When true, the cart listens for `window` keydown. Full-page players pass `true`. Off in deterministic mode. |
140
147
  | `audio` | none | Libraries to unlock if `unlockAudio()` runs before `mount`. After mount, the cart’s `metadata.audio` wins. |
141
148
  | `deterministic` | off | Host-controlled clock, `step`/`advance`, and scripted input/assets. Leave unset for live kaleidoscope / Art Blocks. |
149
+ | `assets` | off | Host `AssetResolver` plus engine cache/preload. Carts keep logical refs. Leave unset when the piece has no media. |
142
150
 
143
151
  `CartHandle` (what `mount` returns):
144
152
 
@@ -228,12 +236,17 @@ const replay = await cart.getReplayMetadata();
228
236
 
229
237
  ## Headless harness
230
238
 
231
- CI and agents should drive the **same** `createRuntime({ deterministic })` path. `installHeadlessCanvas` is the documented jsdom install (test-only). `createHeadlessHarness` sizes a container, mounts, and wraps step / input / inspect / snapshot.
239
+ 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
+
241
+ - Carts and production hosts: `import { createRuntime } from '@cyberart-io/engine'`
242
+ - Tests, CI, and frame capture: `import { createHeadlessHarness } from '@cyberart-io/engine/headless'`
243
+
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.
232
245
 
233
246
  Full options, `click` clock rule, Node-only `captureFrame`, remount, and the reproduce command: [headless harness](docs/headless-harness.md).
234
247
 
235
248
  ```ts
236
- import { createHeadlessHarness } from '@cyberart-io/engine';
249
+ import { createHeadlessHarness } from '@cyberart-io/engine/headless';
237
250
 
238
251
  const harness = createHeadlessHarness({ cart: artProject, seed: 42 });
239
252
  await harness.step(5);
@@ -258,7 +271,124 @@ const cart = runtime.mount(artProject, {
258
271
  cart.dispatch({ type: 'art-project.theme', payload: 'dusk' });
259
272
  ```
260
273
 
261
- Envelope, `attach` / `detach` / `publish` / `turn`, hops, idempotency, budgets, and rejection reasons: [events and router](docs/events.md).
274
+ Envelope, contracts (`defineIntent` / `defineStateEvent` / `defineDiagnostic`), `attach` / `detach` / `publish` / `turn`, hops, idempotency, budgets, and rejection reasons: [events and router](docs/events.md).
275
+
276
+ ```ts
277
+ import {
278
+ defineIntent,
279
+ defineStateEvent,
280
+ createContractRegistry,
281
+ deriveAttachOptions,
282
+ createEventRouter,
283
+ } from '@cyberart-io/engine';
284
+
285
+ const exit = defineIntent('adventure.intent.exit-requested', {
286
+ version: 1,
287
+ fields: { exitId: { type: 'string' } },
288
+ });
289
+ const room = defineStateEvent('adventure.state.room-changed', {
290
+ version: 1,
291
+ fields: { roomId: { type: 'string' } },
292
+ });
293
+ if (!exit.ok || !room.ok) throw new Error('contracts');
294
+ const contracts = [exit.contract, room.contract];
295
+ const registry = createContractRegistry(contracts);
296
+ const router = createEventRouter({
297
+ validate: registry.asRouterValidate,
298
+ });
299
+ router.attach('presentation', runtime.hostChannel, deriveAttachOptions(contracts, 'cart'));
300
+ ```
301
+
302
+ ## Presentation cue
303
+
304
+ Frame-stepped effects (checkmarks, ripples, room fades). No `setTimeout` / rAF. Drive `step` from the same clock as deterministic `cart.step`.
305
+
306
+ ```ts
307
+ import { createPresentationTimeline } from '@cyberart-io/engine';
308
+
309
+ const timeline = createPresentationTimeline({ originFrame: 0, reducedMotion: false });
310
+ timeline.play({
311
+ name: 'checkmark',
312
+ idempotencyKey: 'gold',
313
+ durationFrames: 90,
314
+ easing: 'ease-out',
315
+ onDuplicate: 'replace',
316
+ });
317
+ timeline.step(90);
318
+ ```
319
+
320
+ 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
+
322
+ ## Presentation adapter
323
+
324
+ Host-owned render model in, interaction intents out. Cyberart does not mutate the canonical world. `gameManager` is not this contract.
325
+
326
+ ```ts
327
+ import {
328
+ attachPresentationAdapter,
329
+ createReferencePresentationCart,
330
+ } from '@cyberart-io/engine';
331
+ import { createHeadlessHarness } from '@cyberart-io/engine/headless';
332
+
333
+ const harness = createHeadlessHarness({
334
+ cart: createReferencePresentationCart(),
335
+ seed: 42,
336
+ onEvent: (event) => hostReduce(event),
337
+ });
338
+ const adapter = attachPresentationAdapter(harness);
339
+ adapter.present({
340
+ contractVersion: 1,
341
+ phase: 'ready',
342
+ view: { title: 'Joiner Brook', regions: [] },
343
+ });
344
+ ```
345
+
346
+ `start()` maps to `cart.start()`. Phases: `loading` / `ready` / `error` / `unsupported`. Full contract, router attach (`runtime.hostChannel`), and the reproduce command: [presentation adapter](docs/presentation-adapter.md).
347
+
348
+ ## Assets
349
+
350
+ Hosts resolve logical refs (Library IDs, Moltazine posts, `world:asset/…`, ordinary URLs). The engine caches, dedupes, reports progress, and applies timeouts / CORS-shaped failures / silent fallbacks. Policy stays out of authored cart code.
351
+
352
+ ```ts
353
+ import {
354
+ createRuntime,
355
+ createFixtureAssetResolver,
356
+ createHostedAssetResolver,
357
+ } from '@cyberart-io/engine';
358
+
359
+ const roomAssets = [
360
+ { id: 'room-bg', ref: 'moltazine:post/porch-1#primary-image', type: 'image' as const },
361
+ {
362
+ id: 'ambience',
363
+ ref: 'world:asset/stream-loop',
364
+ type: 'audio' as const,
365
+ fallback: 'silent' as const,
366
+ },
367
+ ];
368
+
369
+ const local = createRuntime({
370
+ container,
371
+ assets: {
372
+ resolver: createFixtureAssetResolver({
373
+ 'moltazine:post/porch-1#primary-image': { url: 'fixture://images/porch.png' },
374
+ 'world:asset/stream-loop': { url: 'fixture://audio/stream-loop.ogg' },
375
+ }),
376
+ },
377
+ });
378
+ await local.assets!.preload(roomAssets);
379
+
380
+ const hosted = createRuntime({
381
+ container,
382
+ assets: {
383
+ resolver: createHostedAssetResolver({
384
+ cdnBase: 'https://cdn.example/library/',
385
+ failures: { 'world:asset/stream-loop': 'cors' },
386
+ }),
387
+ },
388
+ });
389
+ ```
390
+
391
+ The same declarations produce fixture URLs locally and CDN URLs (plus a typed CORS failure and silent audio fallback) when hosted. Live preload dispatches `ASSET_READY_EVENT` / `ASSET_FAILED_EVENT`. Deterministic mode leaves those events to scripted `{ type: 'asset' }` actions so tests do not wait on wall-clock fetch. Full API and the reproduce command: [asset resolver](docs/asset-resolver.md).
262
392
 
263
393
  ## Save and load
264
394
 
@@ -333,7 +463,7 @@ A canvas the host adopted is left in place on destroy; a canvas the engine creat
333
463
 
334
464
  ## Publishing this package (maintainers)
335
465
 
336
- 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 contains minified `dist/index.js`, rolled-up `dist/index.d.ts`, `LICENSE`, `README.md`, `docs/` (event / deterministic / harness API), and `package.json`. Site code that still imports `src/ui/lib/...` hits thin re-export shims so those paths keep working.
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.
337
467
 
338
468
  ```bash
339
469
  pnpm run pack:engine