incanto 0.40.1 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/bin/incanto-frame.mjs +144 -0
  2. package/dist/2d.d.ts +2 -2
  3. package/dist/2d.js +3 -3
  4. package/dist/3d.d.ts +76 -4
  5. package/dist/3d.js +5 -5
  6. package/dist/{behavior-TA8nySqb.d.ts → behavior-62q0HWBO.d.ts} +3 -1
  7. package/dist/{create-game-B7c5icho.js → create-game-9KPppR0L.js} +4 -4
  8. package/dist/{create-game-p-O66Y_U.js → create-game-DItIRyXH.js} +158 -5
  9. package/dist/debug.d.ts +36 -2
  10. package/dist/debug.js +183 -2
  11. package/dist/{environment-presets-CIZXGKnS.js → environment-presets-Vl5xBsXp.js} +2 -2
  12. package/dist/{gameplay-By8mslMc.js → gameplay-DRi9524r.js} +13 -0
  13. package/dist/gameplay.d.ts +13 -1
  14. package/dist/gameplay.js +1 -1
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +3 -167
  17. package/dist/{loader-CvSDKC3v.d.ts → loader-CeyU_bm1.d.ts} +1 -1
  18. package/dist/net.d.ts +1 -1
  19. package/dist/net.js +2 -2
  20. package/dist/{pathfinding-_UlKUoox.d.ts → pathfinding-C49JSNNq.d.ts} +1 -1
  21. package/dist/{physics-2d-CxIqR1cW.js → physics-2d-BmgXBNDB.js} +1 -1
  22. package/dist/{physics-3d-CgIHivyL.js → physics-3d-C2G604O1.js} +2 -2
  23. package/dist/react.d.ts +1 -1
  24. package/dist/react.js +1 -1
  25. package/dist/{register-DMFJI-c_.js → register-BSXV8T9F.js} +5 -1
  26. package/dist/{register-D5v8iqjZ.js → register-D651it1J.js} +1 -1
  27. package/dist/{register-CgMO5JBY.js → register-R2JTnIMw.js} +1 -1
  28. package/dist/{replay-DN-kG4va.d.ts → replay-CAphXMyM.d.ts} +1 -1
  29. package/dist/{replay-D_QY5Xm3.js → replay-DilbZgQI.js} +1 -1
  30. package/dist/src-cU57Uwdw.js +166 -0
  31. package/dist/{test-DUrGse_6.js → test-DuOD1DO8.js} +22 -11
  32. package/dist/test.d.ts +3 -3
  33. package/dist/test.js +2 -2
  34. package/dist/vite.d.ts +61 -1
  35. package/dist/vite.js +149 -2
  36. package/editor/assets/{agent8-CFq3LMrx.js → agent8-Csw0T7jh.js} +1 -1
  37. package/editor/assets/debug-C3qeBD4X.js +3 -0
  38. package/editor/assets/{index-CRwy6mq9.js → index-B1rUkWxB.js} +50 -50
  39. package/editor/index.html +1 -1
  40. package/package.json +3 -2
  41. package/skills/incanto-audio.md +6 -2
  42. package/skills/incanto-verifying-your-game.md +60 -2
  43. package/templates-app/beacon-isle-3d/package.json +1 -1
  44. package/templates-app/tps-3d/package.json +1 -1
  45. package/templates-app/village-quest-3d/package.json +1 -1
  46. package/editor/assets/debug-C2aomumK.js +0 -3
package/editor/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Incanto Scene Editor</title>
7
7
  <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect width='16' height='16' rx='3' fill='%236ee7dc'/><text x='8' y='12' text-anchor='middle' font-size='11' font-family='monospace' fill='%230e1018'>i</text></svg>" />
8
- <script type="module" crossorigin src="./assets/index-CRwy6mq9.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-B1rUkWxB.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="./assets/GameServer-C56iOUgF.js">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incanto",
3
- "version": "0.40.1",
3
+ "version": "0.42.0",
4
4
  "description": "Vibe-coding-first web game engine SDK — JSON-driven scenes on three.js",
