incanto 0.29.0 → 0.30.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 (38) hide show
  1. package/bin/incanto-editor.mjs +101 -28
  2. package/dist/2d.d.ts +16 -1
  3. package/dist/2d.js +2 -2
  4. package/dist/3d.d.ts +34 -1
  5. package/dist/3d.js +4 -4
  6. package/dist/{create-game-CbuLWorm.js → create-game-5z_QVtLx.js} +13 -9
  7. package/dist/{create-game-CNKXGfpr.js → create-game-DuBTv2zI.js} +14 -10
  8. package/dist/debug-draw-BM3DsvtT.js +18 -0
  9. package/dist/debug.js +1 -1
  10. package/dist/editor.js +1346 -1231
  11. package/dist/{gameplay-L05WgWd1.js → gameplay-Cfr6aFZ1.js} +12 -3
  12. package/dist/gameplay.js +1 -1
  13. package/dist/index.d.ts +9 -0
  14. package/dist/index.js +1 -1
  15. package/dist/{physics-2d-CCVTrKOd.js → physics-2d-3kOQCtgd.js} +26 -1
  16. package/dist/{physics-3d-BZZLtwJu.js → physics-3d-CeRH-Ff_.js} +38 -3
  17. package/dist/react.js +1 -1
  18. package/dist/{register-C44aSduO.js → register-DJ0SByQg.js} +1 -1
  19. package/dist/{editor-switch-CAKlJMIY.js → teardown-ByzfDPyu.js} +83 -4
  20. package/dist/test.js +6 -6
  21. package/dist/vite.d.ts +87 -1
  22. package/dist/vite.js +262 -3
  23. package/editor/assets/{agent8-DEVkEa3d.js → agent8-CAp0i5qn.js} +1 -1
  24. package/editor/assets/{debug-zGAtpDF0.js → debug-BoEYfbqK.js} +1 -1
  25. package/editor/assets/index-BO6WU8by.js +10696 -0
  26. package/editor/index.html +1 -1
  27. package/package.json +1 -1
  28. package/skills/incanto-editor.md +61 -8
  29. package/skills/incanto-physics-and-input.md +15 -0
  30. package/skills/incanto-verifying-your-game.md +6 -0
  31. package/templates-app/beacon-isle-3d/package.json +1 -1
  32. package/templates-app/beacon-isle-3d/vite.config.ts +7 -0
  33. package/templates-app/tps-3d/package.json +1 -1
  34. package/templates-app/tps-3d/vite.config.ts +7 -0
  35. package/templates-app/village-quest-3d/package.json +1 -1
  36. package/templates-app/village-quest-3d/vite.config.ts +7 -0
  37. package/dist/debug-draw-CZmOYjL2.js +0 -13
  38. package/editor/assets/index-Bx4UtWYY.js +0 -10586
package/dist/vite.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { validateScene } from "./test.js";
2
- import { readFileSync } from "node:fs";
3
- import { dirname, resolve } from "node:path";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
+ import { basename, dirname, join, normalize, relative, resolve, sep } from "node:path";
4
4
  //#region src/vite/index.ts
5
5
  /**
6
6
  * incanto/vite — dev-server integration: validate every `*.scene.json` edit
@@ -24,6 +24,20 @@ import { dirname, resolve } from "node:path";
24
24
  function incantoScenes(opts = {}) {
25
25
  return {
26
26
  name: "incanto-scenes",
27
+ /**
28
+ * The project's scenes, over HTTP, so the editor opened from a running
29
+ * game can browse and load them the same way the standalone
30
+ * `incanto-editor` does. Dev-only by construction (`configureServer`).
31
+ */
32
+ configureServer(server) {
33
+ const root = opts.root ?? server.config?.root ?? process.cwd();
34
+ server.middlewares.use("/api/scenes", (req, res) => {
35
+ serveSceneList(req, res, root);
36
+ });
37
+ server.middlewares.use("/api/scene", (req, res) => {
38
+ serveSceneFile(req, res, root);
39
+ });
40
+ },
27
41
  async handleHotUpdate(ctx) {
28
42
  if (!ctx.file.endsWith(".scene.json")) return void 0;
29
43
  let json;
@@ -58,6 +72,251 @@ function report(ctx, message) {
58
72
  }
59
73
  });
