incanto 0.59.0 → 0.61.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 (64) hide show
  1. package/bin/incanto-check.mjs +58 -4
  2. package/dist/2d.d.ts +22 -3
  3. package/dist/2d.js +3 -3
  4. package/dist/3d.d.ts +5 -5
  5. package/dist/3d.js +5 -4
  6. package/dist/{pathfinding-BqWBb0kh.d.ts → audio-player-D5GJgb_x.d.ts} +103 -38
  7. package/dist/{behavior-DWKTUzKI.d.ts → behavior-DsgayMsH.d.ts} +79 -1
  8. package/dist/{create-game-CHDLDQsQ.js → create-game-DH7JI5xx.js} +76 -11
  9. package/dist/{create-game-BiW8Men_.js → create-game-IX5lEH0P.js} +21 -294
  10. package/dist/debug.d.ts +1 -1
  11. package/dist/debug.js +1 -1
  12. package/dist/{duplicate-DJQd44CD.js → duplicate-CGqAmK2h.js} +1 -1
  13. package/dist/editor.js +2 -1
  14. package/dist/{environment-presets-DRAz5EV9.js → environment-presets-CNxCuhZF.js} +48 -13
  15. package/dist/{gameplay-BBEjPFsR.js → gameplay-Dtzd2itW.js} +53 -11
  16. package/dist/gameplay.d.ts +1 -1
  17. package/dist/gameplay.js +1 -1
  18. package/dist/index.d.ts +23 -6
  19. package/dist/index.js +7 -7
  20. package/dist/{loader-D8n7TU8W.js → loader-D7jTvDQv.js} +797 -2
  21. package/dist/{loader-TvkRFbyL.d.ts → loader-DolLJWJn.d.ts} +13 -1
  22. package/dist/net.d.ts +2 -2
  23. package/dist/net.js +1 -1
  24. package/dist/pathfinding-_fGrCFmH.d.ts +28 -0
  25. package/dist/{physics-2d-BaRSRrrZ.js → physics-2d-CBnor8Zf.js} +2 -2
  26. package/dist/{physics-3d-CYxjh-HW.js → physics-3d-BTUfUSWO.js} +3 -3
  27. package/dist/react.d.ts +1 -1
  28. package/dist/react.js +1 -1
  29. package/dist/{register-BpFcgdcL.js → register-DL3izw8j.js} +46 -9
  30. package/dist/{register-CDrAQqPp.js → register-xuSRyD6b.js} +119 -21
  31. package/dist/{registry-WWcQcfMr.js → registry-CF70EArN.js} +55 -3
  32. package/dist/{replay-O-yAGM76.d.ts → replay-C5x2vPF5.d.ts} +16 -3
  33. package/dist/{replay-CEPyQtF_.js → replay-D7-yle3s.js} +56 -205
  34. package/dist/{split-screen-DDMZutQ6.js → split-screen-5Ban4q4n.js} +3 -3
  35. package/dist/{split-screen-BQ3tAsf-.d.ts → split-screen-D7OopelJ.d.ts} +2 -2
  36. package/dist/{src-CY21B462.js → src-DozXvyZS.js} +1 -1
  37. package/dist/{teardown-RApWnM1G.js → teardown-B6rwJOyS.js} +1 -1
  38. package/dist/{test-DHYuFyAu.js → test-Dch_7VQD.js} +19 -14
  39. package/dist/test.d.ts +24 -4
  40. package/dist/test.js +2 -2
  41. package/dist/vite.js +2 -2
  42. package/editor/assets/{agent8-BoRGtVxK.js → agent8-CF1JL2tR.js} +1 -1
  43. package/editor/assets/{debug-CzdyCg75.js → debug-3QzYhOPA.js} +1 -1
  44. package/editor/assets/{index-VesuVEhe.js → index-CAD2c5ug.js} +92 -92
  45. package/editor/index.html +1 -1
  46. package/package.json +1 -1
  47. package/schemas/scene.schema.json +4 -0
  48. package/skills/incanto-3d-models.md +1 -1
  49. package/skills/incanto-assets.md +10 -1
  50. package/skills/incanto-audio.md +21 -13
  51. package/skills/incanto-behaviors-and-scripts.md +7 -3
  52. package/skills/incanto-building-2d-games.md +12 -3
  53. package/skills/incanto-editor.md +1 -1
  54. package/skills/incanto-gameplay-behaviors.md +24 -1
  55. package/skills/incanto-hud.md +5 -3
  56. package/skills/incanto-localization.md +31 -0
  57. package/skills/incanto-node-reference.md +1 -0
  58. package/skills/incanto-scene-json-authoring.md +6 -1
  59. package/templates-app/beacon-isle-3d/package.json +1 -1
  60. package/templates-app/platformer-2d/package.json +1 -1
  61. package/templates-app/star-survivor/package.json +1 -1
  62. package/templates-app/tps-3d/package.json +1 -1
  63. package/templates-app/village-quest-3d/package.json +1 -1
  64. package/dist/particle-sim-C5OfBbmU.d.ts +0 -77
@@ -1,196 +1,6 @@
1
- import { D as behaviorSchema, T as parseNodePath } from "./loader-D8n7TU8W.js";
2
- import { L as translationKey, O as HudWidgetBase } from "./register-CDrAQqPp.js";
3
- import { i as getNodeSchema, o as getNodeType } from "./registry-WWcQcfMr.js";
4
- //#region src/core/node-refs.ts
5
- /**
6
- * Node-path PROPS, resolved against scene JSON.
7
- *
8
- * `Chase.target`, `Camera2D.follow`, `Spawner.prefab`, `Joint3D.target`,
9
- * `CharacterController3D.skinPath`, `Foliage3D.terrain` … each holds a path to
10
- * another node, and nothing checked any of them. A connection whose end does not
11
- * resolve is a hard `DANGLING_CONNECTION` at load; a PROP that does not resolve
12
- * is nothing at all — `getNodeOrNull` returns null, the scene loads, the enemy
13
- * never chases, and no error is ever printed.
14
- *
15
- * That makes it exactly the class `auditScene` exists for: loads fine, plays
16
- * wrong. The editor uses the same resolver so the two cannot drift.
17
- *
18
- * The grammar comes from `parseNodePath` — the loader's own parser — so a path
19
- * this module accepts is one the engine accepts.
20
- */
21
- function nodeAt(root, path) {
22
- let node = root;
23
- for (const index of path) {
24
- node = node?.children?.[index];
25
- if (!node) return null;
26
- }
27
- return node ?? null;
28
- }
29
- function byName(root, name) {
30
- const found = [];
31
- const walk = (node, at) => {
32
- if (node.name === name) found.push(at);
33
- (node.children ?? []).forEach((child, i) => {
34
- walk(child, [...at, i]);
35
- });
36
- };
37
- walk(root, []);
38
- return found;
39
- }
40
- /**
41
- * Resolve `value` from the node at `owner`, the way the engine would.
42
- *
43
- * Note the asymmetry with connections, whose paths resolve from the SCENE ROOT:
44
- * a prop's path resolves from the node holding it, which is why `'../Skin'` is
45
- * the documented default of `CharacterController3D.skinPath` and would mean
46
- * nothing in a connection.
47
- */
48
- function resolveRefInJson(root, owner, value) {
49
- if (!root) return {
50
- ok: false,
51
- problem: {
52
- kind: "noMatch",
53
- name: value
54
- }
55
- };
56
- let parsed;
57
- try {
58
- parsed = parseNodePath(value);
59
- } catch (error) {
60
- return {
61
- ok: false,
62
- problem: {
63
- kind: "grammar",
64
- message: error instanceof Error ? error.message : String(error)
65
- }
66
- };
67
- }
68
- if (parsed.kind === "unique") {
69
- const found = byName(root, parsed.name);
70
- if (found.length === 1) return {
71
- ok: true,
72
- path: found[0]
73
- };
74
- if (found.length === 0) return {
75
- ok: false,
76
- problem: {
77
- kind: "noMatch",
78
- name: parsed.name
79
- }
80
- };
81
- return {
82
- ok: false,
83
- problem: {
84
- kind: "ambiguous",
85
- name: parsed.name,
86
- count: found.length
87
- }
88
- };
89
- }
90
- let at;
91
- let segments = parsed.segments;
92
- if (parsed.kind === "absolute") {
93
- const first = segments[0] ?? "";
94
- if (first !== root.name && first !== "root") return {
95
- ok: false,
96
- problem: {
97
- kind: "wrongRoot",
98
- first,
99
- root: String(root.name ?? "")
100
- }
101
- };
102
- at = [];
103
- segments = segments.slice(1);
104
- } else at = [...owner];
105
- for (const segment of segments) {
106
- if (segment === "..") {
107
- if (at.length === 0) return {
108
- ok: false,
109
- problem: { kind: "aboveRoot" }
110
- };
111
- at.pop();
112
- continue;
113
- }
114
- const here = nodeAt(root, at);
115
- const index = (here?.children ?? []).findIndex((child) => child.name === segment);
116
- if (index < 0) return {
117
- ok: false,
118
- problem: {
119
- kind: "noChild",
120
- parent: String(here?.name ?? "?"),
121
- segment
122
- }
123
- };
124
- at.push(index);
125
- }
126
- return {
127
- ok: true,
128
- path: at
129
- };
130
- }
131
- /** The problem in English, for `incanto-check` and as the editor's base text. */
132
- function describeRefProblem(problem, value) {
133
- switch (problem.kind) {
134
- case "grammar": return problem.message;
135
- case "noMatch": return `'%${problem.name}' matches no node in this scene.`;
136
- case "ambiguous": return `'%${problem.name}' matches ${problem.count} nodes — a %name must be unique.`;
137
- case "wrongRoot": return `'${value}' starts at '${problem.first}', but the scene root is '${problem.root}' (or write '/root/…').`;
138
- case "noChild": return `'${value}' leads nowhere — '${problem.parent}' has no child named '${problem.segment}'.`;
139
- case "aboveRoot": return `'${value}' walks above the scene root.`;
140
- }
141
- }
142
- /** Every prop on this node declared `nodePath: true`, per bag. */
143
- function refProps(node) {
144
- const collect = (schema) => Object.entries(schema).filter(([, def]) => def?.nodePath).map(([key]) => key);
145
- let props = [];
146
- if (node.type) try {
147
- props = collect(getNodeSchema(node.type));
148
- } catch {}
149
- const name = node.script?.name;
150
- return {
151
- props,
152
- script: name ? collect(behaviorSchema(name)) : []
153
- };
154
- }
155
- /**
156
- * Scene lints for node-path props that point at nothing.
157
- *
158
- * An EMPTY value is not reported: `''` is the default of most of these props and
159
- * means "not set", which is a choice rather than a typo.
160
- */
161
- function nodeRefWarnings(scene) {
162
- const root = scene.root;
163
- if (!root) return [];
164
- const warnings = [];
165
- const walk = (node, at, path) => {
166
- const keys = refProps(node);
167
- const bags = [[
168
- node.props,
169
- keys.props,
170
- ""
171
- ], [
172
- node.script?.props,
173
- keys.script,
174
- `${node.script?.name ?? "script"}.`
175
- ]];
176
- for (const [bag, propKeys, prefix] of bags) {
177
- if (!bag) continue;
178
- for (const key of propKeys) {
179
- const value = bag[key];
180
- if (typeof value !== "string" || value === "") continue;
181
- const result = resolveRefInJson(root, at, value);
182
- if (result.ok) continue;
183
- warnings.push(`${path}: ${prefix}${key} — ${describeRefProblem(result.problem, value)} The scene loads and the prop does nothing.`);
184
- }
185
- }
186
- (node.children ?? []).forEach((child, i) => {
187
- walk(child, [...at, i], `${path}/${child.name ?? "?"}`);
188
- });
189
- };
190
- walk(root, [], String(root.name ?? "root"));
191
- return warnings;
192
- }
193
- //#endregion
1
+ import { D as handlerShadowWarnings, j as nodeRefWarnings } from "./loader-D7jTvDQv.js";
2
+ import { L as translationKey, O as HudWidgetBase } from "./register-xuSRyD6b.js";
3
+ import { o as getNodeType } from "./registry-CF70EArN.js";
194
4
  //#region src/core/audit.ts
