incanto 0.64.1 → 0.66.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 (59) hide show
  1. package/dist/2d.d.ts +38 -3
  2. package/dist/2d.js +3 -3
  3. package/dist/3d.d.ts +130 -3
  4. package/dist/3d.js +5 -5
  5. package/dist/{audio-player-D5GJgb_x.d.ts → audio-player-_UAcHxnC.d.ts} +1 -1
  6. package/dist/{behavior-DsgayMsH.d.ts → behavior-BXNLfIJk.d.ts} +128 -0
  7. package/dist/{create-game-B746rYbr.js → create-game-DYJCIzO0.js} +67 -6
  8. package/dist/{create-game-B1KM6dA6.js → create-game-Dd7H4bJV.js} +6 -6
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/{duplicate-BOOKmkQ7.js → duplicate-CqSAtdrh.js} +1 -1
  11. package/dist/{environment-presets-D2vzw583.js → environment-presets-BlPsEmq6.js} +70 -6
  12. package/dist/{gameplay-CLgFdkh5.js → gameplay-D1RADWu3.js} +374 -18
  13. package/dist/gameplay.d.ts +94 -3
  14. package/dist/gameplay.js +2 -2
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +5 -5
  17. package/dist/{loader-DolLJWJn.d.ts → loader-Cga7FVP4.d.ts} +1 -1
  18. package/dist/{loader-zDynoew_.js → loader-lQDCwNag.js} +59 -3
  19. package/dist/net.d.ts +2 -2
  20. package/dist/net.js +1 -1
  21. package/dist/{physics-2d-BPJcJRRP.js → physics-2d-Cji5A6sX.js} +32 -4
  22. package/dist/{physics-3d-BOO58xzw.js → physics-3d-BkHjwJgI.js} +49 -5
  23. package/dist/{quiet-rapier-BAJ4K94N.js → quiet-rapier-C6fW4zcW.js} +14 -1
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +1 -1
  26. package/dist/{register-Btm7_Emq.js → register-BmuqYTiY.js} +44 -1
  27. package/dist/{register-dGsbnJ87.js → register-D0CxCveZ.js} +38 -4
  28. package/dist/{replay-C5x2vPF5.d.ts → replay-BHoB6fCU.d.ts} +1 -1
  29. package/dist/{replay-DKRmiVjk.js → replay-IsZbNu6d.js} +2 -2
  30. package/dist/{split-screen-Dx0LvzqS.js → split-screen-D_i7GRcY.js} +2 -2
  31. package/dist/{split-screen-D7OopelJ.d.ts → split-screen-eJUFjhi1.d.ts} +2 -2
  32. package/dist/{src-CNS2xQB1.js → src-BTLbXFPZ.js} +1 -1
  33. package/dist/{teardown-BwhkcNt8.js → teardown-byR9USax.js} +1 -1
  34. package/dist/{test-Ccob3X5i.js → test-DgrD0jHD.js} +13 -13
  35. package/dist/test.d.ts +4 -4
  36. package/dist/test.js +2 -2
  37. package/dist/vite.js +2 -2
  38. package/editor/assets/{agent8-Di-UEn5O.js → agent8-9N-Pd_YS.js} +1 -1
  39. package/editor/assets/{debug-DWztJ5_y.js → debug-CkbJICYp.js} +1 -1
  40. package/editor/assets/{index-fct4H89G.js → index-CeDhIPTC.js} +53 -53
  41. package/editor/index.html +1 -1
  42. package/package.json +1 -1
  43. package/schemas/scene.schema.json +19 -0
  44. package/skills/incanto-3d-character.md +11 -1
  45. package/skills/incanto-behaviors-and-scripts.md +21 -0
  46. package/skills/incanto-gameplay-behaviors.md +177 -9
  47. package/skills/incanto-node-reference.md +59 -1
  48. package/skills/incanto-physics-and-input.md +84 -1
  49. package/skills/incanto-web-integration.md +24 -3
  50. package/templates-app/beacon-isle-3d/package.json +1 -1
  51. package/templates-app/platformer-2d/package.json +1 -1
  52. package/templates-app/star-survivor/package.json +1 -1
  53. package/templates-app/tps-3d/package.json +1 -1
  54. package/templates-app/village-quest-3d/package.json +1 -1
  55. package/templates-app/beacon-isle-3d/coverage.json +0 -9
  56. package/templates-app/platformer-2d/coverage.json +0 -5
  57. package/templates-app/star-survivor/coverage.json +0 -5
  58. package/templates-app/tps-3d/coverage.json +0 -5
  59. package/templates-app/village-quest-3d/coverage.json +0 -9
@@ -293,8 +293,89 @@ Types — 2D: `fixed` (weld) · `revolute` (pin/hinge) · `rope` (max distance)
293
293
  distance at creation. `anchor`/`targetAnchor` are local offsets (px / m).
