incanto 0.65.0 → 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 (55) 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 +4 -4
  5. package/dist/{audio-player-BrEHwbK1.d.ts → audio-player-_UAcHxnC.d.ts} +1 -1
  6. package/dist/{behavior-CmMuQ6CL.d.ts → behavior-BXNLfIJk.d.ts} +19 -0
  7. package/dist/{create-game-W2xzTJhs.js → create-game-DYJCIzO0.js} +66 -5
  8. package/dist/{create-game-DIyh5Zy9.js → create-game-Dd7H4bJV.js} +5 -5
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/{environment-presets-BvHs6HEk.js → environment-presets-BlPsEmq6.js} +69 -5
  11. package/dist/{gameplay-D-H986A4.js → gameplay-D1RADWu3.js} +267 -7
  12. package/dist/gameplay.d.ts +78 -3
  13. package/dist/gameplay.js +2 -2
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.js +3 -3
  16. package/dist/{loader-CTB441z4.d.ts → loader-Cga7FVP4.d.ts} +1 -1
  17. package/dist/net.d.ts +2 -2
  18. package/dist/net.js +1 -1
  19. package/dist/{physics-2d-CmnunaR2.js → physics-2d-Cji5A6sX.js} +29 -3
  20. package/dist/{physics-3d-DULB1lD1.js → physics-3d-BkHjwJgI.js} +46 -4
  21. package/dist/{quiet-rapier-BAJ4K94N.js → quiet-rapier-C6fW4zcW.js} +14 -1
  22. package/dist/react.d.ts +1 -1
  23. package/dist/react.js +1 -1
  24. package/dist/{register-CgeR4ckF.js → register-BmuqYTiY.js} +29 -0
  25. package/dist/{register-DO5_Qp0Q.js → register-D0CxCveZ.js} +37 -3
  26. package/dist/{replay-CThvwkcQ.d.ts → replay-BHoB6fCU.d.ts} +1 -1
  27. package/dist/{replay-CEw2xAhG.js → replay-IsZbNu6d.js} +1 -1
  28. package/dist/{split-screen-gb0c3xdb.js → split-screen-D_i7GRcY.js} +1 -1
  29. package/dist/{split-screen-DxZzC1nm.d.ts → split-screen-eJUFjhi1.d.ts} +2 -2
  30. package/dist/{src-DYffzHbd.js → src-BTLbXFPZ.js} +1 -1
  31. package/dist/{teardown-BordHL9o.js → teardown-byR9USax.js} +1 -1
  32. package/dist/{test-CXZuXs8k.js → test-DgrD0jHD.js} +11 -11
  33. package/dist/test.d.ts +4 -4
  34. package/dist/test.js +2 -2
  35. package/dist/vite.js +2 -2
  36. package/editor/assets/{agent8-DO5qzIOD.js → agent8-9N-Pd_YS.js} +1 -1
  37. package/editor/assets/{debug-DntG9CVD.js → debug-CkbJICYp.js} +1 -1
  38. package/editor/assets/{index-CqEUVaPN.js → index-CeDhIPTC.js} +72 -72
  39. package/editor/index.html +1 -1
  40. package/package.json +1 -1
  41. package/schemas/scene.schema.json +19 -0
  42. package/skills/incanto-gameplay-behaviors.md +85 -6
  43. package/skills/incanto-node-reference.md +59 -1
  44. package/skills/incanto-physics-and-input.md +69 -0
  45. package/skills/incanto-web-integration.md +24 -3
  46. package/templates-app/beacon-isle-3d/package.json +1 -1
  47. package/templates-app/platformer-2d/package.json +1 -1
  48. package/templates-app/star-survivor/package.json +1 -1
  49. package/templates-app/tps-3d/package.json +1 -1
  50. package/templates-app/village-quest-3d/package.json +1 -1
  51. package/templates-app/beacon-isle-3d/coverage.json +0 -9
  52. package/templates-app/platformer-2d/coverage.json +0 -5
  53. package/templates-app/star-survivor/coverage.json +0 -5
  54. package/templates-app/tps-3d/coverage.json +0 -5
  55. 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-CmMuQ6CL.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-CTB441z4.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-BrEHwbK1.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-DIyh5Zy9.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-DO5_Qp0Q.js";