60
74
  }
75
+ const SKIP_DIRS = new Set([
76
+ "node_modules",
77
+ "dist",
78
+ "build",
79
+ "out",
80
+ ".git",
81
+ "coverage",
82
+ ".vite",
83
+ ".next"
84
+ ]);
85
+ /**
86
+ * Is this JSON an incanto scene? The file itself answers.
87
+ *
88
+ * A scene declares what it is — `{"format": 1, "type": "scene", "name", "root"}`
89
+ * — and those are exactly the four keys the loader demands before it will build
90
+ * anything (`validateHeader`). So "is this a scene" is not a guess from the file
91
+ * name; it is a fact readable from the first object in the file. That is what
92
+ * lets the browser mark a row ✓ instead of hoping.
93
+ */
94
+ function sceneFacts(json) {
95
+ if (json === null || typeof json !== "object" || Array.isArray(json)) return {
96
+ ok: false,
97
+ why: "the file is JSON, but not a JSON object"
98
+ };
99
+ const j = json;
100
+ if (j.type !== "scene") return {
101
+ ok: false,
102
+ why: `"type" must be "scene"${j.type === void 0 ? ", and this file has no \"type\"" : `, not ${JSON.stringify(j.type)}`}`
103
+ };
104
+ if (j.format !== 1) return {
105
+ ok: false,
106
+ why: `"format" must be 1, not ${JSON.stringify(j.format)}`
107
+ };
108
+ if (typeof j.root !== "object" || j.root === null) return {
109
+ ok: false,
110
+ why: "\"root\" must be a node object"
111
+ };
112
+ return {
113
+ ok: true,
114
+ name: typeof j.name === "string" ? j.name : "",
115
+ ...j.dimension === "2d" || j.dimension === "3d" ? { dimension: j.dimension } : {},
116
+ nodes: countNodes(j.root)
117
+ };
118
+ }
119
+ function countNodes(node) {
120
+ const children = node.children;
121
+ if (!Array.isArray(children)) return 1;
122
+ let n = 1;
123
+ for (const child of children) n += countNodes(child);
124
+ return n;
125
+ }
126
+ /** Reading a 300 MB "json" to label a row is not worth it — say so instead. */
127
+ const SNIFF_LIMIT = 16e6;
128
+ function inspect(abs, size) {
129
+ if (size > SNIFF_LIMIT) return { notScene: "too large to check without opening it" };
130
+ let parsed;
131
+ try {
132
+ parsed = JSON.parse(readFileSync(abs, "utf-8"));
133
+ } catch (error) {
134
+ return { notScene: `not valid JSON: ${message(error)}` };
135
+ }
136
+ const facts = sceneFacts(parsed);
137
+ return facts.ok ? { scene: {
138
+ name: facts.name,
139
+ ...facts.dimension ? { dimension: facts.dimension } : {},
140
+ nodes: facts.nodes
141
+ } } : { notScene: facts.why };
142
+ }
143
+ /** @internal Exported for tests. Root-bounded walk, deepest 6 levels. */
144
+ function discoverScenes(root) {
145
+ const found = [];
146
+ const walk = (dir, depth) => {
147
+ if (depth > 6) return;
148
+ let entries;
149
+ try {
150
+ entries = readdirSync(dir, { withFileTypes: true });
151
+ } catch {
152
+ return;
153
+ }
154
+ for (const entry of entries) if (entry.isDirectory()) {
155
+ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) walk(join(dir, entry.name), depth + 1);
156
+ } else if (entry.name.endsWith(".json")) {
157
+ const abs = join(dir, entry.name);
158
+ let size = 0;
159
+ let modified = 0;
160
+ try {
161
+ const stat = statSync(abs);
162
+ size = stat.size;
163
+ modified = stat.mtimeMs;
164
+ } catch {}
165
+ found.push({
166
+ rel: relative(root, abs).split(sep).join("/"),
167
+ abs,
168
+ size,
169
+ modified,
170
+ ...inspect(abs, size)
171
+ });
172
+ }
173
+ };
174
+ walk(root, 0);
175
+ return found.sort((a, b) => a.rel.localeCompare(b.rel));
176
+ }
177
+ /**
178
+ * @internal The one place a request's path becomes a file path.
179
+ *
180
+ * `.json` under the project, nothing else — the file browser offers every JSON
181
+ * and the loader decides, but a dev server still must not be a way to read
182
+ * `.env` or somebody's key file. Content is gated separately: a JSON that is
183
+ * not a scene is never sent to the page, only the reason it is not one.
184
+ */
185
+ function resolveSceneFile(root, rel) {
186
+ if (typeof rel !== "string" || !rel.endsWith(".json")) return null;
187
+ const abs = normalize(resolve(root, rel));
188
+ return abs.startsWith(root.endsWith(sep) ? root : root + sep) ? abs : null;
189
+ }
190
+ /**
191
+ * Same-origin assertion for state-changing requests.
192
+ *
193
+ * A dev server is reachable from any page the developer happens to have open:
194
+ * `fetch('http://localhost:5173/api/scenes', {method:'POST', ...})` from
195
+ * evil.example needs no CORS permission to be SENT — only to be read — so
196
+ * without this a visited page can write `*.scene.json` files into the repo.
197
+ * Browsers attach `Origin` to every POST/PUT (including cross-site form posts,
198
+ * the one path that used to omit it), so requiring it to match the host we were
199
+ * addressed as costs a real editor nothing. A request with no `Origin` at all is
200
+ * not a browser, and a non-browser client has no CSRF exposure to exploit —
201
+ * same rule the `incanto-editor` server uses.
202
+ */
203
+ function originAllowed(req) {
204
+ const raw = req.headers.origin;
205
+ const origin = Array.isArray(raw) ? raw[0] : raw;
206
+ if (!origin) return true;
207
+ const rawHost = req.headers.host;
208
+ try {
209
+ return new URL(origin).host === (Array.isArray(rawHost) ? rawHost[0] : rawHost ?? "");
210
+ } catch {
211
+ return false;
212
+ }
213
+ }
214
+ function json(res, status, body) {
215
+ res.statusCode = status;
216
+ res.setHeader("content-type", "application/json; charset=utf-8");
217
+ res.end(typeof body === "string" ? body : JSON.stringify(body));
218
+ }
219
+ function readBody(req) {
220
+ return new Promise((done, fail) => {
221
+ let raw = "";
222
+ req.on("data", (chunk) => {
223
+ raw += String(chunk);
224
+ if (raw.length > 64e6) fail(/* @__PURE__ */ new Error("scene too large"));
225
+ });
226
+ req.on("end", () => done(raw));
227
+ req.on("error", () => fail(/* @__PURE__ */ new Error("request failed")));
228
+ });
229
+ }
230
+ /** @internal Exported for tests — `GET`/`POST /api/scenes`. */
231
+ async function serveSceneList(req, res, root) {
232
+ const base = normalize(resolve(root));
233
+ if (req.method === void 0 || req.method === "GET") return json(res, 200, discoverScenes(base));
234
+ if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
235
+ if (!originAllowed(req)) return json(res, 403, { error: "cross-origin write refused" });
236
+ let body;
237
+ try {
238
+ body = JSON.parse(await readBody(req));
239
+ } catch (error) {
240
+ return json(res, 400, { error: `bad request body: ${message(error)}` });
241
+ }
242
+ const wanted = typeof body.path === "string" && body.path && !body.path.endsWith(".json") ? `${body.path}.scene.json` : body.path;
243
+ const abs = resolveSceneFile(base, wanted);
244
+ if (!abs) return json(res, 400, { error: "path must be a .json file inside the project" });
245
+ if (existsSync(abs)) return json(res, 409, { error: `already exists: ${String(wanted)}` });
246
+ const name = basename(abs).replace(/\.scene\.json$|\.json$/, "") || "Scene";
247
+ try {
248
+ mkdirSync(dirname(abs), { recursive: true });
249
+ writeFileSync(abs, `${JSON.stringify({
250
+ format: 1,
251
+ type: "scene",
252
+ dimension: "3d",
253
+ name,
254
+ root: {
255
+ name,
256
+ type: "Node3D"
257
+ }
258
+ }, null, 2)}\n`);
259
+ } catch (error) {
260
+ return json(res, 400, { error: message(error) });
261
+ }
262
+ return json(res, 201, {
263
+ rel: relative(base, abs).split(sep).join("/"),
264
+ abs,
265
+ size: 0,
266
+ modified: 0
267
+ });
268
+ }
269
+ /** @internal Exported for tests — `GET`/`PUT /api/scene?file=`. */
270
+ async function serveSceneFile(req, res, root) {
271
+ const base = normalize(resolve(root));
272
+ const file = new URL(req.url ?? "/", "http://localhost").searchParams.get("file");
273
+ const abs = resolveSceneFile(base, file);
274
+ if (!abs) return json(res, 400, { error: "?file= must be a .json file inside the project" });
275
+ if (req.method === void 0 || req.method === "GET") {
276
+ let text;
277
+ let parsed;
278
+ try {
279
+ text = readFileSync(abs, "utf-8");
280
+ parsed = JSON.parse(text);
281
+ } catch (error) {
282
+ return json(res, 422, { error: `${file}: ${message(error)}` });
283
+ }
284
+ const facts = sceneFacts(parsed);
285
+ if (!facts.ok) return json(res, 422, {
286
+ error: `${file} is not an incanto scene — ${facts.why}.`,
287
+ notScene: true
288
+ });
289
+ return json(res, 200, text);
290
+ }
291
+ if (req.method !== "PUT") return json(res, 405, { error: "method not allowed" });
292
+ if (!originAllowed(req)) return json(res, 403, { error: "cross-origin write refused" });
293
+ let raw;
294
+ try {
295
+ raw = await readBody(req);
296
+ } catch (error) {
297
+ return json(res, 400, { error: message(error) });
298
+ }
299
+ let parsed;
300
+ try {
301
+ parsed = JSON.parse(raw);
302
+ } catch (error) {
303
+ return json(res, 422, { error: `not JSON — the file was NOT written: ${message(error)}` });
304
+ }
305
+ const shape = sceneFacts(parsed);
306
+ if (!shape.ok) return json(res, 422, { error: `not an incanto scene — ${shape.why}. The file was NOT written.` });
307
+ try {
308
+ writeFileSync(abs, raw.endsWith("\n") ? raw : `${raw}\n`);
309
+ } catch (error) {
310
+ return json(res, 400, { error: message(error) });
311
+ }
312
+ return json(res, 200, {
313
+ ok: true,
314
+ output: abs
315
+ });
316
+ }
317
+ function message(error) {
318
+ return error instanceof Error ? error.message : String(error);
319
+ }
61
320
  /**
62
321
  * Serve the agent8 asset catalog at `/api/library` on the dev server.
63
322
  *
@@ -146,4 +405,4 @@ function libraryItem(row) {
146
405
  };
147
406
  }
148
407
  //#endregion
149
- export { incantoLibrary, incantoScenes, serveLibrary };
408
+ export { discoverScenes, incantoLibrary, incantoScenes, resolveSceneFile, sceneFacts, serveLibrary, serveSceneFile, serveSceneList };
@@ -1 +1 @@
1
- import{n as e}from"./index-Bx4UtWYY.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-BO6WU8by.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,2 +1,2 @@
1
- import{i as e,r as t,t as n}from"./index-Bx4UtWYY.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(`
1
+ import{i as e,r as t,t as n}from"./index-BO6WU8by.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
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)}function h(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 v(e,r,n,t.statsSource,t.actions)}var g=300,_=[`log`,`info`,`warn`,`error`,`debug`],v=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;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(x).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())}))}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))return;let t=new c(this.doc,this.container,{explorer:`Explorer`,inspector:`Inspector`,logs:`Logs`}[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}}[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()}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 _){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(x).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`],[`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 ${b(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;y(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;y(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,S(`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,S(`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,S(`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>g&&this.logRows.splice(0,this.logRows.length-g),this.renderLogs()}renderLogs(){let e=this.panels.get(`logs`);if(!e)return;y(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 y(e){for(let t of[...e.children])t.remove()}function b(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function x(e){if(typeof e==`string`)return e;try{return JSON.stringify(e)}catch{return String(e)}}function S(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{h as attachDebugOverlay};