incanto 0.28.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 (63) hide show
  1. package/README.md +44 -0
  2. package/bin/incanto-editor.mjs +219 -29
  3. package/dist/2d.d.ts +54 -6
  4. package/dist/2d.js +3 -3
  5. package/dist/3d.d.ts +353 -16
  6. package/dist/3d.js +5 -5
  7. package/dist/{behavior-CPibUfnH.d.ts → behavior-BAc0erXF.d.ts} +21 -0
  8. package/dist/{create-game-71TJjW1T.js → create-game-5z_QVtLx.js} +59 -16
  9. package/dist/{create-game-BRgWpNsa.js → create-game-DuBTv2zI.js} +276 -49
  10. package/dist/debug-draw-BM3DsvtT.js +18 -0
  11. package/dist/debug.d.ts +51 -6
  12. package/dist/debug.js +242 -19
  13. package/dist/editor-switch-B0wB_DSr.d.ts +59 -0
  14. package/dist/editor.d.ts +49 -0
  15. package/dist/editor.js +8554 -0
  16. package/dist/{gameplay-DEG-TP7D.js → gameplay-Cfr6aFZ1.js} +219 -40
  17. package/dist/gameplay.d.ts +1 -1
  18. package/dist/gameplay.js +1 -1
  19. package/dist/index.d.ts +31 -3
  20. package/dist/index.js +2 -2
  21. package/dist/{loader-DILt9PGC.d.ts → loader-B242FF6N.d.ts} +1 -1
  22. package/dist/net.d.ts +1 -1
  23. package/dist/net.js +2 -2
  24. package/dist/{pathfinding-RWYkNKx9.d.ts → pathfinding-BwD974Ss.d.ts} +1 -1
  25. package/dist/{physics-2d-DiVFFlH3.js → physics-2d-3kOQCtgd.js} +88 -3
  26. package/dist/{physics-3d--y5clE2j.js → physics-3d-CeRH-Ff_.js} +475 -9
  27. package/dist/react.d.ts +1 -1
  28. package/dist/react.js +1 -1
  29. package/dist/{register-nObreUQR.js → register-BTg0EM7s.js} +35 -3
  30. package/dist/{register-6R75AC7-.js → register-DJ0SByQg.js} +5550 -5073
  31. package/dist/{register-CvpSUU3O.js → register-DWcWq4QG.js} +22 -2
  32. package/dist/{register-BFFE1Mh1.js → register-MelqEdza.js} +1 -1
  33. package/dist/teardown-ByzfDPyu.js +240 -0
  34. package/dist/test.d.ts +35 -3
  35. package/dist/test.js +104 -10
  36. package/dist/vite.d.ts +139 -1
  37. package/dist/vite.js +355 -4
  38. package/editor/assets/{agent8-CojUfCXN.js → agent8-CAp0i5qn.js} +1 -1
  39. package/editor/assets/debug-BoEYfbqK.js +2 -0
  40. package/editor/assets/index-BO6WU8by.js +10696 -0
  41. package/editor/index.html +3 -157
  42. package/package.json +3 -2
  43. package/schemas/scene.schema.json +102 -4
  44. package/skills/incanto-3d-models.md +38 -0
  45. package/skills/incanto-assets.md +13 -0
  46. package/skills/incanto-building-3d-games.md +83 -6
  47. package/skills/incanto-editor.md +212 -8
  48. package/skills/incanto-environment.md +65 -1
  49. package/skills/incanto-node-reference.md +33 -1
  50. package/skills/incanto-physics-and-input.md +60 -6
  51. package/skills/incanto-verifying-your-game.md +57 -2
  52. package/templates-app/beacon-isle-3d/docs/project-3d-rules.md +5 -0
  53. package/templates-app/beacon-isle-3d/package.json +1 -1
  54. package/templates-app/beacon-isle-3d/vite.config.ts +7 -0
  55. package/templates-app/tps-3d/docs/project-3d-rules.md +5 -0
  56. package/templates-app/tps-3d/package.json +1 -1
  57. package/templates-app/tps-3d/vite.config.ts +7 -0
  58. package/templates-app/village-quest-3d/docs/project-3d-rules.md +5 -0
  59. package/templates-app/village-quest-3d/package.json +1 -1
  60. package/templates-app/village-quest-3d/vite.config.ts +7 -0
  61. package/dist/debug-draw-CZmOYjL2.js +0 -13
  62. package/editor/assets/index-D6RQgROR.js +0 -8330
  63. package/editor/assets/index-D8QvwvOm.css +0 -1
