@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.
- package/README.md +41 -18
- package/dist/headless.d.ts +497 -67
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +470 -7
- package/dist/index.js +1 -1
- package/docs/asset-resolver.md +4 -4
- 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/headless-harness.md +44 -14
- 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/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;
|
|
@@ -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
|
-
|
|
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 };
|