@cyberart-io/engine 0.0.4 → 0.0.6
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 +102 -21
- package/dist/headless.d.ts +779 -68
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +1144 -9
- package/dist/index.js +1 -1
- package/docs/asset-resolver.md +4 -4
- package/docs/audio.md +152 -0
- package/docs/browser-harness.md +126 -0
- package/docs/capability-manifest.md +18 -7
- package/docs/compositor.md +103 -0
- package/docs/deterministic-mode.md +1 -1
- package/docs/events.md +75 -13
- package/docs/executable-modules.md +112 -0
- package/docs/headless-harness.md +44 -14
- package/docs/midi.md +128 -0
- package/docs/presentation-adapter.md +8 -8
- package/docs/presentation-cue.md +2 -2
- package/docs/replay-inspector.md +88 -0
- package/docs/runtime-group.md +9 -7
- package/docs/snapshots.md +102 -0
- package/package.json +1 -1
package/dist/headless.d.ts
CHANGED
|
@@ -177,6 +177,7 @@ declare class PointerManager {
|
|
|
177
177
|
*/
|
|
178
178
|
declare const EVENT_ENVELOPE_VERSION: 1;
|
|
179
179
|
type EventKind = 'intent' | 'state' | 'diagnostic';
|
|
180
|
+
type RejectionReason = 'malformed' | 'unauthorized' | 'host-rejected' | 'rate-limited' | 'loop-detected' | 'storm-detected' | 'unknown-target' | 'not-subscribed' | 'hop-limit';
|
|
180
181
|
type EventEnvelope = {
|
|
181
182
|
schemaVersion: typeof EVENT_ENVELOPE_VERSION;
|
|
182
183
|
type: string;
|
|
@@ -327,6 +328,76 @@ type CartStateBundle = {
|
|
|
327
328
|
state: unknown;
|
|
328
329
|
};
|
|
329
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
333
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
334
|
+
* See packages/engine/LICENSE
|
|
335
|
+
*
|
|
336
|
+
* Versioned snapshot envelope. Schema 1 is the previous engine-owned
|
|
337
|
+
* `CartStateBundle` (`CART_STATE_BUNDLE_VERSION = 1`). Schema 2 wraps that
|
|
338
|
+
* blob in `engineState` and separates host-owned payload. Hosts persist
|
|
339
|
+
* the JSON; this module is not a database.
|
|
340
|
+
*/
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Current envelope schema. Independent of `CART_STATE_BUNDLE_VERSION` (still
|
|
344
|
+
* 1 inside `engineState`) and of the npm package version.
|
|
345
|
+
*/
|
|
346
|
+
declare const SNAPSHOT_SCHEMA_VERSION: 2;
|
|
347
|
+
type SnapshotCartRef = {
|
|
348
|
+
id: string;
|
|
349
|
+
version: string;
|
|
350
|
+
generative?: boolean;
|
|
351
|
+
};
|
|
352
|
+
type SnapshotModuleRef = {
|
|
353
|
+
id: string;
|
|
354
|
+
version: string;
|
|
355
|
+
};
|
|
356
|
+
type SnapshotClock = {
|
|
357
|
+
framesElapsed: number;
|
|
358
|
+
elapsedSinceStart?: number;
|
|
359
|
+
now?: number;
|
|
360
|
+
frameRate?: number;
|
|
361
|
+
};
|
|
362
|
+
type SnapshotAssetRef = {
|
|
363
|
+
id: string;
|
|
364
|
+
version?: string;
|
|
365
|
+
ref?: string;
|
|
366
|
+
};
|
|
367
|
+
type SnapshotIntegrity = {
|
|
368
|
+
alg: string;
|
|
369
|
+
hash: string;
|
|
370
|
+
};
|
|
371
|
+
type SnapshotProvenance = {
|
|
372
|
+
source?: string;
|
|
373
|
+
integrity?: SnapshotIntegrity;
|
|
374
|
+
};
|
|
375
|
+
type SnapshotEnvelope = {
|
|
376
|
+
schemaVersion: typeof SNAPSHOT_SCHEMA_VERSION;
|
|
377
|
+
runtimeVersion: string;
|
|
378
|
+
cart: SnapshotCartRef;
|
|
379
|
+
seed: string;
|
|
380
|
+
clock: SnapshotClock;
|
|
381
|
+
engineState: CartStateBundle;
|
|
382
|
+
createdAt: string;
|
|
383
|
+
modules?: SnapshotModuleRef[];
|
|
384
|
+
rng?: RandomState;
|
|
385
|
+
hostState?: unknown;
|
|
386
|
+
hostStateRef?: string;
|
|
387
|
+
assets?: SnapshotAssetRef[];
|
|
388
|
+
provenance?: SnapshotProvenance;
|
|
389
|
+
};
|
|
390
|
+
type ExportSnapshotOptions = {
|
|
391
|
+
cartVersion?: string;
|
|
392
|
+
modules?: SnapshotModuleRef[];
|
|
393
|
+
hostState?: unknown;
|
|
394
|
+
hostStateRef?: string;
|
|
395
|
+
assets?: SnapshotAssetRef[];
|
|
396
|
+
createdAt?: string;
|
|
397
|
+
provenance?: SnapshotProvenance;
|
|
398
|
+
runtimeVersion?: string;
|
|
399
|
+
};
|
|
400
|
+
|
|
330
401
|
/**
|
|
331
402
|
* Copyright (c) 2026 Aaron Boyarsky
|
|
332
403
|
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
@@ -474,6 +545,75 @@ type AssetPreloader = {
|
|
|
474
545
|
dispose(): void;
|
|
475
546
|
};
|
|
476
547
|
|
|
548
|
+
type AudioUnlockState = 'locked' | 'unlocking' | 'unlocked' | 'failed';
|
|
549
|
+
type AudioUnlockStatus = {
|
|
550
|
+
state: AudioUnlockState;
|
|
551
|
+
error?: string;
|
|
552
|
+
};
|
|
553
|
+
type AudioAssetStatus = 'ready' | 'failed';
|
|
554
|
+
type AudioChannelInspect = {
|
|
555
|
+
id: string;
|
|
556
|
+
participantId?: string;
|
|
557
|
+
gain: number;
|
|
558
|
+
muted: boolean;
|
|
559
|
+
duckGain: number;
|
|
560
|
+
priority: number;
|
|
561
|
+
effectiveGain: number;
|
|
562
|
+
};
|
|
563
|
+
type AudioBrokerInspect = {
|
|
564
|
+
status: AudioUnlockStatus;
|
|
565
|
+
reducedSensory: boolean;
|
|
566
|
+
muted: boolean;
|
|
567
|
+
authorized: string[];
|
|
568
|
+
channels: AudioChannelInspect[];
|
|
569
|
+
assets: Record<string, AudioAssetStatus>;
|
|
570
|
+
};
|
|
571
|
+
type AudioBrokerNotice = {
|
|
572
|
+
type: 'asset';
|
|
573
|
+
id: string;
|
|
574
|
+
status: AudioAssetStatus;
|
|
575
|
+
} | {
|
|
576
|
+
type: 'teardown';
|
|
577
|
+
participantId: string;
|
|
578
|
+
} | {
|
|
579
|
+
type: 'mute';
|
|
580
|
+
} | {
|
|
581
|
+
type: 'destroy';
|
|
582
|
+
};
|
|
583
|
+
type AudioBrokerListener = (notice: AudioBrokerNotice) => void;
|
|
584
|
+
type ActiveAudioCue = {
|
|
585
|
+
idempotencyKey: string;
|
|
586
|
+
channelId: string;
|
|
587
|
+
participantId?: string;
|
|
588
|
+
priority: number;
|
|
589
|
+
};
|
|
590
|
+
type AudioBroker = {
|
|
591
|
+
unlock(): Promise<AudioUnlockStatus>;
|
|
592
|
+
status(): AudioUnlockStatus;
|
|
593
|
+
authorize(participantId: string): void;
|
|
594
|
+
revoke(participantId: string): void;
|
|
595
|
+
isAuthorized(participantId: string | undefined): boolean;
|
|
596
|
+
setChannelGain(channelId: string, gain: number, participantId?: string): void;
|
|
597
|
+
setPriority(channelId: string, priority: number, participantId?: string): void;
|
|
598
|
+
mute(): void;
|
|
599
|
+
unmute(): void;
|
|
600
|
+
muteChannel(channelId: string, participantId?: string): void;
|
|
601
|
+
unmuteChannel(channelId: string): void;
|
|
602
|
+
duck(channelId: string, gain?: number): void;
|
|
603
|
+
unduck(channelId: string): void;
|
|
604
|
+
effectiveGain(channelId: string): number;
|
|
605
|
+
handleHostEvent(event: HostEvent): void;
|
|
606
|
+
assetStatus(id: string): AudioAssetStatus | undefined;
|
|
607
|
+
noteCueStarted(cue: ActiveAudioCue): void;
|
|
608
|
+
noteCueEnded(idempotencyKey: string): void;
|
|
609
|
+
teardown(participantId: string): void;
|
|
610
|
+
onNotice(listener: AudioBrokerListener): () => void;
|
|
611
|
+
inspect(): AudioBrokerInspect;
|
|
612
|
+
destroy(): void;
|
|
613
|
+
readonly reducedSensory: boolean;
|
|
614
|
+
readonly muted: boolean;
|
|
615
|
+
};
|
|
616
|
+
|
|
477
617
|
/**
|
|
478
618
|
* Copyright (c) 2026 Aaron Boyarsky
|
|
479
619
|
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
@@ -518,6 +658,17 @@ type CreateRuntimeOptions = {
|
|
|
518
658
|
* scripted `{ type: 'asset' }` actions own delivery timing.
|
|
519
659
|
*/
|
|
520
660
|
assets?: AssetRuntimeOptions;
|
|
661
|
+
/**
|
|
662
|
+
* Shared page-level unlock broker. Optional. Carts that only set
|
|
663
|
+
* `metadata.audio: 'tone'` keep the existing `start()` / `unlockAudio()`
|
|
664
|
+
* path when this is omitted.
|
|
665
|
+
*/
|
|
666
|
+
audioBroker?: AudioBroker;
|
|
667
|
+
/**
|
|
668
|
+
* Runtime-group participant id to authorize on this runtime. Teardown of
|
|
669
|
+
* this id does not close Tone for remaining carts.
|
|
670
|
+
*/
|
|
671
|
+
audioParticipantId?: string;
|
|
521
672
|
};
|
|
522
673
|
type MountOptions<T = unknown> = {
|
|
523
674
|
/** Boot overrides passed as `customState` into `getDefaultState`. Not a live-state replay. */
|
|
@@ -548,7 +699,12 @@ type CartHandle = {
|
|
|
548
699
|
getCartState(): unknown;
|
|
549
700
|
exportState(): Promise<CartStateBundle>;
|
|
550
701
|
exportStateJSON(): Promise<string>;
|
|
551
|
-
importState(bundle: CartStateBundle | string, extras?: {
|
|
702
|
+
importState(bundle: CartStateBundle | SnapshotEnvelope | string, extras?: {
|
|
703
|
+
framebuffer?: ImageData | null;
|
|
704
|
+
}): Promise<void>;
|
|
705
|
+
exportSnapshot(options?: ExportSnapshotOptions): Promise<SnapshotEnvelope>;
|
|
706
|
+
exportSnapshotJSON(options?: ExportSnapshotOptions): Promise<string>;
|
|
707
|
+
importSnapshot(input: SnapshotEnvelope | CartStateBundle | string, extras?: {
|
|
552
708
|
framebuffer?: ImageData | null;
|
|
553
709
|
}): Promise<void>;
|
|
554
710
|
peekExportedFramebuffer(): ImageData | null;
|
|
@@ -589,75 +745,11 @@ type CyberArtRuntime = {
|
|
|
589
745
|
* Survives cart remount; `destroy()` disposes it.
|
|
590
746
|
*/
|
|
591
747
|
readonly assets: AssetPreloader | undefined;
|
|
748
|
+
/** Shared broker when `createRuntime({ audioBroker })` was set. */
|
|
749
|
+
readonly audioBroker: AudioBroker | undefined;
|
|
592
750
|
onError?: (error: unknown, info: FrameErrorInfo) => void;
|
|
593
751
|
};
|
|
594
752
|
|
|
595
|
-
/**
|
|
596
|
-
* Copyright (c) 2026 Aaron Boyarsky
|
|
597
|
-
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
598
|
-
* See packages/engine/LICENSE
|
|
599
|
-
*
|
|
600
|
-
* CI / agent harness around production `createRuntime({ deterministic })`.
|
|
601
|
-
* `installHeadlessCanvas` is test-only — do not call it from Player or kaleidoscope.
|
|
602
|
-
*/
|
|
603
|
-
|
|
604
|
-
/** 1×1 PNG so `captureFrame(path)` writes a file that actually opens. */
|
|
605
|
-
declare const HEADLESS_PNG_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
|
|
606
|
-
declare const DEFAULT_HEADLESS_WIDTH = 320;
|
|
607
|
-
declare const DEFAULT_HEADLESS_HEIGHT = 180;
|
|
608
|
-
/**
|
|
609
|
-
* Documented jsdom canvas install. Mutates `HTMLCanvasElement.prototype`.
|
|
610
|
-
* Idempotent. Not for production playback.
|
|
611
|
-
*/
|
|
612
|
-
declare function installHeadlessCanvas(): void;
|
|
613
|
-
type HeadlessFrameError = {
|
|
614
|
-
error: unknown;
|
|
615
|
-
info: FrameErrorInfo;
|
|
616
|
-
};
|
|
617
|
-
type HeadlessInspect = {
|
|
618
|
-
state: unknown;
|
|
619
|
-
events: HostEvent[];
|
|
620
|
-
errors: HeadlessFrameError[];
|
|
621
|
-
replay: ReplayMetadata;
|
|
622
|
-
clock: ClockSnapshot;
|
|
623
|
-
};
|
|
624
|
-
type CreateHeadlessHarnessOptions<T = unknown> = {
|
|
625
|
-
cart: AnimationCart<T>;
|
|
626
|
-
seed?: CreateRuntimeOptions['seed'];
|
|
627
|
-
width?: number;
|
|
628
|
-
height?: number;
|
|
629
|
-
/** Virtual clock origin in ms. Default 0. */
|
|
630
|
-
origin?: number;
|
|
631
|
-
actions?: ScriptedAction[];
|
|
632
|
-
initialState?: Partial<T>;
|
|
633
|
-
gameManager?: unknown;
|
|
634
|
-
onEvent?: HostEventListener;
|
|
635
|
-
onError?: (error: unknown, info: FrameErrorInfo) => void;
|
|
636
|
-
};
|
|
637
|
-
type HeadlessHarness<T = unknown> = {
|
|
638
|
-
readonly runtime: CyberArtRuntime;
|
|
639
|
-
readonly container: HTMLElement;
|
|
640
|
-
readonly events: readonly HostEvent[];
|
|
641
|
-
readonly errors: readonly HeadlessFrameError[];
|
|
642
|
-
readonly cart: CartHandle;
|
|
643
|
-
step(frames?: number): Promise<void>;
|
|
644
|
-
advance(ms: number): Promise<void>;
|
|
645
|
-
schedule(action: ScriptedAction): void;
|
|
646
|
-
dispatch(event: HostEvent): void;
|
|
647
|
-
start(): Promise<void>;
|
|
648
|
-
pause(): void;
|
|
649
|
-
resume(): void;
|
|
650
|
-
readonly paused: boolean;
|
|
651
|
-
key(key: string): void;
|
|
652
|
-
/** Pointer-down at the next frame. Use `schedule` for move/up. Canvas pixels, not CSS. */
|
|
653
|
-
click(x: number, y: number): void;
|
|
654
|
-
inspect(): Promise<HeadlessInspect>;
|
|
655
|
-
captureFrame(path?: string): Promise<CartSnapshot>;
|
|
656
|
-
remount(options?: MountOptions<T>): CartHandle;
|
|
657
|
-
destroy(): void;
|
|
658
|
-
};
|
|
659
|
-
declare function createHeadlessHarness<T>(options: CreateHeadlessHarnessOptions<T>): HeadlessHarness<T>;
|
|
660
|
-
|
|
661
753
|
/**
|
|
662
754
|
* Copyright (c) 2026 Aaron Boyarsky
|
|
663
755
|
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
@@ -690,15 +782,49 @@ type EventRouterOptions = {
|
|
|
690
782
|
maxHops?: number;
|
|
691
783
|
maxCorrelationPerTurn?: number;
|
|
692
784
|
maxIndex?: number;
|
|
785
|
+
/** Bound on the decision trace (oldest dropped). Default 256. */
|
|
786
|
+
maxDecisions?: number;
|
|
693
787
|
};
|
|
694
788
|
type PublishExtras = {
|
|
695
789
|
cause?: EventEnvelope;
|
|
696
790
|
};
|
|
791
|
+
type RouterParticipantInspect = {
|
|
792
|
+
id: string;
|
|
793
|
+
emit: string[];
|
|
794
|
+
subscribe: string[];
|
|
795
|
+
authoritative: boolean;
|
|
796
|
+
};
|
|
797
|
+
type RouterDecisionOutcome = 'accepted' | 'rejected' | 'duplicate';
|
|
798
|
+
type RouterDecisionReason = RejectionReason | 'duplicate';
|
|
799
|
+
type RouterDecision = {
|
|
800
|
+
outcome: RouterDecisionOutcome;
|
|
801
|
+
reason?: RouterDecisionReason;
|
|
802
|
+
detail?: string;
|
|
803
|
+
source: string;
|
|
804
|
+
type: string;
|
|
805
|
+
kind?: EventKind;
|
|
806
|
+
envelopeId?: string;
|
|
807
|
+
priorEnvelopeId?: string;
|
|
808
|
+
target?: string;
|
|
809
|
+
deliveredTo: string[];
|
|
810
|
+
hops?: number;
|
|
811
|
+
seq?: number;
|
|
812
|
+
correlationId?: string;
|
|
813
|
+
causationId?: string;
|
|
814
|
+
idempotencyKey?: string;
|
|
815
|
+
schemaVersion?: number;
|
|
816
|
+
payload?: unknown;
|
|
817
|
+
turn: number;
|
|
818
|
+
time: number;
|
|
819
|
+
};
|
|
697
820
|
type EventRouter = {
|
|
698
821
|
attach(id: string, channel: HostChannel, options?: AttachOptions): void;
|
|
699
822
|
detach(id: string): void;
|
|
700
823
|
publish(event: EventInput, extras?: PublishExtras): EventEnvelope | undefined;
|
|
701
824
|
subscribe(patterns: string[], listener: (event: EventEnvelope) => void): () => void;
|
|
825
|
+
subscribeDecision(listener: (decision: RouterDecision) => void): () => void;
|
|
826
|
+
inspectParticipants(): RouterParticipantInspect[];
|
|
827
|
+
inspectDecisions(): RouterDecision[];
|
|
702
828
|
turn(): void;
|
|
703
829
|
};
|
|
704
830
|
|
|
@@ -755,6 +881,10 @@ type CreateRuntimeGroupOptions = {
|
|
|
755
881
|
now?: () => number;
|
|
756
882
|
/** Extra router options. Group injects shared `createId` / `now` unless set here. */
|
|
757
883
|
router?: EventRouterOptions;
|
|
884
|
+
/** Bound on the accepted-event trace (oldest dropped). Default 1024. */
|
|
885
|
+
maxTrace?: number;
|
|
886
|
+
/** Shared page-level audio unlock broker. Optional. */
|
|
887
|
+
audioBroker?: AudioBroker;
|
|
758
888
|
};
|
|
759
889
|
type RuntimeGroupParticipantInspect = {
|
|
760
890
|
state: unknown;
|
|
@@ -762,6 +892,9 @@ type RuntimeGroupParticipantInspect = {
|
|
|
762
892
|
errors: RuntimeGroupFrameError[];
|
|
763
893
|
kind: RuntimeGroupKind;
|
|
764
894
|
clock: ClockSnapshot;
|
|
895
|
+
emit: string[];
|
|
896
|
+
subscribe: string[];
|
|
897
|
+
authoritative: boolean;
|
|
765
898
|
};
|
|
766
899
|
type RuntimeGroupDiagnostics = {
|
|
767
900
|
paused: boolean;
|
|
@@ -787,19 +920,597 @@ type RuntimeGroup = {
|
|
|
787
920
|
readonly router: EventRouter;
|
|
788
921
|
readonly origin: number;
|
|
789
922
|
readonly paused: boolean;
|
|
923
|
+
readonly audioBroker: AudioBroker | undefined;
|
|
790
924
|
participant(id: string): RuntimeGroupParticipantHandle;
|
|
791
925
|
step(frames?: number): Promise<void>;
|
|
792
926
|
pause(): void;
|
|
793
927
|
resume(): void;
|
|
794
928
|
reset(): void;
|
|
929
|
+
/** Shared viewport. Sets each container and canvas buffer size. Does not clear sibling pixels on detach. */
|
|
930
|
+
resize(width: number, height: number): void;
|
|
931
|
+
/** Tear down one participant without destroying the group or blanking siblings. */
|
|
932
|
+
detach(id: string): void;
|
|
795
933
|
dispatch(participantId: string, event: HostEvent): void;
|
|
796
934
|
publish(event: EventInput, extras?: PublishExtras): EventEnvelope | undefined;
|
|
935
|
+
inspectParticipants(): Array<RouterParticipantInspect & {
|
|
936
|
+
kind: RuntimeGroupKind;
|
|
937
|
+
}>;
|
|
797
938
|
inspect(): Promise<RuntimeGroupInspect>;
|
|
798
939
|
destroy(): void;
|
|
799
940
|
};
|
|
800
941
|
|
|
942
|
+
declare const COMPOSITOR_BLEND_MODES: readonly ["source-over", "screen"];
|
|
943
|
+
type CompositorBlendMode = (typeof COMPOSITOR_BLEND_MODES)[number];
|
|
944
|
+
declare const COMPOSITOR_CLEAR_POLICIES: readonly ["transparent", "opaque"];
|
|
945
|
+
type CompositorClearPolicy = (typeof COMPOSITOR_CLEAR_POLICIES)[number];
|
|
946
|
+
type CompositorPointerEvents = 'auto' | 'none';
|
|
947
|
+
type CompositorClip = {
|
|
948
|
+
x: number;
|
|
949
|
+
y: number;
|
|
950
|
+
width: number;
|
|
951
|
+
height: number;
|
|
952
|
+
};
|
|
953
|
+
type CompositorLayerConfig = {
|
|
954
|
+
id: string;
|
|
955
|
+
order: number;
|
|
956
|
+
visible?: boolean;
|
|
957
|
+
opacity?: number;
|
|
958
|
+
blend?: CompositorBlendMode;
|
|
959
|
+
clip?: CompositorClip;
|
|
960
|
+
pointerEvents?: CompositorPointerEvents;
|
|
961
|
+
clearPolicy?: CompositorClearPolicy;
|
|
962
|
+
};
|
|
963
|
+
type CompositorLayerInspect = {
|
|
964
|
+
id: string;
|
|
965
|
+
order: number;
|
|
966
|
+
visible: boolean;
|
|
967
|
+
opacity: number;
|
|
968
|
+
blend: CompositorBlendMode;
|
|
969
|
+
clip: CompositorClip | null;
|
|
970
|
+
pointerEvents: CompositorPointerEvents;
|
|
971
|
+
clearPolicy: CompositorClearPolicy;
|
|
972
|
+
};
|
|
973
|
+
type ComposedFrame = {
|
|
974
|
+
imageData: ImageData;
|
|
975
|
+
pngDataUrl: string;
|
|
976
|
+
width: number;
|
|
977
|
+
height: number;
|
|
978
|
+
declaredOrder: CompositorLayerInspect[];
|
|
979
|
+
};
|
|
980
|
+
type CompositorInspect = {
|
|
981
|
+
viewport: {
|
|
982
|
+
width: number;
|
|
983
|
+
height: number;
|
|
984
|
+
dpr: number;
|
|
985
|
+
};
|
|
986
|
+
clearPolicy: CompositorClearPolicy;
|
|
987
|
+
layers: CompositorLayerInspect[];
|
|
988
|
+
declaredOrder: string[];
|
|
989
|
+
};
|
|
990
|
+
type Compositor = {
|
|
991
|
+
readonly canvas: HTMLCanvasElement;
|
|
992
|
+
readonly width: number;
|
|
993
|
+
readonly height: number;
|
|
994
|
+
readonly dpr: number;
|
|
995
|
+
compose(): ImageData;
|
|
996
|
+
captureComposedFrame(): ComposedFrame;
|
|
997
|
+
/**
|
|
998
|
+
* Shared CSS viewport. Updates group canvas buffer size (`width * dpr`).
|
|
999
|
+
* Layer order/blend survive. Carts that cache layout from `DimensionContext`
|
|
1000
|
+
* need `group.reset()` (or remount) so they rebuild at the new size.
|
|
1001
|
+
*/
|
|
1002
|
+
resize(width: number, height: number, dpr?: number): void;
|
|
1003
|
+
setLayer(id: string, patch: Partial<Omit<CompositorLayerConfig, 'id'>>): void;
|
|
1004
|
+
layer(id: string): CompositorLayerInspect;
|
|
1005
|
+
layers(): CompositorLayerInspect[];
|
|
1006
|
+
pointerTarget(x: number, y: number): string | undefined;
|
|
1007
|
+
unmountLayer(id: string): void;
|
|
1008
|
+
inspect(): CompositorInspect;
|
|
1009
|
+
destroy(): void;
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
1014
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
1015
|
+
* See packages/engine/LICENSE
|
|
1016
|
+
*
|
|
1017
|
+
* Deterministic software Canvas2D for jsdom / CI. Integer pixel coverage
|
|
1018
|
+
* (pixel-center samples, nearest-neighbor images, no antialiasing). No
|
|
1019
|
+
* Math.random. Unsupported operations throw HeadlessUnsupportedOperationError
|
|
1020
|
+
* instead of no-op.
|
|
1021
|
+
*/
|
|
1022
|
+
declare class HeadlessUnsupportedOperationError extends Error {
|
|
1023
|
+
readonly operation: string;
|
|
1024
|
+
name: string;
|
|
1025
|
+
constructor(operation: string, detail?: string);
|
|
1026
|
+
}
|
|
1027
|
+
type GlyphAtlas = {
|
|
1028
|
+
cellWidth: number;
|
|
1029
|
+
cellHeight: number;
|
|
1030
|
+
/** Packed 0/1 coverage, `cellWidth * cellHeight` bits per glyph, row-major. */
|
|
1031
|
+
glyphs: Record<string, Uint8Array>;
|
|
1032
|
+
};
|
|
1033
|
+
type HeadlessImageFixture = {
|
|
1034
|
+
readonly __headlessImage: true;
|
|
1035
|
+
width: number;
|
|
1036
|
+
height: number;
|
|
1037
|
+
data: Uint8ClampedArray;
|
|
1038
|
+
};
|
|
1039
|
+
type HeadlessCanvas2DSettings = {
|
|
1040
|
+
alpha?: boolean;
|
|
1041
|
+
glyphAtlas?: GlyphAtlas;
|
|
1042
|
+
};
|
|
1043
|
+
declare class HeadlessSurface {
|
|
1044
|
+
readonly canvas: HTMLCanvasElement;
|
|
1045
|
+
width: number;
|
|
1046
|
+
height: number;
|
|
1047
|
+
readonly alpha: boolean;
|
|
1048
|
+
pixels: Uint8ClampedArray;
|
|
1049
|
+
glyphAtlas: GlyphAtlas;
|
|
1050
|
+
context: CanvasRenderingContext2D | null;
|
|
1051
|
+
onDeviceReset: (() => void) | null;
|
|
1052
|
+
constructor(canvas: HTMLCanvasElement, width: number, height: number, settings: HeadlessCanvas2DSettings);
|
|
1053
|
+
resize(width: number, height: number, clear?: boolean): void;
|
|
1054
|
+
fillOpaqueBlack(): void;
|
|
1055
|
+
}
|
|
1056
|
+
declare function createImageFixture(width: number, height: number, data?: Uint8ClampedArray): HeadlessImageFixture;
|
|
1057
|
+
declare function setDefaultGlyphAtlas(atlas: GlyphAtlas | undefined): void;
|
|
1058
|
+
declare function getHeadlessSurface(canvas: HTMLCanvasElement): HeadlessSurface | undefined;
|
|
1059
|
+
declare function attachHeadlessCanvas2D(canvas: HTMLCanvasElement, settings?: HeadlessCanvas2DSettings): CanvasRenderingContext2D;
|
|
1060
|
+
declare function makeImageData(data: Uint8ClampedArray, width: number, height: number): ImageData;
|
|
1061
|
+
declare function createDefaultGlyphAtlas(): GlyphAtlas;
|
|
1062
|
+
declare function encodePng(width: number, height: number, pixels: Uint8ClampedArray | Uint8Array): Uint8Array;
|
|
1063
|
+
declare function encodePngDataUrl(width: number, height: number, pixels: Uint8ClampedArray | Uint8Array): string;
|
|
1064
|
+
declare function decodePng(bytes: Uint8Array): ImageData;
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
1068
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
1069
|
+
* See packages/engine/LICENSE
|
|
1070
|
+
*
|
|
1071
|
+
* CI / agent harness around production `createRuntime({ deterministic })`.
|
|
1072
|
+
* `installHeadlessCanvas` is test-only — do not call it from Player or kaleidoscope.
|
|
1073
|
+
*/
|
|
1074
|
+
|
|
1075
|
+
/** 1×1 PNG fallback when `toDataURL` runs before a 2D context is attached. */
|
|
1076
|
+
declare const HEADLESS_PNG_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
|
|
1077
|
+
declare const DEFAULT_HEADLESS_WIDTH = 320;
|
|
1078
|
+
declare const DEFAULT_HEADLESS_HEIGHT = 180;
|
|
1079
|
+
type InstallHeadlessCanvasOptions = {
|
|
1080
|
+
glyphAtlas?: GlyphAtlas;
|
|
1081
|
+
};
|
|
1082
|
+
/**
|
|
1083
|
+
* Documented jsdom canvas install. Mutates `HTMLCanvasElement.prototype`.
|
|
1084
|
+
* Idempotent. Not for production playback. Software Canvas2D pixels flow
|
|
1085
|
+
* through `getContext` / `toDataURL` so `cart.snapshot()` is the real frame.
|
|
1086
|
+
*/
|
|
1087
|
+
declare function installHeadlessCanvas(options?: InstallHeadlessCanvasOptions): void;
|
|
1088
|
+
type HeadlessFrameError = {
|
|
1089
|
+
error: unknown;
|
|
1090
|
+
info: FrameErrorInfo;
|
|
1091
|
+
};
|
|
1092
|
+
type HeadlessInspect = {
|
|
1093
|
+
state: unknown;
|
|
1094
|
+
events: HostEvent[];
|
|
1095
|
+
errors: HeadlessFrameError[];
|
|
1096
|
+
replay: ReplayMetadata;
|
|
1097
|
+
clock: ClockSnapshot;
|
|
1098
|
+
};
|
|
1099
|
+
type CreateHeadlessHarnessOptions<T = unknown> = {
|
|
1100
|
+
cart: AnimationCart<T>;
|
|
1101
|
+
seed?: CreateRuntimeOptions['seed'];
|
|
1102
|
+
width?: number;
|
|
1103
|
+
height?: number;
|
|
1104
|
+
/** Virtual clock origin in ms. Default 0. */
|
|
1105
|
+
origin?: number;
|
|
1106
|
+
actions?: ScriptedAction[];
|
|
1107
|
+
initialState?: Partial<T>;
|
|
1108
|
+
gameManager?: unknown;
|
|
1109
|
+
onEvent?: HostEventListener;
|
|
1110
|
+
onError?: (error: unknown, info: FrameErrorInfo) => void;
|
|
1111
|
+
};
|
|
1112
|
+
type HeadlessHarness<T = unknown> = {
|
|
1113
|
+
readonly runtime: CyberArtRuntime;
|
|
1114
|
+
readonly container: HTMLElement;
|
|
1115
|
+
readonly events: readonly HostEvent[];
|
|
1116
|
+
readonly errors: readonly HeadlessFrameError[];
|
|
1117
|
+
readonly cart: CartHandle;
|
|
1118
|
+
step(frames?: number): Promise<void>;
|
|
1119
|
+
advance(ms: number): Promise<void>;
|
|
1120
|
+
schedule(action: ScriptedAction): void;
|
|
1121
|
+
dispatch(event: HostEvent): void;
|
|
1122
|
+
start(): Promise<void>;
|
|
1123
|
+
pause(): void;
|
|
1124
|
+
resume(): void;
|
|
1125
|
+
readonly paused: boolean;
|
|
1126
|
+
key(key: string): void;
|
|
1127
|
+
/** Pointer-down at the next frame. Use `schedule` for move/up. Canvas pixels, not CSS. */
|
|
1128
|
+
click(x: number, y: number): void;
|
|
1129
|
+
inspect(): Promise<HeadlessInspect>;
|
|
1130
|
+
captureFrame(path?: string): Promise<CartSnapshot>;
|
|
1131
|
+
remount(options?: MountOptions<T>): CartHandle;
|
|
1132
|
+
destroy(): void;
|
|
1133
|
+
};
|
|
1134
|
+
declare function createHeadlessHarness<T>(options: CreateHeadlessHarnessOptions<T>): HeadlessHarness<T>;
|
|
1135
|
+
|
|
1136
|
+
declare const UPDATE_GOLDEN_ENV = "CYBERART_UPDATE_GOLDEN";
|
|
1137
|
+
type VisualCompareOptions = {
|
|
1138
|
+
/** Max per-channel absolute delta (0 = exact). */
|
|
1139
|
+
tolerance?: number;
|
|
1140
|
+
/** Max allowed differing pixels after tolerance. */
|
|
1141
|
+
maxDifferingPixels?: number;
|
|
1142
|
+
/**
|
|
1143
|
+
* When set, a pixel matches if weighted RGB distance <= this value.
|
|
1144
|
+
* Weights are 0.299 / 0.587 / 0.114 (documented luma).
|
|
1145
|
+
*/
|
|
1146
|
+
perceptualThreshold?: number;
|
|
1147
|
+
/** Alpha channel max delta. Defaults to `tolerance` (including when perceptual RGB is used). */
|
|
1148
|
+
alphaTolerance?: number;
|
|
1149
|
+
/** Force golden-update behavior regardless of env. */
|
|
1150
|
+
updateGolden?: boolean;
|
|
1151
|
+
};
|
|
1152
|
+
type VisualArtifacts = {
|
|
1153
|
+
expectedPng: Uint8Array;
|
|
1154
|
+
actualPng: Uint8Array;
|
|
1155
|
+
diffPng: Uint8Array;
|
|
1156
|
+
expectedDataUrl: string;
|
|
1157
|
+
actualDataUrl: string;
|
|
1158
|
+
diffDataUrl: string;
|
|
1159
|
+
differingPixels: number;
|
|
1160
|
+
};
|
|
1161
|
+
type VisualCompareResult = {
|
|
1162
|
+
match: boolean;
|
|
1163
|
+
differingPixels: number;
|
|
1164
|
+
width: number;
|
|
1165
|
+
height: number;
|
|
1166
|
+
artifacts: VisualArtifacts;
|
|
1167
|
+
updatedGolden: boolean;
|
|
1168
|
+
};
|
|
1169
|
+
declare function shouldUpdateGolden(env?: {
|
|
1170
|
+
[key: string]: string | undefined;
|
|
1171
|
+
} | undefined): boolean;
|
|
1172
|
+
declare function imageDataFromPngDataUrl(dataUrl: string): ImageData;
|
|
1173
|
+
declare function compareImageData(actual: ImageData, expected: ImageData, options?: VisualCompareOptions): VisualCompareResult;
|
|
1174
|
+
declare function assertPixelsEqual(actual: ImageData, expected: ImageData, options?: VisualCompareOptions): VisualCompareResult;
|
|
1175
|
+
declare function assertPngDataUrlsEqual(actualDataUrl: string, expectedDataUrl: string, options?: VisualCompareOptions): VisualCompareResult;
|
|
1176
|
+
type VisualArtifactPaths = {
|
|
1177
|
+
expectedPath: string;
|
|
1178
|
+
actualPath: string;
|
|
1179
|
+
diffPath: string;
|
|
1180
|
+
};
|
|
1181
|
+
/**
|
|
1182
|
+
* Node-only. Writes expected.png / actual.png / diff.png under `dir`.
|
|
1183
|
+
* Uses a dynamic `node:fs/promises` import so the browser runtime graph stays clean.
|
|
1184
|
+
*/
|
|
1185
|
+
declare function writeVisualArtifacts(dir: string, artifacts: VisualArtifacts, names?: {
|
|
1186
|
+
expected?: string;
|
|
1187
|
+
actual?: string;
|
|
1188
|
+
diff?: string;
|
|
1189
|
+
}): Promise<VisualArtifactPaths>;
|
|
1190
|
+
|
|
801
1191
|
type CreateHeadlessMultiCartHarnessOptions = CreateRuntimeGroupOptions;
|
|
802
1192
|
type HeadlessMultiCartHarness = RuntimeGroup;
|
|
803
1193
|
declare function createHeadlessMultiCartHarness(options: CreateHeadlessMultiCartHarnessOptions): HeadlessMultiCartHarness;
|
|
804
1194
|
|
|
805
|
-
|
|
1195
|
+
/**
|
|
1196
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
1197
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
1198
|
+
* See packages/engine/LICENSE
|
|
1199
|
+
*
|
|
1200
|
+
* One contract definition drives TypeScript payload types, runtime
|
|
1201
|
+
* validation, router permission checks, and machine-readable manifests.
|
|
1202
|
+
* Kind must appear as a dotted segment of `type` so names like
|
|
1203
|
+
* `host.presentation.cue.started` fail at definition time instead of
|
|
1204
|
+
* silently inferring a bogus kind.
|
|
1205
|
+
*/
|
|
1206
|
+
|
|
1207
|
+
type ContractFieldType = 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
1208
|
+
type PayloadFieldSpec = {
|
|
1209
|
+
type: ContractFieldType;
|
|
1210
|
+
optional?: boolean;
|
|
1211
|
+
};
|
|
1212
|
+
type PayloadSchema = {
|
|
1213
|
+
/** Payload schema version. Bump on any field change. */
|
|
1214
|
+
version: number;
|
|
1215
|
+
fields: Record<string, PayloadFieldSpec>;
|
|
1216
|
+
};
|
|
1217
|
+
type ContractDiagnostic = {
|
|
1218
|
+
code: string;
|
|
1219
|
+
detail: string;
|
|
1220
|
+
path?: string;
|
|
1221
|
+
};
|
|
1222
|
+
type EventContractManifest = {
|
|
1223
|
+
type: string;
|
|
1224
|
+
kind: EventKind;
|
|
1225
|
+
version: number;
|
|
1226
|
+
fields: Record<string, {
|
|
1227
|
+
type: ContractFieldType;
|
|
1228
|
+
optional: boolean;
|
|
1229
|
+
}>;
|
|
1230
|
+
emitPattern: string;
|
|
1231
|
+
subscribePattern: string;
|
|
1232
|
+
};
|
|
1233
|
+
type PayloadValidation = {
|
|
1234
|
+
ok: true;
|
|
1235
|
+
payload: unknown;
|
|
1236
|
+
} | {
|
|
1237
|
+
ok: false;
|
|
1238
|
+
errors: ContractDiagnostic[];
|
|
1239
|
+
};
|
|
1240
|
+
type EventContract = {
|
|
1241
|
+
type: string;
|
|
1242
|
+
kind: EventKind;
|
|
1243
|
+
payloadSchema: PayloadSchema;
|
|
1244
|
+
emitPattern: string;
|
|
1245
|
+
subscribePattern: string;
|
|
1246
|
+
toManifest(): EventContractManifest;
|
|
1247
|
+
validatePayload(payload: unknown): PayloadValidation;
|
|
1248
|
+
};
|
|
1249
|
+
type ContractRegistry = {
|
|
1250
|
+
get(type: string): EventContract | undefined;
|
|
1251
|
+
manifest(): EventContractManifest[];
|
|
1252
|
+
validateEnvelope(event: EventEnvelope): PayloadValidation;
|
|
1253
|
+
asRouterValidate(event: EventEnvelope): ValidateResult;
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
declare const REPLAY_INSPECTOR_SCHEMA_VERSION: 1;
|
|
1257
|
+
type ReplayTraceFilter = {
|
|
1258
|
+
types?: string[];
|
|
1259
|
+
sources?: string[];
|
|
1260
|
+
outcomes?: RouterDecisionOutcome[];
|
|
1261
|
+
correlationId?: string;
|
|
1262
|
+
};
|
|
1263
|
+
type CreateReplayInspectorOptions = {
|
|
1264
|
+
redactedKeys?: string[];
|
|
1265
|
+
maxRecords?: number;
|
|
1266
|
+
/** Optional contracts so records can include payload schema version. */
|
|
1267
|
+
registry?: Pick<ContractRegistry, 'get'>;
|
|
1268
|
+
};
|
|
1269
|
+
type InspectorRecord = {
|
|
1270
|
+
index: number;
|
|
1271
|
+
turn: number;
|
|
1272
|
+
time: number;
|
|
1273
|
+
outcome: RouterDecisionOutcome;
|
|
1274
|
+
reason?: RouterDecisionReason;
|
|
1275
|
+
detail?: string;
|
|
1276
|
+
source: string;
|
|
1277
|
+
target?: string;
|
|
1278
|
+
type: string;
|
|
1279
|
+
kind?: EventKind;
|
|
1280
|
+
envelopeId?: string;
|
|
1281
|
+
priorEnvelopeId?: string;
|
|
1282
|
+
correlationId?: string;
|
|
1283
|
+
causationId?: string;
|
|
1284
|
+
hops?: number;
|
|
1285
|
+
seq?: number;
|
|
1286
|
+
idempotencyKey?: string;
|
|
1287
|
+
schemaVersion?: number;
|
|
1288
|
+
payloadSchemaVersion?: number;
|
|
1289
|
+
deliveredTo: string[];
|
|
1290
|
+
payload?: unknown;
|
|
1291
|
+
};
|
|
1292
|
+
type CausationTreeNode = {
|
|
1293
|
+
envelopeId?: string;
|
|
1294
|
+
type: string;
|
|
1295
|
+
source: string;
|
|
1296
|
+
outcome: RouterDecisionOutcome;
|
|
1297
|
+
reason?: RouterDecisionReason;
|
|
1298
|
+
children: CausationTreeNode[];
|
|
1299
|
+
};
|
|
1300
|
+
type ReplayParticipantSummary = {
|
|
1301
|
+
id: string;
|
|
1302
|
+
kind?: RuntimeGroupKind;
|
|
1303
|
+
emit: string[];
|
|
1304
|
+
subscribe: string[];
|
|
1305
|
+
authoritative: boolean;
|
|
1306
|
+
seed?: string;
|
|
1307
|
+
clock?: ClockSnapshot;
|
|
1308
|
+
state?: unknown;
|
|
1309
|
+
errorCount: number;
|
|
1310
|
+
lastError?: string;
|
|
1311
|
+
};
|
|
1312
|
+
type ReplayInspectorReport = {
|
|
1313
|
+
schemaVersion: typeof REPLAY_INSPECTOR_SCHEMA_VERSION;
|
|
1314
|
+
participants: ReplayParticipantSummary[];
|
|
1315
|
+
records: InspectorRecord[];
|
|
1316
|
+
trees: CausationTreeNode[];
|
|
1317
|
+
dropped: number;
|
|
1318
|
+
};
|
|
1319
|
+
type ReplayTapeAction = {
|
|
1320
|
+
kind: 'publish';
|
|
1321
|
+
event: EventInput;
|
|
1322
|
+
extras?: PublishExtras;
|
|
1323
|
+
} | {
|
|
1324
|
+
kind: 'dispatch';
|
|
1325
|
+
participantId: string;
|
|
1326
|
+
event: HostEvent;
|
|
1327
|
+
} | {
|
|
1328
|
+
kind: 'step';
|
|
1329
|
+
frames: number;
|
|
1330
|
+
} | {
|
|
1331
|
+
kind: 'asset';
|
|
1332
|
+
participantId: string;
|
|
1333
|
+
event: HostEvent;
|
|
1334
|
+
};
|
|
1335
|
+
type ReplayInspectorExport = {
|
|
1336
|
+
schemaVersion: typeof REPLAY_INSPECTOR_SCHEMA_VERSION;
|
|
1337
|
+
origin: number;
|
|
1338
|
+
redactedKeys: string[];
|
|
1339
|
+
participants: ReplayParticipantSummary[];
|
|
1340
|
+
tape: ReplayTapeAction[];
|
|
1341
|
+
records: InspectorRecord[];
|
|
1342
|
+
snapshots: Record<string, unknown>;
|
|
1343
|
+
};
|
|
1344
|
+
type ReplayCompareResult = {
|
|
1345
|
+
ok: true;
|
|
1346
|
+
} | {
|
|
1347
|
+
ok: false;
|
|
1348
|
+
detail: string;
|
|
1349
|
+
};
|
|
1350
|
+
type BoundReplaySession = {
|
|
1351
|
+
publish(event: EventInput, extras?: PublishExtras): EventEnvelope | undefined;
|
|
1352
|
+
dispatch(participantId: string, event: HostEvent): void;
|
|
1353
|
+
step(frames?: number): Promise<void>;
|
|
1354
|
+
report(): Promise<ReplayInspectorReport>;
|
|
1355
|
+
exportTrace(filter?: ReplayTraceFilter): Promise<ReplayInspectorExport>;
|
|
1356
|
+
unbind(): void;
|
|
1357
|
+
};
|
|
1358
|
+
type ReplayInspector = {
|
|
1359
|
+
watchRouter(router: EventRouter): () => void;
|
|
1360
|
+
bind(group: RuntimeGroup): BoundReplaySession;
|
|
1361
|
+
importTrace(exported: ReplayInspectorExport | string): void;
|
|
1362
|
+
exportTrace(filter?: ReplayTraceFilter): ReplayInspectorExport;
|
|
1363
|
+
report(filter?: ReplayTraceFilter): ReplayInspectorReport;
|
|
1364
|
+
causationTree(correlationId?: string): CausationTreeNode[];
|
|
1365
|
+
records(): InspectorRecord[];
|
|
1366
|
+
reset(): void;
|
|
1367
|
+
destroy(): void;
|
|
1368
|
+
};
|
|
1369
|
+
declare function compareReplayTraces(expected: InspectorRecord[], actual: InspectorRecord[]): ReplayCompareResult;
|
|
1370
|
+
declare function createReplayInspector(options?: CreateReplayInspectorOptions): ReplayInspector;
|
|
1371
|
+
declare function replayExportedTrace(exported: ReplayInspectorExport, group: RuntimeGroup, options?: CreateReplayInspectorOptions): Promise<{
|
|
1372
|
+
inspector: ReplayInspector;
|
|
1373
|
+
report: ReplayInspectorReport;
|
|
1374
|
+
}>;
|
|
1375
|
+
|
|
1376
|
+
type CueEasing = 'linear' | 'ease-out';
|
|
1377
|
+
type CueDuplicatePolicy = 'ignore' | 'replace' | 'reject';
|
|
1378
|
+
type CueRepeatPolicy = {
|
|
1379
|
+
count: number;
|
|
1380
|
+
} | {
|
|
1381
|
+
forever: true;
|
|
1382
|
+
};
|
|
1383
|
+
type CueReducedMotionPolicy = 'skip' | 'complete' | {
|
|
1384
|
+
durationFrames: number;
|
|
1385
|
+
};
|
|
1386
|
+
type CueSpec = {
|
|
1387
|
+
name: string;
|
|
1388
|
+
idempotencyKey: string;
|
|
1389
|
+
/** Frame when the cue is eligible to start (before delay). Default: play frame. */
|
|
1390
|
+
startFrame?: number;
|
|
1391
|
+
durationFrames: number;
|
|
1392
|
+
delayFrames?: number;
|
|
1393
|
+
easing?: CueEasing;
|
|
1394
|
+
repeat?: CueRepeatPolicy;
|
|
1395
|
+
/**
|
|
1396
|
+
* When the timeline is in reduced-motion mode: skip (complete immediately),
|
|
1397
|
+
* complete (same), or a shorter duration. Default `complete`.
|
|
1398
|
+
*/
|
|
1399
|
+
reducedMotion?: CueReducedMotionPolicy;
|
|
1400
|
+
onDuplicate?: CueDuplicatePolicy;
|
|
1401
|
+
};
|
|
1402
|
+
type CuePhase = 'scheduled' | 'active' | 'completed' | 'cancelled';
|
|
1403
|
+
type CueView = {
|
|
1404
|
+
name: string;
|
|
1405
|
+
idempotencyKey: string;
|
|
1406
|
+
phase: CuePhase;
|
|
1407
|
+
startFrame: number;
|
|
1408
|
+
durationFrames: number;
|
|
1409
|
+
delayFrames: number;
|
|
1410
|
+
easing: CueEasing;
|
|
1411
|
+
progress: number;
|
|
1412
|
+
repeatIndex: number;
|
|
1413
|
+
};
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
1417
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
1418
|
+
* See packages/engine/LICENSE
|
|
1419
|
+
*
|
|
1420
|
+
* Deterministic audio-cue timeline. Scheduling is frame-stepped via
|
|
1421
|
+
* `createPresentationTimeline`; PCM output is not part of the event trace.
|
|
1422
|
+
*/
|
|
1423
|
+
|
|
1424
|
+
declare const AUDIO_CUE_EVENTS: readonly ["audio.cue.scheduled", "audio.cue.started", "audio.cue.skipped", "audio.cue.failed"];
|
|
1425
|
+
type AudioCueEventType = (typeof AUDIO_CUE_EVENTS)[number];
|
|
1426
|
+
type AudioCueSkipReason = 'reduced-sensory' | 'muted' | 'unauthorized';
|
|
1427
|
+
type AudioCueFailReason = 'asset-failed' | 'torn-down' | 'invalid';
|
|
1428
|
+
type AudioCueReason = AudioCueSkipReason | AudioCueFailReason;
|
|
1429
|
+
type AudioCueSpec = CueSpec & {
|
|
1430
|
+
assetId: string;
|
|
1431
|
+
channelId?: string;
|
|
1432
|
+
participantId?: string;
|
|
1433
|
+
priority?: number;
|
|
1434
|
+
gain?: number;
|
|
1435
|
+
};
|
|
1436
|
+
type AudioCueView = CueView & {
|
|
1437
|
+
assetId: string;
|
|
1438
|
+
channelId: string;
|
|
1439
|
+
participantId?: string;
|
|
1440
|
+
priority: number;
|
|
1441
|
+
audioPhase: 'scheduled' | 'started' | 'skipped' | 'failed' | 'completed';
|
|
1442
|
+
};
|
|
1443
|
+
type AudioCueEvent = {
|
|
1444
|
+
type: AudioCueEventType;
|
|
1445
|
+
atFrame: number;
|
|
1446
|
+
name: string;
|
|
1447
|
+
idempotencyKey: string;
|
|
1448
|
+
assetId: string;
|
|
1449
|
+
channelId: string;
|
|
1450
|
+
participantId?: string;
|
|
1451
|
+
reason?: AudioCueReason;
|
|
1452
|
+
progress: number;
|
|
1453
|
+
};
|
|
1454
|
+
type AudioCueTimelineSnapshot = {
|
|
1455
|
+
frame: number;
|
|
1456
|
+
reducedSensory: boolean;
|
|
1457
|
+
cues: AudioCueView[];
|
|
1458
|
+
events: AudioCueEvent[];
|
|
1459
|
+
};
|
|
1460
|
+
type PlayAudioCueResult = {
|
|
1461
|
+
ok: true;
|
|
1462
|
+
cue: AudioCueView;
|
|
1463
|
+
} | {
|
|
1464
|
+
ok: false;
|
|
1465
|
+
reason: 'duplicate' | 'invalid';
|
|
1466
|
+
detail: string;
|
|
1467
|
+
};
|
|
1468
|
+
type AudioCueTimeline = {
|
|
1469
|
+
play(spec: AudioCueSpec): PlayAudioCueResult;
|
|
1470
|
+
step(frames?: number): AudioCueEvent[];
|
|
1471
|
+
cancel(idempotencyKey: string): boolean;
|
|
1472
|
+
reset(): void;
|
|
1473
|
+
snapshot(): AudioCueTimelineSnapshot;
|
|
1474
|
+
get(idempotencyKey: string): AudioCueView | undefined;
|
|
1475
|
+
dispose(): void;
|
|
1476
|
+
readonly frame: number;
|
|
1477
|
+
readonly reducedSensory: boolean;
|
|
1478
|
+
};
|
|
1479
|
+
type HeadlessAudioAdapter = {
|
|
1480
|
+
readonly broker: AudioBroker;
|
|
1481
|
+
readonly timeline: AudioCueTimeline;
|
|
1482
|
+
unlock(): Promise<AudioUnlockStatus>;
|
|
1483
|
+
play(spec: AudioCueSpec): PlayAudioCueResult;
|
|
1484
|
+
step(frames?: number): AudioCueEvent[];
|
|
1485
|
+
handleHostEvent(event: HostEvent): void;
|
|
1486
|
+
snapshot(): AudioCueTimelineSnapshot & {
|
|
1487
|
+
unlock: AudioUnlockStatus;
|
|
1488
|
+
muted: boolean;
|
|
1489
|
+
};
|
|
1490
|
+
destroy(): void;
|
|
1491
|
+
};
|
|
1492
|
+
declare function createHeadlessAudioAdapter(options?: {
|
|
1493
|
+
reducedSensory?: boolean;
|
|
1494
|
+
originFrame?: number;
|
|
1495
|
+
}): HeadlessAudioAdapter;
|
|
1496
|
+
|
|
1497
|
+
/**
|
|
1498
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
1499
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
1500
|
+
* See packages/engine/LICENSE
|
|
1501
|
+
*
|
|
1502
|
+
* Node/jsdom test helpers. Import from `@cyberart-io/engine/headless`.
|
|
1503
|
+
* Production carts and browser hosts must import `@cyberart-io/engine` instead
|
|
1504
|
+
* so Vite never walks `node:fs/promises`.
|
|
1505
|
+
*/
|
|
1506
|
+
|
|
1507
|
+
type WriteComposedFrameResult = ComposedFrame & {
|
|
1508
|
+
path?: string;
|
|
1509
|
+
};
|
|
1510
|
+
/**
|
|
1511
|
+
* Headless composition-order capture. `captureComposedFrame()` stays on the
|
|
1512
|
+
* compositor (browser-safe). This helper writes PNG bytes when a path is given.
|
|
1513
|
+
*/
|
|
1514
|
+
declare function writeComposedFrame(compositor: Compositor, path?: string): Promise<WriteComposedFrameResult>;
|
|
1515
|
+
|
|
1516
|
+
export { type BoundReplaySession, type CausationTreeNode, type CreateHeadlessHarnessOptions, type CreateHeadlessMultiCartHarnessOptions, type CreateReplayInspectorOptions, DEFAULT_HEADLESS_HEIGHT, DEFAULT_HEADLESS_WIDTH, type GlyphAtlas, HEADLESS_PNG_DATA_URL, type HeadlessAudioAdapter, type HeadlessCanvas2DSettings, type HeadlessFrameError, type HeadlessHarness, type HeadlessImageFixture, type HeadlessInspect, type HeadlessMultiCartHarness, HeadlessUnsupportedOperationError, type InspectorRecord, type InstallHeadlessCanvasOptions, type ReplayCompareResult, type ReplayInspector, type ReplayInspectorExport, type ReplayInspectorReport, type ReplayParticipantSummary, type ReplayTapeAction, type ReplayTraceFilter, UPDATE_GOLDEN_ENV, type VisualArtifactPaths, type VisualArtifacts, type VisualCompareOptions, type VisualCompareResult, type WriteComposedFrameResult, assertPixelsEqual, assertPngDataUrlsEqual, attachHeadlessCanvas2D, compareImageData, compareReplayTraces, createDefaultGlyphAtlas, createHeadlessAudioAdapter, createHeadlessHarness, createHeadlessMultiCartHarness, createImageFixture, createReplayInspector, decodePng, encodePng, encodePngDataUrl, getHeadlessSurface, imageDataFromPngDataUrl, installHeadlessCanvas, makeImageData, replayExportedTrace, setDefaultGlyphAtlas, shouldUpdateGolden, writeComposedFrame, writeVisualArtifacts };
|