@@ -49,6 +49,7 @@ Signals: `animationFinished(name)`
49
49
  | `visible` | `true` | boolean |
50
50
  | `renderOrder` | `0` | number |
51
51
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
52
+ | `snapToGround` | `null` | null |
52
53
  | `texture` | `""` | string |
53
54
  | `size` | `[1,1]` | array |
54
55
  | `billboard` | `"y"` | one of: `y` `full` `none` |
@@ -94,6 +95,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
94
95
  | `visible` | `true` | boolean |
95
96
  | `renderOrder` | `0` | number |
96
97
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
98
+ | `snapToGround` | `null` | null |
97
99
  | `collider` | `{}` | object |
98
100
 
99
101
  Signals: `triggerEnter(other)` · `triggerExit(other)`
@@ -128,6 +130,7 @@ Signals: `finished`
128
130
  | `visible` | `true` | boolean |
129
131
  | `renderOrder` | `0` | number |
130
132
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
133
+ | `snapToGround` | `null` | null |
131
134
  | `mode` | `"screen"` | one of: `screen` `y` `none` |
132
135
 
133
136
  ## `BoneAttachment3D` — `incanto/3d`
@@ -141,6 +144,7 @@ Signals: `finished`
141
144
  | `visible` | `true` | boolean |
142
145
  | `renderOrder` | `0` | number |
143
146
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
147
+ | `snapToGround` | `null` | null |
144
148
  | `target` | `""` | string |
145
149
  | `bone` | `""` | string |
146
150
 
@@ -155,6 +159,7 @@ Signals: `finished`
155
159
  | `visible` | `true` | boolean |
156
160
  | `renderOrder` | `0` | number |
157
161
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
162
+ | `snapToGround` | `null` | null |
158
163
  | `target` | `""` | string |
159
164
  | `bone` | `"Head"` | string |
160
165
  | `lookAt` | `""` | string |
@@ -190,6 +195,7 @@ Signals: `finished`
190
195
  | `visible` | `true` | boolean |
191
196
  | `renderOrder` | `0` | number |
192
197
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
198
+ | `snapToGround` | `null` | null |
193
199
  | `fov` | `60` | number |
194
200
  | `near` | `0.1` | number |
195
201
  | `far` | `1000` | number |
@@ -224,9 +230,9 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
224
230
  | `visible` | `true` | boolean |
225
231
  | `renderOrder` | `0` | number |
226
232
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
233
+ | `snapToGround` | `true` | boolean |
227
234
  | `collider` | `{}` | object |
228
235
  | `velocity` | `[0,0,0]` | array |
229
- | `snapToGround` | `true` | boolean |
230
236
  | `slopeLimitDeg` | `45` | number |
231
237
 
232
238
  Signals: `triggerEnter(other)` · `triggerExit(other)`
@@ -252,6 +258,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
252
258
  | `visible` | `true` | boolean |
253
259
  | `renderOrder` | `0` | number |
254
260
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
261
+ | `snapToGround` | `null` | null |
255
262
  | `view` | `"free"` | one of: `free` `firstPerson` `quarter` `side` |
256
263
  | `camDistance` | `4` | number |
257
264
  | `eyeHeight` | `0.64` | number |
@@ -304,6 +311,7 @@ Signals: `movementStateChanged(state)`
304
311
  | `visible` | `true` | boolean |
305
312
  | `renderOrder` | `0` | number |
306
313
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
314
+ | `snapToGround` | `null` | null |
307
315
  | `color` | `"#ffffff"` | string |
308
316
  | `intensity` | `1` | number |
309
317
  | `castShadow` | `false` | boolean |
@@ -322,6 +330,7 @@ Signals: `movementStateChanged(state)`
322
330
  | `visible` | `true` | boolean |
323
331
  | `renderOrder` | `0` | number |
324
332
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
333
+ | `snapToGround` | `null` | null |
325
334
  | `density` | `"sparse"` | one of: `lush` `sparse` `none` |
326
335
  | `area` | `[20,20]` | array |
327
336
  | `seed` | `1` | number |
