incanto 0.40.1 → 0.42.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/bin/incanto-frame.mjs +144 -0
  2. package/dist/2d.d.ts +2 -2
  3. package/dist/2d.js +3 -3
  4. package/dist/3d.d.ts +76 -4
  5. package/dist/3d.js +5 -5
  6. package/dist/{behavior-TA8nySqb.d.ts → behavior-62q0HWBO.d.ts} +3 -1
  7. package/dist/{create-game-B7c5icho.js → create-game-9KPppR0L.js} +4 -4
  8. package/dist/{create-game-p-O66Y_U.js → create-game-DItIRyXH.js} +158 -5
  9. package/dist/debug.d.ts +36 -2
  10. package/dist/debug.js +183 -2
  11. package/dist/{environment-presets-CIZXGKnS.js → environment-presets-Vl5xBsXp.js} +2 -2
  12. package/dist/{gameplay-By8mslMc.js → gameplay-DRi9524r.js} +13 -0
  13. package/dist/gameplay.d.ts +13 -1
  14. package/dist/gameplay.js +1 -1
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +3 -167
  17. package/dist/{loader-CvSDKC3v.d.ts → loader-CeyU_bm1.d.ts} +1 -1
  18. package/dist/net.d.ts +1 -1
  19. package/dist/net.js +2 -2
  20. package/dist/{pathfinding-_UlKUoox.d.ts → pathfinding-C49JSNNq.d.ts} +1 -1
  21. package/dist/{physics-2d-CxIqR1cW.js → physics-2d-BmgXBNDB.js} +1 -1
  22. package/dist/{physics-3d-CgIHivyL.js → physics-3d-C2G604O1.js} +2 -2
  23. package/dist/react.d.ts +1 -1
  24. package/dist/react.js +1 -1
  25. package/dist/{register-DMFJI-c_.js → register-BSXV8T9F.js} +5 -1
  26. package/dist/{register-D5v8iqjZ.js → register-D651it1J.js} +1 -1
  27. package/dist/{register-CgMO5JBY.js → register-R2JTnIMw.js} +1 -1
  28. package/dist/{replay-DN-kG4va.d.ts → replay-CAphXMyM.d.ts} +1 -1
  29. package/dist/{replay-D_QY5Xm3.js → replay-DilbZgQI.js} +1 -1
  30. package/dist/src-cU57Uwdw.js +166 -0
  31. package/dist/{test-DUrGse_6.js → test-DuOD1DO8.js} +22 -11
  32. package/dist/test.d.ts +3 -3
  33. package/dist/test.js +2 -2
  34. package/dist/vite.d.ts +61 -1
  35. package/dist/vite.js +149 -2
  36. package/editor/assets/{agent8-CFq3LMrx.js → agent8-Csw0T7jh.js} +1 -1
  37. package/editor/assets/debug-C3qeBD4X.js +3 -0
  38. package/editor/assets/{index-CRwy6mq9.js → index-B1rUkWxB.js} +50 -50
  39. package/editor/index.html +1 -1
  40. package/package.json +3 -2
  41. package/skills/incanto-audio.md +6 -2
  42. package/skills/incanto-verifying-your-game.md +60 -2
  43. package/templates-app/beacon-isle-3d/package.json +1 -1
  44. package/templates-app/tps-3d/package.json +1 -1
  45. package/templates-app/village-quest-3d/package.json +1 -1
  46. package/editor/assets/debug-C2aomumK.js +0 -3
