@cyberart-io/engine 0.0.1 → 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/dist/index.d.ts CHANGED
@@ -43,6 +43,23 @@ type TokenData = {
43
43
  /** Same idea for Arweave-typed external assets. We don't currently use it. */
44
44
  preferredArweaveGateway?: string;
45
45
  };
46
+ /**
47
+ * Canonical token hash for `Random` / Art Blocks.
48
+ *
49
+ * A full 64-hex hash (optional `0x`) is returned unchanged aside from a
50
+ * lowercase `0x` prefix — this is the kaleidoscope / Art Blocks path.
51
+ * Any other seed (including the Adventure Kit example `42`) is mixed into
52
+ * a 64-hex hash so `Random` always sees the same shape.
53
+ */
54
+ declare function canonicalizeSeed(seed: string | number): string;
55
+ /**
56
+ * Seed policy for `createRuntime`.
57
+ *
58
+ * Live kaleidoscope / Art Blocks must keep a caller-supplied 64-hex hash (and
59
+ * a short `?hash=` string as `0x…`, not remixed). Deterministic mode and
60
+ * numeric seeds such as `42` go through `canonicalizeSeed`.
61
+ */
62
+ declare function resolveRuntimeSeed(seed: string | number, mode: 'live' | 'deterministic'): string;
46
63
 
47
64
  /**
48
65
  * Copyright (c) 2026 Aaron Boyarsky
@@ -50,11 +67,29 @@ type TokenData = {
50
67
  * See packages/engine/LICENSE
51
68
  */
52
69
 