@@ -345,6 +354,7 @@ Signals: `movementStateChanged(state)`
345
354
  | `visible` | `true` | boolean |
346
355
  | `renderOrder` | `2` | number |
347
356
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
357
+ | `snapToGround` | `null` | null |
348
358
  | `kind` | `"grass"` | one of: `grass` `flowers` `reeds` |
349
359
  | `style` | `"mesh"` | one of: `blades` `simple` `mesh` `tufts` |
350
360
  | `tuftStyle` | `"grass"` | one of: `grass` `fern` |
@@ -385,12 +395,14 @@ Signals: `movementStateChanged(state)`
385
395
  | `visible` | `true` | boolean |
386
396
  | `renderOrder` | `0` | number |
387
397
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
398
+ | `snapToGround` | `null` | null |
388
399
  | `mesh` | `"box"` | one of: `box` `sphere` `capsule` `plane` `cylinder` `gem` |
389
400
  | `size` | `[1,1,1]` | array |
390
401
  | `material` | `{}` | object |
391
402
  | `transforms` | `[]` | array |
392
403
  | `castShadow` | `false` | boolean |
393
404
  | `receiveShadow` | `false` | boolean |
405
+ | `collider` | `null` | null |
394
406
 
395
407
  ## `Joint2D` — `incanto/2d`
396
408
 
@@ -422,6 +434,7 @@ Signals: `movementStateChanged(state)`
422
434
  | `visible` | `true` | boolean |
423
435
  | `renderOrder` | `0` | number |
424
436
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
437
+ | `snapToGround` | `null` | null |
425
438
  | `type` | `"spherical"` | one of: `fixed` `spherical` `rope` `spring` |
426
439
  | `target` | `""` | string |
427
440
  | `anchor` | `[0,0,0]` | array |
@@ -458,6 +471,7 @@ Signals: `movementStateChanged(state)`
458
471
  | `visible` | `true` | boolean |
459
472
  | `renderOrder` | `0` | number |
460
473
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
474
+ | `snapToGround` | `null` | null |
461
475
  | `sections` | `[]` | array |
462
476
  | `slices` | `24` | number |
463
477
  | `smooth` | `4` | number |
@@ -476,6 +490,7 @@ Signals: `movementStateChanged(state)`
476
490
  | `visible` | `true` | boolean |
477
491
  | `renderOrder` | `0` | number |
478
492
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
493
+ | `snapToGround` | `null` | null |
479
494
  | `mesh` | `"box"` | one of: `box` `sphere` `capsule` `plane` `cylinder` `gem` |
480
495
  | `size` | `[1,1,1]` | array |
481
496
  | `material` | `{}` | object |
@@ -493,11 +508,14 @@ Signals: `movementStateChanged(state)`
493
508
  | `visible` | `true` | boolean |
494
509
  | `renderOrder` | `0` | number |
495
510
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
511
+ | `snapToGround` | `null` | null |
496
512
  | `model` | `""` | string |
497
513
  | `animation` | `""` | string |
498
514
  | `targetHeight` | `0` | number |
499
515
  | `castShadow` | `false` | boolean |
500
516
  | `animationLoop` | `true` | boolean |
517
+ | `animationSpeed` | `1` | number |
518
+ | `animationOffset` | `0` | number |
501
519
  | `animationUpper` | `""` | string |
502
520
  | `animationUpperLoop` | `false` | boolean |
503
521
  | `upperBodyRoot` | `"Spine"` | string |
@@ -545,6 +563,7 @@ _No props (structural fields only)._
545
563
  | `visible` | `true` | boolean |
546
564
  | `renderOrder` | `0` | number |
547
565
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
566
+ | `snapToGround` | `null` | null |
548
567
 
549
568
  ## `OmniLight3D` — `incanto/3d`
550
569
 
@@ -557,6 +576,7 @@ _No props (structural fields only)._
557
576
  | `visible` | `true` | boolean |
558
577
  | `renderOrder` | `0` | number |
559
578
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
579
+ | `snapToGround` | `null` | null |
560
580
  | `color` | `"#ffffff"` | string |
561
581
  | `intensity` | `1` | number |
562
582
  | `range` | `0` | number |
@@ -606,6 +626,7 @@ Signals: `finished`
606
626
  | `visible` | `true` | boolean |
607
627
  | `renderOrder` | `0` | number |