@@ -0,0 +1,166 @@
1
+ //#region src/core/pathfinding.ts
2
+ /**
3
+ * A* from `from` to `to` (inclusive cell coords). Returns the cell path
4
+ * INCLUDING both endpoints, or null when unreachable. Straight steps cost
5
+ * 1, diagonals √2; the heuristic is octile (admissible for both modes).
6
+ */
7
+ function findPath(grid, from, to, opts) {
8
+ const [sx, sy] = from;
9
+ const [tx, ty] = to;
10
+ const { width, height } = grid;
11
+ const inBounds = (x, y) => x >= 0 && y >= 0 && x < width && y < height;
12
+ if (!inBounds(sx, sy) || !inBounds(tx, ty)) return null;
13
+ if (grid.solid(sx, sy) || grid.solid(tx, ty)) return null;
14
+ if (sx === tx && sy === ty) return [[sx, sy]];
15
+ const diagonal = opts?.diagonal ?? true;
16
+ const maxExpansions = opts?.maxExpansions ?? 2e4;
17
+ const idx = (x, y) => y * width + x;
18
+ const gScore = new Float64Array(width * height).fill(Number.POSITIVE_INFINITY);
19
+ const cameFrom = new Int32Array(width * height).fill(-1);
20
+ const closed = new Uint8Array(width * height);
21
+ const heap = [];
22
+ const fScore = new Float64Array(width * height).fill(Number.POSITIVE_INFINITY);
23
+ const push = (n) => {
24
+ heap.push(n);
25
+ let i = heap.length - 1;
26
+ while (i > 0) {
27
+ const p = i - 1 >> 1;
28
+ if (fScore[heap[p]] <= fScore[heap[i]]) break;
29
+ const t = heap[p];
30
+ heap[p] = heap[i];
31
+ heap[i] = t;
32
+ i = p;
33
+ }
34
+ };
35
+ const pop = () => {
36
+ const top = heap[0];
37
+ const last = heap.pop();
38
+ if (heap.length > 0) {
39
+ heap[0] = last;
40
+ let i = 0;
41
+ for (;;) {
42
+ const l = i * 2 + 1;
43
+ const r = l + 1;
44
+ let m = i;
45
+ if (l < heap.length && fScore[heap[l]] < fScore[heap[m]]) m = l;
46
+ if (r < heap.length && fScore[heap[r]] < fScore[heap[m]]) m = r;
47
+ if (m === i) break;
48
+ const t = heap[m];
49
+ heap[m] = heap[i];
50
+ heap[i] = t;
51
+ i = m;
52
+ }
53
+ }
54
+ return top;
55
+ };
56
+ const SQRT2 = Math.SQRT2;
57
+ const octile = (x, y) => {
58
+ const dx = Math.abs(x - tx);
59
+ const dy = Math.abs(y - ty);
60
+ return dx > dy ? dx - dy + SQRT2 * dy : dy - dx + SQRT2 * dx;
61
+ };
62
+ const start = idx(sx, sy);
63
+ gScore[start] = 0;
64
+ fScore[start] = octile(sx, sy);
65
+ push(start);
66
+ let expansions = 0;
67
+ while (heap.length > 0) {
68
+ if (++expansions > maxExpansions) return null;
69
+ const current = pop();
70
+ if (closed[current]) continue;
71
+ closed[current] = 1;
72
+ const cx = current % width;
73
+ const cy = current / width | 0;
74
+ if (cx === tx && cy === ty) {
75
+ const path = [];
76
+ let n = current;
77
+ while (n !== -1) {
78
+ path.push([n % width, n / width | 0]);
79
+ n = cameFrom[n];
80
+ }
81
+ path.reverse();
82
+ return path;
83
+ }
84
+ for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
85
+ if (dx === 0 && dy === 0) continue;
86
+ const isDiag = dx !== 0 && dy !== 0;
87
+ if (isDiag && !diagonal) continue;
88
+ const nx = cx + dx;
89
+ const ny = cy + dy;
90
+ if (!inBounds(nx, ny) || grid.solid(nx, ny)) continue;
91
+ if (isDiag && (grid.solid(cx + dx, cy) || grid.solid(cx, cy + dy))) continue;
92
+ const n = idx(nx, ny);
93
+ if (closed[n]) continue;
94
+ const tentative = gScore[current] + (isDiag ? SQRT2 : 1);
95
+ if (tentative < gScore[n]) {
96
+ gScore[n] = tentative;
97
+ fScore[n] = tentative + octile(nx, ny);
98
+ cameFrom[n] = current;
99
+ push(n);
100
+ }
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+ /** Build a PathGrid from row-strings ('#' = solid) — tests and tile games. */
106
+ function gridFromRows(rows) {
107
+ const height = rows.length;
108
+ return {
109
+ width: rows[0]?.length ?? 0,
110
+ height,
111
+ solid: (x, y) => (rows[y]?.[x] ?? "#") === "#"
112
+ };
113
+ }
114
+ //#endregion
115
+ //#region src/core/preload.ts
116
+ /**
117
+ * Warm the HTTP cache for a list of asset urls with a progress callback —
118
+ * the loading-bar pattern every template ships. Failures only warn and still
119
+ * count toward progress (a broken url must not block the game), but they are
120
+ * reported in the result so callers can tell success from 100%-with-holes.
121
+ */
122
+ async function preloadUrls(urls, onProgress) {
123
+ let loaded = 0;
124
+ const failed = [];
125
+ const total = urls.length;
126
+ if (total === 0) {
127
+ onProgress?.(0, 0);
128
+ return { failed };
129
+ }
130
+ await Promise.all(urls.map(async (url) => {
131
+ try {
132
+ await (await fetch(url)).arrayBuffer();
133
+ } catch (error) {
134
+ failed.push(url);
135
+ console.warn(`[incanto] preload failed for ${url}:`, error);
136
+ }
137
+ loaded += 1;
138
+ onProgress?.(loaded, total);
139
+ }));
140
+ return { failed };
141
+ }
142
+ /** Every url found in a scene's `assets` block (preload helper). */
143
+ function assetUrls(assets) {
144
+ return Object.values(assets ?? {}).map((a) => a.url).filter((u) => typeof u === "string" && !u.startsWith("data:"));
145
+ }
146
+ //#endregion
147
+ //#region src/core/uid-gen.ts
148
+ /**
149
+ * The ONE way to mint a node uid: crypto-strength, `n_` + 16 base36 chars
150
+ * (~82 bits). Hand-written uids are forbidden by convention — they break the
151
+ * collision-strength contract and read as fakes next to generated ones.
152
+ */
153
+ function newUid() {
154
+ const bytes = new Uint8Array(16);
155
+ if (globalThis.crypto?.getRandomValues) globalThis.crypto.getRandomValues(bytes);
156
+ else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
157
+ let id = "";
158
+ for (const b of bytes) id += (b % 36).toString(36);
159
+ return `n_${id.slice(0, 16)}`;
160
+ }
161
+ //#endregion
162
+ //#region src/index.ts
163
+ /** Engine version. Kept in sync with package.json by the release pipeline. */
164
+ const VERSION = "0.42.0";
165
+ //#endregion
166
+ export { findPath as a, preloadUrls as i, newUid as n, gridFromRows as o, assetUrls as r, VERSION as t };
@@ -1,13 +1,13 @@
1
1
  import { n as loadScene, w as registerBehavior } from "./loader-r49nDwB4.js";
2
- import { h as Engine } from "./register-DMFJI-c_.js";
2
+ import { h as Engine } from "./register-BSXV8T9F.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { n as startRecording } from "./replay-D_QY5Xm3.js";
4
+ import { n as startRecording } from "./replay-DilbZgQI.js";
5
5
  import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
6
6
  import { i as getNodeSchema, s as mergeStaticProps } from "./registry-IyWCGe4q.js";
7
- import { n as registerGameplayBehaviors } from "./gameplay-By8mslMc.js";
8
- import { t as registerNodes2D } from "./register-CgMO5JBY.js";
9
- import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-CIZXGKnS.js";
10
- import { t as registerNodesNet } from "./register-D5v8iqjZ.js";
7
+ import { n as registerGameplayBehaviors } from "./gameplay-DRi9524r.js";
8
+ import { t as registerNodes2D } from "./register-R2JTnIMw.js";
9
+ import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-Vl5xBsXp.js";
10
+ import { t as registerNodesNet } from "./register-D651it1J.js";
11
11
  import { Box3, Euler, Matrix4, PerspectiveCamera, Quaternion, Vector3 } from "three";
12
12
  //#region src/test/framing.ts
13
13
  /**
@@ -663,8 +663,19 @@ function positionOf(node) {
663
663
  p?.[2] ?? 0
664
664
  ];
665
665
  }
666
+ /**
667
+ * A body's velocity, whichever name it keeps it under.
668
+ *
669
+ * `RigidBody2D/3D` call it `linearVelocity` (the solver owns it);
670
+ * `CharacterBody2D/3D` call it `velocity` (Godot semantics — the game owns it
671
+ * and calls moveAndSlide). Reading only the first reported **zero for every
672
+ * CharacterBody game**, so `incanto-feel` answered `topSpeed 0` and every
673
+ * derived window as null — a feel report that cannot see the character move.
674
+ * It went unnoticed because every test in feel.test.ts drives a RigidBody3D.
675
+ */
666
676
  function velocityOf(node) {
667
- const v = node.linearVelocity;
677
+ const body = node;
678
+ const v = body.linearVelocity ?? body.velocity;
668
679
  return [
669
680
  v?.[0] ?? 0,
670
681
  v?.[1] ?? 0,
@@ -1244,10 +1255,10 @@ async function runScript(json, opts) {
1244
1255
  engine.setScene(scene);
1245
1256
  const physics = opts.physics ?? "auto";
1246
1257
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
1247
- const { enablePhysics2D } = await import("./physics-2d-CxIqR1cW.js").then((n) => n.r);
1258
+ const { enablePhysics2D } = await import("./physics-2d-BmgXBNDB.js").then((n) => n.r);
1248
1259
  await enablePhysics2D(engine);
1249
1260
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
1250
- const { enablePhysics3D } = await import("./physics-3d-CgIHivyL.js").then((n) => n.r);
1261
+ const { enablePhysics3D } = await import("./physics-3d-C2G604O1.js").then((n) => n.r);
1251
1262
  await enablePhysics3D(engine);
1252
1263
  }
1253
1264
  const failures = [];
@@ -1360,10 +1371,10 @@ async function createPlaySession(json, opts = {}) {
1360
1371
  engine.setScene(scene);
1361
1372
  const physics = opts.physics ?? "auto";
1362
1373
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
1363
- const { enablePhysics2D } = await import("./physics-2d-CxIqR1cW.js").then((n) => n.r);
1374
+ const { enablePhysics2D } = await import("./physics-2d-BmgXBNDB.js").then((n) => n.r);
1364
1375
  await enablePhysics2D(engine);
1365
1376
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
1366
- const { enablePhysics3D } = await import("./physics-3d-CgIHivyL.js").then((n) => n.r);
1377
+ const { enablePhysics3D } = await import("./physics-3d-C2G604O1.js").then((n) => n.r);
1367
1378
  await enablePhysics3D(engine);
1368
1379
  }
1369
1380
  const stepMs = 1e3 / (opts.fixedHz ?? 60);
package/dist/test.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Et as Node, P as Scene, b as Engine, kt as LogEntry, n as BehaviorCtor } from "./behavior-TA8nySqb.js";
1
+ import { Et as Node, P as Scene, b as Engine, kt as LogEntry, n as BehaviorCtor } from "./behavior-62q0HWBO.js";
2
2
  import { c as JsonValue, i as SceneJson } from "./schema-CFeioQRE.js";
3
- import { t as LoadSceneOptions } from "./loader-CvSDKC3v.js";
4
- import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-DN-kG4va.js";
3
+ import { t as LoadSceneOptions } from "./loader-CeyU_bm1.js";
4
+ import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-CAphXMyM.js";
5
5
 
6
6
  //#region src/test/framing.d.ts
7
7
  /** Where a node's origin sits relative to the current camera. */
package/dist/test.js CHANGED
@@ -1,3 +1,3 @@
1
- import { r as auditScene } from "./replay-D_QY5Xm3.js";
2
- import { a as registerAllNodes, c as feelReport, d as findPlayer, f as playtest, h as framingText, i as findFloatingProps, l as feelText, m as describeFraming, n as createPlaySession, o as runScript, p as playtestText, r as describeCapture, s as validateScene, t as captureScene, u as failingReplays } from "./test-DUrGse_6.js";
1
+ import { r as auditScene } from "./replay-DilbZgQI.js";
2
+ import { a as registerAllNodes, c as feelReport, d as findPlayer, f as playtest, h as framingText, i as findFloatingProps, l as feelText, m as describeFraming, n as createPlaySession, o as runScript, p as playtestText, r as describeCapture, s as validateScene, t as captureScene, u as failingReplays } from "./test-DuOD1DO8.js";
3
3
  export { auditScene, captureScene, createPlaySession, describeCapture, describeFraming, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, playtest, playtestText, registerAllNodes, runScript, validateScene };
package/dist/vite.d.ts CHANGED
@@ -1,3 +1,63 @@
1
+ //#region src/vite/discover.d.ts
2
+ /**
3
+ * The listening TCP ports in `/proc/net/tcp` (and `tcp6`) format.
4
+ *
5
+ * Linux-only and dependency-free ON PURPOSE: `lsof` and `ss` are routinely
6
+ * absent from a slim container, and this has to work in the container a
7
+ * vibe-coding service actually ships, not in a developer's laptop.
8
+ */
9
+ declare function parseProcNetTcp(text: string): number[];
10
+ interface DiscoverDeps {
11
+ /** Reads a proc file; returns null when it is not there (macOS, Windows). */
12
+ read(path: string): string | null;
13
+ }
14
+ /**
15
+ * Ports worth asking, in the order worth asking them.
16
+ *
17
+ * Observed first — those are facts. The usual suspects after, because a
18
+ * container that hides `/proc` should still find a vite server on 5173.
19
+ */
20
+ declare function listeningPorts(deps?: Partial<DiscoverDeps>): Promise<number[]>;
21
+ //#endregion
22
+ //#region src/vite/frame-endpoint.d.ts
23
+ interface FrameHost {
24
+ config?: {
25
+ root?: string;
26
+ };
27
+ middlewares: {
28
+ use(path: string, handler: (req: unknown, res: FrameRes) => void): void;
29
+ };
30
+ /** Vite's HMR channel. `ws` on v5, `hot` on v6+ — both are accepted. */
31
+ ws?: FrameChannel;
32
+ hot?: FrameChannel;
33
+ }
34
+ interface FrameChannel {
35
+ send(payload: {
36
+ type: "custom";
37
+ event: string;
38
+ data?: unknown;
39
+ }): void;
40
+ on(event: string, cb: (data: unknown) => void): void;
41
+ }
42
+ interface FrameRes {
43
+ statusCode: number;
44
+ setHeader(name: string, value: string): void;
45
+ end(body?: string): void;
46
+ }
47
+ interface FrameEndpointOptions {
48
+ /** Overridden in tests; defaults to the real clock. */
49
+ now?: () => number;
50
+ timeoutMs?: number;
51
+ }
52
+ /**
53
+ * Wire `/__incanto/ping` and `/__incanto/frame` onto a dev server.
54
+ *
55
+ * `ping` is how the CLI tells our server apart from anything else listening in
56
+ * the container: it enumerates the ports that are actually open and asks each
57
+ * one. That answer has to be cheap and unmistakable.
58
+ */
59
+ declare function serveFrameEndpoints(host: FrameHost, version: string, opts?: FrameEndpointOptions): void;
60
+ //#endregion
1
61
  //#region src/vite/index.d.ts
2
62
  interface HotUpdateContext {
3
63
  file: string;
@@ -161,4 +221,4 @@ declare function incantoLibrary(opts?: IncantoLibraryOptions): {
161
221
  /** @internal Exported for tests — the whole request/response behaviour. */
162
222
  declare function serveLibrary(req: LibraryReq, res: LibraryRes, opts?: IncantoLibraryOptions, doFetch?: typeof fetch): Promise<void>;
163
223
  //#endregion
164
- export { IncantoLibraryOptions, IncantoScenesOptions, ProjectFileEntry, discoverScenes, incantoLibrary, incantoScenes, resolveSceneFile, sceneFacts, serveLibrary, serveSceneFile, serveSceneList };
224
+ export { type FrameHost, IncantoLibraryOptions, IncantoScenesOptions, ProjectFileEntry, discoverScenes, incantoLibrary, incantoScenes, listeningPorts, parseProcNetTcp, resolveSceneFile, sceneFacts, serveFrameEndpoints, serveLibrary, serveSceneFile, serveSceneList };
package/dist/vite.js CHANGED
@@ -1,6 +1,152 @@
1
- import { s as validateScene } from "./test-DUrGse_6.js";
1
+ import { t as VERSION } from "./src-cU57Uwdw.js";
2
+ import { s as validateScene } from "./test-DuOD1DO8.js";
2
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
4
  import { basename, dirname, join, normalize, relative, resolve, sep } from "node:path";
5
+ //#region src/vite/frame-endpoint.ts
6
+ /**
7
+ * The dev server's half of `incanto-frame`.
8
+ *
9
+ * The server does not have the pixels — the BROWSER does. Vite already keeps a
10
+ * websocket to every connected client (that is how HMR works), so the frame
11
+ * request rides that channel: ask the page, wait, answer the CLI. No new
12
+ * transport, no file, nothing written anywhere.
13
+ *
14
+ * Three outcomes, and they must be told apart, because the fix differs:
15
+ * no server → the preview was never started
16
+ * server, no client → started, but nobody opened the page
17
+ * server + client → the frame
18
+ */
19
+ /** How long to wait for a page to answer before calling it absent. */
20
+ const CLIENT_TIMEOUT_MS = 2e3;
21
+ /**
22
+ * Wire `/__incanto/ping` and `/__incanto/frame` onto a dev server.
23
+ *
24
+ * `ping` is how the CLI tells our server apart from anything else listening in
25
+ * the container: it enumerates the ports that are actually open and asks each
26
+ * one. That answer has to be cheap and unmistakable.
27
+ */
28
+ function serveFrameEndpoints(host, version, opts = {}) {
29
+ const channel = host.hot ?? host.ws;
30
+ const timeoutMs = opts.timeoutMs ?? CLIENT_TIMEOUT_MS;
31
+ let nextId = 1;
32
+ const waiting = /* @__PURE__ */ new Map();
33
+ channel?.on("incanto:frame-report", (data) => {
34
+ const payload = data;
35
+ const id = payload?.id;
36
+ if (typeof id !== "number") return;
37
+ const resolve = waiting.get(id);
38
+ if (!resolve) return;
39
+ waiting.delete(id);
40
+ resolve({
41
+ ok: true,
42
+ report: payload?.report
43
+ });
44
+ });
45
+ host.middlewares.use("/__incanto/ping", (_req, res) => {
46
+ json$1(res, 200, {
47
+ incanto: version,
48
+ frame: Boolean(channel)
49
+ });
50
+ });
51
+ host.middlewares.use("/__incanto/frame", (_req, res) => {
52
+ if (!channel) {
53
+ json$1(res, 503, { error: "no-hmr-channel" });
54
+ return;
55
+ }
56
+ const id = nextId++;
57
+ const timer = setTimeout(() => {
58
+ waiting.delete(id);
59
+ json$1(res, 504, { error: "no-page-connected" });
60
+ }, timeoutMs);
61
+ waiting.set(id, (value) => {
62
+ clearTimeout(timer);
63
+ if (value.ok) json$1(res, 200, { report: value.report });
64
+ else json$1(res, 500, { error: "capture-failed" });
65
+ });
66
+ channel.send({
67
+ type: "custom",
68
+ event: "incanto:frame-request",
69
+ data: { id }
70
+ });
71
+ });
72
+ }
73
+ function json$1(res, status, body) {
74
+ res.statusCode = status;
75
+ res.setHeader("content-type", "application/json");
76
+ res.end(JSON.stringify(body));
77
+ }
78
+ //#endregion
79
+ //#region src/vite/discover.ts
80
+ /**
81
+ * Finding the dev server — the generic half of the frame capture.
82
+ *
83
+ * Nothing here knows what incanto is. It answers one question: which local
84
+ * ports might be serving something, best candidates first. Deciding which of
85
+ * them is OURS is a ping, and that belongs to the caller.
86
+ *
87
+ * This is deliberately code and not documentation. An agent told "check the
88
+ * screen" runs one command; anything it has to work out for itself — which
89
+ * port, whether the preview is even up — is a coin flip that lands differently
90
+ * every session.
91
+ */
92
+ /** Where a vite dev server usually is, tried after anything actually observed. */
93
+ const LIKELY = [
94
+ 5173,
95
+ 5174,
96
+ 5175,
97
+ 5176,
98
+ 4173,
99
+ 3e3,
100
+ 8080
101
+ ];
102
+ /** `/proc/net/tcp` state code for LISTEN. */
103
+ const TCP_LISTEN = "0A";
104
+ /**
105
+ * The listening TCP ports in `/proc/net/tcp` (and `tcp6`) format.
106
+ *
107
+ * Linux-only and dependency-free ON PURPOSE: `lsof` and `ss` are routinely
108
+ * absent from a slim container, and this has to work in the container a
109
+ * vibe-coding service actually ships, not in a developer's laptop.
110
+ */
111
+ function parseProcNetTcp(text) {
112
+ const ports = /* @__PURE__ */ new Set();
113
+ for (const line of text.split("\n")) {
114
+ const cols = line.trim().split(/\s+/);
115
+ if (cols.length < 4) continue;
116
+ const local = cols[1];
117
+ if (cols[3] !== TCP_LISTEN || !local?.includes(":")) continue;
118
+ const hex = local.slice(local.lastIndexOf(":") + 1);
119
+ const port = Number.parseInt(hex, 16);
120
+ if (Number.isFinite(port) && port > 0 && port < 65536) ports.add(port);
121
+ }
122
+ return [...ports];
123
+ }
124
+ /**
125
+ * Ports worth asking, in the order worth asking them.
126
+ *
127
+ * Observed first — those are facts. The usual suspects after, because a
128
+ * container that hides `/proc` should still find a vite server on 5173.
129
+ */
130
+ async function listeningPorts(deps) {
131
+ const read = deps?.read ?? ((path) => {
132
+ try {
133
+ const fs = globalThis["__incantoFs"];
134
+ return fs ? fs.readFileSync(path, "utf-8") : null;
135
+ } catch {
136
+ return null;
137
+ }
138
+ });
139
+ const observed = [...parseProcNetTcp(read("/proc/net/tcp") ?? ""), ...parseProcNetTcp(read("/proc/net/tcp6") ?? "")];
140
+ const seen = /* @__PURE__ */ new Set();
141
+ const out = [];
142
+ for (const port of [...observed, ...LIKELY]) {
143
+ if (seen.has(port)) continue;
144
+ seen.add(port);
145
+ out.push(port);
146
+ }
147
+ return out;
148
+ }
149
+ //#endregion
4
150
  //#region src/vite/index.ts
5
151
  /**
6
152
  * incanto/vite — dev-server integration: validate every `*.scene.json` edit
@@ -30,6 +176,7 @@ function incantoScenes(opts = {}) {
30
176
  * `incanto-editor` does. Dev-only by construction (`configureServer`).
31
177
  */
32
178
  configureServer(server) {
179
+ serveFrameEndpoints(server, VERSION);
33
180
  const root = opts.root ?? server.config?.root ?? process.cwd();
34
181
  server.middlewares.use("/api/scenes", (req, res) => {
35
182
  serveSceneList(req, res, root);
@@ -405,4 +552,4 @@ function libraryItem(row) {
405
552
  };
406
553
  }
407
554
  //#endregion
408
- export { discoverScenes, incantoLibrary, incantoScenes, resolveSceneFile, sceneFacts, serveLibrary, serveSceneFile, serveSceneList };
555
+ export { discoverScenes, incantoLibrary, incantoScenes, listeningPorts, parseProcNetTcp, resolveSceneFile, sceneFacts, serveFrameEndpoints, serveLibrary, serveSceneFile, serveSceneList };
@@ -1 +1 @@
1
- import{n as e}from"./index-CRwy6mq9.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
1
+ import{n as e}from"./index-B1rUkWxB.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
@@ -0,0 +1,3 @@
1
+ import{i as e,r as t,t as n}from"./index-B1rUkWxB.js";function r(e,t,n,r,i,a,o=180,s=120){let c=Math.max(o,n),l=Math.max(s,r);return{x:Math.min(Math.max(0,e),Math.max(0,i-c)),y:Math.min(Math.max(0,t),Math.max(0,a-l)),w:c,h:l}}function i(e,t){for(let[n,r]of Object.entries(t))e.style[n]=r}var a=`rgba(18, 20, 26, 0.92)`,o=`1px solid rgba(255,255,255,0.14)`,s=`12px ui-monospace, SFMono-Regular, Menlo, monospace`,c=class{host;el;body;onClose=()=>{};x;y;w;h;constructor(e,t,n,r){this.host=t,this.x=r.x,this.y=r.y,this.w=r.w,this.h=r.h,this.el=e.createElement(`div`),i(this.el,{position:`absolute`,background:a,border:o,borderRadius:`8px`,color:`rgba(255,255,255,0.88)`,font:s,display:`flex`,flexDirection:`column`,overflow:`hidden`,zIndex:`40`,pointerEvents:`auto`,boxShadow:`0 8px 28px rgba(0,0,0,0.45)`});let c=e.createElement(`div`);c.textContent=n,i(c,{padding:`6px 28px 6px 10px`,background:`rgba(255,255,255,0.07)`,cursor:`move`,userSelect:`none`,touchAction:`none`,fontWeight:`700`}),this.el.appendChild(c);let l=e.createElement(`div`);l.textContent=`×`,l.title=`close`,i(l,{position:`absolute`,top:`2px`,right:`8px`,cursor:`pointer`,fontSize:`16px`,lineHeight:`20px`,opacity:`0.7`}),l.addEventListener(`click`,()=>this.onClose()),this.el.appendChild(l),this.body=e.createElement(`div`),i(this.body,{flex:`1`,overflow:`auto`,padding:`8px 10px`}),this.el.appendChild(this.body);let u=e.createElement(`div`);u.textContent=`◢`,i(u,{position:`absolute`,right:`2px`,bottom:`0`,cursor:`nwse-resize`,opacity:`0.5`,userSelect:`none`,touchAction:`none`}),this.el.appendChild(u),this.wireDrag(c,(e,t)=>{this.x+=e,this.y+=t,this.layout()}),this.wireDrag(u,(e,t)=>{this.w+=e,this.h+=t,this.layout()}),this.layout(),t.appendChild(this.el)}remove(){this.el.remove()}layout(){let e=this.host.getBoundingClientRect(),t=r(this.x,this.y,this.w,this.h,e.width,e.height);this.x=t.x,this.y=t.y,this.w=t.w,this.h=t.h,i(this.el,{left:`${t.x}px`,top:`${t.y}px`,width:`${t.w}px`,height:`${t.h}px`})}wireDrag(e,t){let n=null,r=0,i=0;e.addEventListener(`pointerdown`,t=>{n=t.pointerId,r=t.clientX,i=t.clientY,e.setPointerCapture?.(t.pointerId)}),e.addEventListener(`pointermove`,e=>{e.pointerId===n&&(t(e.clientX-r,e.clientY-i),r=e.clientX,i=e.clientY)});let a=e=>{e.pointerId===n&&(n=null)};e.addEventListener(`pointerup`,a),e.addEventListener(`pointercancel`,a)}},l=96,u=200,d=8192;function f(e){return Array.isArray(e)?`Array(${e.length})`:`Object(${Object.keys(e).length} keys)`}function p(e,t){if(Array.isArray(e)&&e.some(e=>typeof e==`object`&&!!e)){let t=e.slice(0,u).map(e=>JSON.stringify(e)),n=e.length-u;return t.join(`
2
+ `)+(n>0?`\n… ${n} more`:``)}return t.length>d?`${t.slice(0,d)} … (${t.length-d} more chars)`:t}function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var h=2;function g(e,t={}){let n=t.doc??(typeof document<`u`?document:null);if(!n)return null;let r=t.container??(typeof document<`u`?document.body:null);return!r||typeof r.appendChild!=`function`?null:new y(e,r,n,t.statsSource,t.actions)}var _=300,v=[`log`,`info`,`warn`,`error`,`debug`],y=class{engine;container;doc;statsSource;actions;panels=new Map;cleanups=[];menuButton;dropdown=null;selected=null;collapsedFlags=new Map;statsChip=null;logRows=[];levelEnabled={debug:!0,info:!0,warn:!0,error:!0};consoleCapture=!1;consolePatched=[];frame=0;editing=0;detailOpen=new WeakMap;resumeScale=1;timeEls=null;hovering=!1;constructor(e,t,n,r,a=[]){this.engine=e,this.container=t,this.doc=n,this.statsSource=r,this.actions=a,this.menuButton=n.createElement(`div`),this.menuButton.textContent=`☰ debug`,i(this.menuButton,{position:`absolute`,top:`8px`,left:`8px`,padding:`4px 10px`,background:`rgba(18,20,26,0.85)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,color:`rgba(255,255,255,0.85)`,font:`12px ui-monospace, Menlo, monospace`,cursor:`pointer`,userSelect:`none`,zIndex:`50`,pointerEvents:`auto`}),this.menuButton.addEventListener(`click`,()=>this.toggleDropdown()),t.style.position||(t.style.position=`relative`),t.appendChild(this.menuButton),this.cleanups.push(e.log.added.connect(e=>{this.pushLog({level:e.level,source:`engine`,text:e.parts.map(S).join(` `)})})),this.cleanups.push(e.sceneChanged.connect(()=>{this.selected=null,this.applyColliderScope(),this.renderExplorer(),this.renderInspector()})),this.cleanups.push(e.updated.connect(()=>{this.frame+=1,this.frame%30==0&&(this.renderExplorer(),this.renderStats(),this.editing===0&&!this.hovering&&this.renderInspector(),this.syncTime())}))}isOpen(e){return e===`colliders`?this.colliderMode!==`off`:e===`stats`?this.statsChip!==null:this.panels.has(e)}colliderMode=`off`;setColliders(e){this.colliderMode=e,this.applyColliderScope()}applyColliderScope(){let e=this.colliderMode;for(let t of n(`2d`).concat(n(`3d`)))t.debugDraw=e!==`off`,t.debugScope=e===`selected`?this.selected:null}open(e){if(e===`stats`){this.openStatsChip();return}if(this.panels.has(e)){e===`explorer`&&this.renderExplorer(),e===`inspector`&&this.renderInspector(),e===`logs`&&this.renderLogs(),e===`time`&&this.renderTime();return}let t=new c(this.doc,this.container,{explorer:`Explorer`,inspector:`Inspector`,logs:`Logs`,time:`Time`}[e],{explorer:{x:12,y:44,w:240,h:320},inspector:{x:264,y:44,w:280,h:320},logs:{x:12,y:380,w:532,h:200},time:{x:556,y:44,w:260,h:150}}[e]);t.onClose=()=>this.close(e),e===`inspector`&&(t.body.addEventListener(`pointerenter`,()=>{this.hovering=!0}),t.body.addEventListener(`pointerleave`,()=>{this.hovering=!1})),this.panels.set(e,t),e===`explorer`&&this.renderExplorer(),e===`inspector`&&this.renderInspector(),e===`logs`&&this.renderLogs(),e===`time`&&this.renderTime()}close(e){if(e===`inspector`&&(this.hovering=!1),e===`stats`){this.statsChip?.remove(),this.statsChip=null;return}let t=this.panels.get(e);t&&(t.remove(),this.panels.delete(e))}toggle(e){if(e===`colliders`){this.setColliders({off:`all`,all:`selected`,selected:`off`}[this.colliderMode]);return}this.isOpen(e)?this.close(e):this.open(e)}setLevelEnabled(e,t){this.levelEnabled[e]=t,this.renderLogs()}setConsoleCapture(e){if(e!==this.consoleCapture)if(this.consoleCapture=e,e){this.renderLogs();let e=console;for(let t of v){let n=e[t];e[t]=(...e)=>{n.apply(console,e);let r=t===`log`?`info`:t;this.pushLog({level:r,source:`console`,text:e.map(S).join(` `)})},this.consolePatched.push(()=>{e[t]=n})}}else{for(let e of this.consolePatched)e();this.consolePatched=[],this.renderLogs()}}dispose(){this.engine.debugSelection=null,this.setColliders(`off`),this.setConsoleCapture(!1);for(let e of this.cleanups)e();for(let e of[...this.panels.keys()])this.close(e);this.close(`stats`),this.dropdown?.remove(),this.dropdown=null,this.menuButton.remove()}menuLabel(e,t){let n=e===`colliders`&&this.colliderMode!==`off`?` · ${this.colliderMode}`:``;return`${this.isOpen(e)?`✓ `:``}${t}${n}`}toggleDropdown(){if(this.dropdown){this.dropdown.remove(),this.dropdown=null;return}let e=this.doc.createElement(`div`);i(e,{position:`absolute`,top:`34px`,left:`8px`,background:`rgba(18,20,26,0.95)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,font:`12px ui-monospace, Menlo, monospace`,color:`rgba(255,255,255,0.85)`,zIndex:`60`,pointerEvents:`auto`,overflow:`hidden`});for(let[t,n]of[[`explorer`,`Explorer`],[`inspector`,`Inspector`],[`logs`,`Logs`],[`time`,`Time`],[`stats`,`Stats`],[`colliders`,`Colliders`]]){let r=this.doc.createElement(`div`);r.textContent=this.menuLabel(t,n),i(r,{padding:`6px 14px`,cursor:`pointer`,userSelect:`none`}),r.addEventListener(`click`,()=>{if(this.toggle(t),t===`colliders`){r.textContent=this.menuLabel(t,n);return}this.dropdown?.remove(),this.dropdown=null}),e.appendChild(r)}for(let t of this.actions){let n=this.doc.createElement(`div`);n.textContent=t.label,i(n,{padding:`6px 14px`,cursor:`pointer`,userSelect:`none`,borderTop:`1px solid rgba(255,255,255,0.14)`,color:`rgba(158,232,220,0.95)`}),n.addEventListener(`click`,()=>{this.dropdown?.remove(),this.dropdown=null,t.run()}),e.appendChild(n)}this.container.appendChild(e),this.dropdown=e}openStatsChip(){if(this.statsChip)return;let e=this.doc.createElement(`div`);i(e,{position:`absolute`,top:`8px`,right:`8px`,padding:`4px 10px`,background:`rgba(18,20,26,0.85)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,color:`rgba(255,255,255,0.85)`,font:`12px ui-monospace, Menlo, monospace`,textAlign:`right`,whiteSpace:`pre`,userSelect:`none`,pointerEvents:`none`,zIndex:`70`}),this.container.appendChild(e),this.statsChip=e,this.renderStats()}renderStats(){let e=this.statsChip;if(!e)return;let t=this.engine.stats(),n=this.statsSource?.()??{},r=[`nodes ${t.nodes}`,...n.triangles===void 0?[]:[`tris ${x(n.triangles)}`],...n.drawCalls===void 0?[]:[`calls ${n.drawCalls}`]].join(` · `);e.textContent=`${Math.round(t.fps)} fps · ${t.frameMs.toFixed(1)} ms\n${r}`}renderExplorer(){let e=this.panels.get(`explorer`);if(!e)return;let t=e.body.scrollTop;b(e.body);let n=this.engine.scene?.root;if(!n){e.body.scrollTop=t;return}let r=(e,t)=>{let n=e.constructor,a=e.children.length>0,o=this.collapsedFlags.get(e)===!0,s=this.doc.createElement(`div`);i(s,{display:`flex`,alignItems:`center`,cursor:`pointer`,padding:`1px 2px`,borderRadius:`3px`,background:e===this.selected?`rgba(110,160,255,0.25)`:`transparent`});let c=this.doc.createElement(`span`);c.textContent=a?o?`▸`:`▾`:`·`,i(c,{width:`14px`,flex:`none`,textAlign:`center`,opacity:a?`0.85`:`0.25`,userSelect:`none`}),a&&c.addEventListener(`click`,t=>{t.stopPropagation?.(),this.collapsedFlags.set(e,!o),this.renderExplorer()}),s.appendChild(c);let l=this.doc.createElement(`span`);l.textContent=e.name,i(l,{whiteSpace:`nowrap`}),s.appendChild(l);let u=this.doc.createElement(`span`);if(u.textContent=` ${n.typeName}`,i(u,{opacity:`0.45`,whiteSpace:`nowrap`,fontSize:`10px`}),s.appendChild(u),a&&o){let t=this.doc.createElement(`span`);t.textContent=` (${e.children.length})`,i(t,{opacity:`0.35`,fontSize:`10px`}),s.appendChild(t)}if(s.addEventListener(`click`,()=>{this.selected=e,this.engine.debugSelection=e,this.applyColliderScope(),this.open(`inspector`),this.renderExplorer(),this.renderInspector()}),t.appendChild(s),a&&!o){let n=this.doc.createElement(`div`);i(n,{marginLeft:`8px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`});for(let t of e.children)r(t,n);t.appendChild(n)}};r(n,e.body),e.body.scrollTop=t}renderInspector(){let e=this.panels.get(`inspector`);if(!e)return;let n=e.body.scrollTop;b(e.body);let r=this.selected;if(r&&r.tree===null&&(this.selected=null,this.engine.debugSelection=null,this.applyColliderScope(),r=null),!r){let t=this.doc.createElement(`div`);t.textContent=`select a node in the Explorer`,i(t,{opacity:`0.6`}),e.body.appendChild(t);return}let a=r.constructor,o=this.doc.createElement(`div`);if(o.textContent=`${r.getPath()} · ${a.typeName}${r.uid?` · ${r.uid}`:``}`,i(o,{fontWeight:`700`,marginBottom:`6px`,whiteSpace:`pre-wrap`}),e.body.appendChild(o),r.groups.size>0){let t=this.doc.createElement(`div`);t.textContent=`groups: ${[...r.groups].join(`, `)}`,i(t,{opacity:`0.7`,marginBottom:`6px`}),e.body.appendChild(t)}let s=t(a),c=r;for(let t of Object.keys(s)){let n=s[t];this.renderValueRow(e.body,r,t,{read:()=>c[t],write:e=>{c[t]=e},options:n?.options,variants:n?.variants})}e.body.scrollTop=n}renderValueRow(t,n,r,a){let o=a.read(),s=a.options,c={get[r](){return a.read()},set[r](e){a.write(e)}},u=this.doc.createElement(`div`);i(u,{display:`flex`,gap:`6px`,alignItems:`center`,margin:`2px 0`});let d=this.doc.createElement(`div`);d.textContent=r,i(d,{minWidth:`84px`,opacity:`0.75`}),u.appendChild(d);let h=(e,t)=>{let a=this.doc.createElement(`input`);return a.type=`number`,a.value=String(e),i(a,w(`70px`)),this.trackEditing(a),a.addEventListener(`change`,()=>{let e=Number(a.value);Number.isFinite(e)?t(e):a.value=String(n[r])}),a};if(typeof o==`number`)u.appendChild(h(o,e=>{c[r]=e}));else if(typeof o==`boolean`){let e=this.doc.createElement(`input`);e.type=`checkbox`,e.checked=o,this.trackEditing(e),e.addEventListener(`change`,()=>{c[r]=e.checked}),u.appendChild(e)}else if(typeof o==`string`&&s&&s.length>0){let e=this.doc.createElement(`select`);for(let t of s.includes(o)?s:[o,...s]){let n=this.doc.createElement(`option`);n.value=t,n.textContent=t,t===o&&(n.selected=!0),e.appendChild(n)}e.value=o,this.trackEditing(e),e.addEventListener(`change`,()=>{c[r]=e.value}),i(e,w(`110px`)),u.appendChild(e)}else if(typeof o==`string`){let e=this.doc.createElement(`input`);e.type=`text`,e.value=o,this.trackEditing(e),i(e,w(`140px`)),e.addEventListener(`change`,()=>{c[r]=e.value}),u.appendChild(e)}else if(Array.isArray(o)&&o.length<=8&&o.every(e=>typeof e==`number`))for(let e=0;e<o.length;e++)u.appendChild(h(o[e],t=>{let n=[...c[r]];n[e]=t,c[r]=n}));else if(m(o)){let s=this.doc.createElement(`div`);i(s,{marginLeft:`10px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`});let c=a.variants?.tag,l=Object.keys(o);c&&!l.includes(c)&&l.unshift(c);let d=a.path??r;for(let t of l){let r=t===c;this.renderValueRow(s,n,t,{path:`${d}.${t}`,read:()=>a.read()[t]??(r?``:null),write:n=>{if(r&&a.variants){let t=a.variants.byTag[String(n)];if(t!==void 0){a.write(e(t));return}}a.write({...a.read(),[t]:n})},options:r&&a.variants?Object.keys(a.variants.byTag):void 0})}t.appendChild(u),t.appendChild(s);return}else{let s=JSON.stringify(e(o));if(s.length<=l){let e=this.doc.createElement(`div`);e.textContent=s,i(e,{opacity:`0.65`,whiteSpace:`pre-wrap`,wordBreak:`break-all`}),u.appendChild(e)}else{let e=a.path??r,c=this.detailOpen.get(n)?.has(e)??!1,l=this.doc.createElement(`div`);if(l.textContent=`${c?`▾`:`▸`} ${f(o)}`,i(l,{opacity:`0.75`,cursor:`pointer`,userSelect:`none`}),l.addEventListener(`click`,()=>{let t=this.detailOpen.get(n);t||(t=new Set,this.detailOpen.set(n,t)),c?t.delete(e):t.add(e),this.renderInspector()}),u.appendChild(l),t.appendChild(u),c){let e=this.doc.createElement(`div`);e.textContent=p(o,s),i(e,{opacity:`0.6`,whiteSpace:`pre-wrap`,wordBreak:`break-all`,marginLeft:`10px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`}),t.appendChild(e)}return}}t.appendChild(u)}trackEditing(e){e.addEventListener(`focus`,()=>{this.editing+=1}),e.addEventListener(`blur`,()=>{this.editing=Math.max(0,this.editing-1)})}pushLog(e){this.logRows.push(e),this.logRows.length>_&&this.logRows.splice(0,this.logRows.length-_),this.renderLogs()}setTimeScale(e){Number.isFinite(e)&&(this.engine.timeScale=Math.max(0,e),this.syncTime())}setPaused(e){e?(this.engine.timeScale>0&&(this.resumeScale=this.engine.timeScale),this.engine.timeScale=0):this.engine.timeScale=this.resumeScale>0?this.resumeScale:1,this.syncTime()}get paused(){return this.engine.timeScale===0}nextFrame(){this.paused||this.setPaused(!0),this.engine.step(),this.syncTime()}syncTime(){let e=this.timeEls;if(!e)return;let t=this.engine.timeScale,n=String(Math.round(t*1e3)/1e3);this.editing===0&&(e.slider.value=String(Math.min(t,h)),e.box.value=n),e.readout.textContent=`timeScale ${n}${this.paused?` · paused`:``}`,e.pause.textContent=this.paused?`▶ Resume`:`⏸ Pause`}renderTime(){let e=this.panels.get(`time`);if(!e)return;b(e.body),this.timeEls=null;let t=this.doc.createElement(`div`);i(t,{marginBottom:`6px`,opacity:`0.85`}),e.body.appendChild(t);let n=this.doc.createElement(`input`);n.type=`range`,n.min=`0`,n.max=String(h),n.step=`0.05`,n.value=String(Math.min(this.engine.timeScale,h)),i(n,{width:`100%`,marginBottom:`6px`}),this.trackEditing(n),n.addEventListener(`input`,()=>this.setTimeScale(Number(n.value))),e.body.appendChild(n);let r=this.doc.createElement(`div`);i(r,{display:`flex`,gap:`6px`,alignItems:`center`,marginBottom:`8px`});let a=this.doc.createElement(`input`);a.type=`number`,a.min=`0`,a.step=`0.05`,a.value=String(this.engine.timeScale),i(a,w(`72px`)),this.trackEditing(a),a.addEventListener(`change`,()=>{let e=Number(a.value);Number.isFinite(e)&&this.setTimeScale(e),this.syncTimeAfterEdit()}),r.appendChild(a);for(let e of[.25,.5,1,2]){let t=this.doc.createElement(`div`);t.textContent=`${e}×`,i(t,{cursor:`pointer`,opacity:`0.7`,padding:`2px 4px`}),t.addEventListener(`click`,()=>this.setTimeScale(e)),r.appendChild(t)}e.body.appendChild(r);let o=this.doc.createElement(`div`);i(o,{display:`flex`,gap:`8px`});let s=this.doc.createElement(`div`);i(s,C()),s.addEventListener(`click`,()=>this.setPaused(!this.paused)),o.appendChild(s);let c=this.doc.createElement(`div`);c.textContent=`⏭ Next frame`,c.title=`Pauses, then advances one fixed step`,i(c,C()),c.addEventListener(`click`,()=>this.nextFrame()),o.appendChild(c),e.body.appendChild(o),this.timeEls={slider:n,box:a,readout:t,pause:s},this.syncTime()}syncTimeAfterEdit(){let e=this.timeEls;if(!e)return;let t=this.engine.timeScale;e.slider.value=String(Math.min(t,h)),e.box.value=String(Math.round(t*1e3)/1e3)}renderLogs(){let e=this.panels.get(`logs`);if(!e)return;b(e.body);let t=this.doc.createElement(`div`);i(t,{display:`flex`,gap:`8px`,marginBottom:`4px`,flexWrap:`wrap`});for(let e of[`debug`,`info`,`warn`,`error`]){let n=this.doc.createElement(`div`);n.textContent=`${this.levelEnabled[e]?`✓`:`·`}${e}`,i(n,{cursor:`pointer`,opacity:this.levelEnabled[e]?`1`:`0.45`}),n.addEventListener(`click`,()=>this.setLevelEnabled(e,!this.levelEnabled[e])),t.appendChild(n)}let n=this.doc.createElement(`div`);n.textContent=`${this.consoleCapture?`✓`:`·`}console`,i(n,{cursor:`pointer`,marginLeft:`auto`}),n.addEventListener(`click`,()=>this.setConsoleCapture(!this.consoleCapture)),t.appendChild(n),e.body.appendChild(t);let r={debug:`rgba(255,255,255,0.5)`,info:`rgba(255,255,255,0.85)`,warn:`#ffc861`,error:`#ff6b6b`};for(let t of this.logRows){if(!this.levelEnabled[t.level])continue;let n=this.doc.createElement(`div`);n.textContent=`[${t.source===`console`?`console`:t.level}] ${t.text}`,i(n,{color:r[t.level],whiteSpace:`pre-wrap`}),e.body.appendChild(n)}}};function b(e){for(let t of[...e.children])t.remove()}function x(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function S(e){if(typeof e==`string`)return e;if(e instanceof Error){let t=e.stack?.split(`
3
+ `)[1]?.trim();return`${e.name}: ${e.message}${t?` (${t})`:``}`}try{let t=JSON.stringify(e);return t===`{}`||t===void 0?String(e):t}catch{return String(e)}}function C(){return{background:`rgba(255,255,255,0.08)`,border:`1px solid rgba(255,255,255,0.2)`,borderRadius:`4px`,padding:`4px 8px`,cursor:`pointer`,userSelect:`none`}}function w(e){return{width:e,background:`rgba(255,255,255,0.08)`,border:`1px solid rgba(255,255,255,0.2)`,borderRadius:`4px`,color:`inherit`,font:`inherit`,padding:`2px 4px`}}export{g as attachDebugOverlay};