4
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-CmnunaR2.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-CmMuQ6CL.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-CTB441z4.js";
6
- import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-BrEHwbK1.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
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-D-H986A4.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-BvHs6HEk.js";
5
- import { a as Environment3D, i as syncTree, o as setEnvironment3D, r as Renderer3D, s as AssetStore3D, t as createGame3D } from "./create-game-W2xzTJhs.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-DULB1lD1.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-CmMuQ6CL.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
@@ -1649,6 +1649,25 @@ declare class Engine {
1649
1649
  * and cleared on dispose; null when nothing renders (headless).
1650
1650
  */
1651
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;
1652
1671
  /** Global volume buses — `master` × `sfx`/`music` gain + `muted`. AudioPlayer
1653
1672
  * routes its volume through this; games set it for global volume/mute. */
1654
1673
  readonly audio: AudioBuses;
@@ -1,14 +1,14 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
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-CgeR4ckF.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-BordHL9o.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-D-H986A4.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-BvHs6HEk.js";
11
- import { n as enablePhysics3D } from "./physics-3d-DULB1lD1.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
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-CgeR4ckF.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-BordHL9o.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-D-H986A4.js";
9
- import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-DO5_Qp0Q.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-CmnunaR2.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-CmMuQ6CL.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,11 +1,11 @@
1
1
  import { k as diagnose } from "./loader-lQDCwNag.js";
2
- import { L as translateOn, t as registerCoreNodes } from "./register-CgeR4ckF.js";
2
+ import { L as translateOn, t as registerCoreNodes } from "./register-BmuqYTiY.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { i as getNodeSchema, l as registerNode } from "./registry-CF70EArN.js";
6
6
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
7
7
  import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CY-mrr1L.js";
8
- import { B as CharacterBody3D, F as Water3D, H as RigidBody3D, J as colliderFootDrop, L as WaterCutout3D, R as rejectScale, U as StaticBody3D, V as PhysicsBody3D, W as Node3D, w as tolerateUnknownAction, z as Area3D } from "./gameplay-D-H986A4.js";
8
+ import { B as rejectScale, G as StaticBody3D, H as CharacterBody3D, K as Node3D, L as Water3D, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, X as colliderFootDrop, w as tolerateUnknownAction, z as WaterCutout3D } from "./gameplay-D1RADWu3.js";
9
9
  import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
10
10
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
11
11
  import { AdditiveBlending, AnimationClip, AnimationMixer, Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, CapsuleGeometry, ClampToEdgeWrapping, Color, ConeGeometry, CylinderGeometry, DataTexture, DirectionalLight, DoubleSide, DynamicDrawUsage, Euler, Group, IcosahedronGeometry, ImageBitmapLoader, InstancedBufferAttribute, InstancedMesh, LinearFilter, LinearMipmapLinearFilter, LoopOnce, LoopRepeat, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshPhysicalMaterial, MeshStandardMaterial, NearestFilter, NoBlending, NoColorSpace, NormalBlending, PerspectiveCamera, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, QuaternionKeyframeTrack, RGBADepthPacking, RGBAFormat, RepeatWrapping, SRGBColorSpace, ShaderChunk, ShaderMaterial, SphereGeometry, Texture, TextureLoader, UniformsLib, UniformsUtils, Vector3, Vector4, VectorKeyframeTrack } from "three";
@@ -742,7 +742,8 @@ var Joint3D = class extends Node3D {
742
742
  ] },
743
743
  length: { default: 0 },
744
744
  stiffness: { default: 50 },
745
- damping: { default: 5 }
745
+ damping: { default: 5 },
746
+ collide: { default: null }
746
747
  };
747
748
  type = "spherical";
748
749
  target = "";