608
628
  | `orderGroup` | `"effects"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
629
+ | `snapToGround` | `null` | null |
609
630
  | `preset` | `"custom"` | one of: `custom` `fire` `smoke` `sparks` `fireworks` `explosion` `flash` `lightning` `rain` `snow` `magic` |
610
631
  | `emitting` | `true` | boolean |
611
632
  | `rate` | `40` | number |
@@ -662,6 +683,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
662
683
  | `visible` | `true` | boolean |
663
684
  | `renderOrder` | `0` | number |
664
685
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
686
+ | `snapToGround` | `null` | null |
665
687
  | `collider` | `{}` | object |
666
688
  | `mass` | `1` | number |
667
689
  | `gravityScale` | `1` | number |
@@ -683,6 +705,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
683
705
  | `visible` | `true` | boolean |
684
706
  | `renderOrder` | `0` | number |
685
707
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
708
+ | `snapToGround` | `null` | null |
686
709
  | `path` | `[]` | array |
687
710
  | `width` | `6` | number |
688
711
  | `widths` | `[]` | array |
@@ -730,6 +753,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
730
753
  | `visible` | `true` | boolean |
731
754
  | `renderOrder` | `0` | number |
732
755
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
756
+ | `snapToGround` | `null` | null |
733
757
  | `texture` | `""` | string |
734
758
  | `size` | `[1,1]` | array |
735
759
  | `billboard` | `"y"` | one of: `y` `full` `none` |
@@ -768,6 +792,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
768
792
  | `visible` | `true` | boolean |
769
793
  | `renderOrder` | `0` | number |
770
794
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
795
+ | `snapToGround` | `null` | null |
771
796
  | `collider` | `{}` | object |
772
797
 
773
798
  Signals: `triggerEnter(other)` · `triggerExit(other)`
@@ -783,6 +808,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
783
808
  | `visible` | `true` | boolean |
784
809
  | `renderOrder` | `0` | number |
785
810
  | `orderGroup` | `"terrain"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
811
+ | `snapToGround` | `null` | null |
786
812
  | `size` | `[200,200]` | array |
787
813
  | `maxHeight` | `28` | number |
788
814
  | `seed` | `1` | number |
@@ -838,6 +864,7 @@ Signals: `timeout`
838
864
  | `visible` | `true` | boolean |
839
865
  | `renderOrder` | `0` | number |
840
866
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
867
+ | `snapToGround` | `null` | null |
841
868
  | `width` | `0.3` | number |
842
869
  | `color` | `"#ffffff"` | string |
843
870
  | `opacity` | `0.8` | number |
@@ -857,6 +884,7 @@ Signals: `timeout`
857
884
  | `visible` | `true` | boolean |
858
885
  | `renderOrder` | `0` | number |
859
886
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
887
+ | `snapToGround` | `null` | null |
860
888
  | `tier` | `"medium"` | one of: `simple` `medium` `high` |
861
889
  | `type` | `"conifer"` | one of: `conifer` `broadleaf` `dead` `bush` |
862
890
  | `seed` | `1` | number |
@@ -952,6 +980,7 @@ Signals: `lineShown` · `choiceMade` · `dialogueFinished`
952
980
  | `visible` | `true` | boolean |
953
981
  | `renderOrder` | `0` | number |
954
982
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
983
+ | `snapToGround` | `null` | null |
955
984
  | `voxels` | `[]` | array |
956
985
 
957
986
  Signals: `blocksChanged`
@@ -967,6 +996,7 @@ Signals: `blocksChanged`
967
996
  | `visible` | `true` | boolean |
968
997
  | `renderOrder` | `1` | number |
969
998
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
999
+ | `snapToGround` | `null` | null |
970
1000
  | `size` | `[40,40]` | array |
971
1001
  | `color` | `"#2a6fbe"` | string |
972
1002
  | `opacity` | `0.8` | number |
@@ -980,6 +1010,7 @@ Signals: `blocksChanged`
980
1010
  | `mirrorInterval` | `33` | number |
981
1011
  | `swellSteepness` | `0.35` | number |
982
1012
  | `foam` | `true` | boolean |
1013
+ | `reflectivity` | `null` | null |
983
1014
  | `interaction` | `true` | boolean |
984
1015
  | `splash` | `true` | boolean |
