@cyberart-io/engine 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -166,7 +166,32 @@ declare class PointerManager {
166
166
  destroy(): void;
167
167
  }
168
168
 
169
+ /**
170
+ * Copyright (c) 2026 Aaron Boyarsky
171
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
172
+ * See packages/engine/LICENSE
173
+ *
174
+ * Versioned envelope for routed host ↔ cart events. Unattached HostChannel
175
+ * mailboxes still accept thin `{ type, payload }` events; this module is the
176
+ * schema the EventRouter normalizes to.
177
+ */
178
+ declare const EVENT_ENVELOPE_VERSION: 1;
169
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';
181
+ type EventEnvelope = {
182
+ schemaVersion: typeof EVENT_ENVELOPE_VERSION;
183
+ type: string;
184
+ kind: EventKind;
185
+ source: string;
186
+ target?: string;
187
+ id: string;
188
+ correlationId: string;
189
+ causationId?: string;
190
+ seq: number;
191
+ hops: number;
192
+ idempotencyKey?: string;
193
+ payload?: unknown;
194
+ };
170
195
  type EventInput = {
171
196
  type: string;
172
197
  payload?: unknown;
@@ -568,6 +593,316 @@ type CyberArtRuntime = {
568
593
  onError?: (error: unknown, info: FrameErrorInfo) => void;
569
594
  };
570
595
 
596
+ /**
597
+ * Copyright (c) 2026 Aaron Boyarsky
598
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
599
+ * See packages/engine/LICENSE
600
+ *
601
+ * Host-side event router. Carts keep a dumb HostChannel mailbox; the host
602
+ * attaches those channels here so events are permissioned, budgeted, and
603
+ * loop-checked before they reach another cart. Carts never receive this
604
+ * object.
605
+ */
606
+
607
+ type ValidateResult = true | {
608
+ reason: 'host-rejected';
609
+ detail?: string;
610
+ };
611
+ type AttachOptions = {
612
+ emit?: string[];
613
+ subscribe?: string[];
614
+ authoritative?: boolean;
615
+ };
616
+ type EventRouterOptions = {
617
+ validate?: (event: EventEnvelope) => ValidateResult;
618
+ createId?: () => string;
619
+ now?: () => number;
620
+ hostSource?: string;
621
+ maxPerTurn?: number;
622
+ maxPerWindow?: number;
623
+ windowMs?: number;
624
+ maxCausationDepth?: number;
625
+ maxHops?: number;
626
+ maxCorrelationPerTurn?: number;
627
+ maxIndex?: number;
628
+ /** Bound on the decision trace (oldest dropped). Default 256. */
629
+ maxDecisions?: number;
630
+ };
631
+ type PublishExtras = {
632
+ cause?: EventEnvelope;
633
+ };
634
+ type RouterParticipantInspect = {
635
+ id: string;
636
+ emit: string[];
637
+ subscribe: string[];
638
+ authoritative: boolean;
639
+ };
640
+ type RouterDecisionOutcome = 'accepted' | 'rejected' | 'duplicate';
641
+ type RouterDecisionReason = RejectionReason | 'duplicate';
642
+ type RouterDecision = {
643
+ outcome: RouterDecisionOutcome;
644
+ reason?: RouterDecisionReason;
645
+ detail?: string;
646
+ source: string;
647
+ type: string;
648
+ kind?: EventKind;
649
+ envelopeId?: string;
650
+ priorEnvelopeId?: string;
651
+ target?: string;
652
+ deliveredTo: string[];
653
+ hops?: number;
654
+ seq?: number;
655
+ correlationId?: string;
656
+ causationId?: string;
657
+ idempotencyKey?: string;
658
+ schemaVersion?: number;
659
+ payload?: unknown;
660
+ turn: number;
661
+ time: number;
662
+ };
663
+ type EventRouter = {
664
+ attach(id: string, channel: HostChannel, options?: AttachOptions): void;
665
+ detach(id: string): void;
666
+ publish(event: EventInput, extras?: PublishExtras): EventEnvelope | undefined;
667
+ subscribe(patterns: string[], listener: (event: EventEnvelope) => void): () => void;
668
+ subscribeDecision(listener: (decision: RouterDecision) => void): () => void;
669
+ inspectParticipants(): RouterParticipantInspect[];
670
+ inspectDecisions(): RouterDecision[];
671
+ turn(): void;
672
+ };
673
+
674
+ /**
675
+ * Copyright (c) 2026 Aaron Boyarsky
676
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
677
+ * See packages/engine/LICENSE
678
+ *
679
+ * First-class multi-cart runtime group. Creates production `createRuntime`
680
+ * instances, attaches each mailbox to one shared `createEventRouter`, and
681
+ * locksteps a deterministic clock. Carts never receive the router object.
682
+ */
683
+
684
+ type RuntimeGroupKind = 'render' | 'calculation';
685
+ /**
686
+ * Optional capability-shaped attach hints. Explicit participant `emit` /
687
+ * `subscribe` / `authoritative` win. Do not import the capability manifest
688
+ * module from this file.
689
+ */
690
+ type RuntimeGroupCapability = {
691
+ emit?: string[];
692
+ subscribe?: string[];
693
+ authoritative?: boolean;
694
+ };
695
+ type RuntimeGroupFrameError = {
696
+ error: unknown;
697
+ info: FrameErrorInfo;
698
+ };
699
+ type RuntimeGroupParticipantConfig<T = unknown> = {
700
+ id: string;
701
+ cart: AnimationCart<T>;
702
+ /** Rendered surface vs calculation cart (still a real `AnimationCart`). */
703
+ kind?: RuntimeGroupKind;
704
+ seed?: CreateRuntimeOptions['seed'];
705
+ container?: HTMLElement;
706
+ width?: number;
707
+ height?: number;
708
+ initialState?: Partial<T>;
709
+ gameManager?: unknown;
710
+ onEvent?: HostEventListener;
711
+ emit?: string[];
712
+ subscribe?: string[];
713
+ authoritative?: boolean;
714
+ capability?: RuntimeGroupCapability;
715
+ };
716
+ type CreateRuntimeGroupOptions = {
717
+ participants: RuntimeGroupParticipantConfig[];
718
+ /** Shared virtual-clock origin (ms). Default 0. */
719
+ origin?: number;
720
+ width?: number;
721
+ height?: number;
722
+ validate?: EventRouterOptions['validate'];
723
+ createId?: () => string;
724
+ now?: () => number;
725
+ /** Extra router options. Group injects shared `createId` / `now` unless set here. */
726
+ router?: EventRouterOptions;
727
+ /** Bound on the accepted-event trace (oldest dropped). Default 1024. */
728
+ maxTrace?: number;
729
+ };
730
+ type RuntimeGroupParticipantInspect = {
731
+ state: unknown;
732
+ events: HostEvent[];
733
+ errors: RuntimeGroupFrameError[];
734
+ kind: RuntimeGroupKind;
735
+ clock: ClockSnapshot;
736
+ emit: string[];
737
+ subscribe: string[];
738
+ authoritative: boolean;
739
+ };
740
+ type RuntimeGroupDiagnostics = {
741
+ paused: boolean;
742
+ participantIds: string[];
743
+ clocks: Record<string, ClockSnapshot>;
744
+ rejections: unknown[];
745
+ };
746
+ type RuntimeGroupInspect = {
747
+ participants: Record<string, RuntimeGroupParticipantInspect>;
748
+ trace: EventEnvelope[];
749
+ diagnostics: RuntimeGroupDiagnostics;
750
+ };
751
+ type RuntimeGroupParticipantHandle = {
752
+ readonly id: string;
753
+ readonly kind: RuntimeGroupKind;
754
+ readonly runtime: CyberArtRuntime;
755
+ readonly container: HTMLElement;
756
+ readonly events: readonly HostEvent[];
757
+ readonly errors: readonly RuntimeGroupFrameError[];
758
+ get cart(): CartHandle;
759
+ };
760
+ type RuntimeGroup = {
761
+ readonly router: EventRouter;
762
+ readonly origin: number;
763
+ readonly paused: boolean;
764
+ participant(id: string): RuntimeGroupParticipantHandle;
765
+ step(frames?: number): Promise<void>;
766
+ pause(): void;
767
+ resume(): void;
768
+ reset(): void;
769
+ /** Shared viewport. Sets each container and canvas buffer size. Does not clear sibling pixels on detach. */
770
+ resize(width: number, height: number): void;
771
+ /** Tear down one participant without destroying the group or blanking siblings. */
772
+ detach(id: string): void;
773
+ dispatch(participantId: string, event: HostEvent): void;
774
+ publish(event: EventInput, extras?: PublishExtras): EventEnvelope | undefined;
775
+ inspectParticipants(): Array<RouterParticipantInspect & {
776
+ kind: RuntimeGroupKind;
777
+ }>;
778
+ inspect(): Promise<RuntimeGroupInspect>;
779
+ destroy(): void;
780
+ };
781
+
782
+ declare const COMPOSITOR_BLEND_MODES: readonly ["source-over", "screen"];
783
+ type CompositorBlendMode = (typeof COMPOSITOR_BLEND_MODES)[number];
784
+ declare const COMPOSITOR_CLEAR_POLICIES: readonly ["transparent", "opaque"];
785
+ type CompositorClearPolicy = (typeof COMPOSITOR_CLEAR_POLICIES)[number];
786
+ type CompositorPointerEvents = 'auto' | 'none';
787
+ type CompositorClip = {
788
+ x: number;
789
+ y: number;
790
+ width: number;
791
+ height: number;
792
+ };
793
+ type CompositorLayerConfig = {
794
+ id: string;
795
+ order: number;
796
+ visible?: boolean;
797
+ opacity?: number;
798
+ blend?: CompositorBlendMode;
799
+ clip?: CompositorClip;
800
+ pointerEvents?: CompositorPointerEvents;
801
+ clearPolicy?: CompositorClearPolicy;
802
+ };
803
+ type CompositorLayerInspect = {
804
+ id: string;
805
+ order: number;
806
+ visible: boolean;
807
+ opacity: number;
808
+ blend: CompositorBlendMode;
809
+ clip: CompositorClip | null;
810
+ pointerEvents: CompositorPointerEvents;
811
+ clearPolicy: CompositorClearPolicy;
812
+ };
813
+ type ComposedFrame = {
814
+ imageData: ImageData;
815
+ pngDataUrl: string;
816
+ width: number;
817
+ height: number;
818
+ declaredOrder: CompositorLayerInspect[];
819
+ };
820
+ type CompositorInspect = {
821
+ viewport: {
822
+ width: number;
823
+ height: number;
824
+ dpr: number;
825
+ };
826
+ clearPolicy: CompositorClearPolicy;
827
+ layers: CompositorLayerInspect[];
828
+ declaredOrder: string[];
829
+ };
830
+ type Compositor = {
831
+ readonly canvas: HTMLCanvasElement;
832
+ readonly width: number;
833
+ readonly height: number;
834
+ readonly dpr: number;
835
+ compose(): ImageData;
836
+ captureComposedFrame(): ComposedFrame;
837
+ /**
838
+ * Shared CSS viewport. Updates group canvas buffer size (`width * dpr`).
839
+ * Layer order/blend survive. Carts that cache layout from `DimensionContext`
840
+ * need `group.reset()` (or remount) so they rebuild at the new size.
841
+ */
842
+ resize(width: number, height: number, dpr?: number): void;
843
+ setLayer(id: string, patch: Partial<Omit<CompositorLayerConfig, 'id'>>): void;
844
+ layer(id: string): CompositorLayerInspect;
845
+ layers(): CompositorLayerInspect[];
846
+ pointerTarget(x: number, y: number): string | undefined;
847
+ unmountLayer(id: string): void;
848
+ inspect(): CompositorInspect;
849
+ destroy(): void;
850
+ };
851
+
852
+ /**
853
+ * Copyright (c) 2026 Aaron Boyarsky
854
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
855
+ * See packages/engine/LICENSE
856
+ *
857
+ * Deterministic software Canvas2D for jsdom / CI. Integer pixel coverage
858
+ * (pixel-center samples, nearest-neighbor images, no antialiasing). No
859
+ * Math.random. Unsupported operations throw HeadlessUnsupportedOperationError
860
+ * instead of no-op.
861
+ */
862
+ declare class HeadlessUnsupportedOperationError extends Error {
863
+ readonly operation: string;
864
+ name: string;
865
+ constructor(operation: string, detail?: string);
866
+ }
867
+ type GlyphAtlas = {
868
+ cellWidth: number;
869
+ cellHeight: number;
870
+ /** Packed 0/1 coverage, `cellWidth * cellHeight` bits per glyph, row-major. */
871
+ glyphs: Record<string, Uint8Array>;
872
+ };
873
+ type HeadlessImageFixture = {
874
+ readonly __headlessImage: true;
875
+ width: number;
876
+ height: number;
877
+ data: Uint8ClampedArray;
878
+ };
879
+ type HeadlessCanvas2DSettings = {
880
+ alpha?: boolean;
881
+ glyphAtlas?: GlyphAtlas;
882
+ };
883
+ declare class HeadlessSurface {
884
+ readonly canvas: HTMLCanvasElement;
885
+ width: number;
886
+ height: number;
887
+ readonly alpha: boolean;
888
+ pixels: Uint8ClampedArray;
889
+ glyphAtlas: GlyphAtlas;
890
+ context: CanvasRenderingContext2D | null;
891
+ onDeviceReset: (() => void) | null;
892
+ constructor(canvas: HTMLCanvasElement, width: number, height: number, settings: HeadlessCanvas2DSettings);
893
+ resize(width: number, height: number, clear?: boolean): void;
894
+ fillOpaqueBlack(): void;
895
+ }
896
+ declare function createImageFixture(width: number, height: number, data?: Uint8ClampedArray): HeadlessImageFixture;
897
+ declare function setDefaultGlyphAtlas(atlas: GlyphAtlas | undefined): void;
898
+ declare function getHeadlessSurface(canvas: HTMLCanvasElement): HeadlessSurface | undefined;
899
+ declare function attachHeadlessCanvas2D(canvas: HTMLCanvasElement, settings?: HeadlessCanvas2DSettings): CanvasRenderingContext2D;
900
+ declare function makeImageData(data: Uint8ClampedArray, width: number, height: number): ImageData;
901
+ declare function createDefaultGlyphAtlas(): GlyphAtlas;
902
+ declare function encodePng(width: number, height: number, pixels: Uint8ClampedArray | Uint8Array): Uint8Array;
903
+ declare function encodePngDataUrl(width: number, height: number, pixels: Uint8ClampedArray | Uint8Array): string;
904
+ declare function decodePng(bytes: Uint8Array): ImageData;
905
+
571
906
  /**
572
907
  * Copyright (c) 2026 Aaron Boyarsky
573
908
  * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
@@ -577,15 +912,19 @@ type CyberArtRuntime = {
577
912
  * `installHeadlessCanvas` is test-only — do not call it from Player or kaleidoscope.
578
913
  */
579
914
 
580
- /** 1×1 PNG so `captureFrame(path)` writes a file that actually opens. */
915
+ /** 1×1 PNG fallback when `toDataURL` runs before a 2D context is attached. */
581
916
  declare const HEADLESS_PNG_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
582
917
  declare const DEFAULT_HEADLESS_WIDTH = 320;
583
918
  declare const DEFAULT_HEADLESS_HEIGHT = 180;
919
+ type InstallHeadlessCanvasOptions = {
920
+ glyphAtlas?: GlyphAtlas;
921
+ };
584
922
  /**
585
923
  * Documented jsdom canvas install. Mutates `HTMLCanvasElement.prototype`.
586
- * Idempotent. Not for production playback.
924
+ * Idempotent. Not for production playback. Software Canvas2D pixels flow
925
+ * through `getContext` / `toDataURL` so `cart.snapshot()` is the real frame.
587
926
  */
588
- declare function installHeadlessCanvas(): void;
927
+ declare function installHeadlessCanvas(options?: InstallHeadlessCanvasOptions): void;
589
928
  type HeadlessFrameError = {
590
929
  error: unknown;
591
930
  info: FrameErrorInfo;
@@ -634,4 +973,263 @@ type HeadlessHarness<T = unknown> = {
634
973
  };
635
974
  declare function createHeadlessHarness<T>(options: CreateHeadlessHarnessOptions<T>): HeadlessHarness<T>;
636
975
 
637
- export { type CreateHeadlessHarnessOptions, DEFAULT_HEADLESS_HEIGHT, DEFAULT_HEADLESS_WIDTH, HEADLESS_PNG_DATA_URL, type HeadlessFrameError, type HeadlessHarness, type HeadlessInspect, createHeadlessHarness, installHeadlessCanvas };
976
+ declare const UPDATE_GOLDEN_ENV = "CYBERART_UPDATE_GOLDEN";
977
+ type VisualCompareOptions = {
978
+ /** Max per-channel absolute delta (0 = exact). */
979
+ tolerance?: number;
980
+ /** Max allowed differing pixels after tolerance. */
981
+ maxDifferingPixels?: number;
982
+ /**
983
+ * When set, a pixel matches if weighted RGB distance <= this value.
984
+ * Weights are 0.299 / 0.587 / 0.114 (documented luma).
985
+ */
986
+ perceptualThreshold?: number;
987
+ /** Alpha channel max delta. Defaults to `tolerance` (including when perceptual RGB is used). */
988
+ alphaTolerance?: number;
989
+ /** Force golden-update behavior regardless of env. */
990
+ updateGolden?: boolean;
991
+ };
992
+ type VisualArtifacts = {
993
+ expectedPng: Uint8Array;
994
+ actualPng: Uint8Array;
995
+ diffPng: Uint8Array;
996
+ expectedDataUrl: string;
997
+ actualDataUrl: string;
998
+ diffDataUrl: string;
999
+ differingPixels: number;
1000
+ };
1001
+ type VisualCompareResult = {
1002
+ match: boolean;
1003
+ differingPixels: number;
1004
+ width: number;
1005
+ height: number;
1006
+ artifacts: VisualArtifacts;
1007
+ updatedGolden: boolean;
1008
+ };
1009
+ declare function shouldUpdateGolden(env?: {
1010
+ [key: string]: string | undefined;
1011
+ } | undefined): boolean;
1012
+ declare function imageDataFromPngDataUrl(dataUrl: string): ImageData;
1013
+ declare function compareImageData(actual: ImageData, expected: ImageData, options?: VisualCompareOptions): VisualCompareResult;
1014
+ declare function assertPixelsEqual(actual: ImageData, expected: ImageData, options?: VisualCompareOptions): VisualCompareResult;
1015
+ declare function assertPngDataUrlsEqual(actualDataUrl: string, expectedDataUrl: string, options?: VisualCompareOptions): VisualCompareResult;
1016
+ type VisualArtifactPaths = {
1017
+ expectedPath: string;
1018
+ actualPath: string;
1019
+ diffPath: string;
1020
+ };
1021
+ /**
1022
+ * Node-only. Writes expected.png / actual.png / diff.png under `dir`.
1023
+ * Uses a dynamic `node:fs/promises` import so the browser runtime graph stays clean.
1024
+ */
1025
+ declare function writeVisualArtifacts(dir: string, artifacts: VisualArtifacts, names?: {
1026
+ expected?: string;
1027
+ actual?: string;
1028
+ diff?: string;
1029
+ }): Promise<VisualArtifactPaths>;
1030
+
1031
+ type CreateHeadlessMultiCartHarnessOptions = CreateRuntimeGroupOptions;
1032
+ type HeadlessMultiCartHarness = RuntimeGroup;
1033
+ declare function createHeadlessMultiCartHarness(options: CreateHeadlessMultiCartHarnessOptions): HeadlessMultiCartHarness;
1034
+
1035
+ /**
1036
+ * Copyright (c) 2026 Aaron Boyarsky
1037
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
1038
+ * See packages/engine/LICENSE
1039
+ *
1040
+ * One contract definition drives TypeScript payload types, runtime
1041
+ * validation, router permission checks, and machine-readable manifests.
1042
+ * Kind must appear as a dotted segment of `type` so names like
1043
+ * `host.presentation.cue.started` fail at definition time instead of
1044
+ * silently inferring a bogus kind.
1045
+ */
1046
+
1047
+ type ContractFieldType = 'string' | 'number' | 'boolean' | 'object' | 'array';
1048
+ type PayloadFieldSpec = {
1049
+ type: ContractFieldType;
1050
+ optional?: boolean;
1051
+ };
1052
+ type PayloadSchema = {
1053
+ /** Payload schema version. Bump on any field change. */
1054
+ version: number;
1055
+ fields: Record<string, PayloadFieldSpec>;
1056
+ };
1057
+ type ContractDiagnostic = {
1058
+ code: string;
1059
+ detail: string;
1060
+ path?: string;
1061
+ };
1062
+ type EventContractManifest = {
1063
+ type: string;
1064
+ kind: EventKind;
1065
+ version: number;
1066
+ fields: Record<string, {
1067
+ type: ContractFieldType;
1068
+ optional: boolean;
1069
+ }>;
1070
+ emitPattern: string;
1071
+ subscribePattern: string;
1072
+ };
1073
+ type PayloadValidation = {
1074
+ ok: true;
1075
+ payload: unknown;
1076
+ } | {
1077
+ ok: false;
1078
+ errors: ContractDiagnostic[];
1079
+ };
1080
+ type EventContract = {
1081
+ type: string;
1082
+ kind: EventKind;
1083
+ payloadSchema: PayloadSchema;
1084
+ emitPattern: string;
1085
+ subscribePattern: string;
1086
+ toManifest(): EventContractManifest;
1087
+ validatePayload(payload: unknown): PayloadValidation;
1088
+ };
1089
+ type ContractRegistry = {
1090
+ get(type: string): EventContract | undefined;
1091
+ manifest(): EventContractManifest[];
1092
+ validateEnvelope(event: EventEnvelope): PayloadValidation;
1093
+ asRouterValidate(event: EventEnvelope): ValidateResult;
1094
+ };
1095
+
1096
+ declare const REPLAY_INSPECTOR_SCHEMA_VERSION: 1;
1097
+ type ReplayTraceFilter = {
1098
+ types?: string[];
1099
+ sources?: string[];
1100
+ outcomes?: RouterDecisionOutcome[];
1101
+ correlationId?: string;
1102
+ };
1103
+ type CreateReplayInspectorOptions = {
1104
+ redactedKeys?: string[];
1105
+ maxRecords?: number;
1106
+ /** Optional contracts so records can include payload schema version. */
1107
+ registry?: Pick<ContractRegistry, 'get'>;
1108
+ };
1109
+ type InspectorRecord = {
1110
+ index: number;
1111
+ turn: number;
1112
+ time: number;
1113
+ outcome: RouterDecisionOutcome;
1114
+ reason?: RouterDecisionReason;
1115
+ detail?: string;
1116
+ source: string;
1117
+ target?: string;
1118
+ type: string;
1119
+ kind?: EventKind;
1120
+ envelopeId?: string;
1121
+ priorEnvelopeId?: string;
1122
+ correlationId?: string;
1123
+ causationId?: string;
1124
+ hops?: number;
1125
+ seq?: number;
1126
+ idempotencyKey?: string;
1127
+ schemaVersion?: number;
1128
+ payloadSchemaVersion?: number;
1129
+ deliveredTo: string[];
1130
+ payload?: unknown;
1131
+ };
1132
+ type CausationTreeNode = {
1133
+ envelopeId?: string;
1134
+ type: string;
1135
+ source: string;
1136
+ outcome: RouterDecisionOutcome;
1137
+ reason?: RouterDecisionReason;
1138
+ children: CausationTreeNode[];
1139
+ };
1140
+ type ReplayParticipantSummary = {
1141
+ id: string;
1142
+ kind?: RuntimeGroupKind;
1143
+ emit: string[];
1144
+ subscribe: string[];
1145
+ authoritative: boolean;
1146
+ seed?: string;
1147
+ clock?: ClockSnapshot;
1148
+ state?: unknown;
1149
+ errorCount: number;
1150
+ lastError?: string;
1151
+ };
1152
+ type ReplayInspectorReport = {
1153
+ schemaVersion: typeof REPLAY_INSPECTOR_SCHEMA_VERSION;
1154
+ participants: ReplayParticipantSummary[];
1155
+ records: InspectorRecord[];
1156
+ trees: CausationTreeNode[];
1157
+ dropped: number;
1158
+ };
1159
+ type ReplayTapeAction = {
1160
+ kind: 'publish';
1161
+ event: EventInput;
1162
+ extras?: PublishExtras;
1163
+ } | {
1164
+ kind: 'dispatch';
1165
+ participantId: string;
1166
+ event: HostEvent;
1167
+ } | {
1168
+ kind: 'step';
1169
+ frames: number;
1170
+ } | {
1171
+ kind: 'asset';
1172
+ participantId: string;
1173
+ event: HostEvent;
1174
+ };
1175
+ type ReplayInspectorExport = {
1176
+ schemaVersion: typeof REPLAY_INSPECTOR_SCHEMA_VERSION;
1177
+ origin: number;
1178
+ redactedKeys: string[];
1179
+ participants: ReplayParticipantSummary[];
1180
+ tape: ReplayTapeAction[];
1181
+ records: InspectorRecord[];
1182
+ snapshots: Record<string, unknown>;
1183
+ };
1184
+ type ReplayCompareResult = {
1185
+ ok: true;
1186
+ } | {
1187
+ ok: false;
1188
+ detail: string;
1189
+ };
1190
+ type BoundReplaySession = {
1191
+ publish(event: EventInput, extras?: PublishExtras): EventEnvelope | undefined;
1192
+ dispatch(participantId: string, event: HostEvent): void;
1193
+ step(frames?: number): Promise<void>;
1194
+ report(): Promise<ReplayInspectorReport>;
1195
+ exportTrace(filter?: ReplayTraceFilter): Promise<ReplayInspectorExport>;
1196
+ unbind(): void;
1197
+ };
1198
+ type ReplayInspector = {
1199
+ watchRouter(router: EventRouter): () => void;
1200
+ bind(group: RuntimeGroup): BoundReplaySession;
1201
+ importTrace(exported: ReplayInspectorExport | string): void;
1202
+ exportTrace(filter?: ReplayTraceFilter): ReplayInspectorExport;
1203
+ report(filter?: ReplayTraceFilter): ReplayInspectorReport;
1204
+ causationTree(correlationId?: string): CausationTreeNode[];
1205
+ records(): InspectorRecord[];
1206
+ reset(): void;
1207
+ destroy(): void;
1208
+ };
1209
+ declare function compareReplayTraces(expected: InspectorRecord[], actual: InspectorRecord[]): ReplayCompareResult;
1210
+ declare function createReplayInspector(options?: CreateReplayInspectorOptions): ReplayInspector;
1211
+ declare function replayExportedTrace(exported: ReplayInspectorExport, group: RuntimeGroup, options?: CreateReplayInspectorOptions): Promise<{
1212
+ inspector: ReplayInspector;
1213
+ report: ReplayInspectorReport;
1214
+ }>;
1215
+
1216
+ /**
1217
+ * Copyright (c) 2026 Aaron Boyarsky
1218
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
1219
+ * See packages/engine/LICENSE
1220
+ *
1221
+ * Node/jsdom test helpers. Import from `@cyberart-io/engine/headless`.
1222
+ * Production carts and browser hosts must import `@cyberart-io/engine` instead
1223
+ * so Vite never walks `node:fs/promises`.
1224
+ */
1225
+
1226
+ type WriteComposedFrameResult = ComposedFrame & {
1227
+ path?: string;
1228
+ };
1229
+ /**
1230
+ * Headless composition-order capture. `captureComposedFrame()` stays on the
1231
+ * compositor (browser-safe). This helper writes PNG bytes when a path is given.
1232
+ */
1233
+ declare function writeComposedFrame(compositor: Compositor, path?: string): Promise<WriteComposedFrameResult>;
1234
+
1235
+ export { type BoundReplaySession, type CausationTreeNode, type CreateHeadlessHarnessOptions, type CreateHeadlessMultiCartHarnessOptions, type CreateReplayInspectorOptions, DEFAULT_HEADLESS_HEIGHT, DEFAULT_HEADLESS_WIDTH, type GlyphAtlas, HEADLESS_PNG_DATA_URL, 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, createHeadlessHarness, createHeadlessMultiCartHarness, createImageFixture, createReplayInspector, decodePng, encodePng, encodePngDataUrl, getHeadlessSurface, imageDataFromPngDataUrl, installHeadlessCanvas, makeImageData, replayExportedTrace, setDefaultGlyphAtlas, shouldUpdateGolden, writeComposedFrame, writeVisualArtifacts };