@cyberart-io/engine 0.0.4 → 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.
@@ -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;
@@ -592,72 +593,6 @@ type CyberArtRuntime = {
592
593
  onError?: (error: unknown, info: FrameErrorInfo) => void;
593
594
  };
594
595
 
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
596
  /**
662
597
  * Copyright (c) 2026 Aaron Boyarsky
663
598
  * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
@@ -690,15 +625,49 @@ type EventRouterOptions = {
690
625
  maxHops?: number;
691
626
  maxCorrelationPerTurn?: number;
692
627
  maxIndex?: number;
628
+ /** Bound on the decision trace (oldest dropped). Default 256. */
629
+ maxDecisions?: number;
693
630
  };
694
631
  type PublishExtras = {
695
632
  cause?: EventEnvelope;
696
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
+ };
697
663
  type EventRouter = {
698
664
  attach(id: string, channel: HostChannel, options?: AttachOptions): void;
699
665
  detach(id: string): void;
700
666
  publish(event: EventInput, extras?: PublishExtras): EventEnvelope | undefined;
701
667
  subscribe(patterns: string[], listener: (event: EventEnvelope) => void): () => void;
668
+ subscribeDecision(listener: (decision: RouterDecision) => void): () => void;
669
+ inspectParticipants(): RouterParticipantInspect[];
670
+ inspectDecisions(): RouterDecision[];
702
671
  turn(): void;
703
672
  };
704
673
 
@@ -755,6 +724,8 @@ type CreateRuntimeGroupOptions = {
755
724
  now?: () => number;
756
725
  /** Extra router options. Group injects shared `createId` / `now` unless set here. */
757
726
  router?: EventRouterOptions;
727
+ /** Bound on the accepted-event trace (oldest dropped). Default 1024. */
728
+ maxTrace?: number;
758
729
  };
759
730
  type RuntimeGroupParticipantInspect = {
760
731
  state: unknown;
@@ -762,6 +733,9 @@ type RuntimeGroupParticipantInspect = {
762
733
  errors: RuntimeGroupFrameError[];
763
734
  kind: RuntimeGroupKind;
764
735
  clock: ClockSnapshot;
736
+ emit: string[];
737
+ subscribe: string[];
738
+ authoritative: boolean;
765
739
  };
766
740
  type RuntimeGroupDiagnostics = {
767
741
  paused: boolean;
@@ -792,14 +766,470 @@ type RuntimeGroup = {
792
766
  pause(): void;
793
767
  resume(): void;
794
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;
795
773
  dispatch(participantId: string, event: HostEvent): void;
796
774
  publish(event: EventInput, extras?: PublishExtras): EventEnvelope | undefined;
775
+ inspectParticipants(): Array<RouterParticipantInspect & {
776
+ kind: RuntimeGroupKind;
777
+ }>;
797
778
  inspect(): Promise<RuntimeGroupInspect>;
798
779
  destroy(): void;
799
780
  };
800
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
+
906
+ /**
907
+ * Copyright (c) 2026 Aaron Boyarsky
908
+ * SPDX-License-Identifier: LicenseRef-CyberArt-Engine
909
+ * See packages/engine/LICENSE
910
+ *
911
+ * CI / agent harness around production `createRuntime({ deterministic })`.
912
+ * `installHeadlessCanvas` is test-only — do not call it from Player or kaleidoscope.
913
+ */
914
+
915
+ /** 1×1 PNG fallback when `toDataURL` runs before a 2D context is attached. */
916
+ declare const HEADLESS_PNG_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
917
+ declare const DEFAULT_HEADLESS_WIDTH = 320;
918
+ declare const DEFAULT_HEADLESS_HEIGHT = 180;
919
+ type InstallHeadlessCanvasOptions = {
920
+ glyphAtlas?: GlyphAtlas;
921
+ };
922
+ /**
923
+ * Documented jsdom canvas install. Mutates `HTMLCanvasElement.prototype`.
924
+ * Idempotent. Not for production playback. Software Canvas2D pixels flow
925
+ * through `getContext` / `toDataURL` so `cart.snapshot()` is the real frame.
926
+ */
927
+ declare function installHeadlessCanvas(options?: InstallHeadlessCanvasOptions): void;
928
+ type HeadlessFrameError = {
929
+ error: unknown;
930
+ info: FrameErrorInfo;
931
+ };
932
+ type HeadlessInspect = {
933
+ state: unknown;
934
+ events: HostEvent[];
935
+ errors: HeadlessFrameError[];
936
+ replay: ReplayMetadata;
937
+ clock: ClockSnapshot;
938
+ };
939
+ type CreateHeadlessHarnessOptions<T = unknown> = {
940
+ cart: AnimationCart<T>;
941
+ seed?: CreateRuntimeOptions['seed'];
942
+ width?: number;
943
+ height?: number;
944
+ /** Virtual clock origin in ms. Default 0. */
945
+ origin?: number;
946
+ actions?: ScriptedAction[];
947
+ initialState?: Partial<T>;
948
+ gameManager?: unknown;
949
+ onEvent?: HostEventListener;
950
+ onError?: (error: unknown, info: FrameErrorInfo) => void;
951
+ };
952
+ type HeadlessHarness<T = unknown> = {
953
+ readonly runtime: CyberArtRuntime;
954
+ readonly container: HTMLElement;
955
+ readonly events: readonly HostEvent[];
956
+ readonly errors: readonly HeadlessFrameError[];
957
+ readonly cart: CartHandle;
958
+ step(frames?: number): Promise<void>;
959
+ advance(ms: number): Promise<void>;
960
+ schedule(action: ScriptedAction): void;
961
+ dispatch(event: HostEvent): void;
962
+ start(): Promise<void>;
963
+ pause(): void;
964
+ resume(): void;
965
+ readonly paused: boolean;
966
+ key(key: string): void;
967
+ /** Pointer-down at the next frame. Use `schedule` for move/up. Canvas pixels, not CSS. */
968
+ click(x: number, y: number): void;
969
+ inspect(): Promise<HeadlessInspect>;
970
+ captureFrame(path?: string): Promise<CartSnapshot>;
971
+ remount(options?: MountOptions<T>): CartHandle;
972
+ destroy(): void;
973
+ };
974
+ declare function createHeadlessHarness<T>(options: CreateHeadlessHarnessOptions<T>): HeadlessHarness<T>;
975
+
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
+
801
1031
  type CreateHeadlessMultiCartHarnessOptions = CreateRuntimeGroupOptions;
802
1032
  type HeadlessMultiCartHarness = RuntimeGroup;
803
1033
  declare function createHeadlessMultiCartHarness(options: CreateHeadlessMultiCartHarnessOptions): HeadlessMultiCartHarness;
804
1034
 
805
- export { type CreateHeadlessHarnessOptions, type CreateHeadlessMultiCartHarnessOptions, DEFAULT_HEADLESS_HEIGHT, DEFAULT_HEADLESS_WIDTH, HEADLESS_PNG_DATA_URL, type HeadlessFrameError, type HeadlessHarness, type HeadlessInspect, type HeadlessMultiCartHarness, createHeadlessHarness, createHeadlessMultiCartHarness, installHeadlessCanvas };
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 };