294
294
  Chains work (pendulums, bridges: each link a body + joint to the previous).
295
295
 
296
+ ## Pushing a body around (the API that was in no skill)
297
+
298
+ A dynamic `RigidBody2D`/`RigidBody3D` has a force API and none of these appeared
299
+ in any shipped skill, so an agent building a physics puzzle reported the engine
300
+ as having "no force/impulse API at all" and designed around its absence:
301
+
302
+ ```ts
303
+ const body = this.getNode('/root/Crate') as RigidBody3D;
304
+ body.applyImpulse([0, 6, 0]); // an instant kick, in mass*units/second
305
+ body.velocity = [2, 0, 0]; // or set the velocity outright
306
+ ```
307
+
308
+ `applyImpulse` is what the character controller itself runs on. It is an
309
+ IMPULSE, not a force: it changes velocity once, so a continuous push is one call
310
+ per frame from `update(dt)` scaled by `dt`.
311
+
312
+ Readable/writable body state: `velocity`, `mass`, `gravityScale`, `friction`,
313
+ `restitution`, `fixedRotation`. There is no torque call and no `angularVelocity`
314
+ yet — a body's spin can be neither set nor read.
315
+
316
+ ## What is inside this Area right now?
317
+
318
+ `triggerEnter(other)` / `triggerExit(other)` tell you about CROSSINGS. A pressure
319
+ plate, a capture point, "how many enemies are in the blast" and a shop trigger
320
+ all want OCCUPANCY, and asking used to mean keeping your own `Set` fed by those
321
+ two signals — plus a geometric re-check every frame, because a body that is
322
+ teleported or freed inside a sensor does not reliably announce its exit.
323
+
324
+ ```ts
325
+ const load = plate.overlapping('cargo')
326
+ .reduce((kg, b) => kg + ((b as RigidBody3D).mass ?? 0), 0);
327
+ door.open = load >= 60;
328
+ ```
329
+
330
+ `Area2D`/`Area3D` `overlapping(group?)` returns what the solver currently says is
331
+ inside, and drops freed nodes on read. **It includes the STATIC world** — a plate
332
+ laid into the floor genuinely contains the floor — so pass a group to ask the
333
+ question you usually mean.
334
+
335
+ ## Joints: the three things that decide whether yours works
336
+
337
+ 1. **There is no hinge in 3D.** The types are `fixed | spherical | rope | spring`.
338
+ A hinge is **two `spherical` joints at two points along the axis** — that
339
+ constrains rotation to the line between them, and it is exactly how a seesaw
340
+ or a door is built. (2D has `revolute` and needs none of this.)
341
+ 2. **`collide` decides whether the two ends touch, and its default reads the
342
+ type.** Rapier lets jointed bodies collide, and a hinge wants its bodies
343
+ overlapping at the pivot — so the contact solver fights the joint and flings
344
+ them. Before this, a first hinge produced a bar that spun chaotically forever
345
+ and threw a 40 kg box thirty metres, and the author nearly filed "spherical
346
+ joints inject energy".
347
+
348
+ | joint | default | why |
349
+ |---|---|---|
350
+ | `fixed` · `spherical` · `revolute` | **off** | a pivot's bodies overlap by construction |
351
+ | `rope` · `spring` | **on** | a tether anchors a thing to the world, and it still has to rest on it |
352
+
353
+ Set `true`/`false` to say it outright. **It governs the two JOINTED bodies
354
+ only** — never their contact with the rest of the world. Defaulting a spring
355
+ off drops a barrel roped to the floor straight through it, which is how this
356
+ table was arrived at.
357
+ 3. **`anchor`/`targetAnchor` are local offsets from each body's ORIGIN**,
358
+ resolved against the bodies' positions at load. If the two disagree the solver
359
+ snaps the body into place on the first frame, and a 4 cm typo is silent. Author
360
+ them with arithmetic (generate the scene) rather than by hand.
361
+
362
+ There are no joint limits or motors yet: bound a lever's travel with static
363
+ collision geometry in its way.
364
+
296
365
  ## Raycasts
297
366
 
367
+ **Where `physics` comes from.** From a `Behavior` — where AI lives, and where
368
+ line of sight is decided — it is `this.physics`. At the boot site it is
369
+ `game.physics`, and anywhere you hold the engine it is `engine.physics`. All
370
+ three are the same object; it is `null` in a game with no physics bodies, so
371
+ `?.` and treat a missing world as "nothing in the way", or don't.
372
+
373
+ ```ts
374
+ // inside a Behavior
375
+ const hit = this.physics?.castRay(eye, dir, range, this.node);
376
+ const blocked = hit != null && hit.node !== player;
377
+ ```
378
+
298
379
  Both runtimes expose the same query (2D in PIXELS y-down, 3D in meters):