@@ -760,6 +761,31 @@ var Joint3D = class extends Node3D {
760
761
  length = 0;
761
762
  stiffness = 50;
762
763
  damping = 5;
764
+ /**
765
+ * Do the two linked bodies still collide with EACH OTHER?
766
+ *
767
+ * `null` (default) decides from the joint type, because the two families want
768
+ * opposite answers:
769
+ *
770
+ * - **pivots** (`fixed`, `spherical`, `revolute`) → **off**. A hinge's bodies
771
+ * overlap at the pivot by construction, so the contact solver fights the
772
+ * joint and flings them. A puzzle builder's first hinge produced a bar that
773
+ * spun chaotically forever and threw a 40 kg box thirty metres, and they
774
+ * nearly filed "spherical joints inject energy":
775
+ *
776
+ * ```
777
+ * t= 0 Plank r=[0,0,0] Box [ 0.00, 5.50, 2.40]
778
+ * t=3000 Plank r=[-66,24,64] Box [-29.70, 0.40, -4.91]
779
+ * ```
780
+ *
781
+ * - **tethers** (`rope`, `spring`) → **on**. These anchor a thing to the world,
782
+ * and the thing has to rest on what it is tethered to. Defaulting these off
783
+ * drops a barrel roped to the floor straight through it — measured, in this
784
+ * repo's own conformance room.
785
+ *
786
+ * Set `true`/`false` to say it outright.
787
+ */
788
+ collide = null;
763
789
  onEnterTree() {
764
790
  if (!(this.parent instanceof PhysicsBody3D)) throw new IncantoError("TREE_VIOLATION", `Joint3D '${this.name}' must be a child of a physics body (its body A); parent is '${this.parent?.name ?? "none"}'.`);
765
791
  if (this.target === "") throw new IncantoError("BAD_FORMAT", `Joint3D '${this.name}' needs a "target" node path to the body it links to.`);
@@ -2641,6 +2667,7 @@ var CharacterController3D = class extends Node3D {
2641
2667
  /** Gravity multiplier while falling — >1 is the "snappy" arc. */
2642
2668
  fallGravity: { default: 2.5 },
2643
2669
  floatHeight: { default: .01 },
2670
+ stepHeight: { default: .35 },
2644
2671
  mouseLook: { default: true },
2645
2672
  zoomMin: { default: 0 },
2646
2673
  zoomMax: { default: 0 },
@@ -2673,6 +2700,25 @@ var CharacterController3D = class extends Node3D {
2673
2700
  airControl = .2;
2674
2701
  fallGravity = 2.5;
2675
2702
  floatHeight = .01;
2703
+ /**
2704
+ * How high a step the PLAYER walks up without jumping (0 = off).
2705
+ *
2706
+ * The hover spring holds the capsule a fixed distance above whatever is
2707
+ * DIRECTLY below it, so a vertical face stopped the player dead at about
2708
+ * 0.2 m — while `CharacterBody3D.stepHeight` let every enemy climb 0.35 m.
2709
+ * The docs said the player did not need this ("it floats over small ledges
2710
+ * already"); it floats over 18 cm ones.
2711
+ *
2712
+ * This probes just AHEAD of the capsule in the direction of travel: if the
2713
+ * ground there is higher, but by no more than `stepHeight`, the spring aims
2714
+ * at THAT surface and lifts the character onto it. A wall taller than the
2715
+ * step is not a step and still stops you.
2716
+ *
2717
+ * Do not reach for `floatHeight` instead. It does raise the character over
2718
+ * ledges, and it silently makes it WEIGHTLESS: at `floatHeight: 0.28` a
2719
+ * 200 kg character does not move a seesaw a 70 kg crate tips.
2720
+ */
2721
+ stepHeight = .35;
2676
2722
  mouseLook = true;
2677
2723
  zoomMin = 0;
2678
2724
  zoomMax = 0;
@@ -2803,8 +2849,26 @@ var CharacterController3D = class extends Node3D {
2803
2849
  0,
2804
2850
  -vel[2] * DRAG_DAMPING * mass
2805
2851
  ]);
2806
- if (toi !== null && toi < floatingDis * 2 && !this.jumping) {
2807
- const spring = SPRING_K * (floatingDis - toi) - vel[1] * SPRING_DAMPING;
2852
+ let springToi = toi;
2853
+ if (this.stepHeight > 0 && moving && toi !== null && !this.jumping && (vel[1] ?? 0) <= .5) {
2854
+ const ahead = radius + .12;
2855
+ const probeTop = originY + this.stepHeight + .05;
2856
+ const stepHit = physics.castRay([
2857
+ pos[0] + mx * ahead,
2858
+ probeTop,
2859
+ pos[2] + mz * ahead
2860
+ ], [
2861
+ 0,
2862
+ -1,
2863
+ 0
2864
+ ], this.stepHeight + .05 + rayLen, body);
2865
+ if (stepHit) {
2866
+ const rise = probeTop - stepHit.distance - (originY - toi);
2867
+ if (rise > .02 && rise <= this.stepHeight + .02) springToi = toi - rise;
2868
+ }
2869
+ }
2870
+ if (springToi !== null && springToi < floatingDis * 2 && !this.jumping) {
2871
+ const spring = SPRING_K * (floatingDis - springToi) - vel[1] * SPRING_DAMPING;
2808
2872
  body.applyImpulse([
2809
2873
  0,
2810
2874
  spring * mass,