985
1016
  | `sunDirection` | `[0.5,0.8,0.3]` | array |
@@ -1010,6 +1041,7 @@ Signals: `entered(body)` · `exited(body)`
1010
1041
  | `visible` | `true` | boolean |
1011
1042
  | `renderOrder` | `0` | number |
1012
1043
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
1044
+ | `snapToGround` | `null` | null |
1013
1045
  | `size` | `[2,1,1]` | array |
1014
1046
  | `enabled` | `true` | boolean |
1015
1047
 
@@ -30,11 +30,43 @@ independent; enable the one matching your scene.
30
30
 
31
31
  2D collider shapes: `{"shape":"rect","size":[w,h]}` · `{"shape":"circle","radius":r}` ·
32
32
  `{"shape":"capsule","radius":r,"height":h}`.
33
- 3D: `{"shape":"box","size":[x,y,z]}` · `{"shape":"sphere","radius":r}` · `{"shape":"capsule",...}` ·
34
- `{"shape":"trimesh","vertices":[...],"indices":[...]}` · `{"shape":"heightfield"}` (STATIC-only;
35
- reads the grid from a `Terrain3D` child — see the incanto-environment skill).
33
+ 3D: `{"shape":"auto"}` · `{"shape":"box","size":[x,y,z]}` · `{"shape":"sphere","radius":r}` ·
34
+ `{"shape":"capsule",...}` · `{"shape":"trimesh","vertices":[...],"indices":[...]}` ·
35
+ `{"shape":"heightfield"}` (STATIC-only; reads the grid from a `Terrain3D` child — see the
36
+ incanto-environment skill).
36
37
  Malformed colliders are hard `BAD_FORMAT` errors at enable time.
37
38
 
39
+ **`{"shape":"auto"}` is the one to reach for on props.** It shapes the collider like the
40
+ `MeshInstance3D` the body CARRIES — box→cuboid, sphere→ball, cylinder/capsule→their own
41
+ shapes, `gem`→a convex hull of its real vertices — so a blocker can never disagree with what
42
+ the player sees. Hand-typed extents drift: a box "around" a bridge ends up wider than the
43
+ bridge and you stop in mid-air; a sphere "for" a stone ends up inside it and you clip through
44
+ the corners. Give the body its mesh as a CHILD and let the shape follow:
45
+
46
+ ```json
47
+ { "name": "DeckBody", "type": "StaticBody3D",
48
+ "props": { "position": [0, 3.2, -8], "rotation": [0, 34, 0], "collider": { "shape": "auto" } },
49
+ "children": [ { "name": "Deck", "type": "MeshInstance3D",
50
+ "props": { "mesh": "box", "size": [14, 0.22, 2.2] } } ] }
51
+ ```
52
+
53
+ Bodies ignore an ANCESTOR's rotation, so a yawed structure puts each body at top level with
54
+ its own `rotation` — the child mesh then turns with it, visual and collider together.
55
+
56
+ **`auto` works on a `ModelInstance3D` (GLB/glTF/VRM) too** — the shape comes from the model's
57
+ real triangles, baked through their own transforms, so an offset or `targetHeight`-fitted model
58
+ collides where it is DRAWN:
59
+
60
+ - on a **static** body (`StaticBody3D`, `Area3D`) → a triangle mesh: the model keeps its holes,
61
+ so you walk THROUGH an archway instead of into it. Level art can be dense; a collider over
62
+ ~20k triangles logs a note suggesting a low-poly collision model.
63
+ - on a **moving** body (`RigidBody3D`, `CharacterBody3D`) → the convex hull. Rapier's triangle
64
+ meshes are hollow shells; a dynamic one falls through the world.
65
+
66
+ The body simply does not exist until the model has downloaded — nothing is invented in the
67
+ meantime — and it appears the step the asset lands. A body is ONE shape, so `auto` fits one
68
+ mesh or one model and warns when the body carries more.
69
+
38
70
  - **`StaticBody2D/3D`** — immovable (ground, walls). Props: `collider`.
39
71
  - **`RigidBody2D/3D`** — simulated. Props: `collider`, `mass 1`, `gravityScale 1`,
40
72
  `fixedRotation false`, `friction 0.5`, `restitution 0`, `linearVelocity` (write to launch,
@@ -200,9 +232,31 @@ change scan was removed for performance).
200
232
  ## Debug drawing