195
5
  const ANIMATED_TYPES = new Set([
196
6
  "Particles2D",
@@ -266,25 +76,51 @@ function isLit(environment) {
266
76
  const ambient = environment.ambient;
267
77
  return typeof ambient?.intensity === "number" && ambient.intensity > 0;
268
78
  }
269
- /** Human-readable warnings (empty = clean). Pure JSON walk, no registry. */
270
- function auditScene(scene) {
79
+ /**
80
+ * Which of this node's own transforms physics cannot compose, if any.
81
+ *
82
+ * Offsets compose; rotation and scale do not — the body is placed at its WORLD
83
+ * position and given its OWN rotation, so an ancestor's is simply lost.
84
+ */
85
+ function transformOf(props) {
86
+ const rot = props.rotation;
87
+ const scale = props.scale;
88
+ const turned = typeof rot === "number" ? rot !== 0 : Array.isArray(rot) && rot.some((v) => typeof v === "number" && v !== 0);
89
+ const scaled = Array.isArray(scale) && scale.some((v) => typeof v === "number" && v !== 1);
90
+ if (turned && scaled) return "rotation and scale";
91
+ if (turned) return "rotation";
92
+ if (scaled) return "scale";
93
+ return null;
94
+ }
95
+ function auditScene(scene, opts = {}) {
271
96
  const warnings = [];
272
97
  const root = scene.root;
273
98
  if (!root) return warnings;
274
99
  let currentCameras = 0;
275
100
  let cameras = 0;
276
101
  let lights = 0;
102
+ /** Nodes that drive the current camera themselves — see the warning below. */
103
+ const cameraDrivers = [];
104
+ const followedCameras = [];
277
105
  const needUid = [];
278
- const walk = (node, path, insideHud, staticRoot) => {
106
+ const walk = (node, path, insideHud, staticRoot, transformed) => {
279
107
  const type = node.type ?? "";
280
108
  const props = node.props ?? {};
281
109
  const script = node.script?.name;
282
110
  if (script && SAVEABLE_SCRIPTS.has(script) && !node.uid) needUid.push(`${path} (${script})`);
111
+ if (type === "AudioPlayer" && props.spatial === true && scene.dimension === "2d") {
112
+ const ref = props.refDistance;
113
+ const max = props.maxDistance;
114
+ if (ref === void 0 && max === void 0) warnings.push(`${path}: spatial audio in a 2D scene measures distance in PIXELS, and "refDistance"/"maxDistance" default to 1 and 50 — about one sprite, so the sound is at its quietest almost immediately. Set them to your level's scale (e.g. 100 and 900).`);
115
+ }
283
116
  if (type === "Camera2D" || type === "Camera3D") {
284
117
  cameras += 1;
285
118
  if (props.current === true) currentCameras += 1;
119
+ if (script === "FollowCamera") followedCameras.push(path);
286
120
  }
121
+ if (type === "CharacterController3D") cameraDrivers.push(path);
287
122
  if (LIGHT_TYPES.has(type)) lights += 1;
123
+ if (BODY_TYPES.has(type) && transformed) warnings.push(`${path}: ${type} sits under '${transformed.path}', whose ${transformed.what} physics cannot compose — the collider will not line up with what is drawn. Keep body ancestors untransformed or translation-only, or put the body outside them.`);
288
124
  if (BODY_TYPES.has(type)) {
289
125
  const collider = props.collider;
290
126
  if (!collider || collider.shape === void 0) warnings.push(`${path}: ${type} has no collider — physics will skip it (add "collider": { "shape": ... }).`);
@@ -293,15 +129,25 @@ function auditScene(scene) {
293
129
  if (staticRoot && ANIMATED_TYPES.has(type)) warnings.push(`${path}: ${type} is frozen inside static subtree '${staticRoot}' — it will not animate/update on screen.`);
294
130
  const nextStatic = staticRoot ?? (props.static === true ? node.name ?? path : null);
295
131
  const nextHud = insideHud || type === "HudLayer";
296
- for (const child of node.children ?? []) walk(child, `${path}/${child.name ?? "?"}`, nextHud, nextStatic);
132
+ const own = transformOf(props);
133
+ const nextTransformed = own ? {
134
+ path,
135
+ what: own
136
+ } : transformed;
137
+ for (const child of node.children ?? []) walk(child, `${path}/${child.name ?? "?"}`, nextHud, nextStatic, nextTransformed);
297
138
  };
298
- walk(root, root.name ?? "root", false, root.props?.static === true ? root.name ?? "root" : null);
139
+ walk(root, root.name ?? "root", false, root.props?.static === true ? root.name ?? "root" : null, transformOf(root.props ?? {}) ? {
140
+ path: root.name ?? "root",
141
+ what: transformOf(root.props ?? {})
142
+ } : null);
299
143
  if (needUid.length > 0) warnings.push(`these carry state a save keeps and have no uid to key it under, so the save comes back EMPTY and the load reports no problem: ${needUid.join(", ")}. Give each one a "uid" from newUid().`);
144
+ if (cameraDrivers.length > 0 && followedCameras.length > 0) warnings.push(`${followedCameras.join(", ")}: a FollowCamera writes this camera's position every frame, and so does the CharacterController3D at ${cameraDrivers.join(", ")} — the controller owns the camera in every view it has. Drop one of them: use the controller's own \`view\`/\`camDistance\`, or take the controller off the camera duty.`);
300
145
  if (cameras > 0 && currentCameras === 0) warnings.push("no camera has \"current\": true — the screen renders from a default view, probably not what you framed.");
301
146
  if (currentCameras > 1) warnings.push(`${currentCameras} cameras claim "current": true — only the first found wins.`);
302
147
  if (scene.dimension === "3d" && lights === 0 && !isLit(scene.environment)) warnings.push("nothing lights this 3D scene — no light node, no environment sky/hdri, and no ambient intensity. It will render black. Add a DirectionalLight3D, or an \"environment\": { \"sky\": { \"type\": \"atmosphere\" } } / { \"preset\": \"...\" }.");
303
148
  for (const warning of nodeRefWarnings(scene)) warnings.push(warning);
304
- for (const warning of auditStrings(scene)) warnings.push(warning);
149
+ for (const warning of handlerShadowWarnings(scene)) warnings.push(warning);
150
+ for (const warning of auditStrings(scene, opts.declaredElsewhere)) warnings.push(warning);
305
151
  return warnings;
306
152
  }
307
153
  /**
@@ -323,11 +169,16 @@ function auditScene(scene) {
323
169
  * not trip it.
324
170
  */
325
171
  const NEAR_MISS = /^\s*@\s*t\s*[:.]/i;
326
- function auditStrings(scene) {
327
- const tables = scene.strings;
172
+ function auditStrings(scene, elsewhere) {
173
+ const own = scene.strings;
174
+ const tables = {};
175
+ for (const source of [elsewhere, own]) for (const [locale, entries] of Object.entries(source ?? {})) tables[locale] = {
176
+ ...tables[locale] ?? {},
177
+ ...entries
178
+ };
328
179
  const declared = /* @__PURE__ */ new Set();
329
- for (const entries of Object.values(tables ?? {})) for (const key of Object.keys(entries)) declared.add(key);
330
- const inBase = new Set(Object.keys(tables?.["en"] ?? {}));
180
+ for (const entries of Object.values(tables)) for (const key of Object.keys(entries)) declared.add(key);
181
+ const inBase = new Set(Object.keys(tables["en"] ?? {}));
331
182
  const missing = /* @__PURE__ */ new Set();
332
183
  const notInEnglish = /* @__PURE__ */ new Set();
333
184
  const nearMiss = /* @__PURE__ */ new Set();
@@ -337,7 +188,7 @@ function auditStrings(scene) {
337
188
  const key = translationKey(value);
338
189
  if (key !== null) {
339
190
  if (!declared.has(key)) missing.add(key);
340
- else if (tables && !inBase.has(key)) notInEnglish.add(key);
191
+ else if (declared.size > 0 && !inBase.has(key)) notInEnglish.add(key);
341
192
  }
342
193
  return;
343
194
  }
@@ -454,4 +305,4 @@ function replay(engine, recording, opts) {
454
305
  }
455
306
  }
456
307
  //#endregion
457
- export { nodeRefWarnings as a, describeRefProblem as i, startRecording as n, resolveRefInJson as o, auditScene as r, replay as t };
308
+ export { startRecording as n, auditScene as r, replay as t };
@@ -1,8 +1,8 @@
1
- import { C as Node, N as Signal, n as loadScene, t as buildNodeJson, w as diagnose } from "./loader-D8n7TU8W.js";
2
- import { _ as Engine, t as registerCoreNodes } from "./register-CDrAQqPp.js";
1
+ import { O as Node, V as Signal, k as diagnose, n as loadScene, t as buildNodeJson } from "./loader-D7jTvDQv.js";
2
+ import { _ as Engine, t as registerCoreNodes } from "./register-xuSRyD6b.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
5
- import { l as registerNode } from "./registry-WWcQcfMr.js";
5
+ import { l as registerNode } from "./registry-CF70EArN.js";
6
6
  //#region src/net/loopback.ts
7
7
  /**
8
8
  * In-memory multiplayer hub implementing the SAME kernel contract as
@@ -1,6 +1,6 @@
1
- import { At as Node, It as Signal, b as Engine } from "./behavior-DWKTUzKI.js";
1
+ import { Lt as Signal, b as Engine, jt as Node } from "./behavior-DsgayMsH.js";
2
2
  import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
3
- import { t as LoadSceneOptions } from "./loader-TvkRFbyL.js";
3
+ import { t as LoadSceneOptions } from "./loader-DolLJWJn.js";
4
4
 
5
5
  //#region src/net/types.d.ts
6
6
  type Unsubscribe = () => void;
@@ -214,6 +214,6 @@ function newUid() {
214
214
  //#endregion
215
215
  //#region src/index.ts
216
216
  /** Engine version. Kept in sync with package.json by the release pipeline. */
217
- const VERSION = "0.59.0";
217
+ const VERSION = "0.61.0";
218
218
  //#endregion
219
219
  export { preloadUrls as a, preloadSceneAssets as i, newUid as n, findPath as o, assetUrls as r, gridFromRows as s, VERSION as t };
@@ -1,4 +1,4 @@
1
- import { g as AudioPlayer } from "./register-CDrAQqPp.js";
1
+ import { g as AudioPlayer } from "./register-xuSRyD6b.js";
2
2
  import { a as logReport, s as parseDrive } from "./touch-DESwnpOc.js";
3
3
  import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-BSMny7oe.js";
4
4
  //#region src/core/audio-errors.ts
@@ -1,13 +1,13 @@
1
- import { c as resolveViewport, j as registerBehavior, n as loadScene } from "./loader-D8n7TU8W.js";
2
- import { _ as Engine } from "./register-CDrAQqPp.js";
1
+ import { f as resolveViewport, n as loadScene, z as registerBehavior } from "./loader-D7jTvDQv.js";
2
+ import { _ as Engine } from "./register-xuSRyD6b.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
5
- import { i as getNodeSchema, s as mergeStaticProps } from "./registry-WWcQcfMr.js";
6
- import { n as startRecording } from "./replay-CEPyQtF_.js";
7
- import { n as registerGameplayBehaviors } from "./gameplay-BBEjPFsR.js";
8
- import { t as registerNodes2D } from "./register-BpFcgdcL.js";
9
- import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-DRAz5EV9.js";
10
- import { a as NetworkManager, c as readSyncKey, i as sanitizeName, n as registerNodesNet, r as NetworkSpawner, s as findOwnerNode, t as createSplitScreen } from "./split-screen-DDMZutQ6.js";
5
+ import { i as getNodeSchema, s as mergeStaticProps } from "./registry-CF70EArN.js";
6
+ import { n as startRecording } from "./replay-D7-yle3s.js";
7
+ import { n as registerGameplayBehaviors } from "./gameplay-Dtzd2itW.js";
8
+ import { t as registerNodes2D } from "./register-DL3izw8j.js";
9
+ import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-CNxCuhZF.js";
10
+ import { a as NetworkManager, c as readSyncKey, i as sanitizeName, n as registerNodesNet, r as NetworkSpawner, s as findOwnerNode, t as createSplitScreen } from "./split-screen-5Ban4q4n.js";
11
11
  import { Box3, Euler, Matrix4, PerspectiveCamera, Quaternion, Vector3 } from "three";
12
12
  //#region src/test/framing.ts
13
13
  /**
@@ -1669,8 +1669,8 @@ async function playMultiplayer(opts) {
1669
1669
  */
1670
1670
  async function joinLate(server, first, opts) {
1671
1671
  try {
1672
- const { Engine } = await import("./register-CDrAQqPp.js").then((n) => n.v);
1673
- const { loadScene } = await import("./loader-D8n7TU8W.js").then((n) => n.r);
1672
+ const { Engine } = await import("./register-xuSRyD6b.js").then((n) => n.v);
1673
+ const { loadScene } = await import("./loader-D7jTvDQv.js").then((n) => n.r);
1674
1674
  const { jsonClone } = await import("./json-CwwhxQgb.js").then((n) => n.i);
1675
1675
  const engine = new Engine(opts.seed !== void 0 ? { seed: opts.seed + 999 } : {});
1676
1676
  engine.setScene(loadScene(jsonClone(opts.scene), opts.load));
@@ -1925,6 +1925,8 @@ function captureScene(scene) {
1925
1925
  type: ctor.typeName,
1926
1926
  props
1927
1927
  };
1928
+ const painted = node._paintedText?.();
1929
+ if (typeof painted === "string") capture.text = painted;
1928
1930
  if (node.uid) capture.uid = node.uid;
1929
1931
  if (node.groups.size > 0) capture.groups = [...node.groups];
1930
1932
  if (Object.keys(node.tags).length > 0) capture.tags = jsonClone(node.tags);
@@ -1951,6 +1953,7 @@ function describeCapture(capture) {
1951
1953
  const schema = defaultsFor(node.type);
1952
1954
  const deltas = Object.entries(node.props).filter(([key, value]) => !schema || !jsonEquals(value, schema[key]?.default ?? null)).map(([key, value]) => `${key}=${JSON.stringify(value)}`);
1953
1955
  const extras = [
1956
+ node.text !== void 0 ? `paints=${JSON.stringify(node.text)}` : "",
1954
1957
  node.script ? `script=${node.script}` : "",
1955
1958
  node.groups ? `groups=${node.groups.join(",")}` : "",
1956
1959
  ...deltas
@@ -1995,6 +1998,7 @@ function validateScene(json, opts = {}) {
1995
1998
  checkEnvironment(json);
1996
1999
  loadScene(json, {
1997
2000
  resolveScene: opts.resolveScene,
2001
+ ...opts.declaredAssets ? { declaredAssets: opts.declaredAssets } : {},
1998
2002
  stubMissingBehaviors: !opts.strictBehaviors
1999
2003
  }).root.free();
2000
2004
  return { ok: true };
@@ -2021,12 +2025,13 @@ async function runScript(json, opts) {
2021
2025
  });
2022
2026
  const scene = loadScene(structuredClone(json), { resolveScene: opts.resolveScene });
2023
2027
  engine.setScene(scene);
2028
+ if (opts.locale) engine.locale.locale = opts.locale;
2024
2029
  const physics = opts.physics ?? "auto";
2025
2030
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
2026
- const { enablePhysics2D } = await import("./physics-2d-BaRSRrrZ.js").then((n) => n.r);
2031
+ const { enablePhysics2D } = await import("./physics-2d-CBnor8Zf.js").then((n) => n.r);
2027
2032
  await enablePhysics2D(engine);
2028
2033
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
2029
- const { enablePhysics3D } = await import("./physics-3d-CYxjh-HW.js").then((n) => n.r);
2034
+ const { enablePhysics3D } = await import("./physics-3d-BTUfUSWO.js").then((n) => n.r);
2030
2035
  await enablePhysics3D(engine);
2031
2036
  }
2032
2037
  const failures = [];
@@ -2139,10 +2144,10 @@ async function createPlaySession(json, opts = {}) {
2139
2144
  engine.setScene(scene);
2140
2145
  const physics = opts.physics ?? "auto";
2141
2146
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
2142
- const { enablePhysics2D } = await import("./physics-2d-BaRSRrrZ.js").then((n) => n.r);
2147
+ const { enablePhysics2D } = await import("./physics-2d-CBnor8Zf.js").then((n) => n.r);
2143
2148
  await enablePhysics2D(engine);
2144
2149
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
2145
- const { enablePhysics3D } = await import("./physics-3d-CYxjh-HW.js").then((n) => n.r);
2150
+ const { enablePhysics3D } = await import("./physics-3d-BTUfUSWO.js").then((n) => n.r);
2146
2151
  await enablePhysics3D(engine);
2147
2152
  }
2148
2153
  const stepMs = 1e3 / (opts.fixedHz ?? 60);
package/dist/test.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { At as Node, Nt as LogEntry, P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-DWKTUzKI.js";
1
+ import { P as Scene, Pt as LogEntry, b as Engine, jt as Node, n as BehaviorCtor } from "./behavior-DsgayMsH.js";
2
2
  import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
3
- import { t as LoadSceneOptions } from "./loader-TvkRFbyL.js";
4
- import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-O-yAGM76.js";
5
- import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-BQ3tAsf-.js";
3
+ import { t as LoadSceneOptions } from "./loader-DolLJWJn.js";
4
+ import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-C5x2vPF5.js";
5
+ import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-D7OopelJ.js";
6
6
 
7
7
  //#region src/test/facing.d.ts
8
8
  interface FacingOptions {
@@ -490,6 +490,14 @@ interface NodeCapture {
490
490
  script?: string;
491
491
  /** CURRENT value of every schema prop (not delta — captures runtime state). */
492
492
  props: Record<string, JsonValue>;
493
+ /**
494
+ * What this node actually PAINTS, when that differs from its props.
495
+ *
496
+ * A capture printed `text="@t:menu.start"` — byte-identical in every
497
+ * language — so it proved nothing about localization. Absent unless the
498
+ * painted words differ from the authored ones.
499
+ */
500
+ text?: string;
493
501
  }
494
502
  interface SceneCapture {
495
503
  name: string;
@@ -505,6 +513,10 @@ declare function captureScene(scene: Scene): SceneCapture;
505
513
  */
506
514
  declare function describeCapture(capture: SceneCapture): string;
507
515
  interface ValidateSceneOptions {
516
+ /** Assets the rest of the PROJECT declares — see `LoadSceneOptions`. */
517
+ declaredAssets?: Record<string, {
518
+ type?: unknown;
519
+ }>;
508
520
  /** Real behavior classes to register (hot-replacing) before loading. */
509
521
  behaviors?: Record<string, BehaviorCtor>;
510
522
  /** Hard-fail on unregistered behaviors instead of stubbing them. */
@@ -564,6 +576,14 @@ interface RunScriptOptions {
564
576
  physics?: "2d" | "3d" | "auto" | false;
565
577
  /** Collect a snapshot every N simulated ms. */
566
578
  snapshotEveryMs?: number;
579
+ /**
580
+ * Run the whole script in this language.
581
+ *
582
+ * Without it every headless check ran in English, so a translation could
583
+ * only ever be verified by opening a browser and reading — which is exactly
584
+ * the loop the ladder exists to replace.
585
+ */
586
+ locale?: string;
567
587
  resolveScene?: LoadSceneOptions["resolveScene"];
568
588
  }
569
589
  interface RunResult {
package/dist/test.js CHANGED
@@ -1,3 +1,3 @@
1
- import { r as auditScene } from "./replay-CEPyQtF_.js";
2
- import { C as describeFraming, S as playtestText, _ as facingReport, a as registerAllNodes, b as findPlayer, c as ladderText, d as multiplayText, f as playMultiplayer, g as agreementProblems, h as takeAgreementSnapshots, i as findFloatingProps, l as ladderVerdict, m as feelText, n as createPlaySession, o as runScript, p as feelReport, r as describeCapture, s as validateScene, t as captureScene, u as multiplayProblems, v as facingText, w as framingText, x as playtest, y as failingReplays } from "./test-DHYuFyAu.js";
1
+ import { r as auditScene } from "./replay-D7-yle3s.js";
2
+ import { C as describeFraming, S as playtestText, _ as facingReport, a as registerAllNodes, b as findPlayer, c as ladderText, d as multiplayText, f as playMultiplayer, g as agreementProblems, h as takeAgreementSnapshots, i as findFloatingProps, l as ladderVerdict, m as feelText, n as createPlaySession, o as runScript, p as feelReport, r as describeCapture, s as validateScene, t as captureScene, u as multiplayProblems, v as facingText, w as framingText, x as playtest, y as failingReplays } from "./test-Dch_7VQD.js";
3
3
  export { agreementProblems, auditScene, captureScene, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestText, registerAllNodes, runScript, takeAgreementSnapshots, validateScene };
package/dist/vite.js CHANGED
@@ -1,6 +1,6 @@
1
- import { t as VERSION } from "./src-CY21B462.js";
1
+ import { t as VERSION } from "./src-DozXvyZS.js";
2
2
  import { n as diffSignatures } from "./frame-report-BSMny7oe.js";
3
- import { s as validateScene } from "./test-DHYuFyAu.js";
3
+ import { s as validateScene } from "./test-Dch_7VQD.js";
4
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, join, normalize, relative, resolve, sep } from "node:path";
6
6
  //#region src/vite/frame-endpoint.ts
@@ -1 +1 @@
1
- import{n as e}from"./index-VesuVEhe.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-CAD2c5ug.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,3 +1,3 @@
1
- import{i as e,r as t,t as n}from"./index-VesuVEhe.js";function r(e,t,n,r=1){let i=Math.min(e.x0,e.x1)*r,a=Math.max(e.x0,e.x1)*r,o=Math.min(e.y0,e.y1)*r,s=Math.max(e.y0,e.y1)*r,c=Math.max(0,Math.min(Math.round(i),t-1)),l=Math.max(0,Math.min(Math.round(o),n-1));return{x:c,y:l,w:Math.max(1,Math.min(Math.round(a-i),t-c)),h:Math.max(1,Math.min(Math.round(s-o),n-l))}}function i(e,t,n,r){let{x:i,y:a,w:o,h:s}=r,c=new Uint8ClampedArray(o*s*4);for(let n=0;n<s;n++){let r=((a+n)*t+i)*4,s=n*o*4;c.set(e.subarray(r,r+o*4),s)}return{pixels:c,width:o,height:s}}function a(e){if(!e)return`// no scene loaded`;let t=e.source??e;return JSON.stringify(t,null,2)}async function o(e){let t=globalThis.navigator;if(!t?.clipboard?.writeText)return!1;try{return await t.clipboard.writeText(e),!0}catch{return!1}}async function s(e,t){let n=globalThis,r=n.navigator?.clipboard?.write;if(!r||!n.ImageData||!n.ClipboardItem)return!1;try{let i=e.createElement(`canvas`);i.width=t.width,i.height=t.height;let a=i.getContext(`2d`);if(!a)return!1;a.putImageData(new n.ImageData(t.pixels,t.width,t.height),0,0);let o=await new Promise(e=>i.toBlob(e,`image/png`));return o?(await r.call(n.navigator?.clipboard,[new n.ClipboardItem({"image/png":o})]),!0):!1}catch{return!1}}var c=[`nw`,`n`,`ne`,`e`,`se`,`s`,`sw`,`w`],l={nw:[0,0],n:[.5,0],ne:[1,0],e:[1,.5],se:[1,1],s:[.5,1],sw:[0,1],w:[0,.5]},u={nw:`nwse-resize`,se:`nwse-resize`,ne:`nesw-resize`,sw:`nesw-resize`,n:`ns-resize`,s:`ns-resize`,e:`ew-resize`,w:`ew-resize`},d=(e,t,n)=>Math.max(t,Math.min(n,e));function f(e){return{x:Math.min(e.x0,e.x1),y:Math.min(e.y0,e.y1),w:Math.abs(e.x1-e.x0),h:Math.abs(e.y1-e.y0)}}function p(e,t,n,r,i,a){let o=e.x,s=e.y,c=e.x+e.w,l=e.y+e.h;return t.includes(`w`)&&(o=d(o+n,0,i)),t.includes(`e`)&&(c=d(c+n,0,i)),t.includes(`n`)&&(s=d(s+r,0,a)),t.includes(`s`)&&(l=d(l+r,0,a)),{x:Math.min(o,c),y:Math.min(s,l),w:Math.max(1,Math.abs(c-o)),h:Math.max(1,Math.abs(l-s))}}function m(e,t,n,r,i){return{...e,x:d(e.x+t,0,Math.max(0,r-e.w)),y:d(e.y+n,0,Math.max(0,i-e.h))}}function h(e,t,n,r,i,a=8){let o=d(e.x+e.w/2-t/2,a,Math.max(a,r-t-a)),s=e.y+e.h+a,c=e.y-n-a;return{x:o,y:s+n<=i-a?s:c>=a?c:d(s,a,Math.max(a,i-n-a))}}function g(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 _(e,t,n,r,i,a=4){let o=Math.min(Math.max(0,e.x),Math.max(0,r-t)),s=e.y+e.h+a,c=e.y-n-a;return s+n<=i?{x:o,y:s}:c>=0?{x:o,y:c}:{x:o,y:Math.min(Math.max(0,s),Math.max(0,i-n))}}function v(e,t,n){let r=e=>{e.key===`Escape`&&n()},i=e=>{let r=e.target;if(r){for(let e of t)if(e&&(e===r||e.contains?.(r)))return;n()}};return e.addEventListener?.(`keydown`,r),e.addEventListener?.(`pointerdown`,i),()=>{e.removeEventListener?.(`keydown`,r),e.removeEventListener?.(`pointerdown`,i)}}function y(e,t){for(let[n,r]of Object.entries(t))e.style[n]=r}var b=`rgba(18, 20, 26, 0.92)`,x=`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`),y(this.el,{position:`absolute`,background:b,border:x,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 i=e.createElement(`div`);i.textContent=n,y(i,{padding:`6px 28px 6px 10px`,background:`rgba(255,255,255,0.07)`,cursor:`move`,userSelect:`none`,touchAction:`none`,fontWeight:`700`}),this.el.appendChild(i);let a=e.createElement(`div`);a.textContent=`×`,a.title=`close`,y(a,{position:`absolute`,top:`2px`,right:`8px`,cursor:`pointer`,fontSize:`16px`,lineHeight:`20px`,opacity:`0.7`}),a.addEventListener(`click`,()=>this.onClose()),this.el.appendChild(a),this.body=e.createElement(`div`),y(this.body,{flex:`1`,overflow:`auto`,padding:`8px 10px`}),this.el.appendChild(this.body);let o=e.createElement(`div`);o.textContent=`◢`,y(o,{position:`absolute`,right:`2px`,bottom:`0`,cursor:`nwse-resize`,opacity:`0.5`,userSelect:`none`,touchAction:`none`}),this.el.appendChild(o),this.wireDrag(i,(e,t)=>{this.x+=e,this.y+=t,this.layout()}),this.wireDrag(o,(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=g(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,y(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)}},w=4,T=`#6ee7dc`,E=`rgba(18,20,26,0.94)`,D=`12px ui-monospace, Menlo, monospace`;function O(e){let{doc:t,container:n}=e,r=t.createElement(`div`);y(r,{position:`absolute`,inset:`0`,cursor:`crosshair`,zIndex:`80`,pointerEvents:`auto`,userSelect:`none`,touchAction:`none`});let i=t.createElement(`div`);y(i,{position:`absolute`,inset:`0`,background:`rgba(10,14,20,0.35)`,pointerEvents:`none`}),r.appendChild(i);let a=t.createElement(`div`);y(a,{position:`absolute`,outline:`1px solid ${T}`,boxShadow:`0 0 0 9999px rgba(10,14,20,0.35)`,cursor:`grab`,display:`none`,pointerEvents:`auto`}),r.appendChild(a);let o=t.createElement(`div`);y(o,{position:`absolute`,padding:`2px 6px`,borderRadius:`4px`,background:E,color:T,font:D,pointerEvents:`none`,display:`none`,whiteSpace:`nowrap`}),r.appendChild(o);let s=t.createElement(`div`);s.textContent=`drag to select · Esc to cancel`,y(s,{position:`absolute`,top:`10px`,left:`50%`,transform:`translateX(-50%)`,padding:`4px 10px`,borderRadius:`6px`,background:E,color:`rgba(255,255,255,0.85)`,font:D,pointerEvents:`none`,whiteSpace:`nowrap`}),r.appendChild(s);let d=null,g=`idle`,_={x:0,y:0},v={x:0,y:0,w:0,h:0},b=()=>({w:n.clientWidth||1,h:n.clientHeight||1});for(let e of c){let n=t.createElement(`div`),[r,i]=l[e];y(n,{position:`absolute`,left:`${r*100}%`,top:`${i*100}%`,width:`10px`,height:`10px`,marginLeft:`-5px`,marginTop:`-5px`,borderRadius:`2px`,background:T,border:`1px solid rgba(10,14,20,0.75)`,cursor:u[e],pointerEvents:`auto`}),n.addEventListener(`pointerdown`,t=>{k(t,e)}),a.appendChild(n)}let x=t.createElement(`div`);y(x,{position:`absolute`,display:`none`,gap:`6px`,padding:`6px`,borderRadius:`8px`,background:E,border:`1px solid rgba(255,255,255,0.18)`,font:D,pointerEvents:`auto`,whiteSpace:`nowrap`,boxShadow:`0 4px 16px rgba(0,0,0,0.45)`});let S=(e,n,r)=>{let i=t.createElement(`div`);return i.textContent=e,y(i,{padding:`5px 10px`,borderRadius:`5px`,cursor:`pointer`,color:n?`#08121a`:`rgba(255,255,255,0.85)`,background:n?T:`rgba(255,255,255,0.08)`,userSelect:`none`}),i.addEventListener(`pointerdown`,e=>{C(e),r()}),i};x.appendChild(S(`Copy to clipboard`,!0,()=>N())),x.appendChild(S(`Cancel`,!1,()=>I())),r.appendChild(x);function C(e){e.preventDefault?.(),e.stopPropagation?.()}function O(e){let t=e,r=n.getBoundingClientRect?.()??{left:0,top:0};return{x:(t.clientX??0)-r.left,y:(t.clientY??0)-r.top}}function k(e,t){C(e),_=O(e),g=t,t===`create`?d={x:_.x,y:_.y,w:0,h:0}:d&&(v=d),y(x,{display:`none`}),y(a,{cursor:t===`move`?`grabbing`:`crosshair`});let n=e.pointerId;n!==void 0&&r.setPointerCapture?.(n),A()}function A(){if(!d){y(a,{display:`none`}),y(o,{display:`none`}),y(i,{display:`block`});return}y(i,{display:`none`}),y(a,{display:`block`,left:`${d.x}px`,top:`${d.y}px`,width:`${d.w}px`,height:`${d.h}px`});let t=e.scale();o.textContent=`${Math.round(d.w*t)}×${Math.round(d.h*t)}`;let n=d.y-24;y(o,{display:`block`,left:`${Math.max(2,d.x)}px`,top:`${n>=2?n:d.y+4}px`})}function j(){if(!d||d.w<w||d.h<w)return;y(x,{display:`flex`});let e=b(),t=x.offsetWidth||200,n=x.offsetHeight||36,r=h(d,t,n,e.w,e.h);y(x,{left:`${r.x}px`,top:`${r.y}px`})}r.addEventListener(`pointerdown`,e=>{k(e,`create`)}),a.addEventListener(`pointerdown`,e=>{k(e,`move`)}),r.addEventListener(`pointermove`,e=>{if(g===`idle`||!d)return;let t=O(e),n=b();if(g===`create`){let e=f({x0:_.x,y0:_.y,x1:t.x,y1:t.y});d={x:Math.max(0,Math.min(e.x,n.w)),y:Math.max(0,Math.min(e.y,n.h)),w:Math.min(e.w,n.w-Math.max(0,Math.min(e.x,n.w))),h:Math.min(e.h,n.h-Math.max(0,Math.min(e.y,n.h)))}}else d=g===`move`?m(v,t.x-_.x,t.y-_.y,n.w,n.h):p(v,g,t.x-_.x,t.y-_.y,n.w,n.h);A()});let M=()=>{g!==`idle`&&(g===`create`&&d&&(d.w<w||d.h<w)&&(d=null),g=`idle`,y(a,{cursor:`grab`}),s.textContent=d?`drag the handles or the middle · Enter to copy · Esc to cancel`:`drag to select · Esc to cancel`,A(),j())};r.addEventListener(`pointerup`,M),r.addEventListener(`pointercancel`,M);function N(){let t=d;I(),t&&e.onCopy(t)}let P=e=>{let t=e.key;t===`Escape`?I():t===`Enter`&&d&&N()};t.addEventListener?.(`keydown`,P);let F=!1;function I(){F||(F=!0,t.removeEventListener?.(`keydown`,P),r.remove(),e.onClose())}return n.appendChild(r),{element:r,close:I}}var k=96,A=200,j=8192;function M(e){return Array.isArray(e)?`Array(${e.length})`:`Object(${Object.keys(e).length} keys)`}function N(e,t){if(Array.isArray(e)&&e.some(e=>typeof e==`object`&&!!e)){let t=e.slice(0,A).map(e=>JSON.stringify(e)),n=e.length-A;return t.join(`
1
+ import{i as e,r as t,t as n}from"./index-CAD2c5ug.js";function r(e,t,n,r=1){let i=Math.min(e.x0,e.x1)*r,a=Math.max(e.x0,e.x1)*r,o=Math.min(e.y0,e.y1)*r,s=Math.max(e.y0,e.y1)*r,c=Math.max(0,Math.min(Math.round(i),t-1)),l=Math.max(0,Math.min(Math.round(o),n-1));return{x:c,y:l,w:Math.max(1,Math.min(Math.round(a-i),t-c)),h:Math.max(1,Math.min(Math.round(s-o),n-l))}}function i(e,t,n,r){let{x:i,y:a,w:o,h:s}=r,c=new Uint8ClampedArray(o*s*4);for(let n=0;n<s;n++){let r=((a+n)*t+i)*4,s=n*o*4;c.set(e.subarray(r,r+o*4),s)}return{pixels:c,width:o,height:s}}function a(e){if(!e)return`// no scene loaded`;let t=e.source??e;return JSON.stringify(t,null,2)}async function o(e){let t=globalThis.navigator;if(!t?.clipboard?.writeText)return!1;try{return await t.clipboard.writeText(e),!0}catch{return!1}}async function s(e,t){let n=globalThis,r=n.navigator?.clipboard?.write;if(!r||!n.ImageData||!n.ClipboardItem)return!1;try{let i=e.createElement(`canvas`);i.width=t.width,i.height=t.height;let a=i.getContext(`2d`);if(!a)return!1;a.putImageData(new n.ImageData(t.pixels,t.width,t.height),0,0);let o=await new Promise(e=>i.toBlob(e,`image/png`));return o?(await r.call(n.navigator?.clipboard,[new n.ClipboardItem({"image/png":o})]),!0):!1}catch{return!1}}var c=[`nw`,`n`,`ne`,`e`,`se`,`s`,`sw`,`w`],l={nw:[0,0],n:[.5,0],ne:[1,0],e:[1,.5],se:[1,1],s:[.5,1],sw:[0,1],w:[0,.5]},u={nw:`nwse-resize`,se:`nwse-resize`,ne:`nesw-resize`,sw:`nesw-resize`,n:`ns-resize`,s:`ns-resize`,e:`ew-resize`,w:`ew-resize`},d=(e,t,n)=>Math.max(t,Math.min(n,e));function f(e){return{x:Math.min(e.x0,e.x1),y:Math.min(e.y0,e.y1),w:Math.abs(e.x1-e.x0),h:Math.abs(e.y1-e.y0)}}function p(e,t,n,r,i,a){let o=e.x,s=e.y,c=e.x+e.w,l=e.y+e.h;return t.includes(`w`)&&(o=d(o+n,0,i)),t.includes(`e`)&&(c=d(c+n,0,i)),t.includes(`n`)&&(s=d(s+r,0,a)),t.includes(`s`)&&(l=d(l+r,0,a)),{x:Math.min(o,c),y:Math.min(s,l),w:Math.max(1,Math.abs(c-o)),h:Math.max(1,Math.abs(l-s))}}function m(e,t,n,r,i){return{...e,x:d(e.x+t,0,Math.max(0,r-e.w)),y:d(e.y+n,0,Math.max(0,i-e.h))}}function h(e,t,n,r,i,a=8){let o=d(e.x+e.w/2-t/2,a,Math.max(a,r-t-a)),s=e.y+e.h+a,c=e.y-n-a;return{x:o,y:s+n<=i-a?s:c>=a?c:d(s,a,Math.max(a,i-n-a))}}function g(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 _(e,t,n,r,i,a=4){let o=Math.min(Math.max(0,e.x),Math.max(0,r-t)),s=e.y+e.h+a,c=e.y-n-a;return s+n<=i?{x:o,y:s}:c>=0?{x:o,y:c}:{x:o,y:Math.min(Math.max(0,s),Math.max(0,i-n))}}function v(e,t,n){let r=e=>{e.key===`Escape`&&n()},i=e=>{let r=e.target;if(r){for(let e of t)if(e&&(e===r||e.contains?.(r)))return;n()}};return e.addEventListener?.(`keydown`,r),e.addEventListener?.(`pointerdown`,i),()=>{e.removeEventListener?.(`keydown`,r),e.removeEventListener?.(`pointerdown`,i)}}function y(e,t){for(let[n,r]of Object.entries(t))e.style[n]=r}var b=`rgba(18, 20, 26, 0.92)`,x=`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`),y(this.el,{position:`absolute`,background:b,border:x,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 i=e.createElement(`div`);i.textContent=n,y(i,{padding:`6px 28px 6px 10px`,background:`rgba(255,255,255,0.07)`,cursor:`move`,userSelect:`none`,touchAction:`none`,fontWeight:`700`}),this.el.appendChild(i);let a=e.createElement(`div`);a.textContent=`×`,a.title=`close`,y(a,{position:`absolute`,top:`2px`,right:`8px`,cursor:`pointer`,fontSize:`16px`,lineHeight:`20px`,opacity:`0.7`}),a.addEventListener(`click`,()=>this.onClose()),this.el.appendChild(a),this.body=e.createElement(`div`),y(this.body,{flex:`1`,overflow:`auto`,padding:`8px 10px`}),this.el.appendChild(this.body);let o=e.createElement(`div`);o.textContent=`◢`,y(o,{position:`absolute`,right:`2px`,bottom:`0`,cursor:`nwse-resize`,opacity:`0.5`,userSelect:`none`,touchAction:`none`}),this.el.appendChild(o),this.wireDrag(i,(e,t)=>{this.x+=e,this.y+=t,this.layout()}),this.wireDrag(o,(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=g(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,y(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)}},w=4,T=`#6ee7dc`,E=`rgba(18,20,26,0.94)`,D=`12px ui-monospace, Menlo, monospace`;function O(e){let{doc:t,container:n}=e,r=t.createElement(`div`);y(r,{position:`absolute`,inset:`0`,cursor:`crosshair`,zIndex:`80`,pointerEvents:`auto`,userSelect:`none`,touchAction:`none`});let i=t.createElement(`div`);y(i,{position:`absolute`,inset:`0`,background:`rgba(10,14,20,0.35)`,pointerEvents:`none`}),r.appendChild(i);let a=t.createElement(`div`);y(a,{position:`absolute`,outline:`1px solid ${T}`,boxShadow:`0 0 0 9999px rgba(10,14,20,0.35)`,cursor:`grab`,display:`none`,pointerEvents:`auto`}),r.appendChild(a);let o=t.createElement(`div`);y(o,{position:`absolute`,padding:`2px 6px`,borderRadius:`4px`,background:E,color:T,font:D,pointerEvents:`none`,display:`none`,whiteSpace:`nowrap`}),r.appendChild(o);let s=t.createElement(`div`);s.textContent=`drag to select · Esc to cancel`,y(s,{position:`absolute`,top:`10px`,left:`50%`,transform:`translateX(-50%)`,padding:`4px 10px`,borderRadius:`6px`,background:E,color:`rgba(255,255,255,0.85)`,font:D,pointerEvents:`none`,whiteSpace:`nowrap`}),r.appendChild(s);let d=null,g=`idle`,_={x:0,y:0},v={x:0,y:0,w:0,h:0},b=()=>({w:n.clientWidth||1,h:n.clientHeight||1});for(let e of c){let n=t.createElement(`div`),[r,i]=l[e];y(n,{position:`absolute`,left:`${r*100}%`,top:`${i*100}%`,width:`10px`,height:`10px`,marginLeft:`-5px`,marginTop:`-5px`,borderRadius:`2px`,background:T,border:`1px solid rgba(10,14,20,0.75)`,cursor:u[e],pointerEvents:`auto`}),n.addEventListener(`pointerdown`,t=>{k(t,e)}),a.appendChild(n)}let x=t.createElement(`div`);y(x,{position:`absolute`,display:`none`,gap:`6px`,padding:`6px`,borderRadius:`8px`,background:E,border:`1px solid rgba(255,255,255,0.18)`,font:D,pointerEvents:`auto`,whiteSpace:`nowrap`,boxShadow:`0 4px 16px rgba(0,0,0,0.45)`});let S=(e,n,r)=>{let i=t.createElement(`div`);return i.textContent=e,y(i,{padding:`5px 10px`,borderRadius:`5px`,cursor:`pointer`,color:n?`#08121a`:`rgba(255,255,255,0.85)`,background:n?T:`rgba(255,255,255,0.08)`,userSelect:`none`}),i.addEventListener(`pointerdown`,e=>{C(e),r()}),i};x.appendChild(S(`Copy to clipboard`,!0,()=>N())),x.appendChild(S(`Cancel`,!1,()=>I())),r.appendChild(x);function C(e){e.preventDefault?.(),e.stopPropagation?.()}function O(e){let t=e,r=n.getBoundingClientRect?.()??{left:0,top:0};return{x:(t.clientX??0)-r.left,y:(t.clientY??0)-r.top}}function k(e,t){C(e),_=O(e),g=t,t===`create`?d={x:_.x,y:_.y,w:0,h:0}:d&&(v=d),y(x,{display:`none`}),y(a,{cursor:t===`move`?`grabbing`:`crosshair`});let n=e.pointerId;n!==void 0&&r.setPointerCapture?.(n),A()}function A(){if(!d){y(a,{display:`none`}),y(o,{display:`none`}),y(i,{display:`block`});return}y(i,{display:`none`}),y(a,{display:`block`,left:`${d.x}px`,top:`${d.y}px`,width:`${d.w}px`,height:`${d.h}px`});let t=e.scale();o.textContent=`${Math.round(d.w*t)}×${Math.round(d.h*t)}`;let n=d.y-24;y(o,{display:`block`,left:`${Math.max(2,d.x)}px`,top:`${n>=2?n:d.y+4}px`})}function j(){if(!d||d.w<w||d.h<w)return;y(x,{display:`flex`});let e=b(),t=x.offsetWidth||200,n=x.offsetHeight||36,r=h(d,t,n,e.w,e.h);y(x,{left:`${r.x}px`,top:`${r.y}px`})}r.addEventListener(`pointerdown`,e=>{k(e,`create`)}),a.addEventListener(`pointerdown`,e=>{k(e,`move`)}),r.addEventListener(`pointermove`,e=>{if(g===`idle`||!d)return;let t=O(e),n=b();if(g===`create`){let e=f({x0:_.x,y0:_.y,x1:t.x,y1:t.y});d={x:Math.max(0,Math.min(e.x,n.w)),y:Math.max(0,Math.min(e.y,n.h)),w:Math.min(e.w,n.w-Math.max(0,Math.min(e.x,n.w))),h:Math.min(e.h,n.h-Math.max(0,Math.min(e.y,n.h)))}}else d=g===`move`?m(v,t.x-_.x,t.y-_.y,n.w,n.h):p(v,g,t.x-_.x,t.y-_.y,n.w,n.h);A()});let M=()=>{g!==`idle`&&(g===`create`&&d&&(d.w<w||d.h<w)&&(d=null),g=`idle`,y(a,{cursor:`grab`}),s.textContent=d?`drag the handles or the middle · Enter to copy · Esc to cancel`:`drag to select · Esc to cancel`,A(),j())};r.addEventListener(`pointerup`,M),r.addEventListener(`pointercancel`,M);function N(){let t=d;I(),t&&e.onCopy(t)}let P=e=>{let t=e.key;t===`Escape`?I():t===`Enter`&&d&&N()};t.addEventListener?.(`keydown`,P);let F=!1;function I(){F||(F=!0,t.removeEventListener?.(`keydown`,P),r.remove(),e.onClose())}return n.appendChild(r),{element:r,close:I}}var k=96,A=200,j=8192;function M(e){return Array.isArray(e)?`Array(${e.length})`:`Object(${Object.keys(e).length} keys)`}function N(e,t){if(Array.isArray(e)&&e.some(e=>typeof e==`object`&&!!e)){let t=e.slice(0,A).map(e=>JSON.stringify(e)),n=e.length-A;return t.join(`
2
2
  `)+(n>0?`\n… ${n} more`:``)}return t.length>j?`${t.slice(0,j)} … (${t.length-j} more chars)`:t}function P(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var F=2,I=4;function L(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 B(e,r,n,t.statsSource,t.actions,t.frameSource)}var R=300,z=[`log`,`info`,`warn`,`error`,`debug`],B=class{engine;container;doc;statsSource;actions;frameSource;panels=new Map;cleanups=[];menuButton;dropdown=null;dismissDropdown=null;menuPos={x:8,y:8};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,i=[],a){this.engine=e,this.container=t,this.doc=n,this.statsSource=r,this.actions=i,this.frameSource=a,this.menuButton=n.createElement(`div`),this.menuButton.textContent=`☰ debug`,y(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.bindMenuDrag(),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(U).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===`copyScene`||e===`captureRegion`?!1: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===`copyScene`){this.copyScene();return}if(e===`captureRegion`){this.captureRegion();return}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 z){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(U).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.closeDropdown(),this.menuButton.remove()}menuLabel(e,t){let n=e===`colliders`&&this.colliderMode!==`off`?` · ${this.colliderMode}`:``;return`${this.isOpen(e)?`✓ `:``}${t}${n}`}bindMenuDrag(){let e=null,t=!1;this.menuButton.addEventListener(`pointerdown`,n=>{let r=n;e={x:r.clientX??0,y:r.clientY??0,bx:this.menuPos.x,by:this.menuPos.y},t=!1,r.pointerId!==void 0&&this.menuButton.setPointerCapture?.(r.pointerId)}),this.menuButton.addEventListener(`pointermove`,n=>{if(!e)return;let r=n,i=(r.clientX??0)-e.x,a=(r.clientY??0)-e.y;!t&&Math.hypot(i,a)<I||(t||(t=!0,this.closeDropdown(),y(this.menuButton,{cursor:`grabbing`})),this.moveMenuButton(e.bx+i,e.by+a))});let n=()=>{if(!e)return;let n=t;e=null,t=!1,y(this.menuButton,{cursor:`pointer`}),n||this.toggleDropdown()};this.menuButton.addEventListener(`pointerup`,n),this.menuButton.addEventListener(`pointercancel`,n)}moveMenuButton(e,t){let n=this.menuButton,r=g(e,t,n.offsetWidth??0,n.offsetHeight??0,this.container.clientWidth||0,this.container.clientHeight||0,0,0);this.menuPos={x:r.x,y:r.y},y(this.menuButton,{left:`${r.x}px`,top:`${r.y}px`})}closeDropdown(){this.dismissDropdown?.(),this.dismissDropdown=null,this.dropdown?.remove(),this.dropdown=null}toggleDropdown(){if(this.dropdown){this.closeDropdown();return}let e=this.doc.createElement(`div`);y(e,{position:`absolute`,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`],[`copyScene`,`Copy scene JSON`],[`captureRegion`,`Capture region`],[`stats`,`Stats`],[`colliders`,`Colliders`]]){let r=this.doc.createElement(`div`);r.textContent=this.menuLabel(t,n),y(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.closeDropdown()}),e.appendChild(r)}for(let t of this.actions){let n=this.doc.createElement(`div`);n.textContent=t.label,y(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.closeDropdown(),t.run()}),e.appendChild(n)}this.container.appendChild(e),this.dropdown=e;let t=e,n=this.menuButton,r=_({x:this.menuPos.x,y:this.menuPos.y,w:n.offsetWidth??0,h:n.offsetHeight??26},t.offsetWidth??0,t.offsetHeight??0,this.container.clientWidth||0,this.container.clientHeight||0);y(e,{left:`${r.x}px`,top:`${r.y}px`}),this.dismissDropdown=v(this.doc,[e,this.menuButton],()=>this.closeDropdown())}openStatsChip(){if(this.statsChip)return;let e=this.doc.createElement(`div`);y(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 ${H(n.triangles)}`],...n.drawCalls===void 0?[]:[`calls ${n.drawCalls}`]].join(` · `),i=t.phases,a=`\nphys ${i.fixedMs.toFixed(1)} · logic ${i.updateMs.toFixed(1)} · draw ${i.renderMs.toFixed(1)} · other ${i.otherMs.toFixed(1)}`;e.textContent=`${Math.round(t.fps)} fps · ${t.frameMs.toFixed(1)} ms\n${r}${a}`}renderExplorer(){let e=this.panels.get(`explorer`);if(!e)return;let t=e.body.scrollTop;V(e.body);let n=this.engine.scene?.root;if(!n){e.body.scrollTop=t;return}let r=(e,t)=>{let n=e.constructor,i=e.children.length>0,a=this.collapsedFlags.get(e)===!0,o=this.doc.createElement(`div`);y(o,{display:`flex`,alignItems:`center`,cursor:`pointer`,padding:`1px 2px`,borderRadius:`3px`,background:e===this.selected?`rgba(110,160,255,0.25)`:`transparent`});let s=this.doc.createElement(`span`);s.textContent=i?a?`▸`:`▾`:`·`,y(s,{width:`14px`,flex:`none`,textAlign:`center`,opacity:i?`0.85`:`0.25`,userSelect:`none`}),i&&s.addEventListener(`click`,t=>{t.stopPropagation?.(),this.collapsedFlags.set(e,!a),this.renderExplorer()}),o.appendChild(s);let c=this.doc.createElement(`span`);c.textContent=e.name,y(c,{whiteSpace:`nowrap`}),o.appendChild(c);let l=this.doc.createElement(`span`);if(l.textContent=` ${n.typeName}`,y(l,{opacity:`0.45`,whiteSpace:`nowrap`,fontSize:`10px`}),o.appendChild(l),i&&a){let t=this.doc.createElement(`span`);t.textContent=` (${e.children.length})`,y(t,{opacity:`0.35`,fontSize:`10px`}),o.appendChild(t)}if(o.addEventListener(`click`,()=>{this.selected=e,this.engine.debugSelection=e,this.applyColliderScope(),this.open(`inspector`),this.renderExplorer(),this.renderInspector()}),t.appendChild(o),i&&!a){let n=this.doc.createElement(`div`);y(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;V(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`,y(t,{opacity:`0.6`}),e.body.appendChild(t);return}let i=r.constructor,a=this.doc.createElement(`div`);if(a.textContent=`${r.getPath()} · ${i.typeName}${r.uid?` · ${r.uid}`:``}`,y(a,{fontWeight:`700`,marginBottom:`6px`,whiteSpace:`pre-wrap`}),e.body.appendChild(a),r.groups.size>0){let t=this.doc.createElement(`div`);t.textContent=`groups: ${[...r.groups].join(`, `)}`,y(t,{opacity:`0.7`,marginBottom:`6px`}),e.body.appendChild(t)}let o=t(i),s=r;for(let t of Object.keys(o)){let n=o[t];this.renderValueRow(e.body,r,t,{read:()=>s[t],write:e=>{s[t]=e},options:n?.options,variants:n?.variants})}e.body.scrollTop=n}renderValueRow(t,n,r,i){let a=i.read(),o=i.options,s={get[r](){return i.read()},set[r](e){i.write(e)}},c=this.doc.createElement(`div`);y(c,{display:`flex`,gap:`6px`,alignItems:`center`,margin:`2px 0`});let l=this.doc.createElement(`div`);l.textContent=r,y(l,{minWidth:`84px`,opacity:`0.75`}),c.appendChild(l);let u=(e,t)=>{let i=this.doc.createElement(`input`);return i.type=`number`,i.value=String(e),y(i,G(`70px`)),this.trackEditing(i),i.addEventListener(`change`,()=>{let e=Number(i.value);Number.isFinite(e)?t(e):i.value=String(n[r])}),i};if(typeof a==`number`)c.appendChild(u(a,e=>{s[r]=e}));else if(typeof a==`boolean`){let e=this.doc.createElement(`input`);e.type=`checkbox`,e.checked=a,this.trackEditing(e),e.addEventListener(`change`,()=>{s[r]=e.checked}),c.appendChild(e)}else if(typeof a==`string`&&o&&o.length>0){let e=this.doc.createElement(`select`);for(let t of o.includes(a)?o:[a,...o]){let n=this.doc.createElement(`option`);n.value=t,n.textContent=t,t===a&&(n.selected=!0),e.appendChild(n)}e.value=a,this.trackEditing(e),e.addEventListener(`change`,()=>{s[r]=e.value}),y(e,G(`110px`)),c.appendChild(e)}else if(typeof a==`string`){let e=this.doc.createElement(`input`);e.type=`text`,e.value=a,this.trackEditing(e),y(e,G(`140px`)),e.addEventListener(`change`,()=>{s[r]=e.value}),c.appendChild(e)}else if(Array.isArray(a)&&a.length<=8&&a.every(e=>typeof e==`number`))for(let e=0;e<a.length;e++)c.appendChild(u(a[e],t=>{let n=[...s[r]];n[e]=t,s[r]=n}));else if(P(a)){let o=this.doc.createElement(`div`);y(o,{marginLeft:`10px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`});let s=i.variants?.tag,l=Object.keys(a);s&&!l.includes(s)&&l.unshift(s);let u=i.path??r;for(let t of l){let r=t===s;this.renderValueRow(o,n,t,{path:`${u}.${t}`,read:()=>i.read()[t]??(r?``:null),write:n=>{if(r&&i.variants){let t=i.variants.byTag[String(n)];if(t!==void 0){i.write(e(t));return}}i.write({...i.read(),[t]:n})},options:r&&i.variants?Object.keys(i.variants.byTag):void 0})}t.appendChild(c),t.appendChild(o);return}else{let o=JSON.stringify(e(a));if(o.length<=k){let e=this.doc.createElement(`div`);e.textContent=o,y(e,{opacity:`0.65`,whiteSpace:`pre-wrap`,wordBreak:`break-all`}),c.appendChild(e)}else{let e=i.path??r,s=this.detailOpen.get(n)?.has(e)??!1,l=this.doc.createElement(`div`);if(l.textContent=`${s?`▾`:`▸`} ${M(a)}`,y(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)),s?t.delete(e):t.add(e),this.renderInspector()}),c.appendChild(l),t.appendChild(c),s){let e=this.doc.createElement(`div`);e.textContent=N(a,o),y(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(c)}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>R&&this.logRows.splice(0,this.logRows.length-R),this.renderLogs()}async copyScene(){let e=await o(a(this.engine.scene??null));return this.toast(e?`scene JSON copied`:`could not reach the clipboard`),e}captureRegion(){this.capture||=(this.captureScale=globalThis.devicePixelRatio??1,this.frameSource?.().then(e=>{this.captureScale=e.width/Math.max(1,this.container.clientWidth||e.width)}).catch(()=>{}),O({doc:this.doc,container:this.container,scale:()=>this.captureScale,onCopy:e=>void this.copyRegion({x0:e.x,y0:e.y,x1:e.x+e.w,y1:e.y+e.h}),onClose:()=>{this.capture=null}}))}capture=null;captureScale=1;async copyRegion(e){let t=this.frameSource;if(!t)return this.toast(`no renderer to capture from`),!1;try{let n=await t(),a=n.width/Math.max(1,this.container.clientWidth||n.width),o=r(e,n.width,n.height,a),c=i(n.pixels,n.width,n.height,o),l=await s(this.doc,c);return this.toast(l?`copied ${c.width}×${c.height}`:`could not copy the image`),l}catch(e){return this.toast(`capture failed: ${e instanceof Error?e.message:String(e)}`),!1}}toast(e){let t=this.doc.createElement(`div`);t.textContent=e,y(t,{position:`absolute`,bottom:`16px`,left:`50%`,transform:`translateX(-50%)`,padding:`6px 12px`,borderRadius:`6px`,background:`rgba(18,20,26,0.92)`,color:`rgba(255,255,255,0.9)`,font:`12px ui-monospace, Menlo, monospace`,zIndex:`90`,pointerEvents:`none`}),this.container.appendChild(t),setTimeout(()=>t.remove(),1800)}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({ignorePause:!0}),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,F)),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;V(e.body),this.timeEls=null;let t=this.doc.createElement(`div`);y(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(F),n.step=`0.05`,n.value=String(Math.min(this.engine.timeScale,F)),y(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`);y(r,{display:`flex`,gap:`6px`,alignItems:`center`,marginBottom:`8px`});let i=this.doc.createElement(`input`);i.type=`number`,i.min=`0`,i.step=`0.05`,i.value=String(this.engine.timeScale),y(i,G(`72px`)),this.trackEditing(i),i.addEventListener(`change`,()=>{let e=Number(i.value);Number.isFinite(e)&&this.setTimeScale(e),this.syncTimeAfterEdit()}),r.appendChild(i);for(let e of[.25,.5,1,2]){let t=this.doc.createElement(`div`);t.textContent=`${e}×`,y(t,{cursor:`pointer`,opacity:`0.7`,padding:`2px 4px`}),t.addEventListener(`click`,()=>this.setTimeScale(e)),r.appendChild(t)}e.body.appendChild(r);let a=this.doc.createElement(`div`);y(a,{display:`flex`,gap:`8px`});let o=this.doc.createElement(`div`);y(o,W()),o.addEventListener(`click`,()=>this.setPaused(!this.paused)),a.appendChild(o);let s=this.doc.createElement(`div`);s.textContent=`⏭ Next frame`,s.title=`Pauses, then advances one fixed step`,y(s,W()),s.addEventListener(`click`,()=>this.nextFrame()),a.appendChild(s),e.body.appendChild(a),this.timeEls={slider:n,box:i,readout:t,pause:o},this.syncTime()}syncTimeAfterEdit(){let e=this.timeEls;if(!e)return;let t=this.engine.timeScale;e.slider.value=String(Math.min(t,F)),e.box.value=String(Math.round(t*1e3)/1e3)}renderLogs(){let e=this.panels.get(`logs`);if(!e)return;V(e.body);let t=this.doc.createElement(`div`);y(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}`,y(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`,y(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}`,y(n,{color:r[t.level],whiteSpace:`pre-wrap`}),e.body.appendChild(n)}}};function V(e){for(let t of[...e.children])t.remove()}function H(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function U(e){if(typeof e==`string`)return e;if(e instanceof Error){let t=e.stack?.split(`
3
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 W(){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 G(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{L as attachDebugOverlay};