@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.
- package/README.md +96 -12
- package/dist/headless.d.ts +602 -4
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +835 -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 +95 -0
- package/docs/compositor.md +103 -0
- package/docs/events.md +27 -22
- package/docs/headless-harness.md +46 -16
- package/docs/normalized-geometry.md +88 -0
- package/docs/presentation-adapter.md +10 -10
- package/docs/presentation-cue.md +1 -1
- package/docs/replay-inspector.md +88 -0
- package/docs/runtime-group.md +122 -0
- package/package.json +2 -2
package/dist/headless.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 };
|