201
233
 
202
234
  `physics.debugDraw = true` (the instance `enablePhysics2D/3D` returns) renders
203
- every collider as light-green wireframe lines IN THE GAME VIEW — Rapier's own
204
- debug geometry, so what you see is exactly what the solver sees. Default OFF;
205
- toggle it live whenever physics feels wrong.
235
+ every collider as light-green wireframe lines IN THE GAME VIEW — the shape data
236
+ the solver holds, so what you see is what it collides with. Default OFF; toggle
237
+ it live whenever physics feels wrong.
238
+
239
+ `physics.debugScope = node` narrows those lines to ONE node: its own colliders,
240
+ its descendants', or the nearest body above it when it owns none (so a visual
241
+ mesh child shows the body it belongs to). Null again for the whole world. Without
242
+ it, a map with terrain is a wall of grid lines and the shape you came to check is
243
+ somewhere inside it. The debug overlay's **Colliders** menu item cycles
244
+ off → all → selected over exactly these two fields.
245
+
246
+ ### Colliders without a simulation
247
+
248
+ `enablePhysics3D(engine, { simulate: false })` (and the 2D twin) builds the world
249
+ and never advances it: the body set is kept in step with the tree, every body is
250
+ posed FROM the tree each tick and propagated to its colliders, and then it stops
251
+ — no solver, no collision events, no write-back into node props. `debugDraw`
252
+ works exactly as above.
253
+
254
+ This is how the scene editor shows colliders while you edit: the real shapes,
255
+ including the ones that are not in the scene JSON at all (a mesh-fitted body, an
256
+ InstancedMesh3D scatter's hulls, a terrain, a character capsule), sitting where
257
+ the tree says, without your scene falling over while you look at it. Use it for
258
+ any "show me the collision geometry" tool. Do NOT use it to freeze a running
259
+ game — that is `engine.timeScale = 0`, which keeps the world coherent.
206
260
 
207
261
  ## Placement rules
208
262
 
@@ -155,6 +155,12 @@ export default { plugins: [incantoScenes()] };
155
155
  Invalid `*.scene.json` saves keep the last good scene running and surface the
156
156
  IncantoError in the terminal AND as a browser overlay.
157
157
 
158
+ The same plugin serves this project's scenes to the editor, so the one you switch
159
+ into from the running game (`☰ debug ▸ ✎ edit this scene`) can open, create and save
160
+ ANY scene in the project — not just the one the game booted with. `incantoScenes({
161
+ root })` bounds that to a directory other than vite's; nothing outside it is readable
162
+ or writable, and none of it exists in a build (`configureServer`).
163
+
158
164
  ## 7. See inside the RUNNING game (debug overlay)
159
165
 
160
166
  ```ts
@@ -168,12 +174,61 @@ VITE_INCANTO_DEBUG=1 bun run dev # overlay on; plain `bun run dev` / prod buil
168
174
  A ☰ debug menu (top-left) toggles three draggable/resizable panels over the
169
175
  live game: **Explorer** (the scene tree — click any node), **Inspector** (the
170
176
  selected node's current state; numbers/strings/booleans/vectors editable
171
- in-place), **Logs** (an `engine.log` tail with level filters and an optional
177
+ in-place, and an OBJECT prop like `collider` opens into a row per key with the
178
+ `shape` as a dropdown — picking a shape writes a complete valid collider, never
179
+ a box's `size` under a sphere's tag; bulky values like a 54-row `transforms`
180
+ fold behind `▸ Array(54)` and expand on click, one row per element), **Logs** (an `engine.log` tail with level filters and an optional
172
181
  browser-console capture toggle) — plus **Stats**, an always-on-top chip pinned
173
182
  top-right (`60 fps · 12.1 ms` / `nodes 34 · tris 18.2k · calls 21`, refreshed
174
- ~2×/s; the same numbers any code can read via `game.stats()`). Manual boots:
183
+ ~2×/s; the same numbers any code can read via `game.stats()`) and
184
+ **Colliders**, a switch that draws every physics shape as light-green wireframe
185
+ in the game view. Manual boots:
175
186
  `(await import('incanto/debug')).attachDebugOverlay(engine)`.
176
187
 
188
+ The Inspector HOLDS STILL while you use it: it stops refreshing whenever an
189
+ input has focus or the pointer is inside the panel, and it keeps your scroll
190
+ position across refreshes. Live values resume the moment you move away.
191
+
192
+ **Colliders is the answer to "why did I stop in mid-air / fall through the
193
+ floor".** What you see is the shape data the solver holds — put it next to the
194
+ art and a collider that disagrees with its mesh is obvious in one glance.
195
+
196
+ One click cycles **off → all → selected** (the menu stays open while you cycle —
197
+ the item's label shows the state you landed on):
198
+
199
+ - `all` is every collider in the world. On a real map that means a terrain
200
+ heightfield's grid over everything, which is why the third state exists.
201
+ - `selected` draws only what the Explorer has picked: that node's colliders, its
202
+ descendants', or — if it owns none — the nearest BODY above it, so clicking a
203
+ visual `MeshInstance3D` shows the collider it hangs under rather than nothing.
204
+ A scatter field gives you all of its rows.
205
+
206
+ From code: `physics.debugDraw = true` and `physics.debugScope = node` (null for
207
+ everything).
208
+
209
+ **✎ edit this scene** is the last item in that menu, and it is the fastest loop
210
+ in the engine: the page you are playing in BECOMES the scene editor, from the
211
+ camera you were standing at — no reload, no second tab. Drag a boulder out of
212
+ the path, then **▶ play ▸ ⤢ run as the game** and the page is the game again,
213
+ running your edit. `game.openEditor()` is the same switch from code.
214
+
215
+ It follows `debug` (so a build with the overlay has it) and loads lazily, so a
216
+ game that never opens it never downloads it. `editor: false` opts out;
217
+ `editor: { save }` adds the editor's save button, wired to your own writer:
218
+
219
+ ```ts
220
+ createGame3D({
221
+ ...,
222
+ debug: import.meta.env.VITE_INCANTO_DEBUG === '1',
223
+ editor: { save: (json) => fetch('/api/scene', { method: 'PUT', body: JSON.stringify(json) }) },
224
+ });
225
+ ```
226
+
227
+ What crosses the switch is the AUTHORED scene JSON, not the live tree — physics
228
+ has moved the live one, and "edit this scene" must not mean "edit wherever the
229
+ simulation happened to be". Unsaved edits survive the round trip both ways; a
230
+ page reload is what drops them.
231
+
177
232
  **Security — `debug` defaults to `false` and there is NO URL/query toggle**, so
178
233
  a DEPLOYED build can never be switched on by an end user (no more `?incanto_debug=1`).
179
234
  Enable it only from your own dev environment via the env-var gate above. The
@@ -35,4 +35,9 @@ YOU MUST follow these rules EXACTLY when vibe-coding on this Incanto template:
35
35
  11. ✅ VERIFY LIKE A USER: `bun run check` validates the scene, `bun run verify`
36
36
  proves the loop headlessly, then `bun run dev` and actually PLAY what you
37
37
  changed (move, shoot, clear a wave) before declaring it done.
38
+ 12. 🔍 X-RAY WHEN IT LOOKS WRONG: `VITE_INCANTO_DEBUG=1 bun run dev` adds a ☰
39
+ debug menu — Explorer/Inspector (click a node, see and edit its live props),
40
+ Logs, Stats (fps · tris · draw calls) and Colliders (every physics shape as
41
+ wireframe). Blocked in mid-air or falling through the floor is a Colliders
42
+ question; a mystery frame-rate is a Stats question. Never ship the flag on.
38
43
  </userRequest>
@@ -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.28.0",
17
+ "incanto": "^0.30.0",
18
18
  "three": "^0.184.0"
19
19
  },
20
20
  "devDependencies": {
@@ -1,5 +1,12 @@
1
+ import { incantoLibrary, incantoScenes } from 'incanto/vite';
1
2
  import { defineConfig } from 'vite';
2
3
 
3
4
  export default defineConfig({
4
5
  base: './',
6
+ // Dev-server only, and both are why the editor is useful here:
7
+ // - incantoScenes(): validates every *.scene.json the moment you save it,
8
+ // AND serves this project's scenes, so the editor (☰ debug ▸ edit this
9
+ // scene ▸ scenes) can open, create and save any scene in the project.
10
+ // - incantoLibrary(): the agent8 asset catalog behind the 📚 buttons.
11
+ plugins: [incantoScenes(), incantoLibrary()],
5
12
  });
@@ -35,4 +35,9 @@ YOU MUST follow these rules EXACTLY when vibe-coding on this Incanto template:
35
35
  11. ✅ VERIFY LIKE A USER: `bun run check` validates the scene, `bun run verify`
36
36
  proves the loop headlessly, then `bun run dev` and actually PLAY what you
37
37
  changed (move, shoot, clear a wave) before declaring it done.
38
+ 12. 🔍 X-RAY WHEN IT LOOKS WRONG: `VITE_INCANTO_DEBUG=1 bun run dev` adds a ☰
39
+ debug menu — Explorer/Inspector (click a node, see and edit its live props),
40
+ Logs, Stats (fps · tris · draw calls) and Colliders (every physics shape as
41
+ wireframe). Blocked in mid-air or falling through the floor is a Colliders
42
+ question; a mystery frame-rate is a Stats question. Never ship the flag on.
38
43
  </userRequest>
@@ -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.28.0",
16
+ "incanto": "^0.30.0",
17
17
  "three": "^0.184.0"
18
18
  },
19
19
  "devDependencies": {
@@ -1,5 +1,12 @@
1
+ import { incantoLibrary, incantoScenes } from 'incanto/vite';
1
2
  import { defineConfig } from 'vite';
2
3
 
3
4
  export default defineConfig({
4
5
  base: './',
6
+ // Dev-server only, and both are why the editor is useful here:
7
+ // - incantoScenes(): validates every *.scene.json the moment you save it,
8
+ // AND serves this project's scenes, so the editor (☰ debug ▸ edit this
9
+ // scene ▸ scenes) can open, create and save any scene in the project.
10
+ // - incantoLibrary(): the agent8 asset catalog behind the 📚 buttons.
11
+ plugins: [incantoScenes(), incantoLibrary()],
5
12
  });
@@ -35,4 +35,9 @@ YOU MUST follow these rules EXACTLY when vibe-coding on this Incanto template:
35
35
  11. ✅ VERIFY LIKE A USER: `bun run check` validates the scene, `bun run verify`
36
36
  proves the loop headlessly, then `bun run dev` and actually PLAY what you
37
37
  changed (move, shoot, clear a wave) before declaring it done.
38
+ 12. 🔍 X-RAY WHEN IT LOOKS WRONG: `VITE_INCANTO_DEBUG=1 bun run dev` adds a ☰
39
+ debug menu — Explorer/Inspector (click a node, see and edit its live props),
40
+ Logs, Stats (fps · tris · draw calls) and Colliders (every physics shape as
41
+ wireframe). Blocked in mid-air or falling through the floor is a Colliders
42
+ question; a mystery frame-rate is a Stats question. Never ship the flag on.
38
43
  </userRequest>
@@ -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.28.0",
16
+ "incanto": "^0.30.0",
17
17
  "three": "^0.184.0"
18
18
  },
19
19
  "devDependencies": {
@@ -1,5 +1,12 @@
1
+ import { incantoLibrary, incantoScenes } from 'incanto/vite';
1
2
  import { defineConfig } from 'vite';
2
3
 
3
4
  export default defineConfig({
4
5
  base: './',
6
+ // Dev-server only, and both are why the editor is useful here:
7
+ // - incantoScenes(): validates every *.scene.json the moment you save it,
8
+ // AND serves this project's scenes, so the editor (☰ debug ▸ edit this
9
+ // scene ▸ scenes) can open, create and save any scene in the project.
10
+ // - incantoLibrary(): the agent8 asset catalog behind the 📚 buttons.
11
+ plugins: [incantoScenes(), incantoLibrary()],
5
12
  });
@@ -1,13 +0,0 @@
1
- //#region src/core/debug-draw.ts
2
- const sources = /* @__PURE__ */ new Set();
3
- /** @internal physics runtimes self-register on enable. */
4
- function registerDebugSource(source) {
5
- sources.add(source);
6
- return () => sources.delete(source);
7
- }
8
- /** @internal renderers pull their dimension's sources. */
9
- function debugSources(dimension) {
10
- return [...sources].filter((s) => s.dimension === dimension);
11
- }
12
- //#endregion
13
- export { registerDebugSource as n, debugSources as t };