@uptimizr/replay 0.1.0

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.
Files changed (46) hide show
  1. package/AGENTS.md +57 -0
  2. package/LICENSE +201 -0
  3. package/README.md +96 -0
  4. package/dist/drivers/babylon-lite.d.ts +134 -0
  5. package/dist/drivers/babylon-lite.d.ts.map +1 -0
  6. package/dist/drivers/babylon-lite.js +167 -0
  7. package/dist/drivers/babylon-lite.js.map +1 -0
  8. package/dist/drivers/babylon.d.ts +99 -0
  9. package/dist/drivers/babylon.d.ts.map +1 -0
  10. package/dist/drivers/babylon.js +176 -0
  11. package/dist/drivers/babylon.js.map +1 -0
  12. package/dist/drivers/playcanvas.d.ts +122 -0
  13. package/dist/drivers/playcanvas.d.ts.map +1 -0
  14. package/dist/drivers/playcanvas.js +206 -0
  15. package/dist/drivers/playcanvas.js.map +1 -0
  16. package/dist/drivers/three.d.ts +127 -0
  17. package/dist/drivers/three.d.ts.map +1 -0
  18. package/dist/drivers/three.js +194 -0
  19. package/dist/drivers/three.js.map +1 -0
  20. package/dist/fetchSession.d.ts +43 -0
  21. package/dist/fetchSession.d.ts.map +1 -0
  22. package/dist/fetchSession.js +103 -0
  23. package/dist/fetchSession.js.map +1 -0
  24. package/dist/global.d.ts +62 -0
  25. package/dist/global.d.ts.map +1 -0
  26. package/dist/global.js +95 -0
  27. package/dist/global.js.map +1 -0
  28. package/dist/index.d.ts +14 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +11 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/player.d.ts +35 -0
  33. package/dist/player.d.ts.map +1 -0
  34. package/dist/player.js +104 -0
  35. package/dist/player.js.map +1 -0
  36. package/dist/reconstruct.d.ts +74 -0
  37. package/dist/reconstruct.d.ts.map +1 -0
  38. package/dist/reconstruct.js +266 -0
  39. package/dist/reconstruct.js.map +1 -0
  40. package/dist/types.d.ts +39 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +2 -0
  43. package/dist/types.js.map +1 -0
  44. package/dist/uptimizr-replay.global.js +71 -0
  45. package/llms.txt +17 -0
  46. package/package.json +100 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"babylon-lite.js","sourceRoot":"","sources":["../../src/drivers/babylon-lite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import type { Camera, Scene } from "@babylonjs/core";
2
+ import type { CustomPropValue } from "@uptimizr/schema";
3
+ import type { ReplayDriver } from "../types.js";
4
+ export interface BabylonReplayDriverOptions {
5
+ /** Scene to re-drive. */
6
+ scene: Scene;
7
+ /** Camera to move. Defaults to `scene.activeCamera`. */
8
+ camera?: Camera;
9
+ /** Called for each replayed pointer event, so the host can render a marker. */
10
+ onPointer?: (screen: [number, number] | undefined, hitPoint: [number, number, number] | undefined, hitMesh: string | undefined, type: "pointer_move" | "pointer_click" | "pointer_down" | "pointer_up") => void;
11
+ /** Called for each replayed mesh interaction, so the host can highlight it. */
12
+ onMeshInteraction?: (mesh: string, kind: string, point: [number, number, number] | undefined) => void;
13
+ /**
14
+ * Scene-actor resolvers for `node_transform` drive-back (ADR 0027). Maps a
15
+ * recorded `nodeId` to the live Babylon node to re-drive — a direct
16
+ * `TransformNode`/`AbstractMesh` or a `() => node` resolver (robust to load
17
+ * order). Tier-1 samples set the node's world transform; Tier-2 samples
18
+ * (`boneId` present) set the matching skeleton bone's local transform.
19
+ */
20
+ nodes?: Record<string, BabylonReplayNode | (() => BabylonReplayNode | null | undefined)>;
21
+ /**
22
+ * Called for **every** replayed `node_transform` (Tier 1 and Tier 2), after any
23
+ * node/bone drive-back. Lets a host render a proxy marker for an actor it has
24
+ * no live node for, or annotate the timeline. `boneId` is set for Tier-2 bone
25
+ * samples; the transform is in the sample's frame (world for Tier 1,
26
+ * skeleton-local for Tier 2).
27
+ */
28
+ onNodeTransform?: (nodeId: string, transform: {
29
+ boneId: string | undefined;
30
+ childPath: string | undefined;
31
+ position: [number, number, number];
32
+ rotation: [number, number, number, number];
33
+ scale: [number, number, number] | undefined;
34
+ }, ts: number) => void;
35
+ /**
36
+ * Called for each replayed developer-defined `custom` event. The driver can't
37
+ * know how to render an arbitrary domain event, so it forwards the name,
38
+ * props, and timestamp for the host to visualize (marker, toast, log, ...).
39
+ */
40
+ onCustom?: (name: string, props: Record<string, CustomPropValue> | undefined, ts: number) => void;
41
+ /**
42
+ * Called for each replayed `input_action` (discrete keyboard/gamepad action,
43
+ * ADR 0023). The driver can't re-drive an app-defined action, so it forwards
44
+ * the action, raw `code`/`button`, source, and timestamp for the host to
45
+ * annotate the timeline.
46
+ */
47
+ onInputAction?: (input: {
48
+ action: string;
49
+ code: string | undefined;
50
+ button: number | undefined;
51
+ pressed: boolean | undefined;
52
+ source: string | undefined;
53
+ }, ts: number) => void;
54
+ /**
55
+ * Called for each replayed browser/engine lifecycle event (viewport resize,
56
+ * tab visibility, window focus, GPU context loss/restore). The driver doesn't
57
+ * re-drive these — it forwards them so a replay UI can annotate the timeline
58
+ * with what was happening around the scene.
59
+ */
60
+ onLifecycle?: (event: {
61
+ type: "viewport_resize";
62
+ width: number;
63
+ height: number;
64
+ dpr: number | undefined;
65
+ } | {
66
+ type: "visibility_change";
67
+ state: "visible" | "hidden";
68
+ } | {
69
+ type: "focus_change";
70
+ focused: boolean;
71
+ } | {
72
+ type: "context_lost";
73
+ } | {
74
+ type: "context_restored";
75
+ }, ts: number) => void;
76
+ /**
77
+ * Called for each replayed `runtime_error` event (opt-in capture, ADR 0013).
78
+ * Forwarded so a replay UI can mark where a JS error or unhandled rejection
79
+ * interrupted the session.
80
+ */
81
+ onError?: (error: {
82
+ kind: "error" | "unhandledrejection";
83
+ message: string;
84
+ source: string | undefined;
85
+ lineno: number | undefined;
86
+ colno: number | undefined;
87
+ stack: string | undefined;
88
+ }, ts: number) => void;
89
+ }
90
+ /** A Babylon node a replay can drive (a `TransformNode` or `AbstractMesh`). */
91
+ export type BabylonReplayNode = object;
92
+ /**
93
+ * Babylon replay driver. Re-drives camera pose (and surfaces pointer/mesh events
94
+ * to host callbacks) in the user's own scene.
95
+ *
96
+ * It only reads/writes the scene — it never emits analytics events (ADR 0006).
97
+ */
98
+ export declare function createBabylonReplayDriver(options: BabylonReplayDriverOptions): ReplayDriver;
99
+ //# sourceMappingURL=babylon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"babylon.d.ts","sourceRoot":"","sources":["../../src/drivers/babylon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,176 @@
1
+ import { Vector3 } from "@babylonjs/core/Maths/math.vector.js";
2
+ import { Quaternion } from "@babylonjs/core/Maths/math.vector.js";
3
+ /** Resolve a node entry (direct ref or `() => node`) to a live node, or `null`. */
4
+ function resolveReplayNode(entry) {
5
+ if (!entry)
6
+ return null;
7
+ if (typeof entry === "function") {
8
+ return entry() ?? null;
9
+ }
10
+ return entry;
11
+ }
12
+ /**
13
+ * Walk a `/`-separated {@link NodeTransformEvent.childPath} from a resolved root
14
+ * actor to the descendant node, matching each segment against direct children by
15
+ * `name` (ADR 0033). Returns `null` if any segment is missing.
16
+ */
17
+ function resolveChildByPath(root, childPath) {
18
+ let cur = root;
19
+ for (const segment of childPath.split("/")) {
20
+ if (!segment)
21
+ continue;
22
+ const children = cur?.getChildren?.();
23
+ if (!Array.isArray(children))
24
+ return null;
25
+ cur = children.find((c) => c.name === segment) ?? null;
26
+ if (!cur)
27
+ return null;
28
+ }
29
+ return cur;
30
+ }
31
+ /**
32
+ * Babylon replay driver. Re-drives camera pose (and surfaces pointer/mesh events
33
+ * to host callbacks) in the user's own scene.
34
+ *
35
+ * It only reads/writes the scene — it never emits analytics events (ADR 0006).
36
+ */
37
+ export function createBabylonReplayDriver(options) {
38
+ const target = new Vector3();
39
+ const position = new Vector3();
40
+ const nodePosition = new Vector3();
41
+ return {
42
+ reset() {
43
+ // Camera state is fully determined by the next camera_sample, so there is
44
+ // nothing to restore; host-rendered markers are the host's responsibility.
45
+ },
46
+ apply(event) {
47
+ switch (event.type) {
48
+ case "camera_sample": {
49
+ const cam = (options.camera ??
50
+ options.scene.activeCamera);
51
+ if (!cam)
52
+ return;
53
+ const [px, py, pz] = event.position;
54
+ if (event.target) {
55
+ target.set(event.target[0], event.target[1], event.target[2]);
56
+ }
57
+ else {
58
+ const [dx, dy, dz] = event.direction;
59
+ target.set(px + dx, py + dy, pz + dz);
60
+ }
61
+ // Set the target first so ArcRotateCamera derives alpha/beta/radius
62
+ // relative to it, then place the camera.
63
+ cam.setTarget?.(target);
64
+ if (typeof cam.setPosition === "function") {
65
+ position.set(px, py, pz);
66
+ cam.setPosition(position);
67
+ }
68
+ else {
69
+ cam.position.set(px, py, pz);
70
+ }
71
+ break;
72
+ }
73
+ case "pointer_move":
74
+ case "pointer_click":
75
+ case "pointer_down":
76
+ case "pointer_up":
77
+ options.onPointer?.(event.screen, event.hitPoint, event.hitMesh, event.type);
78
+ break;
79
+ case "mesh_interaction":
80
+ options.onMeshInteraction?.(event.mesh, event.kind, event.point);
81
+ break;
82
+ case "custom":
83
+ options.onCustom?.(event.name, event.props, event.ts);
84
+ break;
85
+ case "input_action":
86
+ options.onInputAction?.({
87
+ action: event.action,
88
+ code: event.code,
89
+ button: event.button,
90
+ pressed: event.pressed,
91
+ source: event.source,
92
+ }, event.ts);
93
+ break;
94
+ case "viewport_resize":
95
+ options.onLifecycle?.({ type: "viewport_resize", width: event.width, height: event.height, dpr: event.dpr }, event.ts);
96
+ break;
97
+ case "visibility_change":
98
+ options.onLifecycle?.({ type: "visibility_change", state: event.state }, event.ts);
99
+ break;
100
+ case "focus_change":
101
+ options.onLifecycle?.({ type: "focus_change", focused: event.focused }, event.ts);
102
+ break;
103
+ case "context_lost":
104
+ options.onLifecycle?.({ type: "context_lost" }, event.ts);
105
+ break;
106
+ case "context_restored":
107
+ options.onLifecycle?.({ type: "context_restored" }, event.ts);
108
+ break;
109
+ case "runtime_error":
110
+ options.onError?.({
111
+ kind: event.kind,
112
+ message: event.message,
113
+ source: event.source,
114
+ lineno: event.lineno,
115
+ colno: event.colno,
116
+ stack: event.stack,
117
+ }, event.ts);
118
+ break;
119
+ case "camera_gesture":
120
+ // Derived navigation annotation (ADR 0025); the camera trajectory is
121
+ // reconstructed from camera_sample, so replay intentionally ignores it.
122
+ break;
123
+ case "node_transform": {
124
+ const node = resolveReplayNode(options.nodes?.[event.nodeId]);
125
+ if (node) {
126
+ if (event.boneId !== undefined) {
127
+ // Tier 2: drive the matching skeleton bone's LOCAL pose.
128
+ const bone = node.skeleton?.bones?.find((b) => b.name === event.boneId);
129
+ if (bone) {
130
+ bone.position?.set(event.position[0], event.position[1], event.position[2]);
131
+ bone.rotationQuaternion = new Quaternion(event.rotation[0], event.rotation[1], event.rotation[2], event.rotation[3]);
132
+ if (event.scale) {
133
+ bone.scaling?.set(event.scale[0], event.scale[1], event.scale[2]);
134
+ }
135
+ bone.markAsDirty?.();
136
+ }
137
+ }
138
+ else {
139
+ // Tier 1: the declared root (no childPath) or a subtree child resolved
140
+ // by its relative path (ADR 0033). Both carry a world-frame pose;
141
+ // `setAbsolutePosition` honours any parent, a plain root falls back
142
+ // to `position`.
143
+ const target = event.childPath === undefined ? node : resolveChildByPath(node, event.childPath);
144
+ if (target) {
145
+ if (typeof target.setAbsolutePosition === "function") {
146
+ nodePosition.set(event.position[0], event.position[1], event.position[2]);
147
+ target.setAbsolutePosition(nodePosition);
148
+ }
149
+ else {
150
+ target.position?.set(event.position[0], event.position[1], event.position[2]);
151
+ }
152
+ // Babylon ignores `rotation` (Euler) once a quaternion is present;
153
+ // assign a fresh one so the world orientation is applied verbatim.
154
+ target.rotationQuaternion = new Quaternion(event.rotation[0], event.rotation[1], event.rotation[2], event.rotation[3]);
155
+ if (event.scale) {
156
+ target.scaling?.set(event.scale[0], event.scale[1], event.scale[2]);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ options.onNodeTransform?.(event.nodeId, {
162
+ boneId: event.boneId,
163
+ childPath: event.childPath,
164
+ position: event.position,
165
+ rotation: event.rotation,
166
+ scale: event.scale,
167
+ }, event.ts);
168
+ break;
169
+ }
170
+ default:
171
+ break;
172
+ }
173
+ },
174
+ };
175
+ }
176
+ //# sourceMappingURL=babylon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"babylon.js","sourceRoot":"","sources":["../../src/drivers/babylon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ import type { CustomPropValue } from "@uptimizr/schema";
2
+ import type { ReplayDriver } from "../types.js";
3
+ /**
4
+ * Structural view of the PlayCanvas objects this driver writes to. Kept minimal and
5
+ * structural so `playcanvas` stays an **optional peer dependency** — the driver
6
+ * never imports PlayCanvas at runtime; the host page owns the app and the real
7
+ * Entity.
8
+ */
9
+ interface PlayCanvasReplayCamera {
10
+ /** `Entity.setPosition(x, y, z)` — world-space move. */
11
+ setPosition(x: number, y: number, z: number): void;
12
+ /** `Entity.lookAt(x, y, z)` — orients the entity's forward axis at the point. */
13
+ lookAt(x: number, y: number, z: number): void;
14
+ /** The camera component; `fov` is the vertical FOV in **degrees** (PlayCanvas). */
15
+ camera?: {
16
+ fov?: number;
17
+ };
18
+ }
19
+ /** A PlayCanvas node a replay can drive (an `Entity`). */
20
+ export type PlayCanvasReplayNode = object;
21
+ export interface PlayCanvasReplayDriverOptions {
22
+ /**
23
+ * Camera Entity to move. PlayCanvas has no single active camera, so the one to
24
+ * re-drive is required. (Read/write only — the driver never emits analytics.)
25
+ */
26
+ camera: PlayCanvasReplayCamera;
27
+ /** Called for each replayed pointer event, so the host can render a marker. */
28
+ onPointer?: (screen: [number, number] | undefined, hitPoint: [number, number, number] | undefined, hitMesh: string | undefined, type: "pointer_move" | "pointer_click" | "pointer_down" | "pointer_up") => void;
29
+ /** Called for each replayed mesh interaction, so the host can highlight it. */
30
+ onMeshInteraction?: (mesh: string, kind: string, point: [number, number, number] | undefined) => void;
31
+ /**
32
+ * Scene-actor resolvers for `node_transform` drive-back (ADR 0027). Maps a
33
+ * recorded `nodeId` to the live PlayCanvas `Entity` to re-drive — a direct
34
+ * reference or a `() => entity` resolver. Tier-1 (world-frame) samples are
35
+ * converted canonical → PlayCanvas and applied via `setPosition`/`setRotation`.
36
+ * Tier-2 bone samples drive the matching named bone of the entity's skin in its
37
+ * local frame; every sample is also forwarded via {@link onNodeTransform}.
38
+ */
39
+ nodes?: Record<string, PlayCanvasReplayNode | (() => PlayCanvasReplayNode | null | undefined)>;
40
+ /**
41
+ * Called for **every** replayed `node_transform` (Tier 1 and Tier 2), after any
42
+ * node drive-back. The position/rotation are converted into PlayCanvas'
43
+ * right-handed frame; `boneId` is set for Tier-2 samples.
44
+ */
45
+ onNodeTransform?: (nodeId: string, transform: {
46
+ boneId: string | undefined;
47
+ childPath: string | undefined;
48
+ position: [number, number, number];
49
+ rotation: [number, number, number, number];
50
+ scale: [number, number, number] | undefined;
51
+ }, ts: number) => void;
52
+ /**
53
+ * Called for each replayed developer-defined `custom` event. The driver can't
54
+ * know how to render an arbitrary domain event, so it forwards the name, props,
55
+ * and timestamp for the host to visualize (marker, toast, log, ...).
56
+ */
57
+ onCustom?: (name: string, props: Record<string, CustomPropValue> | undefined, ts: number) => void;
58
+ /**
59
+ * Called for each replayed `input_action` (discrete keyboard/gamepad action,
60
+ * ADR 0023). Forwarded with the action, raw `code`/`button`, source, and
61
+ * timestamp so a replay UI can annotate the timeline.
62
+ */
63
+ onInputAction?: (input: {
64
+ action: string;
65
+ code: string | undefined;
66
+ button: number | undefined;
67
+ pressed: boolean | undefined;
68
+ source: string | undefined;
69
+ }, ts: number) => void;
70
+ /**
71
+ * Called for each replayed browser/engine lifecycle event (viewport resize, tab
72
+ * visibility, window focus, GPU context loss/restore). The driver doesn't
73
+ * re-drive these — it forwards them so a replay UI can annotate the timeline.
74
+ */
75
+ onLifecycle?: (event: {
76
+ type: "viewport_resize";
77
+ width: number;
78
+ height: number;
79
+ dpr: number | undefined;
80
+ } | {
81
+ type: "visibility_change";
82
+ state: "visible" | "hidden";
83
+ } | {
84
+ type: "focus_change";
85
+ focused: boolean;
86
+ } | {
87
+ type: "context_lost";
88
+ } | {
89
+ type: "context_restored";
90
+ }, ts: number) => void;
91
+ /**
92
+ * Called for each replayed `runtime_error` event (opt-in capture, ADR 0013).
93
+ * Forwarded so a replay UI can mark where a JS error or unhandled rejection
94
+ * interrupted the session.
95
+ */
96
+ onError?: (error: {
97
+ kind: "error" | "unhandledrejection";
98
+ message: string;
99
+ source: string | undefined;
100
+ lineno: number | undefined;
101
+ colno: number | undefined;
102
+ stack: string | undefined;
103
+ }, ts: number) => void;
104
+ }
105
+ /**
106
+ * PlayCanvas replay driver. Re-drives camera pose (and surfaces pointer/mesh events
107
+ * to host callbacks) in the user's own PlayCanvas app.
108
+ *
109
+ * ## Coordinate frame (canonical → PlayCanvas)
110
+ * Events arrive in the canonical **left-handed, y-up** frame (ADR 0018); PlayCanvas
111
+ * is **right-handed**, so world-space data is converted back with the shared
112
+ * `fromCanonical*` helpers (the Z-negation reflection is its own inverse). Camera
113
+ * orientation is applied via `camera.lookAt(target)` — `lookAt` resolves
114
+ * PlayCanvas' forward-axis convention internally, so the driver does **not**
115
+ * re-derive a forward axis from the recorded direction (avoiding the orientation
116
+ * trap documented in sdk-core `coordinates.ts`).
117
+ *
118
+ * It only reads/writes the scene — it never emits analytics events (ADR 0006).
119
+ */
120
+ export declare function createPlayCanvasReplayDriver(options: PlayCanvasReplayDriverOptions): ReplayDriver;
121
+ export {};
122
+ //# sourceMappingURL=playcanvas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playcanvas.d.ts","sourceRoot":"","sources":["../../src/drivers/playcanvas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,206 @@
1
+ import { fromCanonicalDirection, fromCanonicalPosition, fromCanonicalQuat, } from "@uptimizr/sdk-core";
2
+ /** Find a skeleton bone of `entity` by name across its mesh-instance skins, or `null`. */
3
+ function findEntityBone(entity, boneId) {
4
+ const instances = [
5
+ ...(entity.render?.meshInstances ?? []),
6
+ ...(entity.model?.meshInstances ?? []),
7
+ ];
8
+ for (const mi of instances) {
9
+ const bone = mi.skinInstance?.bones?.find((b) => b?.name === boneId);
10
+ if (bone)
11
+ return bone;
12
+ }
13
+ return null;
14
+ }
15
+ /** Resolve a node entry (direct ref or `() => node`) to a live Entity, or `null`. */
16
+ function resolveReplayNode(entry) {
17
+ if (!entry)
18
+ return null;
19
+ if (typeof entry === "function") {
20
+ return entry() ?? null;
21
+ }
22
+ return entry;
23
+ }
24
+ /**
25
+ * Walk a `/`-separated {@link NodeTransformEvent.childPath} from a resolved root
26
+ * actor to the descendant entity, matching each segment against direct children
27
+ * by `name` (ADR 0033). Returns `null` if any segment is missing.
28
+ */
29
+ function resolveChildByPath(root, childPath) {
30
+ let cur = root;
31
+ for (const segment of childPath.split("/")) {
32
+ if (!segment)
33
+ continue;
34
+ const children = cur?.children;
35
+ if (!Array.isArray(children))
36
+ return null;
37
+ cur = children.find((c) => c.name === segment) ?? null;
38
+ if (!cur)
39
+ return null;
40
+ }
41
+ return cur;
42
+ }
43
+ /**
44
+ * PlayCanvas replay driver. Re-drives camera pose (and surfaces pointer/mesh events
45
+ * to host callbacks) in the user's own PlayCanvas app.
46
+ *
47
+ * ## Coordinate frame (canonical → PlayCanvas)
48
+ * Events arrive in the canonical **left-handed, y-up** frame (ADR 0018); PlayCanvas
49
+ * is **right-handed**, so world-space data is converted back with the shared
50
+ * `fromCanonical*` helpers (the Z-negation reflection is its own inverse). Camera
51
+ * orientation is applied via `camera.lookAt(target)` — `lookAt` resolves
52
+ * PlayCanvas' forward-axis convention internally, so the driver does **not**
53
+ * re-derive a forward axis from the recorded direction (avoiding the orientation
54
+ * trap documented in sdk-core `coordinates.ts`).
55
+ *
56
+ * It only reads/writes the scene — it never emits analytics events (ADR 0006).
57
+ */
58
+ export function createPlayCanvasReplayDriver(options) {
59
+ return {
60
+ reset() {
61
+ // Camera state is fully determined by the next camera_sample, so there is
62
+ // nothing to restore; host-rendered markers are the host's responsibility.
63
+ },
64
+ apply(event) {
65
+ switch (event.type) {
66
+ case "camera_sample": {
67
+ const cam = options.camera;
68
+ if (!cam)
69
+ return;
70
+ // Position: canonical → PlayCanvas (Z-negated).
71
+ const [px, py, pz] = fromCanonicalPosition(event.position, "right");
72
+ cam.setPosition(px, py, pz);
73
+ // Look target: an explicit recorded target, else position + direction —
74
+ // both converted to PlayCanvas' frame. lookAt handles the forward axis.
75
+ let tx;
76
+ let ty;
77
+ let tz;
78
+ if (event.target) {
79
+ [tx, ty, tz] = fromCanonicalPosition(event.target, "right");
80
+ }
81
+ else {
82
+ const [dx, dy, dz] = fromCanonicalDirection(event.direction, "right");
83
+ tx = px + dx;
84
+ ty = py + dy;
85
+ tz = pz + dz;
86
+ }
87
+ cam.lookAt(tx, ty, tz);
88
+ // PlayCanvas' camera FOV is in degrees; the wire carries radians.
89
+ if (cam.camera && typeof event.fov === "number") {
90
+ cam.camera.fov = (event.fov * 180) / Math.PI;
91
+ }
92
+ break;
93
+ }
94
+ case "pointer_move":
95
+ case "pointer_click":
96
+ case "pointer_down":
97
+ case "pointer_up": {
98
+ // Convert the world-space hit point back into PlayCanvas' frame so a host
99
+ // marker lands correctly; screen coords are engine-independent.
100
+ const hitPoint = event.hitPoint
101
+ ? fromCanonicalPosition(event.hitPoint, "right")
102
+ : undefined;
103
+ options.onPointer?.(event.screen, hitPoint, event.hitMesh, event.type);
104
+ break;
105
+ }
106
+ case "mesh_interaction": {
107
+ const point = event.point ? fromCanonicalPosition(event.point, "right") : undefined;
108
+ options.onMeshInteraction?.(event.mesh, event.kind, point);
109
+ break;
110
+ }
111
+ case "custom":
112
+ options.onCustom?.(event.name, event.props, event.ts);
113
+ break;
114
+ case "input_action":
115
+ options.onInputAction?.({
116
+ action: event.action,
117
+ code: event.code,
118
+ button: event.button,
119
+ pressed: event.pressed,
120
+ source: event.source,
121
+ }, event.ts);
122
+ break;
123
+ case "viewport_resize":
124
+ options.onLifecycle?.({ type: "viewport_resize", width: event.width, height: event.height, dpr: event.dpr }, event.ts);
125
+ break;
126
+ case "visibility_change":
127
+ options.onLifecycle?.({ type: "visibility_change", state: event.state }, event.ts);
128
+ break;
129
+ case "focus_change":
130
+ options.onLifecycle?.({ type: "focus_change", focused: event.focused }, event.ts);
131
+ break;
132
+ case "context_lost":
133
+ options.onLifecycle?.({ type: "context_lost" }, event.ts);
134
+ break;
135
+ case "context_restored":
136
+ options.onLifecycle?.({ type: "context_restored" }, event.ts);
137
+ break;
138
+ case "runtime_error":
139
+ options.onError?.({
140
+ kind: event.kind,
141
+ message: event.message,
142
+ source: event.source,
143
+ lineno: event.lineno,
144
+ colno: event.colno,
145
+ stack: event.stack,
146
+ }, event.ts);
147
+ break;
148
+ case "camera_gesture":
149
+ // Derived navigation annotation (ADR 0025); the camera trajectory is
150
+ // reconstructed from camera_sample, so replay intentionally ignores it.
151
+ break;
152
+ case "node_transform": {
153
+ const node = resolveReplayNode(options.nodes?.[event.nodeId]);
154
+ if (node) {
155
+ if (event.boneId !== undefined) {
156
+ // Tier 2: drive the named bone's parent-relative LOCAL pose. The
157
+ // canonical reflection conjugates a local transform the same way it
158
+ // does a world one, so the same convert applies (ADR 0027).
159
+ const bone = findEntityBone(node, event.boneId);
160
+ if (bone) {
161
+ const [px, py, pz] = fromCanonicalPosition(event.position, "right");
162
+ bone.setLocalPosition?.(px, py, pz);
163
+ const [qx, qy, qz, qw] = fromCanonicalQuat(event.rotation, "right");
164
+ bone.setLocalRotation?.(qx, qy, qz, qw);
165
+ if (event.scale) {
166
+ bone.setLocalScale?.(event.scale[0], event.scale[1], event.scale[2]);
167
+ }
168
+ }
169
+ }
170
+ else {
171
+ // Tier 1: the declared root (no childPath) or a subtree child resolved
172
+ // by its relative path (ADR 0033). Both carry a world-frame pose;
173
+ // `setPosition`/`setRotation` are world setters, correct for either.
174
+ const target = event.childPath === undefined ? node : resolveChildByPath(node, event.childPath);
175
+ if (target) {
176
+ const [px, py, pz] = fromCanonicalPosition(event.position, "right");
177
+ target.setPosition?.(px, py, pz);
178
+ const [qx, qy, qz, qw] = fromCanonicalQuat(event.rotation, "right");
179
+ target.setRotation?.(qx, qy, qz, qw);
180
+ if (event.scale) {
181
+ target.setLocalScale?.(event.scale[0], event.scale[1], event.scale[2]);
182
+ }
183
+ }
184
+ }
185
+ }
186
+ // Forward the (frame-converted) sample so the host can render a proxy
187
+ // marker or annotate the timeline; `boneId` is set for Tier-2 samples,
188
+ // `childPath` for Tier-1 subtree children (ADR 0033).
189
+ const fwdPos = fromCanonicalPosition(event.position, "right");
190
+ const fwdRot = fromCanonicalQuat(event.rotation, "right");
191
+ options.onNodeTransform?.(event.nodeId, {
192
+ boneId: event.boneId,
193
+ childPath: event.childPath,
194
+ position: fwdPos,
195
+ rotation: fwdRot,
196
+ scale: event.scale,
197
+ }, event.ts);
198
+ break;
199
+ }
200
+ default:
201
+ break;
202
+ }
203
+ },
204
+ };
205
+ }
206
+ //# sourceMappingURL=playcanvas.js.map