5
5
  "keywords": [
6
6
  "game-engine",
@@ -97,6 +97,7 @@
97
97
  "incanto-play": "bin/incanto-play.mjs",
98
98
  "incanto-playtest": "bin/incanto-playtest.mjs",
99
99
  "incanto-feel": "bin/incanto-feel.mjs",
100
- "incanto-new": "bin/incanto-new.mjs"
100
+ "incanto-new": "bin/incanto-new.mjs",
101
+ "incanto-frame": "bin/incanto-frame.mjs"
101
102
  }
102
103
  }
@@ -274,9 +274,13 @@ For **background music** you usually want ONE track at a time and the ability to
274
274
  **crossfade** to another (exploration → boss, level A → level B). That's
275
275
  `engine.music` — a single-track manager that streams a clip and ramps gains:
276
276
 
277
+ **The bundle ships SFX only** — twelve effects, no music track. A background
278
+ track is your game's own file (`public/audio/…`) or a URL; `incanto/assets/audio/…`
279
+ has nothing to loop.
280
+
277
281
  ```ts
278
- // Loop a track (full volume, music bus):
279
- engine.music.play('incanto/assets/audio/theme.mp3');
282
+ // Loop a track (full volume, music bus) — YOUR file, from public/:
283
+ engine.music.play('/audio/theme.mp3');
280
284
 
281
285
  // Options: { loop = true, fadeIn = 0 (seconds), bus = 'music' }
282
286
  engine.music.play('intro.mp3', { loop: false, fadeIn: 2 });
@@ -289,20 +289,78 @@ createGame2D({ ..., debug: import.meta.env.VITE_INCANTO_DEBUG === '1' });
289
289
  VITE_INCANTO_DEBUG=1 bun run dev # overlay on; plain `bun run dev` / prod build = off
290
290
  ```
291
291
 
292
- A ☰ debug menu (top-left) toggles three draggable/resizable panels over the
292
+ A ☰ debug menu (top-left) toggles four draggable/resizable panels over the
293
293
  live game: **Explorer** (the scene tree — click any node), **Inspector** (the
294
294
  selected node's current state; numbers/strings/booleans/vectors editable
295
295
  in-place, and an OBJECT prop like `collider` opens into a row per key with the
296
296
  `shape` as a dropdown — picking a shape writes a complete valid collider, never
297
297
  a box's `size` under a sphere's tag; bulky values like a 54-row `transforms`
298
298
  fold behind `▸ Array(54)` and expand on click, one row per element), **Logs** (an `engine.log` tail with level filters and an optional
299
- browser-console capture toggle) — plus **Stats**, an always-on-top chip pinned
299
+ browser-console capture toggle), **Time** (see below) — plus **Stats**, an always-on-top chip pinned
300
300
  top-right (`60 fps · 12.1 ms` / `nodes 34 · tris 18.2k · calls 21`, refreshed
301
301
  ~2×/s; the same numbers any code can read via `game.stats()`) — and
302
302
  **Colliders**, a switch that draws every physics shape as light-green wireframe
303
303
  in the game view. Manual boots:
304
304
  `(await import('incanto/debug')).attachDebugOverlay(engine)`.
305
305
 