299
380
 
300
381
  ```ts
@@ -302,7 +383,9 @@ const hit = physics.castRay(origin, dir, maxLen, excludeBody?, { staticOnly?: tr
302
383
  // → { distance, normal, node } | null (sensors never block rays)
303
384
  ```
304
385
 
305
- Exclude the shooter's own body when casting from inside it.
386
+ Exclude the shooter's own body when casting from inside it — a ray that starts
387
+ inside its own collider hits itself at distance 0, which reads as "blocked" and
388
+ is the reason a vision cone can come back permanently blind.
306
389
 
307
390
  3D also has a THICK ray — a sphere sweep — for probes where skimming matters
308
391
  (the camera boom, ledge feelers):
@@ -58,9 +58,30 @@ export function GamePage() {
58
58
  Rule of thumb: score/menus/dialogs that look like WEB UI → DOM overlay;
59
59
  anything the game itself must own (and runScript must verify) → UILayer.
60
60
 
61
- Pin a DOM element to a world position: `game.renderer.screenFromWorld(wx, wy)`
62
- (and `worldFromScreen(sx, sy)` the other way, for taps and clicks)
63
- each frame (subscribe `engine.updated`), then `transform: translate(x, y)`.
61
+ Pin a DOM element to a world position each frame (subscribe `engine.updated`),
62
+ then `transform: translate(x, y)`:
63
+
64
+ ```ts
65
+ game.renderer.screenFromWorld(wx, wy) // 2D
66
+ game.renderer.screenFromWorld(wx, wy, wz) // 3D — takes THREE arguments
67
+ ```
68
+
69
+ Going the other way, for taps and clicks:
70
+
71
+ ```ts
72
+ game.renderer.worldFromScreen(sx, sy) // 2D → { x, y }
73
+ game.renderer.worldFromScreen(sx, sy, planeY?) // 3D → { x, y, z } on a
74
+ // horizontal plane (default y=0)
75
+ game.renderer.rayFromScreen(sx, sy) // 3D → { origin, dir } for
76
+ // physics.castRay on uneven ground
77
+ ```
78
+
79
+ **A pixel in 3D is a ray, not a point**, which is why the 3D form needs a
80
+ surface: `worldFromScreen` lands it on the ground plane, and `rayFromScreen`
81
+ hands you the ray when the ground is terrain or a stack of crates. Both return
82
+ null when the ray misses (looking at the sky). `pick(sx, sy)` gives you the NODE
83
+ under a pixel — a different question, and the only one 3D could answer before
84
+ 0.66.
64
85
 
65
86
  Perf note: `useNodeProp` deep-compares per frame — subscribe to LEAF values
66
87
  (`'UI/Score', 'text'`), not big objects like a whole `animations` map.
@@ -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.64.1",
17
+ "incanto": "^0.66.0",
18
18
  "three": "^0.184.0"
19
19
  },
20
20
  "devDependencies": {
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
- "incanto": "^0.64.1",
14
+ "incanto": "^0.66.0",
15
15
  "three": "^0.184.0"
16
16
  },
17
17
  "devDependencies": {
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
- "incanto": "^0.64.1",
14
+ "incanto": "^0.66.0",
15
15
  "three": "^0.184.0"
16
16
  },
17
17
  "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.64.1",
16
+ "incanto": "^0.66.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.64.1",
16
+ "incanto": "^0.66.0",
17
17
  "three": "^0.184.0"
18
18
  },
19
19
  "devDependencies": {
@@ -1,9 +0,0 @@
1
- {
2
- "hurt": {
3
- "onto": "%Shade1_1"
4
- },
5
- "lose": {
6
- "skip": "no fail state by design \u2014 `onPlayerDown` shows a banner and reloads the level after 1.6s"
7
- },
8
- "seconds": 20
9
- }
@@ -1,5 +0,0 @@
1
- {
2
- "hurt": { "onto": "%Spikes1" },
3
- "lose": true,
4
- "seconds": 20
5
- }
@@ -1,5 +0,0 @@
1
- {
2
- "hurt": "afk",
3
- "lose": true,
4
- "seconds": 30
5
- }
@@ -1,5 +0,0 @@
1
- {
2
- "hurt": "afk",
3
- "lose": true,
4
- "seconds": 30
5
- }
@@ -1,9 +0,0 @@
1
- {
2
- "hurt": {
3
- "onto": "%Wolf1"
4
- },
5
- "lose": {
6
- "skip": "no fail state by design \u2014 `onPlayerDown` shows a banner and reloads the level after 1.6s"
7
- },
8
- "seconds": 20
9
- }