70
+ type Sfc32Regs = {
71
+ a: number;
72
+ b: number;
73
+ c: number;
74
+ d: number;
75
+ };
76
+ /** Snapshot of the dual sfc32 generators after warmup (or after `setState`). */
77
+ type RandomState = {
78
+ seed: string;
79
+ useA: boolean;
80
+ prngA: Sfc32Regs;
81
+ prngB: Sfc32Regs;
82
+ };
53
83
  declare class Random {
84
+ readonly seed: string;
54
85
  private useA;
55
86
  private prngA;
56
87
  private prngB;
88
+ private genA;
89
+ private genB;
57
90
  constructor(tokenData: TokenData);
91
+ getState(): RandomState;
92
+ setState(state: RandomState): void;
58
93
  r_zero_one(): number;
59
94
  dec(min?: number, max?: number): number;
60
95
  int(min: number, max?: number): number;
@@ -86,12 +121,18 @@ type KeypressHandler = () => void;
86
121
  declare class KeyboardManager {
87
122
  private actionMap;
88
123
  private debugMode;
124
+ private listening;
89
125
  constructor(debugMode?: boolean, captureKeyboard?: boolean);
90
126
  /**
91
127
  * When a key is pressed, check if there's a corresponding action, and execute it.
92
128
  * @param e
93
129
  */
94
130
  checkKeypress({ key }: KeyboardEvent): void;
131
+ /**
132
+ * Inject a key without a DOM event. Deterministic hosts call this at a
133
+ * chosen frame instead of listening on `window`.
134
+ */
135
+ inject(key: string): void;
95
136
  /**
96
137
  * Register a certain action to be performed when a given key is pressed.
97
138
  * @param key
@@ -116,22 +157,112 @@ type PointerClick = {
116
157
  x: number;
117
158
  y: number;
118
159
  };
160
+ type PointerManagerOptions = {
161
+ /**
162
+ * When false, do not attach canvas pointer listeners. Deterministic mode
163
+ * injects coordinates instead of reading the live pointer.
164
+ */
165
+ listen?: boolean;
166
+ };
119
167
  declare class PointerManager {
120
168
  x: number;
121
169
  y: number;
122
170
  isDown: boolean;
123
171
  private clicks;
124
172
  private canvas;
125
- constructor(canvas: HTMLCanvasElement);
173
+ private listening;
174
+ constructor(canvas: HTMLCanvasElement, options?: PointerManagerOptions);
126
175
  private toCanvasCoords;
127
176
  private onPointerDown;
128
177
  private onPointerMove;
129
178
  private onPointerUp;
130
179
  hasClick(): boolean;
131
180
  consumeClick(): PointerClick | null;
181
+ /**
182
+ * Inject a pointer sample in canvas pixel space. Deterministic hosts call
183
+ * this at a chosen frame instead of waiting on DOM pointer events.
184
+ */
185
+ inject(kind: 'down' | 'move' | 'up', x: number, y: number): void;
132
186
  destroy(): void;
133
187
  }
134
188
 
189
+ /**
190
+ * Copyright (c) 2026 Aaron Boyarsky
191
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
192
+ * See packages/engine/LICENSE
193
+ *
194
+ * Versioned envelope for routed host ↔ cart events. Unattached HostChannel
195
+ * mailboxes still accept thin `{ type, payload }` events; this module is the
196
+ * schema the EventRouter normalizes to.
197
+ */
198
+ declare const EVENT_ENVELOPE_VERSION: 1;
199
+ type EventKind = 'intent' | 'state' | 'diagnostic';
200
+ type RejectionReason = 'malformed' | 'unauthorized' | 'host-rejected' | 'rate-limited' | 'loop-detected' | 'storm-detected' | 'unknown-target' | 'not-subscribed' | 'hop-limit';
201
+ declare const REJECTED_EVENT_TYPE = "cyberart.diagnostic.rejected";
202
+ /** Remaining hops on a new root event; each caused follow-up decrements by 1. */
203
+ declare const DEFAULT_MAX_HOPS = 8;
204
+ type EventEnvelope = {
205
+ schemaVersion: typeof EVENT_ENVELOPE_VERSION;
206
+ type: string;
207
+ kind: EventKind;
208
+ source: string;
209
+ target?: string;
210
+ id: string;
211
+ correlationId: string;
212
+ causationId?: string;
213
+ seq: number;
214
+ hops: number;
215
+ idempotencyKey?: string;
216
+ payload?: unknown;
217
+ };
218
+ type EventInput = {
219
+ type: string;
220
+ payload?: unknown;
221
+ schemaVersion?: number;
222
+ kind?: EventKind;
223
+ source?: string;
224
+ target?: string;
225
+ id?: string;
226
+ correlationId?: string;
227
+ causationId?: string;
228
+ seq?: number;
229
+ hops?: number;
230
+ idempotencyKey?: string;
231
+ };
232
+ type RejectionPayload = {
233
+ reason: RejectionReason;
234
+ detail?: string;
235
+ eventType: string;
236
+ source: string;
237
+ };
238
+ type NormalizeContext = {
239
+ source: string;
240
+ createId: () => string;
241
+ seq: number;
242
+ causationId?: string;
243
+ maxHops?: number;
244
+ parentHops?: number;
245
+ };
246
+ type NormalizeSuccess = {
247
+ ok: true;
248
+ envelope: EventEnvelope;
249
+ };
250
+ type NormalizeFailure = {
251
+ ok: false;
252
+ reason: 'malformed';
253
+ detail: string;
254
+ };
255
+ type NormalizeResult = NormalizeSuccess | NormalizeFailure;
256
+ /**
257
+ * `kind` on the input wins. Otherwise persistence types are intents, then
258
+ * the first dotted segment that is `intent` | `state` | `diagnostic`.
259
+ * `adventure.presentation.*` does not infer a kind (the presentation segment
260
+ * is not one); put the kind in the name, e.g. `adventure.presentation.intent.*`.
261
+ */
262
+ declare function inferEventKind(input: EventInput): EventKind | undefined;
263
+ declare function matchEventPattern(pattern: string, type: string): boolean;
264
+ declare function normalizeEvent(input: EventInput, context: NormalizeContext): NormalizeResult;
265
+
135
266
  /**
136
267
  * Copyright (c) 2026 Aaron Boyarsky
137
268
  * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
@@ -142,22 +273,49 @@ declare class PointerManager {
142
273
  * `emit`ted by the cart and forwarded to host `onEvent` listeners.
143
274
  *
144
275
  * Carts that never mention this object behave as they do today.
276
+ *
277
+ * Routed traffic is a full EventEnvelope; thin `{ type, payload }` remains
278
+ * valid on an unattached mailbox. Extra envelope fields are optional here.
145
279
  */
146
- type HostEvent = {
147
- type: string;
148
- payload?: unknown;
149
- };
280
+
281
+ type HostEvent = EventInput;
150
282
  type HostEventListener = (event: HostEvent) => void;
151
283
  declare class HostChannel {
152
284
  private inbound;
153
285
  private listeners;
286
+ private closed;
154
287
  dispatch(event: HostEvent): void;
155
288
  consume(): HostEvent[];
156
289
  emit(event: HostEvent): void;
157
290
  onEvent(listener: HostEventListener): () => void;
291
+ /** Drop queued inbound events. Does not remove `onEvent` listeners. */
292
+ clearInbound(): void;
293
+ /** Drop inbound events and listeners. Runtime teardown; not cart unload. */
158
294
  clear(): void;
295
+ /** Permanent close. Further dispatch / emit / consume / onEvent throw. */
296
+ close(): void;
297
+ private requireOpen;
159
298
  }
160
299
 
300
+ /**
301
+ * Copyright (c) 2026 Aaron Boyarsky
302
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
303
+ * See packages/engine/LICENSE
304
+ *
305
+ * Time source for animation. Production uses the wall clock (`performance.now`);
306
+ * deterministic mode uses a host-advanced virtual clock so replays do not
307
+ * depend on rAF, `setTimeout`, or wall time.
308
+ */
309
+ type Clock = {
310
+ now(): number;
311
+ };
312
+ type VirtualClock = Clock & {
313
+ set(ms: number): void;
314
+ advance(ms: number): void;
315
+ };
316
+ declare function createWallClock(): Clock;
317
+ declare function createVirtualClock(origin?: number): VirtualClock;
318
+
161
319
  /**
162
320
  * Copyright (c) 2026 Aaron Boyarsky
163
321
  * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
@@ -248,6 +406,217 @@ declare class IncompatibleCartStateError extends Error {
248
406
  constructor(message: string);
249
407
  }
250
408
 
409
+ /**
410
+ * Copyright (c) 2026 Aaron Boyarsky
411
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
412
+ * See packages/engine/LICENSE
413
+ *
414
+ * Host-controlled time, input, and asset completion for deterministic replays.
415
+ * Production kaleidoscope / Art Blocks playback does not enable this mode.
416
+ * Logical asset URLs are resolved by the host preloader (`assetResolver.ts`);
417
+ * this module only times `ASSET_READY_EVENT` / `ASSET_FAILED_EVENT` delivery.
418
+ */
419
+
420
+ declare const ASSET_READY_EVENT = "cyberart.asset.ready";
421
+ declare const ASSET_FAILED_EVENT = "cyberart.asset.failed";
422
+ type PointerKind = 'down' | 'move' | 'up';
423
+ type ScriptedAction = {
424
+ atFrame: number;
425
+ } & ({
426
+ type: 'pointer';
427
+ pointer: {
428
+ kind: PointerKind;
429
+ x: number;
430
+ y: number;
431
+ };
432
+ } | {
433
+ type: 'key';
434
+ key: string;
435
+ } | {
436
+ type: 'event';
437
+ event: HostEvent;
438
+ } | {
439
+ type: 'asset';
440
+ id: string;
441
+ status: 'ready' | 'failed';
442
+ /** Optional structured failure or resolved resource. Envelope is unchanged. */
443
+ detail?: unknown;
444
+ });
445
+ type DeterministicRuntimeOptions = {
446
+ /** Virtual clock origin in ms. Default 0. */
447
+ origin?: number;
448
+ /** Actions applied at the start of `atFrame`, before `update`. */
449
+ actions?: ScriptedAction[];
450
+ };
451
+ type ClockSnapshot = {
452
+ now: number;
453
+ framesElapsed: number;
454
+ frameRate: number;
455
+ };
456
+ type ReplayMetadata = {
457
+ seed: string;
458
+ clock: ClockSnapshot;
459
+ rng: RandomState;
460
+ actions: ScriptedAction[];
461
+ applied: AppliedAction[];
462
+ events: HostEvent[];
463
+ state: unknown;
464
+ };
465
+ type AppliedAction = {
466
+ frame: number;
467
+ action: ScriptedAction;
468
+ };
469
+ /**
470
+ * Compare two replay captures. Empty array means identical; otherwise each
471
+ * string names the first disagreement on that field.
472
+ */
473
+ declare function describeReplayMismatch(a: ReplayMetadata, b: ReplayMetadata): string[];
474
+
475
+ declare const ASSET_KINDS: readonly ["image", "audio", "font", "spritesheet"];
476
+ type AssetKind = (typeof ASSET_KINDS)[number];
477
+ declare const ASSET_FAILURE_CODES: readonly ["timeout", "cors", "not-found", "invalid", "aborted", "resolver"];
478
+ type AssetFailureCode = (typeof ASSET_FAILURE_CODES)[number];
479
+ type AssetCorsMode = 'anonymous' | 'use-credentials' | 'omit';
480
+ /** Logical silent placeholder. Carts/hosts may treat it as “no media”. */
481
+ declare const SILENT_ASSET_FALLBACK_REF = "cyberart:fallback/silent";
482
+ type AssetProvenance = {
483
+ readonly [key: string]: string | number | boolean | null | undefined;
484
+ };
485
+ type AssetDeclaration = {
486
+ /** Cache key and `ASSET_*_EVENT` payload `id`. */
487
+ id: string;
488
+ /** Logical URI: `https://…`, `moltazine:post/<id>#fragment`, `world:asset/…`, `library:…`. */
489
+ ref: string;
490
+ type: AssetKind;
491
+ integrity?: string;
492
+ provenance?: AssetProvenance;
493
+ /** Per-asset timeout. Ignored when wall-clock timeouts are off (deterministic). */
494
+ timeoutMs?: number;
495
+ /**
496
+ * `'silent'` synthesizes `SILENT_ASSET_FALLBACK_REF`. A string is another
497
+ * logical ref of the same type. A declaration is resolved as-is.
498
+ */
499
+ fallback?: 'silent' | string | AssetDeclaration;
500
+ };
501
+ type AssetResolveRequest = {
502
+ id: string;
503
+ ref: string;
504
+ type: AssetKind;
505
+ integrity?: string;
506
+ provenance?: AssetProvenance;
507
+ };
508
+ type ResolvedAsset = {
509
+ id: string;
510
+ ref: string;
511
+ type: AssetKind;
512
+ url: string;
513
+ integrity?: string;
514
+ provenance?: AssetProvenance;
515
+ cors?: AssetCorsMode;
516
+ usedFallback?: boolean;
517
+ /** When true, `dispose` / `forget` call `URL.revokeObjectURL`. */
518
+ managed?: boolean;
519
+ };
520
+ type AssetFailure = {
521
+ id: string;
522
+ ref: string;
523
+ code: AssetFailureCode;
524
+ message: string;
525
+ };
526
+ type AssetItemStatus = {
527
+ state: 'pending';
528
+ } | {
529
+ state: 'loading';
530
+ } | {
531
+ state: 'ready';
532
+ resource: ResolvedAsset;
533
+ failure?: AssetFailure;
534
+ } | {
535
+ state: 'failed';
536
+ failure: AssetFailure;
537
+ };
538
+ type AssetPreloadSnapshot = {
539
+ total: number;
540
+ pending: number;
541
+ ready: number;
542
+ failed: number;
543
+ fallbacks: number;
544
+ items: Record<string, AssetItemStatus>;
545
+ failures: AssetFailure[];
546
+ };
547
+ type AssetResolver = {
548
+ resolve(request: AssetResolveRequest, signal?: AbortSignal): Promise<ResolvedAsset>;
549
+ };
550
+ type CreateAssetPreloaderOptions = {
551
+ resolver: AssetResolver;
552
+ timeoutMs?: number;
553
+ /**
554
+ * Dispatch `ASSET_READY_EVENT` / `ASSET_FAILED_EVENT` as loads settle.
555
+ * Default true. Deterministic runtimes pass false so scripted `asset`
556
+ * actions own delivery timing.
557
+ */
558
+ emitEvents?: boolean;
559
+ /**
560
+ * Apply `timeoutMs` with `setTimeout`. Default true. Deterministic
561
+ * runtimes pass false so tests do not wait on wall-clock fetch.
562
+ */
563
+ wallClockTimeout?: boolean;
564
+ dispatch?: (event: HostEvent) => void;
565
+ };
566
+ type AssetRuntimeOptions = {
567
+ resolver: AssetResolver;
568
+ timeoutMs?: number;
569
+ emitEvents?: boolean;
570
+ };
571
+ type AssetPreloader = {
572
+ preload(declarations: readonly AssetDeclaration[]): Promise<AssetPreloadSnapshot>;
573
+ get(id: string): ResolvedAsset | undefined;
574
+ getProgress(): AssetPreloadSnapshot;
575
+ onProgress(listener: (snapshot: AssetPreloadSnapshot) => void): () => void;
576
+ abort(id?: string): void;
577
+ forget(id?: string): void;
578
+ dispose(): void;
579
+ };
580
+ type FixtureAssetRecord = {
581
+ url: string;
582
+ integrity?: string;
583
+ cors?: AssetCorsMode;
584
+ provenance?: AssetProvenance;
585
+ } | {
586
+ error: AssetFailureCode;
587
+ message?: string;
588
+ };
589
+ type FixtureAssetCatalog = Readonly<Record<string, FixtureAssetRecord>>;
590
+ type HostedAssetResolverOptions = {
591
+ /** Prefix for rewritten logical refs. `http(s)` / `data:` / `blob:` pass through. */
592
+ cdnBase: string;
593
+ /** Authored refs that fail with a structured code. No network. */
594
+ failures?: Readonly<Record<string, AssetFailureCode | {
595
+ code: AssetFailureCode;
596
+ message?: string;
597
+ }>>;
598
+ };
599
+ declare function isAssetKind(value: unknown): value is AssetKind;
600
+ declare function isAssetFailureCode(value: unknown): value is AssetFailureCode;
601
+ declare function isAssetFailure(value: unknown): value is AssetFailure;
602
+ declare function createAssetFailure(input: {
603
+ id: string;
604
+ ref: string;
605
+ code: AssetFailureCode;
606
+ message?: string;
607
+ }): AssetFailure;
608
+ declare function assetStatusEvent(status: 'ready' | 'failed', payload: {
609
+ id: string;
610
+ ref?: string;
611
+ resource?: ResolvedAsset;
612
+ failure?: AssetFailure;
613
+ }): HostEvent;
614
+ /** Map a logical ref through a hosted CDN prefix. Ordinary URLs are unchanged. */
615
+ declare function rewriteHostedAssetRef(ref: string, cdnBase: string): string;
616
+ declare function createFixtureAssetResolver(catalog: FixtureAssetCatalog): AssetResolver;
617
+ declare function createHostedAssetResolver(options: HostedAssetResolverOptions): AssetResolver;
618
+ declare function createAssetPreloader(options: CreateAssetPreloaderOptions): AssetPreloader;
619
+
251
620
  /**
252
621
  * Copyright (c) 2026 Aaron Boyarsky
253
622
  * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
@@ -262,11 +631,16 @@ type FrameErrorInfo = {
262
631
  type CreateRuntimeOptions = {
263
632
  /** Required mount point. The runtime creates or adopts a canvas inside this element. */
264
633
  container: HTMLElement;
265
- /** Optional token hash (`0x` + 64 hex). Instance-local; does not clobber an existing global cache. */
266
- seed?: string;
634
+ /**
635
+ * Token hash (`0x` + 64 hex) or any seed mixed into one. Instance-local;
636
+ * does not clobber an existing global cache. Kaleidoscope / Art Blocks
637
+ * pass the platform hash unchanged.
638
+ */
639
+ seed?: string | number;
267
640
  /**
268
641
  * When true, the cart listens for window keydown (full-page players).
269
642
  * Defaults to false so an embed does not steal keys from the host.
643
+ * Ignored when `deterministic` is set — input is injected per frame.
270
644
  */
271
645
  captureKeyboard?: boolean;
272
646
  /**
@@ -274,6 +648,19 @@ type CreateRuntimeOptions = {
274
648
  * (click can beat idle prebuild). After mount, `cart.metadata.audio` wins.
275
649
  */
276
650
  audio?: AudioLibrarySpec;
651
+ /**
652
+ * Host-controlled time, input, and asset events. Production playback
653
+ * (kaleidoscope locally and `build:art`) leaves this unset so rAF and the
654
+ * token hash drive the piece as they do today.
655
+ */
656
+ deterministic?: boolean | DeterministicRuntimeOptions;
657
+ /**
658
+ * Host-pluggable asset resolver/preloader. Carts request logical refs;
659
+ * the host maps them to loadable URLs or blobs. Leave unset when unused.
660
+ * In deterministic mode, preload does not dispatch `ASSET_*` events —
661
+ * scripted `{ type: 'asset' }` actions own delivery timing.
662
+ */
663
+ assets?: AssetRuntimeOptions;
277
664
  };
