incanto 0.42.0 → 0.44.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.
- package/bin/incanto-feel.mjs +20 -1
- package/bin/incanto-frame.mjs +55 -11
- package/dist/2d.js +1 -1
- package/dist/3d.d.ts +2 -40
- package/dist/3d.js +6 -5
- package/dist/{create-game-DItIRyXH.js → create-game-D90pyPMx.js} +17 -81
- package/dist/{create-game-9KPppR0L.js → create-game-DFRbG0Bj.js} +3 -2
- package/dist/debug.d.ts +44 -3
- package/dist/debug.js +562 -4
- package/dist/{environment-presets-Vl5xBsXp.js → environment-presets-Ds5kXLoF.js} +1 -1
- package/dist/frame-report-Ct8XgsmV.d.ts +80 -0
- package/dist/frame-report-Lr3VO24R.js +197 -0
- package/dist/{gameplay-DRi9524r.js → gameplay-BQOeAid6.js} +37 -0
- package/dist/gameplay.d.ts +17 -0
- package/dist/gameplay.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{physics-3d-C2G604O1.js → physics-3d-CLPFv99o.js} +2 -2
- package/dist/react.js +1 -1
- package/dist/{src-cU57Uwdw.js → src-B3n06SsL.js} +1 -1
- package/dist/{teardown-CCtAMDLB.js → teardown-yePMOE1K.js} +32 -1
- package/dist/{test-DuOD1DO8.js → test-BMgiiD5i.js} +117 -5
- package/dist/test.d.ts +35 -1
- package/dist/test.js +2 -2
- package/dist/vite.d.ts +15 -1
- package/dist/vite.js +49 -6
- package/editor/assets/{agent8-Csw0T7jh.js → agent8-MgX6kYWH.js} +1 -1
- package/editor/assets/debug-Czri9cgX.js +3 -0
- package/editor/assets/{index-B1rUkWxB.js → index-D9MX3UHF.js} +60 -60
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/skills/incanto-3d-character.md +19 -0
- package/skills/incanto-gameplay-behaviors.md +27 -0
- package/skills/incanto-node-reference.md +2 -0
- package/skills/incanto-verifying-your-game.md +60 -4
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +47 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +53 -0
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/grove.scene.json +43 -0
- package/editor/assets/debug-C3qeBD4X.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-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-D9MX3UHF.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="./assets/GameServer-C56iOUgF.js">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -269,6 +269,25 @@ button is held — a free-roaming cursor never spins the camera. Vertical look i
|
|
|
269
269
|
STANDARD (non-inverted): mouse UP looks up (in free view the camera swings down so
|
|
270
270
|
it looks up at the character against the sky), mouse DOWN looks down.
|
|
271
271
|
|
|
272
|
+
### Check it instead of reasoning about it
|
|
273
|
+
|
|
274
|
+
`bunx incanto-feel <scene.json>` now ends with the answer:
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
facing: /Game/Player/Skin faces its travel (dot 1.00)
|
|
278
|
+
facing: /Game/Husk/Skin RUNS BACKWARDS (dot -1.00) — the skin points away from
|
|
279
|
+
the direction of travel.
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
It drives the character, measures where it actually went, and dots that with the
|
|
283
|
+
skin's forward axis. **+1 is right, −1 is backwards**, and a declared
|
|
284
|
+
`skinYawOffset` is subtracted first — so a model whose art faces another way is
|
|
285
|
+
correctly configured, not a false alarm.
|
|
286
|
+
|
|
287
|
+
This cannot be an `auditScene` warning: a scene file has no velocity, and the bug
|
|
288
|
+
is a behavior writing a heading at runtime. `facingReport()` from `incanto/test`
|
|
289
|
+
is the same check for your own tests.
|
|
290
|
+
|
|
272
291
|
## First-person weapon viewmodel + the tracer-from-the-muzzle rule
|
|
273
292
|
|
|
274
293
|
A weapon VIEWMODEL is just a `MeshInstance3D`/group child of the **root** `Camera`
|
|
@@ -797,6 +797,33 @@ flow.win('AREA CLEAR');
|
|
|
797
797
|
flow.pause(); flow.resume();
|
|
798
798
|
```
|
|
799
799
|
|
|
800
|
+
### A pause menu, without a script
|
|
801
|
+
|
|
802
|
+
`GameFlow` drives one for you. Declare a `pause` action, put a `UiPanel` named
|
|
803
|
+
`PauseMenu` in the HUD, and that is the whole feature:
|
|
804
|
+
|
|
805
|
+
```json
|
|
806
|
+
{ "input": { "pause": { "type": "button", "keys": ["Escape"] } },
|
|
807
|
+
"root": { "name": "Game", "type": "Node3D", "children": [
|
|
808
|
+
{ "name": "Flow", "type": "Node", "script": { "name": "GameFlow" } },
|
|
809
|
+
{ "name": "HUD", "type": "HudLayer", "children": [
|
|
810
|
+
{ "name": "PauseMenu", "type": "UiPanel", "props": { "anchor": "center" },
|
|
811
|
+
"children": [{ "name": "Title", "type": "UiText", "props": { "text": "Paused" } }] }
|
|
812
|
+
] }
|
|
813
|
+
] } }
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
The panel is hidden while playing and shown while paused — `GameFlow` owns its
|
|
817
|
+
`visible`, so do not set it yourself. `pauseAction` and `pausePanelPath` rename
|
|
818
|
+
either half; `pausePanelPath: ""` turns it off.
|
|
819
|
+
|
|
820
|
+
The pause key is polled in `update()`, which still runs at `timeScale 0` — a
|
|
821
|
+
menu that cannot be closed by the key that opened it is worse than no menu.
|
|
822
|
+
|
|
823
|
+
**Give the Flow its own node.** A node holds one behavior, and your root
|
|
824
|
+
probably already carries the game's director script.
|
|
825
|
+
|
|
826
|
+
|
|
800
827
|
Terminal states ignore further transitions; the restart action (or
|
|
801
828
|
`flow.restart()`) reloads the scene from its source JSON — fresh nodes,
|
|
802
829
|
physics, input. Listen to the `flowChanged(state)` signal for custom UI.
|
|
@@ -1324,8 +1324,10 @@ Signals: `dayPhaseChanged`
|
|
|
1324
1324
|
| Prop | Default | Kind |
|
|
1325
1325
|
|---|---|---|
|
|
1326
1326
|
| `restartAction` | `"restart"` | string |
|
|
1327
|
+
| `pauseAction` | `"pause"` | string |
|
|
1327
1328
|
| `freezeOnEnd` | `true` | boolean |
|
|
1328
1329
|
| `bannerPath` | `"%Banner"` | node path |
|
|
1330
|
+
| `pausePanelPath` | `"%PauseMenu"` | node path |
|
|
1329
1331
|
|
|
1330
1332
|
Signals: `flowChanged`
|
|
1331
1333
|
|
|
@@ -356,10 +356,66 @@ Three outcomes, each with its own fix:
|
|
|
356
356
|
|
|
357
357
|
Exit code 1 on a black screen, so CI can gate on it.
|
|
358
358
|
|
|
359
|
-
**What
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
359
|
+
**What one frame cannot tell you:** a 16×9 grid does not see a one-pixel seam,
|
|
360
|
+
and no report of a single frame ever could — averaged into a cell, a hairline is
|
|
361
|
+
a fraction of a level, and nothing distinguishes it from a thin rope in the
|
|
362
|
+
scene. Absolutely, it is undecidable.
|
|
363
|
+
|
|
364
|
+
Relatively, it is obvious.
|
|
365
|
+
|
|
366
|
+
### `--remember` / `--diff` — did my change do what I think it did
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
$ bunx incanto-frame --remember # before
|
|
370
|
+
frame 1280×720 · luminance 0.62
|
|
371
|
+
remembered as "last"
|
|
372
|
+
|
|
373
|
+
...edit the shader...
|
|
374
|
+
|
|
375
|
+
$ bunx incanto-frame --diff # after
|
|
376
|
+
frame 1280×720 · luminance 0.62
|
|
377
|
+
changed 0.9% of the frame · largest shift 74/255, mean 0.4
|
|
378
|
+
region x 636..660, y 0..720 — a vertical band
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
The single-frame report is **identical** across that change — same luminance,
|
|
382
|
+
same verdict. The comparison names it and says where.
|
|
383
|
+
|
|
384
|
+
**Pause the game first** (debug ☰ → Time → Pause), or you are measuring time
|
|
385
|
+
passing rather than your change; when more than 90% of the frame moved, the
|
|
386
|
+
output says so. Baselines live in the **dev server's memory** and die with it —
|
|
387
|
+
no file is written anywhere, and a restarted server has forgotten. Name them
|
|
388
|
+
(`--remember shader-work`, `--diff shader-work`) to keep more than one.
|
|
389
|
+
|
|
390
|
+
`--threshold N` (default 3) is how far a cell must move to count. Sensitivity is
|
|
391
|
+
a function of size: at 720p a one-pixel line covers 5% of a cell, so the default
|
|
392
|
+
catches it when it is ~60 levels from its surroundings — a dark seam on bright
|
|
393
|
+
water, comfortably. Drop to `--threshold 1` for fainter ones, and expect the
|
|
394
|
+
frame's own noise to start showing up.
|
|
395
|
+
|
|
396
|
+
For regressions ACROSS builds — where the browser, the window and the session
|
|
397
|
+
are all different — use the numbers instead (`incanto-feel`, conformance
|
|
398
|
+
baselines). They are diffable and they do not flap.
|
|
399
|
+
|
|
400
|
+
### Handing a person's eyes to the agent
|
|
401
|
+
|
|
402
|
+
Two menu entries for the moment you can see something wrong and the agent
|
|
403
|
+
cannot:
|
|
404
|
+
|
|
405
|
+
- **Copy scene JSON** — the whole scene on the clipboard, ready to paste into a
|
|
406
|
+
conversation. The half a screenshot cannot carry.
|
|
407
|
+
- **Capture region** — drag a rectangle over the game, then adjust it: eight
|
|
408
|
+
handles resize, the middle grabs and slides, and the running label shows the
|
|
409
|
+
size of the image you will get. **Copy to clipboard** confirms; Enter does the
|
|
410
|
+
same, Esc and **Cancel** get out. Nothing leaves the page until you say so.
|
|
411
|
+
|
|
412
|
+
The image comes from the renderer's own end-of-frame read, not
|
|
413
|
+
`canvas.toDataURL()`, which returns black once a frame has composited. The
|
|
414
|
+
selection is scaled by `devicePixelRatio`, so what you dragged is what you get.
|
|
415
|
+
|
|
416
|
+
Clipboard writes need a secure context (a dev server on localhost counts) and a
|
|
417
|
+
click, which the menu is. When a browser refuses, the overlay says so rather
|
|
418
|
+
than pretending.
|
|
363
419
|
|
|
364
420
|
The Inspector HOLDS STILL while you use it: it stops refreshing whenever an
|
|
365
421
|
input has focus or the pointer is inside the panel, and it keeps your scroll
|
|
@@ -67,6 +67,14 @@
|
|
|
67
67
|
"type": "button",
|
|
68
68
|
"keys": ["Mouse0", "KeyF"],
|
|
69
69
|
"touch": "button"
|
|
70
|
+
},
|
|
71
|
+
"pause": {
|
|
72
|
+
"type": "button",
|
|
73
|
+
"keys": ["Escape"]
|
|
74
|
+
},
|
|
75
|
+
"restart": {
|
|
76
|
+
"type": "button",
|
|
77
|
+
"keys": ["KeyR"]
|
|
70
78
|
}
|
|
71
79
|
},
|
|
72
80
|
"assets": {
|
|
@@ -106,6 +114,16 @@
|
|
|
106
114
|
"name": "IsleDirector"
|
|
107
115
|
},
|
|
108
116
|
"children": [
|
|
117
|
+
{
|
|
118
|
+
"name": "Flow",
|
|
119
|
+
"type": "Node",
|
|
120
|
+
"script": {
|
|
121
|
+
"name": "GameFlow",
|
|
122
|
+
"props": {
|
|
123
|
+
"pausePanelPath": "%PauseMenu"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
109
127
|
{
|
|
110
128
|
"name": "Terrain",
|
|
111
129
|
"type": "Node3D",
|
|
@@ -2946,6 +2964,35 @@
|
|
|
2946
2964
|
"anchor": "bottomRight",
|
|
2947
2965
|
"shadow": true
|
|
2948
2966
|
}
|
|
2967
|
+
},
|
|
2968
|
+
{
|
|
2969
|
+
"name": "PauseMenu",
|
|
2970
|
+
"type": "UiPanel",
|
|
2971
|
+
"props": {
|
|
2972
|
+
"anchor": "center",
|
|
2973
|
+
"layout": "column",
|
|
2974
|
+
"gap": 10,
|
|
2975
|
+
"padding": 18
|
|
2976
|
+
},
|
|
2977
|
+
"children": [
|
|
2978
|
+
{
|
|
2979
|
+
"name": "PausedTitle",
|
|
2980
|
+
"type": "UiText",
|
|
2981
|
+
"props": {
|
|
2982
|
+
"text": "Paused",
|
|
2983
|
+
"size": 24
|
|
2984
|
+
}
|
|
2985
|
+
},
|
|
2986
|
+
{
|
|
2987
|
+
"name": "ResumeHint",
|
|
2988
|
+
"type": "UiText",
|
|
2989
|
+
"props": {
|
|
2990
|
+
"text": "Esc to resume · R to restart",
|
|
2991
|
+
"size": 13,
|
|
2992
|
+
"color": "#c9d2dd"
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
]
|
|
2949
2996
|
}
|
|
2950
2997
|
]
|
|
2951
2998
|
}
|
|
@@ -54,6 +54,14 @@
|
|
|
54
54
|
"reload": {
|
|
55
55
|
"type": "button",
|
|
56
56
|
"keys": ["KeyR"]
|
|
57
|
+
},
|
|
58
|
+
"pause": {
|
|
59
|
+
"type": "button",
|
|
60
|
+
"keys": ["Escape"]
|
|
61
|
+
},
|
|
62
|
+
"restart": {
|
|
63
|
+
"type": "button",
|
|
64
|
+
"keys": ["KeyR"]
|
|
57
65
|
}
|
|
58
66
|
},
|
|
59
67
|
"assets": {
|
|
@@ -92,6 +100,16 @@
|
|
|
92
100
|
}
|
|
93
101
|
},
|
|
94
102
|
"children": [
|
|
103
|
+
{
|
|
104
|
+
"name": "Flow",
|
|
105
|
+
"type": "Node",
|
|
106
|
+
"script": {
|
|
107
|
+
"name": "GameFlow",
|
|
108
|
+
"props": {
|
|
109
|
+
"pausePanelPath": "%PauseMenu"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
95
113
|
{
|
|
96
114
|
"name": "Arena",
|
|
97
115
|
"type": "Node3D",
|
|
@@ -651,6 +669,41 @@
|
|
|
651
669
|
"script": {
|
|
652
670
|
"name": "HudUpdater"
|
|
653
671
|
}
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
"name": "HUD",
|
|
675
|
+
"type": "HudLayer",
|
|
676
|
+
"children": [
|
|
677
|
+
{
|
|
678
|
+
"name": "PauseMenu",
|
|
679
|
+
"type": "UiPanel",
|
|
680
|
+
"props": {
|
|
681
|
+
"anchor": "center",
|
|
682
|
+
"layout": "column",
|
|
683
|
+
"gap": 10,
|
|
684
|
+
"padding": 18
|
|
685
|
+
},
|
|
686
|
+
"children": [
|
|
687
|
+
{
|
|
688
|
+
"name": "PausedTitle",
|
|
689
|
+
"type": "UiText",
|
|
690
|
+
"props": {
|
|
691
|
+
"text": "Paused",
|
|
692
|
+
"size": 24
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
"name": "ResumeHint",
|
|
697
|
+
"type": "UiText",
|
|
698
|
+
"props": {
|
|
699
|
+
"text": "Esc to resume · R to restart",
|
|
700
|
+
"size": 13,
|
|
701
|
+
"color": "#c9d2dd"
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
]
|
|
705
|
+
}
|
|
706
|
+
]
|
|
654
707
|
}
|
|
655
708
|
]
|
|
656
709
|
},
|
|
@@ -67,6 +67,10 @@
|
|
|
67
67
|
"restart": {
|
|
68
68
|
"type": "button",
|
|
69
69
|
"keys": ["KeyR"]
|
|
70
|
+
},
|
|
71
|
+
"pause": {
|
|
72
|
+
"type": "button",
|
|
73
|
+
"keys": ["Escape"]
|
|
70
74
|
}
|
|
71
75
|
},
|
|
72
76
|
"assets": {
|
|
@@ -106,6 +110,16 @@
|
|
|
106
110
|
"name": "GroveDirector"
|
|
107
111
|
},
|
|
108
112
|
"children": [
|
|
113
|
+
{
|
|
114
|
+
"name": "Flow",
|
|
115
|
+
"type": "Node",
|
|
116
|
+
"script": {
|
|
117
|
+
"name": "GameFlow",
|
|
118
|
+
"props": {
|
|
119
|
+
"pausePanelPath": "%PauseMenu"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
109
123
|
{
|
|
110
124
|
"name": "Ground",
|
|
111
125
|
"type": "StaticBody3D",
|
|
@@ -792,6 +806,35 @@
|
|
|
792
806
|
"anchor": "bottomRight",
|
|
793
807
|
"shadow": true
|
|
794
808
|
}
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
"name": "PauseMenu",
|
|
812
|
+
"type": "UiPanel",
|
|
813
|
+
"props": {
|
|
814
|
+
"anchor": "center",
|
|
815
|
+
"layout": "column",
|
|
816
|
+
"gap": 10,
|
|
817
|
+
"padding": 18
|
|
818
|
+
},
|
|
819
|
+
"children": [
|
|
820
|
+
{
|
|
821
|
+
"name": "PausedTitle",
|
|
822
|
+
"type": "UiText",
|
|
823
|
+
"props": {
|
|
824
|
+
"text": "Paused",
|
|
825
|
+
"size": 24
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
"name": "ResumeHint",
|
|
830
|
+
"type": "UiText",
|
|
831
|
+
"props": {
|
|
832
|
+
"text": "Esc to resume · R to restart",
|
|
833
|
+
"size": 13,
|
|
834
|
+
"color": "#c9d2dd"
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
]
|
|
795
838
|
}
|
|
796
839
|
]
|
|
797
840
|
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{i as e,r as t,t as n}from"./index-B1rUkWxB.js";function r(e,t,n,r,i,a,o=180,s=120){let c=Math.max(o,n),l=Math.max(s,r);return{x:Math.min(Math.max(0,e),Math.max(0,i-c)),y:Math.min(Math.max(0,t),Math.max(0,a-l)),w:c,h:l}}function i(e,t){for(let[n,r]of Object.entries(t))e.style[n]=r}var a=`rgba(18, 20, 26, 0.92)`,o=`1px solid rgba(255,255,255,0.14)`,s=`12px ui-monospace, SFMono-Regular, Menlo, monospace`,c=class{host;el;body;onClose=()=>{};x;y;w;h;constructor(e,t,n,r){this.host=t,this.x=r.x,this.y=r.y,this.w=r.w,this.h=r.h,this.el=e.createElement(`div`),i(this.el,{position:`absolute`,background:a,border:o,borderRadius:`8px`,color:`rgba(255,255,255,0.88)`,font:s,display:`flex`,flexDirection:`column`,overflow:`hidden`,zIndex:`40`,pointerEvents:`auto`,boxShadow:`0 8px 28px rgba(0,0,0,0.45)`});let c=e.createElement(`div`);c.textContent=n,i(c,{padding:`6px 28px 6px 10px`,background:`rgba(255,255,255,0.07)`,cursor:`move`,userSelect:`none`,touchAction:`none`,fontWeight:`700`}),this.el.appendChild(c);let l=e.createElement(`div`);l.textContent=`×`,l.title=`close`,i(l,{position:`absolute`,top:`2px`,right:`8px`,cursor:`pointer`,fontSize:`16px`,lineHeight:`20px`,opacity:`0.7`}),l.addEventListener(`click`,()=>this.onClose()),this.el.appendChild(l),this.body=e.createElement(`div`),i(this.body,{flex:`1`,overflow:`auto`,padding:`8px 10px`}),this.el.appendChild(this.body);let u=e.createElement(`div`);u.textContent=`◢`,i(u,{position:`absolute`,right:`2px`,bottom:`0`,cursor:`nwse-resize`,opacity:`0.5`,userSelect:`none`,touchAction:`none`}),this.el.appendChild(u),this.wireDrag(c,(e,t)=>{this.x+=e,this.y+=t,this.layout()}),this.wireDrag(u,(e,t)=>{this.w+=e,this.h+=t,this.layout()}),this.layout(),t.appendChild(this.el)}remove(){this.el.remove()}layout(){let e=this.host.getBoundingClientRect(),t=r(this.x,this.y,this.w,this.h,e.width,e.height);this.x=t.x,this.y=t.y,this.w=t.w,this.h=t.h,i(this.el,{left:`${t.x}px`,top:`${t.y}px`,width:`${t.w}px`,height:`${t.h}px`})}wireDrag(e,t){let n=null,r=0,i=0;e.addEventListener(`pointerdown`,t=>{n=t.pointerId,r=t.clientX,i=t.clientY,e.setPointerCapture?.(t.pointerId)}),e.addEventListener(`pointermove`,e=>{e.pointerId===n&&(t(e.clientX-r,e.clientY-i),r=e.clientX,i=e.clientY)});let a=e=>{e.pointerId===n&&(n=null)};e.addEventListener(`pointerup`,a),e.addEventListener(`pointercancel`,a)}},l=96,u=200,d=8192;function f(e){return Array.isArray(e)?`Array(${e.length})`:`Object(${Object.keys(e).length} keys)`}function p(e,t){if(Array.isArray(e)&&e.some(e=>typeof e==`object`&&!!e)){let t=e.slice(0,u).map(e=>JSON.stringify(e)),n=e.length-u;return t.join(`
|
|
2
|
-
`)+(n>0?`\n… ${n} more`:``)}return t.length>d?`${t.slice(0,d)} … (${t.length-d} more chars)`:t}function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var h=2;function g(e,t={}){let n=t.doc??(typeof document<`u`?document:null);if(!n)return null;let r=t.container??(typeof document<`u`?document.body:null);return!r||typeof r.appendChild!=`function`?null:new y(e,r,n,t.statsSource,t.actions)}var _=300,v=[`log`,`info`,`warn`,`error`,`debug`],y=class{engine;container;doc;statsSource;actions;panels=new Map;cleanups=[];menuButton;dropdown=null;selected=null;collapsedFlags=new Map;statsChip=null;logRows=[];levelEnabled={debug:!0,info:!0,warn:!0,error:!0};consoleCapture=!1;consolePatched=[];frame=0;editing=0;detailOpen=new WeakMap;resumeScale=1;timeEls=null;hovering=!1;constructor(e,t,n,r,a=[]){this.engine=e,this.container=t,this.doc=n,this.statsSource=r,this.actions=a,this.menuButton=n.createElement(`div`),this.menuButton.textContent=`☰ debug`,i(this.menuButton,{position:`absolute`,top:`8px`,left:`8px`,padding:`4px 10px`,background:`rgba(18,20,26,0.85)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,color:`rgba(255,255,255,0.85)`,font:`12px ui-monospace, Menlo, monospace`,cursor:`pointer`,userSelect:`none`,zIndex:`50`,pointerEvents:`auto`}),this.menuButton.addEventListener(`click`,()=>this.toggleDropdown()),t.style.position||(t.style.position=`relative`),t.appendChild(this.menuButton),this.cleanups.push(e.log.added.connect(e=>{this.pushLog({level:e.level,source:`engine`,text:e.parts.map(S).join(` `)})})),this.cleanups.push(e.sceneChanged.connect(()=>{this.selected=null,this.applyColliderScope(),this.renderExplorer(),this.renderInspector()})),this.cleanups.push(e.updated.connect(()=>{this.frame+=1,this.frame%30==0&&(this.renderExplorer(),this.renderStats(),this.editing===0&&!this.hovering&&this.renderInspector(),this.syncTime())}))}isOpen(e){return e===`colliders`?this.colliderMode!==`off`:e===`stats`?this.statsChip!==null:this.panels.has(e)}colliderMode=`off`;setColliders(e){this.colliderMode=e,this.applyColliderScope()}applyColliderScope(){let e=this.colliderMode;for(let t of n(`2d`).concat(n(`3d`)))t.debugDraw=e!==`off`,t.debugScope=e===`selected`?this.selected:null}open(e){if(e===`stats`){this.openStatsChip();return}if(this.panels.has(e)){e===`explorer`&&this.renderExplorer(),e===`inspector`&&this.renderInspector(),e===`logs`&&this.renderLogs(),e===`time`&&this.renderTime();return}let t=new c(this.doc,this.container,{explorer:`Explorer`,inspector:`Inspector`,logs:`Logs`,time:`Time`}[e],{explorer:{x:12,y:44,w:240,h:320},inspector:{x:264,y:44,w:280,h:320},logs:{x:12,y:380,w:532,h:200},time:{x:556,y:44,w:260,h:150}}[e]);t.onClose=()=>this.close(e),e===`inspector`&&(t.body.addEventListener(`pointerenter`,()=>{this.hovering=!0}),t.body.addEventListener(`pointerleave`,()=>{this.hovering=!1})),this.panels.set(e,t),e===`explorer`&&this.renderExplorer(),e===`inspector`&&this.renderInspector(),e===`logs`&&this.renderLogs(),e===`time`&&this.renderTime()}close(e){if(e===`inspector`&&(this.hovering=!1),e===`stats`){this.statsChip?.remove(),this.statsChip=null;return}let t=this.panels.get(e);t&&(t.remove(),this.panels.delete(e))}toggle(e){if(e===`colliders`){this.setColliders({off:`all`,all:`selected`,selected:`off`}[this.colliderMode]);return}this.isOpen(e)?this.close(e):this.open(e)}setLevelEnabled(e,t){this.levelEnabled[e]=t,this.renderLogs()}setConsoleCapture(e){if(e!==this.consoleCapture)if(this.consoleCapture=e,e){this.renderLogs();let e=console;for(let t of v){let n=e[t];e[t]=(...e)=>{n.apply(console,e);let r=t===`log`?`info`:t;this.pushLog({level:r,source:`console`,text:e.map(S).join(` `)})},this.consolePatched.push(()=>{e[t]=n})}}else{for(let e of this.consolePatched)e();this.consolePatched=[],this.renderLogs()}}dispose(){this.engine.debugSelection=null,this.setColliders(`off`),this.setConsoleCapture(!1);for(let e of this.cleanups)e();for(let e of[...this.panels.keys()])this.close(e);this.close(`stats`),this.dropdown?.remove(),this.dropdown=null,this.menuButton.remove()}menuLabel(e,t){let n=e===`colliders`&&this.colliderMode!==`off`?` · ${this.colliderMode}`:``;return`${this.isOpen(e)?`✓ `:``}${t}${n}`}toggleDropdown(){if(this.dropdown){this.dropdown.remove(),this.dropdown=null;return}let e=this.doc.createElement(`div`);i(e,{position:`absolute`,top:`34px`,left:`8px`,background:`rgba(18,20,26,0.95)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,font:`12px ui-monospace, Menlo, monospace`,color:`rgba(255,255,255,0.85)`,zIndex:`60`,pointerEvents:`auto`,overflow:`hidden`});for(let[t,n]of[[`explorer`,`Explorer`],[`inspector`,`Inspector`],[`logs`,`Logs`],[`time`,`Time`],[`stats`,`Stats`],[`colliders`,`Colliders`]]){let r=this.doc.createElement(`div`);r.textContent=this.menuLabel(t,n),i(r,{padding:`6px 14px`,cursor:`pointer`,userSelect:`none`}),r.addEventListener(`click`,()=>{if(this.toggle(t),t===`colliders`){r.textContent=this.menuLabel(t,n);return}this.dropdown?.remove(),this.dropdown=null}),e.appendChild(r)}for(let t of this.actions){let n=this.doc.createElement(`div`);n.textContent=t.label,i(n,{padding:`6px 14px`,cursor:`pointer`,userSelect:`none`,borderTop:`1px solid rgba(255,255,255,0.14)`,color:`rgba(158,232,220,0.95)`}),n.addEventListener(`click`,()=>{this.dropdown?.remove(),this.dropdown=null,t.run()}),e.appendChild(n)}this.container.appendChild(e),this.dropdown=e}openStatsChip(){if(this.statsChip)return;let e=this.doc.createElement(`div`);i(e,{position:`absolute`,top:`8px`,right:`8px`,padding:`4px 10px`,background:`rgba(18,20,26,0.85)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:`6px`,color:`rgba(255,255,255,0.85)`,font:`12px ui-monospace, Menlo, monospace`,textAlign:`right`,whiteSpace:`pre`,userSelect:`none`,pointerEvents:`none`,zIndex:`70`}),this.container.appendChild(e),this.statsChip=e,this.renderStats()}renderStats(){let e=this.statsChip;if(!e)return;let t=this.engine.stats(),n=this.statsSource?.()??{},r=[`nodes ${t.nodes}`,...n.triangles===void 0?[]:[`tris ${x(n.triangles)}`],...n.drawCalls===void 0?[]:[`calls ${n.drawCalls}`]].join(` · `);e.textContent=`${Math.round(t.fps)} fps · ${t.frameMs.toFixed(1)} ms\n${r}`}renderExplorer(){let e=this.panels.get(`explorer`);if(!e)return;let t=e.body.scrollTop;b(e.body);let n=this.engine.scene?.root;if(!n){e.body.scrollTop=t;return}let r=(e,t)=>{let n=e.constructor,a=e.children.length>0,o=this.collapsedFlags.get(e)===!0,s=this.doc.createElement(`div`);i(s,{display:`flex`,alignItems:`center`,cursor:`pointer`,padding:`1px 2px`,borderRadius:`3px`,background:e===this.selected?`rgba(110,160,255,0.25)`:`transparent`});let c=this.doc.createElement(`span`);c.textContent=a?o?`▸`:`▾`:`·`,i(c,{width:`14px`,flex:`none`,textAlign:`center`,opacity:a?`0.85`:`0.25`,userSelect:`none`}),a&&c.addEventListener(`click`,t=>{t.stopPropagation?.(),this.collapsedFlags.set(e,!o),this.renderExplorer()}),s.appendChild(c);let l=this.doc.createElement(`span`);l.textContent=e.name,i(l,{whiteSpace:`nowrap`}),s.appendChild(l);let u=this.doc.createElement(`span`);if(u.textContent=` ${n.typeName}`,i(u,{opacity:`0.45`,whiteSpace:`nowrap`,fontSize:`10px`}),s.appendChild(u),a&&o){let t=this.doc.createElement(`span`);t.textContent=` (${e.children.length})`,i(t,{opacity:`0.35`,fontSize:`10px`}),s.appendChild(t)}if(s.addEventListener(`click`,()=>{this.selected=e,this.engine.debugSelection=e,this.applyColliderScope(),this.open(`inspector`),this.renderExplorer(),this.renderInspector()}),t.appendChild(s),a&&!o){let n=this.doc.createElement(`div`);i(n,{marginLeft:`8px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`});for(let t of e.children)r(t,n);t.appendChild(n)}};r(n,e.body),e.body.scrollTop=t}renderInspector(){let e=this.panels.get(`inspector`);if(!e)return;let n=e.body.scrollTop;b(e.body);let r=this.selected;if(r&&r.tree===null&&(this.selected=null,this.engine.debugSelection=null,this.applyColliderScope(),r=null),!r){let t=this.doc.createElement(`div`);t.textContent=`select a node in the Explorer`,i(t,{opacity:`0.6`}),e.body.appendChild(t);return}let a=r.constructor,o=this.doc.createElement(`div`);if(o.textContent=`${r.getPath()} · ${a.typeName}${r.uid?` · ${r.uid}`:``}`,i(o,{fontWeight:`700`,marginBottom:`6px`,whiteSpace:`pre-wrap`}),e.body.appendChild(o),r.groups.size>0){let t=this.doc.createElement(`div`);t.textContent=`groups: ${[...r.groups].join(`, `)}`,i(t,{opacity:`0.7`,marginBottom:`6px`}),e.body.appendChild(t)}let s=t(a),c=r;for(let t of Object.keys(s)){let n=s[t];this.renderValueRow(e.body,r,t,{read:()=>c[t],write:e=>{c[t]=e},options:n?.options,variants:n?.variants})}e.body.scrollTop=n}renderValueRow(t,n,r,a){let o=a.read(),s=a.options,c={get[r](){return a.read()},set[r](e){a.write(e)}},u=this.doc.createElement(`div`);i(u,{display:`flex`,gap:`6px`,alignItems:`center`,margin:`2px 0`});let d=this.doc.createElement(`div`);d.textContent=r,i(d,{minWidth:`84px`,opacity:`0.75`}),u.appendChild(d);let h=(e,t)=>{let a=this.doc.createElement(`input`);return a.type=`number`,a.value=String(e),i(a,w(`70px`)),this.trackEditing(a),a.addEventListener(`change`,()=>{let e=Number(a.value);Number.isFinite(e)?t(e):a.value=String(n[r])}),a};if(typeof o==`number`)u.appendChild(h(o,e=>{c[r]=e}));else if(typeof o==`boolean`){let e=this.doc.createElement(`input`);e.type=`checkbox`,e.checked=o,this.trackEditing(e),e.addEventListener(`change`,()=>{c[r]=e.checked}),u.appendChild(e)}else if(typeof o==`string`&&s&&s.length>0){let e=this.doc.createElement(`select`);for(let t of s.includes(o)?s:[o,...s]){let n=this.doc.createElement(`option`);n.value=t,n.textContent=t,t===o&&(n.selected=!0),e.appendChild(n)}e.value=o,this.trackEditing(e),e.addEventListener(`change`,()=>{c[r]=e.value}),i(e,w(`110px`)),u.appendChild(e)}else if(typeof o==`string`){let e=this.doc.createElement(`input`);e.type=`text`,e.value=o,this.trackEditing(e),i(e,w(`140px`)),e.addEventListener(`change`,()=>{c[r]=e.value}),u.appendChild(e)}else if(Array.isArray(o)&&o.length<=8&&o.every(e=>typeof e==`number`))for(let e=0;e<o.length;e++)u.appendChild(h(o[e],t=>{let n=[...c[r]];n[e]=t,c[r]=n}));else if(m(o)){let s=this.doc.createElement(`div`);i(s,{marginLeft:`10px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`});let c=a.variants?.tag,l=Object.keys(o);c&&!l.includes(c)&&l.unshift(c);let d=a.path??r;for(let t of l){let r=t===c;this.renderValueRow(s,n,t,{path:`${d}.${t}`,read:()=>a.read()[t]??(r?``:null),write:n=>{if(r&&a.variants){let t=a.variants.byTag[String(n)];if(t!==void 0){a.write(e(t));return}}a.write({...a.read(),[t]:n})},options:r&&a.variants?Object.keys(a.variants.byTag):void 0})}t.appendChild(u),t.appendChild(s);return}else{let s=JSON.stringify(e(o));if(s.length<=l){let e=this.doc.createElement(`div`);e.textContent=s,i(e,{opacity:`0.65`,whiteSpace:`pre-wrap`,wordBreak:`break-all`}),u.appendChild(e)}else{let e=a.path??r,c=this.detailOpen.get(n)?.has(e)??!1,l=this.doc.createElement(`div`);if(l.textContent=`${c?`▾`:`▸`} ${f(o)}`,i(l,{opacity:`0.75`,cursor:`pointer`,userSelect:`none`}),l.addEventListener(`click`,()=>{let t=this.detailOpen.get(n);t||(t=new Set,this.detailOpen.set(n,t)),c?t.delete(e):t.add(e),this.renderInspector()}),u.appendChild(l),t.appendChild(u),c){let e=this.doc.createElement(`div`);e.textContent=p(o,s),i(e,{opacity:`0.6`,whiteSpace:`pre-wrap`,wordBreak:`break-all`,marginLeft:`10px`,paddingLeft:`8px`,borderLeft:`1px solid rgba(255,255,255,0.14)`}),t.appendChild(e)}return}}t.appendChild(u)}trackEditing(e){e.addEventListener(`focus`,()=>{this.editing+=1}),e.addEventListener(`blur`,()=>{this.editing=Math.max(0,this.editing-1)})}pushLog(e){this.logRows.push(e),this.logRows.length>_&&this.logRows.splice(0,this.logRows.length-_),this.renderLogs()}setTimeScale(e){Number.isFinite(e)&&(this.engine.timeScale=Math.max(0,e),this.syncTime())}setPaused(e){e?(this.engine.timeScale>0&&(this.resumeScale=this.engine.timeScale),this.engine.timeScale=0):this.engine.timeScale=this.resumeScale>0?this.resumeScale:1,this.syncTime()}get paused(){return this.engine.timeScale===0}nextFrame(){this.paused||this.setPaused(!0),this.engine.step(),this.syncTime()}syncTime(){let e=this.timeEls;if(!e)return;let t=this.engine.timeScale,n=String(Math.round(t*1e3)/1e3);this.editing===0&&(e.slider.value=String(Math.min(t,h)),e.box.value=n),e.readout.textContent=`timeScale ${n}${this.paused?` · paused`:``}`,e.pause.textContent=this.paused?`▶ Resume`:`⏸ Pause`}renderTime(){let e=this.panels.get(`time`);if(!e)return;b(e.body),this.timeEls=null;let t=this.doc.createElement(`div`);i(t,{marginBottom:`6px`,opacity:`0.85`}),e.body.appendChild(t);let n=this.doc.createElement(`input`);n.type=`range`,n.min=`0`,n.max=String(h),n.step=`0.05`,n.value=String(Math.min(this.engine.timeScale,h)),i(n,{width:`100%`,marginBottom:`6px`}),this.trackEditing(n),n.addEventListener(`input`,()=>this.setTimeScale(Number(n.value))),e.body.appendChild(n);let r=this.doc.createElement(`div`);i(r,{display:`flex`,gap:`6px`,alignItems:`center`,marginBottom:`8px`});let a=this.doc.createElement(`input`);a.type=`number`,a.min=`0`,a.step=`0.05`,a.value=String(this.engine.timeScale),i(a,w(`72px`)),this.trackEditing(a),a.addEventListener(`change`,()=>{let e=Number(a.value);Number.isFinite(e)&&this.setTimeScale(e),this.syncTimeAfterEdit()}),r.appendChild(a);for(let e of[.25,.5,1,2]){let t=this.doc.createElement(`div`);t.textContent=`${e}×`,i(t,{cursor:`pointer`,opacity:`0.7`,padding:`2px 4px`}),t.addEventListener(`click`,()=>this.setTimeScale(e)),r.appendChild(t)}e.body.appendChild(r);let o=this.doc.createElement(`div`);i(o,{display:`flex`,gap:`8px`});let s=this.doc.createElement(`div`);i(s,C()),s.addEventListener(`click`,()=>this.setPaused(!this.paused)),o.appendChild(s);let c=this.doc.createElement(`div`);c.textContent=`⏭ Next frame`,c.title=`Pauses, then advances one fixed step`,i(c,C()),c.addEventListener(`click`,()=>this.nextFrame()),o.appendChild(c),e.body.appendChild(o),this.timeEls={slider:n,box:a,readout:t,pause:s},this.syncTime()}syncTimeAfterEdit(){let e=this.timeEls;if(!e)return;let t=this.engine.timeScale;e.slider.value=String(Math.min(t,h)),e.box.value=String(Math.round(t*1e3)/1e3)}renderLogs(){let e=this.panels.get(`logs`);if(!e)return;b(e.body);let t=this.doc.createElement(`div`);i(t,{display:`flex`,gap:`8px`,marginBottom:`4px`,flexWrap:`wrap`});for(let e of[`debug`,`info`,`warn`,`error`]){let n=this.doc.createElement(`div`);n.textContent=`${this.levelEnabled[e]?`✓`:`·`}${e}`,i(n,{cursor:`pointer`,opacity:this.levelEnabled[e]?`1`:`0.45`}),n.addEventListener(`click`,()=>this.setLevelEnabled(e,!this.levelEnabled[e])),t.appendChild(n)}let n=this.doc.createElement(`div`);n.textContent=`${this.consoleCapture?`✓`:`·`}console`,i(n,{cursor:`pointer`,marginLeft:`auto`}),n.addEventListener(`click`,()=>this.setConsoleCapture(!this.consoleCapture)),t.appendChild(n),e.body.appendChild(t);let r={debug:`rgba(255,255,255,0.5)`,info:`rgba(255,255,255,0.85)`,warn:`#ffc861`,error:`#ff6b6b`};for(let t of this.logRows){if(!this.levelEnabled[t.level])continue;let n=this.doc.createElement(`div`);n.textContent=`[${t.source===`console`?`console`:t.level}] ${t.text}`,i(n,{color:r[t.level],whiteSpace:`pre-wrap`}),e.body.appendChild(n)}}};function b(e){for(let t of[...e.children])t.remove()}function x(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function S(e){if(typeof e==`string`)return e;if(e instanceof Error){let t=e.stack?.split(`
|
|
3
|
-
`)[1]?.trim();return`${e.name}: ${e.message}${t?` (${t})`:``}`}try{let t=JSON.stringify(e);return t===`{}`||t===void 0?String(e):t}catch{return String(e)}}function C(){return{background:`rgba(255,255,255,0.08)`,border:`1px solid rgba(255,255,255,0.2)`,borderRadius:`4px`,padding:`4px 8px`,cursor:`pointer`,userSelect:`none`}}function w(e){return{width:e,background:`rgba(255,255,255,0.08)`,border:`1px solid rgba(255,255,255,0.2)`,borderRadius:`4px`,color:`inherit`,font:`inherit`,padding:`2px 4px`}}export{g as attachDebugOverlay};
|