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
package/dist/2d.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats } from "./behavior-DsgayMsH.js";
1
+ import { P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats } from "./behavior-BXNLfIJk.js";
2
2
  import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DAvWQeld.js";
3
3
  import { i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
4
4
  import { r as FrameStats } from "./frame-report-DCnHFmto.js";
5
- import { t as LoadSceneOptions } from "./loader-DolLJWJn.js";
5
+ import { t as LoadSceneOptions } from "./loader-Cga7FVP4.js";
6
6
  import { n as AnimationEntry } from "./sprite-animation-CMr6f1K2.js";
7
- import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-D5GJgb_x.js";
7
+ import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-_UAcHxnC.js";
8
8
  import { Group, Mesh, Object3D, Scene, Texture } from "three";
9
9
  import * as RapierNs from "@dimforge/rapier2d-compat";
10
10
 
@@ -157,6 +157,11 @@ declare class StaticBody2D extends PhysicsBody2D {
157
157
  */
158
158
  declare class Area2D extends PhysicsBody2D {
159
159
  static override readonly typeName: string;
160
+ /**
161
+ * Who is inside RIGHT NOW — the standing answer behind the enter/exit pair.
162
+ * See `Area3D.overlapping`. Empty in a scene with no physics world.
163
+ */
164
+ overlapping(group?: string): PhysicsBody2D[];
160
165
  }
161
166
  /** Dynamic simulated body. */
162
167
  declare class RigidBody2D extends PhysicsBody2D {
@@ -271,6 +276,11 @@ declare class Physics2D {
271
276
  private scopedLines;
272
277
  /** Create newly-arrived joints; tear down departed ones. */
273
278
  private syncJoints;
279
+ /** Who is currently inside each sensor/body, maintained from the event drain. */
280
+ private readonly overlaps;
281
+ private trackOverlap;
282
+ /** The bodies currently overlapping `node`. Freed nodes are dropped on read. */
283
+ overlapping(node: PhysicsBody2D): PhysicsBody2D[];
274
284
  /**
275
285
  * World-space raycast in PIXELS (y-down). `exclude` skips that body — a
276
286
  * shooter probing from inside its own collider needs it. Sensors (Area2D)
@@ -719,6 +729,31 @@ declare class Joint2D extends Node2D {
719
729
  length: number;
720
730
  stiffness: number;
721
731
  damping: number;
732
+ /**
733
+ * Do the two linked bodies still collide with EACH OTHER?
734
+ *
735
+ * `null` (default) decides from the joint type, because the two families want
736
+ * opposite answers:
737
+ *
738
+ * - **pivots** (`fixed`, `spherical`, `revolute`) → **off**. A hinge's bodies
739
+ * overlap at the pivot by construction, so the contact solver fights the
740
+ * joint and flings them. A puzzle builder's first hinge produced a bar that
741
+ * spun chaotically forever and threw a 40 kg box thirty metres, and they
742
+ * nearly filed "spherical joints inject energy":
743
+ *
744
+ * ```
745
+ * t= 0 Plank r=[0,0,0] Box [ 0.00, 5.50, 2.40]
746
+ * t=3000 Plank r=[-66,24,64] Box [-29.70, 0.40, -4.91]
747
+ * ```
748
+ *
749
+ * - **tethers** (`rope`, `spring`) → **on**. These anchor a thing to the world,
750
+ * and the thing has to rest on what it is tethered to. Defaulting these off
751
+ * drops a barrel roped to the floor straight through it — measured, in this
752
+ * repo's own conformance room.
753
+ *
754
+ * Set `true`/`false` to say it outright.
755
+ */
756
+ collide: boolean | null;
722
757
  override onEnterTree(): void;
723
758
  /** @internal The two bodies, resolved (throws on a bad target at step time). */
724
759
  _resolveBodies(): {
package/dist/2d.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
- import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-B1KM6dA6.js";
3
- import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-dGsbnJ87.js";
4
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-BPJcJRRP.js";
2
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-Dd7H4bJV.js";
3
+ import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-D0CxCveZ.js";
4
+ import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-Cji5A6sX.js";
5
5
  //#region src/2d/library-sprite.ts
6
6
  /**
7
7
  * What a `CharacterController2D`/`3D` will ask a skin to play, and the clip in
package/dist/3d.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { D as QualityCaps, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats } from "./behavior-DsgayMsH.js";
1
+ import { D as QualityCaps, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats } from "./behavior-BXNLfIJk.js";
2
2
  import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DAvWQeld.js";
3
3
  import { i as SceneJson$1, s as JsonObject } from "./schema-CFeioQRE.js";
4
4
  import { a as GridCell, c as diffText, d as frameText, i as FrameStatsOptions, l as frameSignature, n as FrameSignature, o as SIGNATURE_GRID, r as FrameStats, s as diffSignatures, t as FrameDiff, u as frameStats } from "./frame-report-DCnHFmto.js";
5
- import { t as LoadSceneOptions } from "./loader-DolLJWJn.js";
6
- import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-D5GJgb_x.js";
5
+ import { t as LoadSceneOptions } from "./loader-Cga7FVP4.js";
6
+ import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-_UAcHxnC.js";
7
7
  import { n as PathGrid } from "./pathfinding-_fGrCFmH.js";
8
8
  import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
9
9
  import { VRM } from "@pixiv/three-vrm";
@@ -358,6 +358,33 @@ declare class StaticBody3D extends PhysicsBody3D {
358
358
  /** Sensor volume emitting `triggerEnter(other)` / `triggerExit(other)`. */
359
359
  declare class Area3D extends PhysicsBody3D {
360
360
  static override readonly typeName: string;
361
+ /**
362
+ * Who is inside RIGHT NOW — the standing answer behind the enter/exit pair.
363
+ *
364
+ * Enter and exit tell you about crossings; a pressure plate, a capture point,
365
+ * a "how many enemies are in the blast" count and a shop trigger all want
366
+ * OCCUPANCY. Without this every one of them kept a private `Set` fed by the
367
+ * two signals, and had to guard it, because a body that is teleported or
368
+ * freed inside a sensor does not reliably announce its exit. Physics already
369
+ * knows; now it says.
370
+ *
371
+ * ```ts
372
+ * const load = this.area.overlapping()
373
+ * .reduce((kg, b) => kg + ((b as RigidBody3D).mass ?? 0), 0);
374
+ * door.open = load >= 60;
375
+ * ```
376
+ *
377
+ * It reports everything the solver says is inside, STATIC WORLD INCLUDED — a
378
+ * plate laid into the floor genuinely contains the floor. Pass a group to ask
379
+ * the question you usually mean:
380
+ *
381
+ * ```ts
382
+ * area.overlapping('crate') // only things tagged `crate`
383
+ * ```
384
+ *
385
+ * Empty in a scene with no physics world.
386
+ */
387
+ overlapping(group?: string): PhysicsBody3D[];
361
388
  }
362
389
  /** Dynamic simulated body. */
363
390
  declare class RigidBody3D extends PhysicsBody3D {
@@ -370,6 +397,14 @@ declare class RigidBody3D extends PhysicsBody3D {
370
397
  restitution: number;
371
398
  /** m/s, y-up. Read back every step; write to launch. */
372
399
  linearVelocity: number[];
400
+ /**
401
+ * rad/s about each world axis. Read back every step; write to spin.
402
+ *
403
+ * A puzzle builder could neither launch a spinning body nor read how fast a
404
+ * lever was swinging — only its angle, differenced by hand — because this had
405
+ * no counterpart to `linearVelocity`. `fixedRotation` pins it at zero.
406
+ */
407
+ angularVelocity: number[];
373
408
  /** @internal set by Physics3D */
374
409
  _physics3d: Physics3D | null;
375
410
  /** World-space impulse (kg·m/s) — the floating-capsule controller's verb. */
@@ -539,6 +574,17 @@ declare class Physics3D {
539
574
  velocityOf(node: PhysicsBody3D): [number, number, number];
540
575
  /** Create newly-arrived joints; tear down departed ones. */
541
576
  private syncJoints;
577
+ /** Who is currently inside each sensor/body, maintained from the event drain. */
578
+ private readonly overlaps;
579
+ private trackOverlap;
580
+ /**
581
+ * The bodies currently overlapping `node` — the standing answer behind
582
+ * `triggerEnter`/`triggerExit`.
583
+ *
584
+ * Freed nodes are dropped on read rather than tracked, so a hazard that
585
+ * despawns inside a plate cannot leave a ghost holding a door open.
586
+ */
587
+ overlapping(node: PhysicsBody3D): PhysicsBody3D[];
542
588
  /**
543
589
  * World-space raycast (meters). `exclude` skips that body's collider —
544
590
  * ground probes from inside a capsule need it. `opts.staticOnly` hits ONLY the
@@ -1264,6 +1310,25 @@ declare class CharacterController3D extends Node3D {
1264
1310
  airControl: number;
1265
1311
  fallGravity: number;
1266
1312
  floatHeight: number;
1313
+ /**
1314
+ * How high a step the PLAYER walks up without jumping (0 = off).
1315
+ *
1316
+ * The hover spring holds the capsule a fixed distance above whatever is
1317
+ * DIRECTLY below it, so a vertical face stopped the player dead at about
1318
+ * 0.2 m — while `CharacterBody3D.stepHeight` let every enemy climb 0.35 m.
1319
+ * The docs said the player did not need this ("it floats over small ledges
1320
+ * already"); it floats over 18 cm ones.
1321
+ *
1322
+ * This probes just AHEAD of the capsule in the direction of travel: if the
1323
+ * ground there is higher, but by no more than `stepHeight`, the spring aims
1324
+ * at THAT surface and lifts the character onto it. A wall taller than the
1325
+ * step is not a step and still stops you.
1326
+ *
1327
+ * Do not reach for `floatHeight` instead. It does raise the character over
1328
+ * ledges, and it silently makes it WEIGHTLESS: at `floatHeight: 0.28` a
1329
+ * 200 kg character does not move a seesaw a 70 kg crate tips.
1330
+ */
1331
+ stepHeight: number;
1267
1332
  mouseLook: boolean;
1268
1333
  zoomMin: number;
1269
1334
  zoomMax: number;
@@ -1836,6 +1901,31 @@ declare class Joint3D extends Node3D {
1836
1901
  length: number;
1837
1902
  stiffness: number;
1838
1903
  damping: number;
1904
+ /**
1905
+ * Do the two linked bodies still collide with EACH OTHER?
1906
+ *
1907
+ * `null` (default) decides from the joint type, because the two families want
1908
+ * opposite answers:
1909
+ *
1910
+ * - **pivots** (`fixed`, `spherical`, `revolute`) → **off**. A hinge's bodies
1911
+ * overlap at the pivot by construction, so the contact solver fights the
1912
+ * joint and flings them. A puzzle builder's first hinge produced a bar that
1913
+ * spun chaotically forever and threw a 40 kg box thirty metres, and they
1914
+ * nearly filed "spherical joints inject energy":
1915
+ *
1916
+ * ```
1917
+ * t= 0 Plank r=[0,0,0] Box [ 0.00, 5.50, 2.40]
1918
+ * t=3000 Plank r=[-66,24,64] Box [-29.70, 0.40, -4.91]
1919
+ * ```
1920
+ *
1921
+ * - **tethers** (`rope`, `spring`) → **on**. These anchor a thing to the world,
1922
+ * and the thing has to rest on what it is tethered to. Defaulting these off
1923
+ * drops a barrel roped to the floor straight through it — measured, in this
1924
+ * repo's own conformance room.
1925
+ *
1926
+ * Set `true`/`false` to say it outright.
1927
+ */
1928
+ collide: boolean | null;
1839
1929
  override onEnterTree(): void;
1840
1930
  /** @internal The two bodies, resolved (throws on a bad target at step time). */
1841
1931
  _resolveBodies(): {
@@ -3719,6 +3809,43 @@ declare class Renderer3D {
3719
3809
  behind: boolean;
3720
3810
  };
3721
3811
  /**
3812
+ * The WORLD POINT under canvas pixel (sx, sy) — the other half of
3813
+ * `screenFromWorld`, and the call `incanto-web-integration.md` has always
3814
+ * offered "for taps and clicks".
3815
+ *
3816
+ * It did not exist in 3D. `Renderer2D` has had it since the beginning, the
3817
+ * doc covers both runtimes, and an agent building a tower defense went looking
3818
+ * for it and measured `typeof game.renderer.worldFromScreen === "undefined"`.
3819
+ * `pick()` gives you the NODE under a pixel but never the POINT, so "put the
3820
+ * thing where the player clicked on the ground" was not expressible and that
3821
+ * game discretised its whole map into clickable pads instead.
3822
+ *
3823
+ * A pixel in 3D is a RAY, not a point, so this needs a surface to land on and
3824
+ * takes the horizontal plane at `planeY` (default 0 — the ground). Returns
3825
+ * null when the ray runs parallel to that plane or would only meet it behind
3826
+ * the camera, which is what "the sky" looks like from here.
3827
+ *
3828
+ * For anything not flat — terrain, a hillside, a stack of crates — raycast
3829
+ * physics instead: `rayFromScreen` gives you the origin and direction to feed
3830
+ * `physics.castRay`.
3831
+ */
3832
+ worldFromScreen(sx: number, sy: number, planeY?: number): {
3833
+ x: number;
3834
+ y: number;
3835
+ z: number;
3836
+ } | null;
3837
+ /**
3838
+ * The camera ray through canvas pixel (sx, sy) — origin and unit direction,
3839
+ * ready for `physics.castRay(origin, dir, maxLen)`.
3840
+ *
3841
+ * This is the honest 3D answer to "where did the player click": on uneven
3842
+ * ground the only thing that knows is the collision world.
3843
+ */
3844
+ rayFromScreen(sx: number, sy: number): {
3845
+ origin: [number, number, number];
3846
+ dir: [number, number, number];
3847
+ } | null;
3848
+ /**
3722
3849
  * The node rendered under canvas pixel (sx, sy), or null — raycasts the last
3723
3850
  * rendered camera into the scene and resolves the hit up to its `incantoNode`
3724
3851
  * (every Node3D stamps that in `_ensureObject3D`). 3D depth-sorts, so the
package/dist/3d.js CHANGED
@@ -1,10 +1,10 @@
1
- import { c as parseEnvironment3D, l as sunDirectionFromElevationAzimuth, s as horizonColorFromSky, u as sunDirectionFromSky } from "./loader-zDynoew_.js";
1
+ import { c as parseEnvironment3D, l as sunDirectionFromElevationAzimuth, s as horizonColorFromSky, u as sunDirectionFromSky } from "./loader-lQDCwNag.js";
2
2
  import { a as frameSignature, n as diffSignatures, o as frameStats, r as diffText, s as frameText, t as SIGNATURE_GRID } from "./frame-report-BSMny7oe.js";
3
- import { B as CharacterBody3D, F as Water3D, H as RigidBody3D, I as WATER_CUTOUT_MAX, L as WaterCutout3D, U as StaticBody3D, V as PhysicsBody3D, W as Node3D, q as WATER_MAX_RIPPLES, z as Area3D } from "./gameplay-CLgFdkh5.js";
4
- import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-D2vzw583.js";
5
- import { a as Environment3D, i as syncTree, o as setEnvironment3D, r as Renderer3D, s as AssetStore3D, t as createGame3D } from "./create-game-B746rYbr.js";
3
+ import { G as StaticBody3D, H as CharacterBody3D, K as Node3D, L as Water3D, R as WATER_CUTOUT_MAX, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, Y as WATER_MAX_RIPPLES, z as WaterCutout3D } from "./gameplay-D1RADWu3.js";
4
+ import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-BlPsEmq6.js";
5
+ import { a as Environment3D, i as syncTree, o as setEnvironment3D, r as Renderer3D, s as AssetStore3D, t as createGame3D } from "./create-game-DYJCIzO0.js";
6
6
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
7
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-BOO58xzw.js";
7
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-BkHjwJgI.js";
8
8
  //#region src/3d/model-verdict.ts
9
9
  /** Mixamo exports every bone as `mixamorigX`; the retargeter binds by that name. */
10
10
  const MIXAMO = /^mixamorig[:_]?/i;
@@ -1,4 +1,4 @@
1
- import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-DsgayMsH.js";
1
+ import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-BXNLfIJk.js";
2
2
  import { a as Rng } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/core/particle-sim.d.ts
@@ -1104,6 +1104,51 @@ declare function resolveOrderGroups(declared?: Record<string, number>): OrderGro
1104
1104
  */
1105
1105
  declare function effectiveOrder(group: string, renderOrder: number, table?: OrderGroupTable): number;
1106
1106
  //#endregion
1107
+ //#region src/core/physics-query.d.ts
1108
+ /** What a ray found: how far, the surface normal, and the body it belongs to. */
1109
+ interface RayHit {
1110
+ distance: number;
1111
+ normal: number[];
1112
+ node: Node | null;
1113
+ }
1114
+ /**
1115
+ * The physics world, as core knows it — the QUERIES a behavior asks, with no
1116
+ * mention of Rapier or three, so `Engine` can hold one without core learning
1117
+ * about either runtime. `Physics2D` and `Physics3D` satisfy it structurally
1118
+ * (2D in PIXELS y-down with 2-vectors, 3D in METERS y-up with 3-vectors).
1119
+ *
1120
+ * This exists because `incanto-physics-and-input.md` has always taught
1121
+ * `physics.castRay(origin, dir, maxLen, exclude?)` without ever saying where
1122
+ * that `physics` comes from. It lived only on the object `createGame2D/3D()`
1123
+ * returns — the BOOT site — so a `Behavior`, which is where AI lives and where
1124
+ * line of sight is decided, could not reach it. A stealth game's guard had to
1125
+ * cast the private `node._physics` to call a documented API.
1126
+ */
1127
+ interface PhysicsQuery {
1128
+ /**
1129
+ * Fire a ray and report the first thing it hits, or `null`.
1130
+ *
1131
+ * Sensors never block a ray. Pass `exclude` for the body you are casting from
1132
+ * — a ray starting inside its own collider hits itself at distance 0.
1133
+ */
1134
+ castRay(origin: readonly number[], dir: readonly number[], maxLen: number, exclude?: Node, opts?: {
1135
+ staticOnly?: boolean;
1136
+ }): RayHit | null;
1137
+ /**
1138
+ * A THICK ray (sphere sweep) — 3D only, hence optional. Use it where skimming
1139
+ * matters: a thin ray that passes 10 cm over a wall reports "clear" while the
1140
+ * wall still fills the frame.
1141
+ */
1142
+ castSphere?(origin: readonly number[], dir: readonly number[], radius: number, maxLen: number, exclude?: Node, opts?: {
1143
+ staticOnly?: boolean;
1144
+ }): {
1145
+ distance: number;
1146
+ node: Node | null;
1147
+ } | null;
1148
+ /** Tear the world down and detach from the engine. */
1149
+ dispose(): void;
1150
+ }
1151
+ //#endregion
1107
1152
  //#region src/core/save.d.ts
1108
1153
  /**
1109
1154
  * Namespaced game persistence — high scores, unlocks, settings. localStorage
@@ -1576,6 +1621,20 @@ declare class Engine {
1576
1621
  readonly sceneChanged: Signal<[Scene | null]>;
1577
1622
  /** Declarative input — scene `input{}` declarations load on setScene. */
1578
1623
  readonly input: InputMap;
1624
+ /**
1625
+ * The physics world, once something enabled it — `null` in a game that has
1626
+ * none, which is most 2D puzzle games and every menu scene.
1627
+ *
1628
+ * `enablePhysics2D/3D` set this, and `dispose()` clears it. It is the handle
1629
+ * `incanto-physics-and-input.md` has always assumed you had: the skill teaches
1630
+ * `physics.castRay(...)` but the object lived only where the game BOOTED, so
1631
+ * a `Behavior` — where AI lives, and where line of sight is decided — had no
1632
+ * way to reach it short of casting the private `node._physics`.
1633
+ *
1634
+ * Typed as the query surface rather than the concrete class so core never
1635
+ * learns about Rapier or three. From a behavior, prefer `this.physics`.
1636
+ */
1637
+ physics: PhysicsQuery | null;
1579
1638
  /** Seeded randomness for game logic (deterministic when `seed` is set). */
1580
1639
  readonly rng: Rng;
1581
1640
  /** The engine log channel (debug overlay + headless harness tail this). */
@@ -1590,6 +1649,25 @@ declare class Engine {
1590
1649
  * and cleared on dispose; null when nothing renders (headless).
1591
1650
  */
1592
1651
  picker: ((x: number, y: number) => Node | null) | null;
1652
+ /** Frames the engine has stepped — monotonic, and the pick cache's key. */
1653
+ frameNumber: number;
1654
+ /** @internal frame number + answer, so N Clickables cost ONE raycast. */
1655
+ private pickCacheFrame;
1656
+ private pickCacheKey;
1657
+ private pickCacheHit;
1658
+ /**
1659
+ * The node under (x, y), answered at most ONCE per frame per cursor position.
1660
+ *
1661
+ * `Clickable` asks every frame, and a board game has one per cell: 42 pads
1662
+ * measured **420 picker calls over ten frames** — one GPU raycast per pad per
1663
+ * frame, all answering the same question about the same pixel. At 60 fps that
1664
+ * is ~2500 raycasts a second to find out what the cursor is on, and a 200-tile
1665
+ * board was unusable.
1666
+ *
1667
+ * Cached on (frame, x, y), so a game that picks at two different points in one
1668
+ * frame still gets two real answers.
1669
+ */
1670
+ pickAt(x: number, y: number): Node | null;
1593
1671
  /** Global volume buses — `master` × `sfx`/`music` gain + `muted`. AudioPlayer
1594
1672
  * routes its volume through this; games set it for global volume/mute. */
1595
1673
  readonly audio: AudioBuses;
@@ -1942,8 +2020,44 @@ interface BehaviorCtor {
1942
2020
  * schema/defaults/delta model as node props.
1943
2021
  */
1944
2022
  declare abstract class Behavior {
2023
+ /**
2024
+ * Every behavior accepts `enabled`, inherited down the static chain — see
2025
+ * `enabled` below for why it is a universal prop rather than one each AI
2026
+ * behavior declares for itself.
2027
+ */
1945
2028
  static props?: PropSchema;
1946
2029
  /**
2030
+ * Whether this behavior's `update`/`fixedUpdate` run. `false` stops it
2031
+ * without removing it — and this is what makes ENEMY AI expressible in JSON.
2032
+ *
2033
+ * A node holds ONE behavior, so `Patrol` and `Chase` cannot share one. Both
2034
+ * can ride child nodes with `moveParent`, but then both run at once, and
2035
+ * before this nothing could stop either — so "patrols until it spots you,
2036
+ * then chases", the most ordinary AI there is, had to be hand-written in
2037
+ * TypeScript, which is exactly what the gameplay library exists to prevent.
2038
+ * With this the whole state machine is two wires:
2039
+ *
2040
+ * ```jsonc
2041
+ * { "signal": "spotted", "from": "Eyes", "to": "Walk", "handler": "disable" },
2042
+ * { "signal": "spotted", "from": "Eyes", "to": "Hunt", "handler": "enable" }
2043
+ * ```
2044
+ *
2045
+ * `Clickable` had already hand-rolled this exact prop for itself. One
2046
+ * behavior needed it and solved it locally; nothing generalized it.
2047
+ *
2048
+ * Asleep is NOT detached: the behavior keeps its props and its accumulated
2049
+ * state, stays reachable as `node.behavior`, keeps its signal connections,
2050
+ * and still saves through `serialize()`. Only the per-frame hooks pause. The
2051
+ * lifecycle hooks (`onEnterTree`/`onReady`/`onExitTree`) are not gated — they
2052
+ * are structural, they fire once, and a behavior that skipped its own
2053
+ * `onReady` would wake up uninitialized.
2054
+ */
2055
+ enabled: boolean;
2056
+ /** Start running again — a connection handler, so a wire can do it. */
2057
+ enable(): void;
2058
+ /** Stop running without being removed — a connection handler. */
2059
+ disable(): void;
2060
+ /**
1947
2061
  * Custom signals this behavior emits via `this.emit(...)` — declared onto
1948
2062
  * its node at load. Undeclared emits are hard errors (typo safety).
1949
2063
  */
@@ -1958,6 +2072,20 @@ declare abstract class Behavior {
1958
2072
  get rng(): Rng;
1959
2073
  /** The engine log channel (visible in the debug overlay and test harness). */
1960
2074
  get log(): LogManager;
2075
+ /**
2076
+ * The physics world, for the queries AI asks — `castRay` for line of sight,
2077
+ * `castSphere` for a probe that must not skim past a wall.
2078
+ *
2079
+ * ```ts
2080
+ * const hit = this.physics?.castRay(eye, dir, range, this.node);
2081
+ * const blocked = hit !== null && hit.node !== player;
2082
+ * ```
2083
+ *
2084
+ * `null` in a game with no physics, so a behavior that only sometimes needs
2085
+ * a ray degrades instead of throwing. Pass the caster as `exclude` — a ray
2086
+ * that starts inside its own collider hits itself at distance 0.
2087
+ */
2088
+ get physics(): PhysicsQuery | null;
1961
2089
  getNode(path: string): Node;
1962
2090
  emit(signal: string, ...args: unknown[]): void;
1963
2091
  on(signal: string, fn: SignalListener<unknown[]>, opts?: {
@@ -1,14 +1,14 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { c as parseEnvironment3D, k as diagnose, n as loadScene, s as horizonColorFromSky, u as sunDirectionFromSky, z as registerBehavior } from "./loader-zDynoew_.js";
3
- import { C as qualityRendering, _ as AudioPlayer, v as Engine, x as qualityCaps } from "./register-Btm7_Emq.js";
2
+ import { c as parseEnvironment3D, k as diagnose, n as loadScene, s as horizonColorFromSky, u as sunDirectionFromSky, z as registerBehavior } from "./loader-lQDCwNag.js";
3
+ import { C as qualityRendering, _ as AudioPlayer, v as Engine, x as qualityCaps } from "./register-BmuqYTiY.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { i as resolveRendering, n as attachTouchControls } from "./touch-DESwnpOc.js";
6
- import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-BwhkcNt8.js";
6
+ import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-byR9USax.js";
7
7
  import { o as frameStats } from "./frame-report-BSMny7oe.js";
8
- import { K as createCausticsQuad, V as PhysicsBody3D, W as Node3D, n as registerGameplayBehaviors } from "./gameplay-CLgFdkh5.js";
8
+ import { J as createCausticsQuad, K as Node3D, U as PhysicsBody3D, n as registerGameplayBehaviors } from "./gameplay-D1RADWu3.js";
9
9
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
10
- import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-D2vzw583.js";
11
- import { n as enablePhysics3D } from "./physics-3d-BOO58xzw.js";
10
+ import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-BlPsEmq6.js";
11
+ import { n as enablePhysics3D } from "./physics-3d-BkHjwJgI.js";
12
12
  import { ACESFilmicToneMapping, AmbientLight, Box3, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, HalfFloatType, LineBasicMaterial, LineSegments, Matrix4, Mesh, PCFShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, Quaternion, Raycaster, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
13
13
  import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
14
14
  import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
@@ -1967,6 +1967,67 @@ var Renderer3D = class {
1967
1967
  };
1968
1968
  }
1969
1969
  /**
1970
+ * The WORLD POINT under canvas pixel (sx, sy) — the other half of
1971
+ * `screenFromWorld`, and the call `incanto-web-integration.md` has always
1972
+ * offered "for taps and clicks".
1973
+ *
1974
+ * It did not exist in 3D. `Renderer2D` has had it since the beginning, the
1975
+ * doc covers both runtimes, and an agent building a tower defense went looking
1976
+ * for it and measured `typeof game.renderer.worldFromScreen === "undefined"`.
1977
+ * `pick()` gives you the NODE under a pixel but never the POINT, so "put the
1978
+ * thing where the player clicked on the ground" was not expressible and that
1979
+ * game discretised its whole map into clickable pads instead.
1980
+ *
1981
+ * A pixel in 3D is a RAY, not a point, so this needs a surface to land on and
1982
+ * takes the horizontal plane at `planeY` (default 0 — the ground). Returns
1983
+ * null when the ray runs parallel to that plane or would only meet it behind
1984
+ * the camera, which is what "the sky" looks like from here.
1985
+ *
1986
+ * For anything not flat — terrain, a hillside, a stack of crates — raycast
1987
+ * physics instead: `rayFromScreen` gives you the origin and direction to feed
1988
+ * `physics.castRay`.
1989
+ */
1990
+ worldFromScreen(sx, sy, planeY = 0) {
1991
+ const ray = this.rayFromScreen(sx, sy);
1992
+ if (!ray) return null;
1993
+ const dy = ray.dir[1];
1994
+ if (Math.abs(dy) < 1e-6) return null;
1995
+ const t = (planeY - ray.origin[1]) / dy;
1996
+ if (t < 0) return null;
1997
+ return {
1998
+ x: ray.origin[0] + ray.dir[0] * t,
1999
+ y: planeY,
2000
+ z: ray.origin[2] + ray.dir[2] * t
2001
+ };
2002
+ }
2003
+ /**
2004
+ * The camera ray through canvas pixel (sx, sy) — origin and unit direction,
2005
+ * ready for `physics.castRay(origin, dir, maxLen)`.
2006
+ *
2007
+ * This is the honest 3D answer to "where did the player click": on uneven
2008
+ * ground the only thing that knows is the collision world.
2009
+ */
2010
+ rayFromScreen(sx, sy) {
2011
+ const cam = this.lastCamera;
2012
+ if (!cam) return null;
2013
+ pickNdc.set(sx / this.lastSize.w * 2 - 1, -(sy / this.lastSize.h * 2 - 1));
2014
+ pickRaycaster.setFromCamera(pickNdc, cam);
2015
+ const o = pickRaycaster.ray.origin;
2016
+ const d = pickRaycaster.ray.direction;
2017
+ return {
2018
+ origin: [
2019
+ o.x,
2020
+ o.y,
2021
+ o.z
2022
+ ],
2023
+ dir: [
2024
+ d.x,
2025
+ d.y,
2026
+ d.z
2027
+ ]
2028
+ };
2029
+ }
2030
+ /**
1970
2031
  * The node rendered under canvas pixel (sx, sy), or null — raycasts the last
1971
2032
  * rendered camera into the scene and resolves the hit up to its `incantoNode`
1972
2033
  * (every Node3D stamps that in `_ensureObject3D`). 3D depth-sorts, so the
@@ -1,14 +1,14 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { d as computeViewport, f as resolveViewport, k as diagnose, n as loadScene, z as registerBehavior } from "./loader-zDynoew_.js";
3
- import { _ as AudioPlayer, v as Engine } from "./register-Btm7_Emq.js";
2
+ import { d as computeViewport, f as resolveViewport, k as diagnose, n as loadScene, z as registerBehavior } from "./loader-lQDCwNag.js";
3
+ import { _ as AudioPlayer, v as Engine } from "./register-BmuqYTiY.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { i as resolveRendering, n as attachTouchControls } from "./touch-DESwnpOc.js";
6
- import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-BwhkcNt8.js";
6
+ import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-byR9USax.js";
7
7
  import { o as frameStats } from "./frame-report-BSMny7oe.js";
8
- import { n as registerGameplayBehaviors } from "./gameplay-CLgFdkh5.js";
9
- import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-dGsbnJ87.js";
8
+ import { n as registerGameplayBehaviors } from "./gameplay-D1RADWu3.js";
9
+ import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-D0CxCveZ.js";
10
10
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
11
- import { n as enablePhysics2D } from "./physics-2d-BPJcJRRP.js";
11
+ import { n as enablePhysics2D } from "./physics-2d-Cji5A6sX.js";
12
12
  import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
13
13
  //#region src/2d/assets.ts
14
14
  /**
package/dist/debug.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Ft as LogLevel, T as RendererStats, b as Engine } from "./behavior-DsgayMsH.js";
1
+ import { Ft as LogLevel, T as RendererStats, b as Engine } from "./behavior-BXNLfIJk.js";
2
2
 
3
3
  //#region src/debug/panel.d.ts
4
4
  /** Minimal document surface the overlay needs (injectable for tests). */
@@ -1,4 +1,4 @@
1
- import { a as serializeNode, t as buildNodeJson } from "./loader-zDynoew_.js";
1
+ import { a as serializeNode, t as buildNodeJson } from "./loader-lQDCwNag.js";
2
2
  //#region src/core/scene/duplicate.ts
3
3
  function stripUids(json) {
4
4
  const { uid: _uid, ...rest } = json;