278
665
  type MountOptions<T = unknown> = {
279
666
  /** Boot overrides passed as `customState` into `getDefaultState`. Not a live-state replay. */
@@ -310,6 +697,18 @@ type CartHandle = {
310
697
  peekExportedFramebuffer(): ImageData | null;
311
698
  peekSeed(): string | undefined;
312
699
  isGenerative(): boolean;
700
+ /**
701
+ * Run `frames` ticks on the virtual clock. Requires `deterministic`.
702
+ * Does not use rAF; live kaleidoscope playback never calls this.
703
+ */
704
+ step(frames?: number): Promise<void>;
705
+ /** Run the frames that span `ms` at the cart frame rate. Requires `deterministic`. */
706
+ advance(ms: number): Promise<void>;
707
+ /** Queue a scripted input/asset/host event for a future frame. */
708
+ schedule(action: ScriptedAction): void;
709
+ getClock(): ClockSnapshot;
710
+ getRandomState(): RandomState;
711
+ getReplayMetadata(): Promise<ReplayMetadata>;
313
712
  readonly canvas: HTMLCanvasElement | undefined;
314
713
  paused: boolean;
315
714
  readonly tokenData: TokenData;
@@ -323,6 +722,16 @@ type CyberArtRuntime = {
323
722
  unlockAudio(): Promise<void>;
324
723
  destroy(): void;
325
724
  readonly tokenData: TokenData;
725
+ /**
726
+ * This runtime's mailbox. Attach it to `createEventRouter` from the host;
727
+ * carts never receive the router. Survives cart remount; `destroy()` clears it.
728
+ */
729
+ readonly hostChannel: HostChannel;
730
+ /**
731
+ * Preloader for this runtime. Undefined when `assets` was omitted.
732
+ * Survives cart remount; `destroy()` disposes it.
733
+ */
734
+ readonly assets: AssetPreloader | undefined;
326
735
  onError?: (error: unknown, info: FrameErrorInfo) => void;
327
736
  };
328
737
  declare function createRuntime(options: CreateRuntimeOptions): CyberArtRuntime;
@@ -374,4 +783,340 @@ type CartStateHotkeyOptions = {
374
783
  */
375
784
  declare function registerCartStateHotkeys(keyboardManager: KeyboardManager, hostChannel: HostChannel, options?: CartStateHotkeyOptions): void;
376
785
 
377
- export { type AnimationCart, type AnimationTiming, type AudioLibraryId, type AudioLibrarySpec, CYBERART_CANVAS_ATTR, type CartHandle, type CartSnapshot, type CartStateBundle, type CartStateHotkeyOptions, type CartStateMessageHandler, type CartStatePersister, type CreateRuntimeOptions, type CyberArtRuntime, type DimensionContext, type FrameErrorInfo, HostChannel, type HostEvent, type HostEventListener, type ImportCartStateExtras, IncompatibleCartStateError, KeyboardManager, type MountOptions, type PointerClick, PointerManager, Random, type TokenData, attachCartStatePersistence, createRuntime, registerCartStateHotkeys };
786
+ /**
787
+ * Copyright (c) 2026 Aaron Boyarsky
788
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
789
+ * See packages/engine/LICENSE
790
+ *
791
+ * Host-side event router. Carts keep a dumb HostChannel mailbox; the host
792
+ * attaches those channels here so events are permissioned, budgeted, and
793
+ * loop-checked before they reach another cart. Carts never receive this
794
+ * object.
795
+ */
796
+
797
+ type ValidateResult = true | {
798
+ reason: 'host-rejected';
799
+ detail?: string;
800
+ };
801
+ type AttachOptions = {
802
+ emit?: string[];
803
+ subscribe?: string[];
804
+ authoritative?: boolean;
805
+ };
806
+ type EventRouterOptions = {
807
+ validate?: (event: EventEnvelope) => ValidateResult;
808
+ createId?: () => string;
809
+ now?: () => number;
810
+ hostSource?: string;
811
+ maxPerTurn?: number;
812
+ maxPerWindow?: number;
813
+ windowMs?: number;
814
+ maxCausationDepth?: number;
815
+ maxHops?: number;
816
+ maxCorrelationPerTurn?: number;
817
+ maxIndex?: number;
818
+ };
819
+ type PublishExtras = {
820
+ cause?: EventEnvelope;
821
+ };
822
+ type EventRouter = {
823
+ attach(id: string, channel: HostChannel, options?: AttachOptions): void;
824
+ detach(id: string): void;
825
+ publish(event: EventInput, extras?: PublishExtras): EventEnvelope | undefined;
826
+ subscribe(patterns: string[], listener: (event: EventEnvelope) => void): () => void;
827
+ turn(): void;
828
+ };
829
+ declare function createEventRouter(options?: EventRouterOptions): EventRouter;
830
+
831
+ /**
832
+ * Copyright (c) 2026 Aaron Boyarsky
833
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
834
+ * See packages/engine/LICENSE
835
+ *
836
+ * One contract definition drives TypeScript payload types, runtime
837
+ * validation, router permission checks, and machine-readable manifests.
838
+ * Kind must appear as a dotted segment of `type` so names like
839
+ * `adventure.presentation.cue.started` fail at definition time instead of
840
+ * silently inferring a bogus kind.
841
+ */
842
+
843
+ type ContractFieldType = 'string' | 'number' | 'boolean' | 'object' | 'array';
844
+ type PayloadFieldSpec = {
845
+ type: ContractFieldType;
846
+ optional?: boolean;
847
+ };
848
+ type PayloadSchema = {
849
+ /** Payload schema version. Bump on any field change. */
850
+ version: number;
851
+ fields: Record<string, PayloadFieldSpec>;
852
+ };
853
+ type ContractDiagnostic = {
854
+ code: string;
855
+ detail: string;
856
+ path?: string;
857
+ };
858
+ type EventContractManifest = {
859
+ type: string;
860
+ kind: EventKind;
861
+ version: number;
862
+ fields: Record<string, {
863
+ type: ContractFieldType;
864
+ optional: boolean;
865
+ }>;
866
+ emitPattern: string;
867
+ subscribePattern: string;
868
+ };
869
+ type PayloadValidation = {
870
+ ok: true;
871
+ payload: unknown;
872
+ } | {
873
+ ok: false;
874
+ errors: ContractDiagnostic[];
875
+ };
876
+ type EventContract = {
877
+ type: string;
878
+ kind: EventKind;
879
+ payloadSchema: PayloadSchema;
880
+ emitPattern: string;
881
+ subscribePattern: string;
882
+ toManifest(): EventContractManifest;
883
+ validatePayload(payload: unknown): PayloadValidation;
884
+ };
885
+ type DefineContractResult = {
886
+ ok: true;
887
+ contract: EventContract;
888
+ } | {
889
+ ok: false;
890
+ errors: ContractDiagnostic[];
891
+ };
892
+ type SchemaCompatibility = 'identical' | 'backward-compatible' | 'breaking';
893
+ type FieldTs<T extends ContractFieldType> = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : T extends 'array' ? unknown[] : Record<string, unknown>;
894
+ type OptionalFieldKeys<S extends PayloadSchema> = {
895
+ [K in keyof S['fields']]: S['fields'][K] extends {
896
+ optional: true;
897
+ } ? K : never;
898
+ }[keyof S['fields']];
899
+ type RequiredFieldKeys<S extends PayloadSchema> = Exclude<keyof S['fields'], OptionalFieldKeys<S>>;
900
+ type InferredPayload<S extends PayloadSchema> = {
901
+ [K in RequiredFieldKeys<S>]: FieldTs<S['fields'][K]['type']>;
902
+ } & {
903
+ [K in OptionalFieldKeys<S>]?: FieldTs<S['fields'][K]['type']>;
904
+ };
905
+ /** First dotted segment that is `intent` | `state` | `diagnostic`. */
906
+ declare function kindSegmentInType(type: string): EventKind | undefined;
907
+ declare function familyPatternForType(type: string): string;
908
+ declare function defineIntent(type: string, schema: PayloadSchema): DefineContractResult;
909
+ declare function defineStateEvent(type: string, schema: PayloadSchema): DefineContractResult;
910
+ declare function defineDiagnostic(type: string, schema: PayloadSchema): DefineContractResult;
911
+ declare function comparePayloadSchemas(from: PayloadSchema, to: PayloadSchema): SchemaCompatibility;
912
+ declare function deriveAttachOptions(contracts: EventContract[], role: 'cart' | 'authoritative'): AttachOptions;
913
+ declare function verifyAttachOptions(options: AttachOptions, contracts: EventContract[]): {
914
+ ok: true;
915
+ } | {
916
+ ok: false;
917
+ errors: ContractDiagnostic[];
918
+ };
919
+ type ContractRegistry = {
920
+ get(type: string): EventContract | undefined;
921
+ manifest(): EventContractManifest[];
922
+ validateEnvelope(event: EventEnvelope): PayloadValidation;
923
+ asRouterValidate(event: EventEnvelope): ValidateResult;
924
+ };
925
+ declare function createContractRegistry(contracts: EventContract[]): ContractRegistry;
926
+
927
+ /**
928
+ * Copyright (c) 2026 Aaron Boyarsky
929
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
930
+ * See packages/engine/LICENSE
931
+ *
932
+ * Versioned presentation-adapter contract. Hosts own canonical state and
933
+ * push a render model; Cyberart presents it and emits interaction intents.
934
+ * Adventure Kit concepts stay in the host.
935
+ */
936
+
937
+ declare const PRESENTATION_ADAPTER_VERSION: 1;
938
+ /** Host → cart render model. Kind is `state`. */
939
+ declare const PRESENTATION_MODEL_EVENT = "presentation.state.model";
940
+ /**
941
+ * Cart → host when a model cannot be presented. Kind is `intent` so a
942
+ * non-authoritative cart can emit it through the router.
943
+ */
944
+ declare const PRESENTATION_UNSUPPORTED_EVENT = "presentation.intent.unsupported";
945
+ declare const PRESENTATION_PHASES: readonly ["loading", "ready", "error", "unsupported"];
946
+ type PresentationPhase = (typeof PRESENTATION_PHASES)[number];
947
+ /**
948
+ * Recommended router `subscribe` for a presentation cart. Intent type names
949
+ * are host-owned (`adventure.intent.*`); do not put domain objects in `target`.
950
+ */
951
+ declare const PRESENTATION_SUBSCRIBE_PATTERNS: readonly ["presentation.state.*", "cyberart.diagnostic.rejected"];
952
+ declare const INVALID_PRESENTATION_MODEL_MESSAGE = "Presentation adapter: model is invalid or not JSON-serializable";
953
+ type PresentationRegion = {
954
+ id: string;
955
+ /** Drawing-space pixels — same space as `PointerManager` / harness `click`. */
956
+ x: number;
957
+ y: number;
958
+ width: number;
959
+ height: number;
960
+ /** Emitted on click while `phase === 'ready'`. Prefer `*.intent.*` type names. */
961
+ intent: EventInput;
962
+ };
963
+ type PresentationView = {
964
+ background?: string;
965
+ title?: string;
966
+ regions?: readonly PresentationRegion[];
967
+ };
968
+ type PresentationModel<TView = PresentationView> = {
969
+ contractVersion: typeof PRESENTATION_ADAPTER_VERSION;
970
+ phase: PresentationPhase;
971
+ /** Host-owned render model. The engine does not interpret domain fields. */
972
+ view?: TView;
973
+ /** Machine-readable when `phase` is `loading` | `error` | `unsupported`. */
974
+ reason?: string;
975
+ };
976
+ type PresentationCartState<TView = PresentationView> = {
977
+ model: PresentationModel<TView>;
978
+ };
979
+ type PresentationAdapterTarget = {
980
+ dispatch(event: HostEvent): void;
981
+ start(): Promise<void>;
982
+ pause(): void;
983
+ resume(): void;
984
+ destroy(): void;
985
+ readonly paused?: boolean;
986
+ };
987
+ type PresentationAdapter<TView = PresentationView, TTarget extends PresentationAdapterTarget = PresentationAdapterTarget> = {
988
+ readonly version: typeof PRESENTATION_ADAPTER_VERSION;
989
+ readonly target: TTarget;
990
+ /** Last model this session presented (or the boot model). A copy — mutating it does not update the cart. */
991
+ readonly model: PresentationModel<TView>;
992
+ readonly phase: PresentationPhase;
993
+ readonly paused: boolean;
994
+ present(model: PresentationModel<TView>): void;
995
+ start(): Promise<void>;
996
+ pause(): void;
997
+ resume(): void;
998
+ /** Point at a new handle after `runtime.mount` / `harness.remount`. */
999
+ retarget(target: TTarget): void;
1000
+ destroy(): void;
1001
+ };
1002
+ type AttachPresentationAdapterOptions<TView = PresentationView> = {
1003
+ model?: PresentationModel<TView>;
1004
+ /**
1005
+ * Dispatch `model` onto the mailbox. Default `true` when `model` is set,
1006
+ * `false` otherwise (leave the cart's current state alone).
1007
+ */
1008
+ present?: boolean;
1009
+ };
1010
+ type MountPresentationAdapterOptions<TView = PresentationView> = {
1011
+ cart?: AnimationCart<PresentationCartState<TView>>;
1012
+ onEvent?: HostEventListener;
1013
+ model?: PresentationModel<TView>;
1014
+ initialState?: MountOptions<PresentationCartState<TView>>['initialState'];
1015
+ };
1016
+ declare function isPresentationPhase(value: unknown): value is PresentationPhase;
1017
+ declare function isPresentationModel(value: unknown): value is PresentationModel;
1018
+ declare function createPresentationModelEvent<TView = PresentationView>(model: PresentationModel<TView>, extras?: Pick<EventInput, 'correlationId' | 'causationId' | 'idempotencyKey'>): EventInput;
1019
+ /**
1020
+ * Minimal presentation cart. Hit-tests `view.regions` while `phase === 'ready'`.
1021
+ * Hosts with a custom view shape may pass their own cart to `mountPresentationAdapter`.
1022
+ * `title` is inspectable state; only `background` is painted.
1023
+ */
1024
+ declare function createReferencePresentationCart<TView = PresentationView>(): AnimationCart<PresentationCartState<TView>>;
1025
+ declare function attachPresentationAdapter<TView = PresentationView, TTarget extends PresentationAdapterTarget = PresentationAdapterTarget>(target: TTarget, options?: AttachPresentationAdapterOptions<TView>): PresentationAdapter<TView, TTarget>;
1026
+ /** Mount a presentation cart and return the host-facing adapter. */
1027
+ declare function mountPresentationAdapter<TView = PresentationView>(runtime: CyberArtRuntime, options?: MountPresentationAdapterOptions<TView>): PresentationAdapter<TView, CartHandle>;
1028
+
1029
+ /**
1030
+ * Copyright (c) 2026 Aaron Boyarsky
1031
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
1032
+ * See packages/engine/LICENSE
1033
+ *
1034
+ * Deterministic presentation cue / timeline. Carts step this with a frame
1035
+ * index; there are no wall-clock timers. Lifecycle event type names are
1036
+ * stable for later contract registries.
1037
+ */
1038
+ declare const CUE_STARTED_EVENT: "cue.started";
1039
+ declare const CUE_COMPLETED_EVENT: "cue.completed";
1040
+ declare const CUE_CANCELLED_EVENT: "cue.cancelled";
1041
+ declare const CUE_REPLACED_EVENT: "cue.replaced";
1042
+ declare const CUE_LIFECYCLE_EVENTS: readonly ["cue.started", "cue.completed", "cue.cancelled", "cue.replaced"];
1043
+ type CueLifecycleType = (typeof CUE_LIFECYCLE_EVENTS)[number];
1044
+ type CueEasing = 'linear' | 'ease-out';
1045
+ type CueDuplicatePolicy = 'ignore' | 'replace' | 'reject';
1046
+ type CueRepeatPolicy = {
1047
+ count: number;
1048
+ } | {
1049
+ forever: true;
1050
+ };
1051
+ type CueReducedMotionPolicy = 'skip' | 'complete' | {
1052
+ durationFrames: number;
1053
+ };
1054
+ type CueSpec = {
1055
+ name: string;
1056
+ idempotencyKey: string;
1057
+ /** Frame when the cue is eligible to start (before delay). Default: play frame. */
1058
+ startFrame?: number;
1059
+ durationFrames: number;
1060
+ delayFrames?: number;
1061
+ easing?: CueEasing;
1062
+ repeat?: CueRepeatPolicy;
1063
+ /**
1064
+ * When the timeline is in reduced-motion mode: skip (complete immediately),
1065
+ * complete (same), or a shorter duration. Default `complete`.
1066
+ */
1067
+ reducedMotion?: CueReducedMotionPolicy;
1068
+ onDuplicate?: CueDuplicatePolicy;
1069
+ };
1070
+ type CuePhase = 'scheduled' | 'active' | 'completed' | 'cancelled';
1071
+ type CueView = {
1072
+ name: string;
1073
+ idempotencyKey: string;
1074
+ phase: CuePhase;
1075
+ startFrame: number;
1076
+ durationFrames: number;
1077
+ delayFrames: number;
1078
+ easing: CueEasing;
1079
+ progress: number;
1080
+ repeatIndex: number;
1081
+ };
1082
+ type CueLifecycleEvent = {
1083
+ type: CueLifecycleType;
1084
+ atFrame: number;
1085
+ name: string;
1086
+ idempotencyKey: string;
1087
+ progress: number;
1088
+ };
1089
+ type CueTimelineSnapshot = {
1090
+ frame: number;
1091
+ reducedMotion: boolean;
1092
+ cues: CueView[];
1093
+ events: CueLifecycleEvent[];
1094
+ };
1095
+ type PlayCueResult = {
1096
+ ok: true;
1097
+ cue: CueView;
1098
+ } | {
1099
+ ok: false;
1100
+ reason: 'duplicate' | 'invalid';
1101
+ detail: string;
1102
+ };
1103
+ type CreatePresentationTimelineOptions = {
1104
+ /** Host-controlled reduced-motion / reduced-sensory flag. Not a CSS query. */
1105
+ reducedMotion?: boolean;
1106
+ originFrame?: number;
1107
+ };
1108
+ type PresentationTimeline = {
1109
+ play(spec: CueSpec): PlayCueResult;
1110
+ step(frames?: number): CueLifecycleEvent[];
1111
+ cancel(idempotencyKey: string): boolean;
1112
+ reset(): void;
1113
+ snapshot(): CueTimelineSnapshot;
1114
+ get(idempotencyKey: string): CueView | undefined;
1115
+ readonly frame: number;
1116
+ readonly reducedMotion: boolean;
1117
+ };
1118
+ declare function isCueLifecycleType(value: unknown): value is CueLifecycleType;
1119
+ declare function applyCueEasing(t: number, easing: CueEasing): number;
1120
+ declare function createPresentationTimeline(options?: CreatePresentationTimelineOptions): PresentationTimeline;
1121
+
1122
+ export { ASSET_FAILED_EVENT, ASSET_FAILURE_CODES, ASSET_KINDS, ASSET_READY_EVENT, type AnimationCart, type AnimationTiming, type AppliedAction, type AssetCorsMode, type AssetDeclaration, type AssetFailure, type AssetFailureCode, type AssetItemStatus, type AssetKind, type AssetPreloadSnapshot, type AssetPreloader, type AssetProvenance, type AssetResolveRequest, type AssetResolver, type AssetRuntimeOptions, type AttachOptions, type AttachPresentationAdapterOptions, type AudioLibraryId, type AudioLibrarySpec, CUE_CANCELLED_EVENT, CUE_COMPLETED_EVENT, CUE_LIFECYCLE_EVENTS, CUE_REPLACED_EVENT, CUE_STARTED_EVENT, CYBERART_CANVAS_ATTR, type CartHandle, type CartSnapshot, type CartStateBundle, type CartStateHotkeyOptions, type CartStateMessageHandler, type CartStatePersister, type Clock, type ClockSnapshot, type ContractDiagnostic, type ContractFieldType, type ContractRegistry, type CreateAssetPreloaderOptions, type CreatePresentationTimelineOptions, type CreateRuntimeOptions, type CueDuplicatePolicy, type CueEasing, type CueLifecycleEvent, type CueLifecycleType, type CuePhase, type CueReducedMotionPolicy, type CueRepeatPolicy, type CueSpec, type CueTimelineSnapshot, type CueView, type CyberArtRuntime, DEFAULT_MAX_HOPS, type DefineContractResult, type DeterministicRuntimeOptions, type DimensionContext, EVENT_ENVELOPE_VERSION, type EventContract, type EventContractManifest, type EventEnvelope, type EventInput, type EventKind, type EventRouter, type EventRouterOptions, type FixtureAssetCatalog, type FixtureAssetRecord, type FrameErrorInfo, HostChannel, type HostEvent, type HostEventListener, type HostedAssetResolverOptions, INVALID_PRESENTATION_MODEL_MESSAGE, type ImportCartStateExtras, IncompatibleCartStateError, type InferredPayload, KeyboardManager, type MountOptions, type MountPresentationAdapterOptions, type NormalizeContext, type NormalizeResult, PRESENTATION_ADAPTER_VERSION, PRESENTATION_MODEL_EVENT, PRESENTATION_PHASES, PRESENTATION_SUBSCRIBE_PATTERNS, PRESENTATION_UNSUPPORTED_EVENT, type PayloadFieldSpec, type PayloadSchema, type PayloadValidation, type PlayCueResult, type PointerClick, PointerManager, type PresentationAdapter, type PresentationAdapterTarget, type PresentationCartState, type PresentationModel, type PresentationPhase, type PresentationRegion, type PresentationTimeline, type PresentationView, type PublishExtras, REJECTED_EVENT_TYPE, Random, type RandomState, type RejectionPayload, type RejectionReason, type ReplayMetadata, type ResolvedAsset, SILENT_ASSET_FALLBACK_REF, type SchemaCompatibility, type ScriptedAction, type TokenData, type ValidateResult, type VirtualClock, applyCueEasing, assetStatusEvent, attachCartStatePersistence, attachPresentationAdapter, canonicalizeSeed, comparePayloadSchemas, createAssetFailure, createAssetPreloader, createContractRegistry, createEventRouter, createFixtureAssetResolver, createHostedAssetResolver, createPresentationModelEvent, createPresentationTimeline, createReferencePresentationCart, createRuntime, createVirtualClock, createWallClock, defineDiagnostic, defineIntent, defineStateEvent, deriveAttachOptions, describeReplayMismatch, familyPatternForType, inferEventKind, isAssetFailure, isAssetFailureCode, isAssetKind, isCueLifecycleType, isPresentationModel, isPresentationPhase, kindSegmentInType, matchEventPattern, mountPresentationAdapter, normalizeEvent, registerCartStateHotkeys, resolveRuntimeSeed, rewriteHostedAssetRef, verifyAttachOptions };