306
+ **Time** is the panel for anything that happens too fast to see. A slider
307
+ (0–2) and a number box — synced, and the box takes values past the slider's
308
+ ceiling — drive `engine.timeScale`, with `0.25× 0.5× 1× 2×` presets:
309
+
310
+ - **slow motion** is how you watch a hit land, a jump arc, a particle burst
311
+ - **⏸ Pause** freezes GAME time and nothing else. Rendering keeps running, so
312
+ the camera still moves and the panels stay live — it is a pause, not a stop.
313
+ Resume returns to the scale you paused at.
314
+ - **⏭ Next frame** pauses first, then advances exactly one fixed step (1/60 s
315
+ by default) — the engine's clock included. Click it repeatedly to walk a
316
+ collision, a state machine or a spawn one frame at a time.
317
+
318
+ `timeScale` is just `engine.timeScale`, so a behavior can do the same thing
319
+ (`hitStop()` in `incanto/gameplay` is this on a timer). A value that is not a
320
+ number is refused and negatives clamp to 0: the scale multiplies every dt in
321
+ the engine, so one NaN would take physics, timers and animation with it.
322
+
323
+ ### `bunx incanto-frame` — what is actually on screen
324
+
325
+ The rung above framing. `framing` PREDICTS what the camera should see from the
326
+ scene graph; this measures what the GPU drew — the only one that can catch a
327
+ black screen, a material that failed, or a shader drawing where it should not.
328
+
329
+ ```
330
+ $ bunx incanto-frame
331
+ frame 1280×720 · luminance 0.62
332
+ ```
333
+
334
+ ```
335
+ $ bunx incanto-frame
336
+ BLACK SCREEN — nothing was drawn (no light, no camera, or nothing in view)
337
+ frame 1280×720 · luminance 0.00
338
+ ```
339
+
340
+ Start your game's dev server and open the page first: **the pixels are in the
341
+ browser, not in the dev server.** You do not have to tell it where anything is —
342
+ it reads the ports that are actually listening, asks each one whether it is an
343
+ incanto dev server, and asks that page for a frame over vite's own HMR channel.
344
+
345
+ It writes **no files**. `--json` gives the full report, including a 16×9 grid of
346
+ mean cell colours — enough to answer *where* the frame changed, small enough to
347
+ read. From game code or the console, `game.frame()` returns the same object.
348
+
349
+ Three outcomes, each with its own fix:
350
+
351
+ | | |
352
+ | --- | --- |
353
+ | `no incanto dev server found` | start the dev server |
354
+ | `running on :5173, but no page is connected` | open the preview |
355
+ | a report | read it |
356
+
357
+ Exit code 1 on a black screen, so CI can gate on it.
358
+
359
+ **What it cannot do:** a 16×9 grid does not see a one-pixel seam. It answers
360
+ "did anything render, and roughly what does it look like", not "is this frame
361
+ pixel-identical to last week's". For exact regression detection use the numbers
362
+ (`incanto-feel`, conformance baselines), which are diffable and do not flap.
363
+
306
364
  The Inspector HOLDS STILL while you use it: it stops refreshing whenever an
307
365
  input has focus or the pointer is inside the panel, and it keeps your scroll
308
366
  position across refreshes. Live values resume the moment you move away.
@@ -14,7 +14,7 @@
14
14
  "@dimforge/rapier2d-compat": "0.19.3",
15
15
  "@dimforge/rapier3d-compat": "0.19.3",
16
16
  "@pixiv/three-vrm": "^3.5.3",
17
- "incanto": "^0.40.1",
17
+ "incanto": "^0.42.0",
18
18
  "three": "^0.184.0"
19
19
  },
20
20
  "devDependencies": {
@@ -13,7 +13,7 @@
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
14
  "@dimforge/rapier3d-compat": "0.19.3",
15
15
  "@pixiv/three-vrm": "^3.5.3",
16
- "incanto": "^0.40.1",
16
+ "incanto": "^0.42.0",
17
17
  "three": "^0.184.0"
18
18
  },
19
19
  "devDependencies": {
@@ -13,7 +13,7 @@
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
14
  "@dimforge/rapier3d-compat": "0.19.3",
15
15
  "@pixiv/three-vrm": "^3.5.3",
16
- "incanto": "^0.40.1",
16
+ "incanto": "^0.42.0",
17
17
  "three": "^0.184.0"
18
18
  },
19
19
  "devDependencies": {
@@ -1,3 +0,0 @@
1
- import{i as e,r as t,t as n}from"./index-CRwy6mq9.js";function r(e,t,n,r,i,a,o=180,s=120){let c=Math.max(o,n),l=Math.max(s,r);return{x:Math.min(Math.max(0,e),Math.max(0,i-c)),y:Math.min(Math.max(0,t),Math.max(0,a-l)),w:c,h:l}}function i(e,t){for(let[n,r]of Object.entries(t))e.style[n]=r}var a=`rgba(18, 20, 26, 0.92)`,o=`1px solid rgba(255,255,255,0.14)`,s=`12px ui-monospace, SFMono-Regular, Menlo, monospace`,c=class{host;el;body;onClose=()=>{};x;y;w;h;constructor(e,t,n,r){this.host=t,this.x=r.x,this.y=r.y,this.w=r.w,this.h=r.h,this.el=e.createElement(`div`),i(this.el,{position:`absolute`,background:a,border:o,borderRadius:`8px`,color:`rgba(255,255,255,0.88)`,font:s,display:`flex`,flexDirection:`column`,overflow:`hidden`,zIndex:`40`,pointerEvents:`auto`,boxShadow:`0 8px 28px rgba(0,0,0,0.45)`});let c=e.createElement(`div`);c.textContent=n,i(c,{padding:`6px 28px 6px 10px`,background:`rgba(255,255,255,0.07)`,cursor:`move`,userSelect:`none`,touchAction:`none`,fontWeight:`700`}),this.el.appendChild(c);let l=e.createElement(`div`);l.textContent=`×`,l.title=`close`,i(l,{position:`absolute`,top:`2px`,right:`8px`,cursor:`pointer`,fontSize:`16px`,lineHeight:`20px`,opacity:`0.7`}),l.addEventListener(`click`,()=>this.onClose()),this.el.appendChild(l),this.body=e.createElement(`div`),i(this.body,{flex:`1`,overflow:`auto`,padding:`8px 10px`}),this.el.appendChild(this.body);let u=e.createElement(`div`);u.textContent=`◢`,i(u,{position:`absolute`,right:`2px`,bottom:`0`,cursor:`nwse-resize`,opacity:`0.5`,userSelect:`none`,touchAction:`none`}),this.el.appendChild(u),this.wireDrag(c,(e,t)=>{this.x+=e,this.y+=t,this.layout()}),this.wireDrag(u,(e,t)=>{this.w+=e,this.h+=t,this.layout()}),this.layout(),t.appendChild(this.el)}remove(){this.el.remove()}layout(){let e=this.host.getBoundingClientRect(),t=r(this.x,this.y,this.w,this.h,e.width,e.height);this.x=t.x,this.y=t.y,this.w=t.w,this.h=t.h,i(this.el,{left:`${t.x}px`,top:`${t.y}px`,width:`${t.w}px`,height:`${t.h}px`})}wireDrag(e,t){let n=null,r=0,i=0;e.addEventListener(`pointerdown`,t=>{n=t.pointerId,r=t.clientX,i=t.clientY,e.setPointerCapture?.(t.pointerId)}),e.addEventListener(`pointermove`,e=>{e.pointerId===n&&(t(e.clientX-r,e.clientY-i),r=e.clientX,i=e.clientY)});let a=e=>{e.pointerId===n&&(n=null)};e.addEventListener(`pointerup`,a),e.addEventListener(`pointercancel`,a)}},l=96,u=200,d=8192;function f(e){return Array.isArray(e)?`Array(${e.length})`:`Object(${Object.keys(e).length} keys)`}function p(e,t){if(Array.isArray(e)&&e.some(e=>typeof e==`object`&&!!e)){let t=e.slice(0,u).map(e=>JSON.stringify(e)),n=e.length-u;return t.join(`
2
- `)+(n>0?`\n… ${n} more`:``)}return t.length>d?`${t.slice(0,d)} … (${t.length-d} more chars)`:t}function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}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;if(e instanceof Error){let t=e.stack?.split(`
3
- `)[1]?.trim();return`${e.name}: ${e.message}${t?` (${t})`:``}`}try{let t=JSON.stringify(e);return t===`{}`||t===void 0?String(e):t}catch{return String(e)}}function 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};