incanto 0.74.0 → 0.76.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 (92) hide show
  1. package/bin/incanto-multiplay.mjs +52 -4
  2. package/dist/2d.d.ts +83 -7
  3. package/dist/2d.js +4 -3
  4. package/dist/3d.d.ts +861 -167
  5. package/dist/3d.js +6 -6
  6. package/dist/{agent8-CHTT4unP.js → agent8-B-bh3n0X.js} +1 -1
  7. package/dist/{audio-player-BOrk4eQx.d.ts → audio-player-BRo2uvG6.d.ts} +23 -2
  8. package/dist/{behavior-J1cmVah0.d.ts → behavior-B_245qRy.d.ts} +15 -1
  9. package/dist/{create-game-ssvGvP7W.js → create-game-BMIPKrQW.js} +84 -8
  10. package/dist/{create-game-BAiA-FjP.js → create-game-Dai5vfVu.js} +80 -11
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/{editor-switch-CFU9mCec.d.ts → editor-switch-Bzt0GzVp.d.ts} +13 -3
  13. package/dist/editor.js +1600 -1529
  14. package/dist/env.d.ts +1 -1
  15. package/dist/{environment-presets-BkqlWewf.js → environment-presets-e_9YCnGu.js} +2361 -104
  16. package/dist/{gameplay-Bxe1sMVT.js → gameplay-Bfff_beb.js} +10919 -6798
  17. package/dist/gameplay.d.ts +573 -31
  18. package/dist/gameplay.js +3 -2
  19. package/dist/index.d.ts +150 -6
  20. package/dist/index.js +6 -6
  21. package/dist/{loader-BAeWMYfF.d.ts → loader-CbkVdXL8.d.ts} +2 -2
  22. package/dist/net.d.ts +2 -2
  23. package/dist/net.js +2 -2
  24. package/dist/{physics-2d-C-SfvjzH.js → physics-2d-Tuf5LAyq.js} +250 -31
  25. package/dist/{physics-3d-rOzeg890.js → physics-3d-C5MOujfQ.js} +673 -51
  26. package/dist/{picking-BMV34Pjl.js → picking-DIwE48sU.js} +10 -7
  27. package/dist/{save-slots-CEUGeLuB.js → pose-ByFB_J3O.js} +217 -11
  28. package/dist/react.d.ts +2 -2
  29. package/dist/react.js +1 -1
  30. package/dist/{register-DRQyZKGU.js → register-7E3P1gz6.js} +191 -430
  31. package/dist/{register-CjociOtt.js → register-B768Qedj.js} +596 -2
  32. package/dist/{replay-B6BbgGM_.js → replay-DrqQ6dZj.js} +133 -18
  33. package/dist/{replay-wZK5Ok5c.d.ts → replay-W5nCw_cU.d.ts} +21 -2
  34. package/dist/{rng-Bb-IutXB.d.ts → rng-BsXZg3D6.d.ts} +7 -0
  35. package/dist/{split-screen-BxZ5rBe7.js → split-screen-B-dIcQsQ.js} +10 -5
  36. package/dist/{split-screen-D6sx8z3Z.d.ts → split-screen-CiLJf2-C.d.ts} +3 -3
  37. package/dist/{sprite-animation-Bz3DHD_4.js → sprite-animation-SQa5gIu2.js} +8 -6
  38. package/dist/{src-CqtvYtSN.js → src-D823V07k.js} +1 -1
  39. package/dist/{test-CEl0y2Sw.js → test-CVbxnXlv.js} +487 -82
  40. package/dist/test.d.ts +90 -12
  41. package/dist/test.js +2 -2
  42. package/dist/vite.js +34 -5
  43. package/editor/assets/{agent8-BlzturGi.js → agent8-DA-I5qhv.js} +1 -1
  44. package/editor/assets/{debug-BMUi8usj.js → debug-99tEPoGm.js} +1 -1
  45. package/editor/assets/index-Db74LZY-.js +11579 -0
  46. package/editor/index.html +1 -1
  47. package/package.json +1 -1
  48. package/schemas/scene.schema.json +1639 -280
  49. package/skills/incanto-3d-character.md +383 -20
  50. package/skills/incanto-audio.md +24 -2
  51. package/skills/incanto-building-2d-games.md +21 -2
  52. package/skills/incanto-building-3d-games.md +38 -3
  53. package/skills/incanto-editor.md +29 -8
  54. package/skills/incanto-environment.md +257 -14
  55. package/skills/incanto-game-feel.md +60 -1
  56. package/skills/incanto-gameplay-behaviors.md +668 -20
  57. package/skills/incanto-hud.md +116 -0
  58. package/skills/incanto-multiplayer.md +141 -2
  59. package/skills/incanto-node-reference.md +398 -16
  60. package/skills/incanto-performance.md +32 -0
  61. package/skills/incanto-physics-and-input.md +241 -13
  62. package/skills/incanto-playtesting.md +2 -2
  63. package/skills/incanto-save-slots.md +61 -2
  64. package/skills/incanto-scene-json-authoring.md +13 -0
  65. package/skills/incanto-verifying-your-game.md +86 -3
  66. package/templates-app/beacon-isle-3d/PROJECT/Status.md +12 -0
  67. package/templates-app/beacon-isle-3d/package.json +1 -1
  68. package/templates-app/beacon-isle-3d/src/game.scene.json +157 -3
  69. package/templates-app/molehill-2d/PROJECT/Status.md +7 -0
  70. package/templates-app/molehill-2d/package.json +1 -1
  71. package/templates-app/molehill-2d/src/game.scene.json +129 -2
  72. package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
  73. package/templates-app/platformer-2d/docs/project-2d-rules.md +3 -2
  74. package/templates-app/platformer-2d/package.json +1 -1
  75. package/templates-app/platformer-2d/src/behaviors.ts +40 -26
  76. package/templates-app/platformer-2d/src/game.scene.json +144 -2
  77. package/templates-app/platformer-2d/verify.ts +18 -1
  78. package/templates-app/star-survivor/PROJECT/Status.md +7 -0
  79. package/templates-app/star-survivor/package.json +1 -1
  80. package/templates-app/star-survivor/src/behaviors.ts +9 -8
  81. package/templates-app/star-survivor/src/game.scene.json +142 -4
  82. package/templates-app/star-survivor/verify.ts +13 -0
  83. package/templates-app/tps-3d/PROJECT/Status.md +6 -0
  84. package/templates-app/tps-3d/package.json +1 -1
  85. package/templates-app/tps-3d/src/game.scene.json +71 -10
  86. package/templates-app/village-quest-3d/PROJECT/Status.md +7 -0
  87. package/templates-app/village-quest-3d/package.json +1 -1
  88. package/templates-app/village-quest-3d/src/behaviors.ts +8 -16
  89. package/templates-app/village-quest-3d/src/grove.scene.json +121 -5
  90. package/templates-app/village-quest-3d/src/village.scene.json +126 -2
  91. package/templates-app/village-quest-3d/verify.ts +52 -2
  92. package/editor/assets/index-CrUCQoaB.js +0 -11046
package/dist/3d.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-BOrk4eQx.js";
2
- 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, x as EngineOptions } from "./behavior-J1cmVah0.js";
1
+ import { c as showBootFailure, i as ParticleSim, o as ParticleView, r as SpatialPose, s as isWebGLAvailable } from "./audio-player-BRo2uvG6.js";
2
+ 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, x as EngineOptions } from "./behavior-B_245qRy.js";
3
3
  import { t as DiagnosticSink } from "./diagnostics-Cu85N3tL.js";
4
- import { a as SceneJson$1, s as JsonObject } from "./rng-Bb-IutXB.js";
5
- import { t as EditorSwitchOptions } from "./editor-switch-CFU9mCec.js";
4
+ import { a as SceneJson$1, s as JsonObject } from "./rng-BsXZg3D6.js";
5
+ import { t as EditorSwitchOptions } from "./editor-switch-Bzt0GzVp.js";
6
6
  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-DNxDAb1w.js";
7
- import { t as LoadSceneOptions } from "./loader-BAeWMYfF.js";
7
+ import { t as LoadSceneOptions } from "./loader-CbkVdXL8.js";
8
8
  import { n as PathGrid } from "./pathfinding-_fGrCFmH.js";
9
- import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
9
+ import { AnimationClip, AnimationMixer, BufferGeometry, Color, DataTexture, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Quaternion, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
10
10
  import { VRM } from "@pixiv/three-vrm";
11
11
  import { Sky } from "three/examples/jsm/objects/Sky.js";
12
12
  import * as RapierNs from "@dimforge/rapier3d-compat";
@@ -451,6 +451,17 @@ declare class RigidBody3D extends PhysicsBody3D {
451
451
  friction: number;
452
452
  restitution: number;
453
453
  /**
454
+ * How much a `Water3D` lifts this body: `0` (default) = it falls through
455
+ * water like air, `1` = water's own upthrust — the submerged part of the
456
+ * collider's volume, at 1000 kg/m³. So `mass` against the collider decides:
457
+ * a 1 m³ crate at 200 kg floats a fifth under, at 3000 kg it sinks, and a
458
+ * heavier hull sits deeper. Eight sample points across the collider give a
459
+ * tilted hull a righting moment, and the water's `drag` slows it. A body with
460
+ * a `CharacterController3D` under it is never lifted here — the swim spring
461
+ * owns that one.
462
+ */
463
+ buoyancy: number;
464
+ /**
454
465
  * Drag, per second: velocity decays by `e^(-linearDamping·t)`. `0` = none.
455
466
  * A body flying through nothing keeps its speed forever otherwise.
456
467
  */
@@ -494,6 +505,16 @@ declare class CharacterBody3D extends PhysicsBody3D {
494
505
  /** m/s, y-up (up = +y). */
495
506
  velocity: number[];
496
507
  /**
508
+ * The mass this body pushes dynamic bodies with, in kg. Rapier's character
509
+ * controller treats every dynamic body as an OBSTACLE — a character walks
510
+ * around things — so a beam on a `Patrol` slid up to the player and stopped
511
+ * dead against them, forever. A piston, a sweeping arm, a moving wall or a
512
+ * bruiser enemy is a thing that shoves: give it a mass and it pushes what it
513
+ * meets, and the harder the heavier. `0` (the default) keeps every scene as
514
+ * it was.
515
+ */
516
+ pushes: number;
517
+ /**
497
518
  * Hold the capsule against the surface while it moves, so walking down a
498
519
  * slope does not become a series of small falls.
499
520
  *
@@ -577,6 +598,94 @@ declare class CharacterBody3D extends PhysicsBody3D {
577
598
  isOnFloor(): boolean;
578
599
  }
579
600
  //#endregion
601
+ //#region src/3d/nodes/vehicle-3d.d.ts
602
+ /** One wheel, as authored in `wheels`. */
603
+ interface WheelSpec {
604
+ /** Where the spring hangs from, in the CHASSIS's frame (m). */
605
+ position: [number, number, number];
606
+ /** Tyre radius (m). */
607
+ radius: number;
608
+ /** Spring length at rest (m) — the wheel hangs this far below its mount, less the load. */
609
+ rest: number;
610
+ /** Turns with the steer. */
611
+ steer: boolean;
612
+ /** Takes the engine force. */
613
+ drive: boolean;
614
+ }
615
+ /** What one wheel is doing this step. */
616
+ interface WheelState {
617
+ /** The spring found ground. */
618
+ contact: boolean;
619
+ /** Current spring length (m) — the wheel's centre is this far below its mount. */
620
+ suspension: number;
621
+ /** Total roll, radians. */
622
+ rotation: number;
623
+ /** Current steer, radians. */
624
+ steering: number;
625
+ }
626
+ /**
627
+ * A car in one node — Rapier's raycast vehicle under a `RigidBody3D` chassis.
628
+ *
629
+ * ```json
630
+ * { "name": "Car", "type": "RigidBody3D",
631
+ * "props": { "mass": 800, "collider": { "shape": "box", "size": [1.8, 0.6, 4] } },
632
+ * "children": [
633
+ * { "name": "Drive", "type": "Vehicle3D", "props": { "wheels": [
634
+ * { "position": [-0.8, -0.1, 1.4], "steer": true },
635
+ * { "position": [ 0.8, -0.1, 1.4], "steer": true },
636
+ * { "position": [-0.8, -0.1, -1.4], "drive": true },
637
+ * { "position": [ 0.8, -0.1, -1.4], "drive": true } ] } },
638
+ * { "name": "Wheel0", "type": "Node3D", "props": { "position": [-0.8, -0.1, 1.4] },
639
+ * "children": [ { "name": "Hub", "type": "Node3D", "children": [ …the tyre mesh… ] } ] }
640
+ * ] }
641
+ * ```
642
+ *
643
+ * The wheels are springs that never touch anything (a ray each), so a car
644
+ * cannot catch a kerb on a wheel body or disagree with its axle; the throttle,
645
+ * steer and brake come from the `move` vector and the `brake` button (or from
646
+ * code, with `moveAction: ""`). Forward is +z, the +Z-forward rule. A child
647
+ * named `Wheel<i>` is posed every step — it hangs on its spring and turns to
648
+ * steer — and its child `Hub` spins as the wheel rolls.
649
+ */
650
+ declare class Vehicle3D extends Node3D {
651
+ static override readonly typeName: string;
652
+ static override readonly props: PropSchema;
653
+ wheels: WheelSpec[];
654
+ suspensionStiffness: number;
655
+ suspensionCompression: number;
656
+ suspensionRelaxation: number;
657
+ maxTravel: number;
658
+ friction: number;
659
+ sideFriction: number;
660
+ maxSteerDeg: number;
661
+ engineForce: number;
662
+ brakeForce: number;
663
+ moveAction: string;
664
+ brakeAction: string;
665
+ enabled: boolean;
666
+ /** Live inputs, −1..1 (throttle, steer) and 0..1 (brake) — the actions write these, or your code does. */
667
+ throttle: number;
668
+ steer: number;
669
+ brake: number;
670
+ /** Forward speed, m/s (negative in reverse). */
671
+ speed: number;
672
+ /** @internal Written by the physics adapter each step. */
673
+ _states: WheelState[];
674
+ private reportedActions;
675
+ /** What wheel `i` is doing. */
676
+ wheel(i: number): WheelState;
677
+ /** The wheels with every default filled in. */
678
+ specs(): WheelSpec[];
679
+ get body(): RigidBody3D;
680
+ override onEnterTree(): void;
681
+ override onExitTree(): void;
682
+ override fixedUpdate(dt: number): void;
683
+ /** `Wheel<i>` hangs on its spring and turns to steer; its `Hub` spins. */
684
+ private poseWheels;
685
+ /** Full lock, radians. */
686
+ get maxSteer(): number;
687
+ }
688
+ //#endregion
580
689
  //#region src/3d/physics/physics-3d.d.ts
581
690
  type Rapier = typeof RapierNs;
582
691
  interface Physics3DOptions {
@@ -616,6 +725,11 @@ declare class Physics3D {
616
725
  private readonly warnedNoCollider;
617
726
  private readonly entries;
618
727
  private readonly byColliderHandle;
728
+ /** Every Water3D in the tree, rescanned when the structure changes. */
729
+ private readonly waters;
730
+ private watersStale;
731
+ /** Raycast vehicles, one per Vehicle3D in the tree, on their chassis entries. */
732
+ private readonly vehicles;
619
733
  private readonly world;
620
734
  private readonly events;
621
735
  private readonly kcc;
@@ -639,6 +753,15 @@ declare class Physics3D {
639
753
  * down to the places where that pair disagrees with a rotated parent.
640
754
  */
641
755
  private poseFromTree;
756
+ /**
757
+ * @internal Called by Vehicle3D.fixedUpdate: push the throttle, steer and
758
+ * brake into Rapier's raycast vehicle on the chassis and let it apply the
759
+ * step's forces; read the wheels back. The controller is made on first
760
+ * use and remade if the chassis body was rebuilt.
761
+ */
762
+ _driveVehicle(node: Vehicle3D, dt: number): void;
763
+ /** @internal The Vehicle3D left the tree: drop its controller. */
764
+ _freeVehicle(node: Vehicle3D): void;
642
765
  /** @internal Called by CharacterBody3D.moveAndSlide (during tree fixedUpdate). */
643
766
  /**
644
767
  * May `candidate` carry `rider`? — see the 2D twin.
@@ -662,6 +785,28 @@ declare class Physics3D {
662
785
  private lastRoot;
663
786
  private readonly jointSet;
664
787
  private readonly joints;
788
+ /** The two bodies each live joint ties, so a departed joint can be unlinked. */
789
+ private readonly jointBodies;
790
+ /** What a hinge's limits/motor were last pushed as, so a write re-applies. */
791
+ private readonly hingeState;
792
+ /** Joints refused at creation (bodies that disagree about the axis) — said once. */
793
+ private readonly refusedJoints;
794
+ /** A rope's or spring's numbers as last made, so a write REMAKES the joint. */
795
+ private readonly tetherState;
796
+ private tetherKey;
797
+ /**
798
+ * Who is jointed to whom, and whether that pair's contacts are on.
799
+ *
800
+ * A joint's own bodies are not the world to the character controller: a
801
+ * body jointed to a character with its contacts off is not an obstacle to
802
+ * its shape-cast (the joint said so, for the contact solver; the cast asks
803
+ * its own question), and a body jointed to it is never its GROUND — a
804
+ * chain hanging from a crane hook was the hook's "moving platform", and the
805
+ * carry dragged the hook along by however far the hook had just moved.
806
+ */
807
+ private readonly jointed;
808
+ private linkJointed;
809
+ private unlinkJointed;
665
810
  /** One fixed body per solid scatter field, carrying a collider per row. */
666
811
  private readonly scatterBodies;
667
812
  private readonly bodySet;
@@ -714,15 +859,41 @@ declare class Physics3D {
714
859
  * meantime. syncBodies runs every step, so the body appears the moment the
715
860
  * download lands.
716
861
  */
862
+ /**
863
+ * Lift every buoyant dynamic body by the water it is in — before the solver
864
+ * step, so the impulses are part of this step's motion.
865
+ *
866
+ * `Water3D.heightAt` had answered "so gameplay can float a raft, bob a
867
+ * buoy" since the water shipped, and nothing floated anything but the
868
+ * swimmer: a RigidBody3D dropped into a lake fell through it to the bed,
869
+ * whatever it weighed. This is Archimedes on eight sample points: each
870
+ * octant of the collider is an eighth of its volume, lifted by the water's
871
+ * density times however much of it is under the surface AT THAT POINT, at
872
+ * that point — so a tilted hull's low corners push harder and it rights
873
+ * itself, and a wave under one end rocks it. The water's `drag` then bleeds
874
+ * velocity and spin in proportion to how much is under, which is what stops
875
+ * a pushed crate and lets a bob die out.
876
+ */
877
+ private applyBuoyancy;
878
+ /** The water whose footprint covers world (x, z), or null. */
879
+ private waterAt;
717
880
  private ensureEntry;
718
881
  /** Apply a world-space impulse to a dynamic body (character controllers). */
719
882
  applyImpulse(node: PhysicsBody3D, impulse: [number, number, number]): void;
720
883
  /** Mass the solver actually uses (collider-derived unless overridden). */
721
884
  massOf(node: PhysicsBody3D): number;
885
+ /** The solver's rotation of a body, as a quaternion `[x, y, z, w]`. */
886
+ rotationOf(node: PhysicsBody3D): [number, number, number, number];
722
887
  /** Current solver velocity (fresher than the node prop mid-step). */
723
888
  velocityOf(node: PhysicsBody3D): [number, number, number];
724
889
  /** Create newly-arrived joints; tear down departed ones. */
725
890
  private syncJoints;
891
+ /**
892
+ * Push a hinge's `limits` and motor to the solver — on creation, and again
893
+ * whenever either changes. Limits and `angle` are this body relative to the
894
+ * target; Rapier measures the other way round, hence the flip.
895
+ */
896
+ private applyHinge;
726
897
  /** Who is currently inside each sensor/body, maintained from the event drain. */
727
898
  private readonly overlaps;
728
899
  private trackOverlap;
@@ -835,6 +1006,19 @@ interface CreateGame3DOptions {
835
1006
  * to development, e.g. `debug: import.meta.env.VITE_INCANTO_DEBUG === '1'`.
836
1007
  */
837
1008
  debug?: boolean;
1009
+ /**
1010
+ * Start the frame loop as soon as the game is built (default `true`).
1011
+ *
1012
+ * `false` hands back a game that has not run a single frame: no
1013
+ * `engine.start()`, and no warm-up tick either. Call `game.engine.start()`
1014
+ * when everything that must exist BEFORE the first frame does — a
1015
+ * `NetworkManager`, whose join takes a moment during which every behaviour
1016
+ * would otherwise run the game OFFLINE. The first race built from the
1017
+ * tarball gridded itself to slot 0 in those frames and sent that pose as
1018
+ * its first replicated state, on top of the other player's car. The shader
1019
+ * warm-up still runs; the canvas stays blank until you start.
1020
+ */
1021
+ autoStart?: boolean;
838
1022
  /** @internal Test seam — replaces `document` for the debug overlay. */
839
1023
  _debugDoc?: {
840
1024
  createElement(tag: string): HTMLElement;
@@ -1465,6 +1649,16 @@ declare class BoneLookAt3D extends Node3D {
1465
1649
  private lookedUpFor;
1466
1650
  /** 0..1 engage blend — eases in/out so tracking never snaps. */
1467
1651
  private engage;
1652
+ /**
1653
+ * How far the head is COMMITTED to the target, 0..1.
1654
+ *
1655
+ * `attachedBone` was a test hook for "did it find the bone"; this is the one
1656
+ * for "is it watching" — the whole observable effect of the node, and it eased
1657
+ * in and out where nothing could read it. A harness asking whether an NPC
1658
+ * tracks the player, and lets go when the player walks behind it, has no other
1659
+ * question to ask.
1660
+ */
1661
+ get engaged(): number;
1468
1662
  /** Test hook. */
1469
1663
  get attachedBone(): Object3D | null;
1470
1664
  _onRender3D(_ctx: RenderContext3D): void;
@@ -1482,9 +1676,20 @@ declare class Camera3D extends Node3D {
1482
1676
  /** A frustum whose planes are the wrong way round draws nothing at all. */
1483
1677
  static validateJson(node: Node): void;
1484
1678
  fov: number;
1679
+ minHorizontalFov: number;
1485
1680
  near: number;
1486
1681
  far: number;
1487
1682
  current: boolean;
1683
+ /**
1684
+ * Make THIS the scene's camera and no other: the renderer takes the first
1685
+ * camera whose `current` is true in tree order, so setting your own without
1686
+ * clearing the gameplay camera's changed nothing visible. A handler a
1687
+ * connection can name — `triggerEnter → DoorCam.makeCurrent`.
1688
+ */
1689
+ makeCurrent(blendSeconds?: number | undefined): void;
1690
+ blendSeconds: number;
1691
+ /** @internal `makeCurrent(seconds)` — read once by the renderer's sync. */
1692
+ _blendRequest: number | undefined;
1488
1693
  lookAt: string;
1489
1694
  /**
1490
1695
  * Aim, every frame, whenever `lookAt` names something — see `aimAt`, which
@@ -1494,6 +1699,15 @@ declare class Camera3D extends Node3D {
1494
1699
  private warnedLookAt;
1495
1700
  override update(_dt: number): void;
1496
1701
  protected override _createObject3D(): Object3D;
1702
+ /**
1703
+ * The vertical fov to actually render with at this aspect.
1704
+ *
1705
+ * Plain `fov` unless `minHorizontalFov` asks for more width than the screen
1706
+ * carries, in which case it is widened until it does. Public because a game
1707
+ * that wants to know what it is really showing — a harness, a debug overlay —
1708
+ * could not otherwise ask, and the answer is not the authored number.
1709
+ */
1710
+ viewFov(aspect: number): number;
1497
1711
  override _syncObject3D(): void;
1498
1712
  }
1499
1713
  //#endregion
@@ -1502,7 +1716,7 @@ declare class Camera3D extends Node3D {
1502
1716
  * The states this controller enters, and therefore the only keys its
1503
1717
  * `animations` map may carry.
1504
1718
  */
1505
- declare const MOVEMENT_STATES: readonly ["idle", "walk", "run", "fastRun", "airborne"];
1719
+ declare const MOVEMENT_STATES: readonly ["idle", "walk", "run", "fastRun", "airborne", "swim", "climb", "crouch", "sneak", "dash"];
1506
1720
  /** One of {@link MOVEMENT_STATES}. */
1507
1721
  type MovementState = (typeof MOVEMENT_STATES)[number];
1508
1722
  /**
@@ -1517,6 +1731,7 @@ declare class CharacterController3D extends Node3D {
1517
1731
  static override readonly typeName: string;
1518
1732
  static override readonly signals: readonly string[];
1519
1733
  static override readonly props: PropSchema;
1734
+ enabled: boolean;
1520
1735
  view: RigView;
1521
1736
  camDistance: number;
1522
1737
  eyeHeight: number;
@@ -1543,6 +1758,60 @@ declare class CharacterController3D extends Node3D {
1543
1758
  fallGravity: number;
1544
1759
  floatHeight: number;
1545
1760
  /**
1761
+ * Swimming pace in water too deep to stand in (m/s; `sprint` still
1762
+ * multiplies). `0` turns swimming OFF and the body sinks to the bed, which
1763
+ * is what a drowning pit wants and what every scene got before this.
1764
+ */
1765
+ swimSpeed: number;
1766
+ /**
1767
+ * How far the body's ORIGIN rides below the surface while swimming (m), or
1768
+ * `null` (the default) to take it from the body: deep enough that the head
1769
+ * rides 15 cm clear and the rest is in the water, which is what treading
1770
+ * water looks like.
1771
+ *
1772
+ * The origin is the middle of the capsule, so a fixed number is only ever
1773
+ * right for one character size. The 0.35 m this used to default to left the
1774
+ * head of a standard 1.68 m rig 0.46 m clear of the surface — a person
1775
+ * floating like a boat, chest and shoulders in the air.
1776
+ */
1777
+ swimDepth: number | null;
1778
+ /** Held: swim down at `swimSpeed`. Missing from the input map is fine. */
1779
+ diveAction: string;
1780
+ /**
1781
+ * Ladder pace (m/s). A ladder is any `Area3D` in `ladderGroup`: push UP into
1782
+ * it and the body climbs, gravity off, held to the rungs; at the top it
1783
+ * pulls itself onto the ledge, down steps off at the bottom, `jump` lets go.
1784
+ * `0` = no ladders (every scene before this).
1785
+ */
1786
+ climbSpeed: number;
1787
+ ladderGroup: string;
1788
+ /**
1789
+ * The pull-up, for any jump. A jump that falls a hand short of a ledge is
1790
+ * the most common near-miss in a platformer, and every one handles it the
1791
+ * same way: catch the edge, pull up. On the way down from a jump, a ledge
1792
+ * ahead no higher than this above the capsule's bottom is taken — the same
1793
+ * climb that takes a swimmer out of the water and a climber off a ladder.
1794
+ * `0` (the default) = a jump near a wall feels exactly as it did.
1795
+ */
1796
+ mantleHeight: number;
1797
+ crouchHeight: number;
1798
+ crouchSpeedMultiplier: number;
1799
+ crouchAction: string;
1800
+ dashSpeed: number;
1801
+ dashSeconds: number;
1802
+ dashCooldown: number;
1803
+ dashAction: string;
1804
+ dashBackstep: boolean;
1805
+ private dashLeft;
1806
+ private dashCooldownLeft;
1807
+ private dashDir;
1808
+ /** Read-only: shorter right now — by the key, or by a ceiling. */
1809
+ crouching: boolean;
1810
+ /** The capsule height to stand back up to. */
1811
+ private standingHeight;
1812
+ /** The skin's authored y offset, restored on standing. */
1813
+ private skinRest;
1814
+ /**
1546
1815
  * How high a step the PLAYER walks up without jumping (0 = off).
1547
1816
  *
1548
1817
  * The hover spring holds the capsule a fixed distance above whatever is
@@ -1561,13 +1830,29 @@ declare class CharacterController3D extends Node3D {
1561
1830
  * 200 kg character does not move a seesaw a 70 kg crate tips.
1562
1831
  */
1563
1832
  stepHeight: number;
1833
+ /**
1834
+ * The steepest ground this character STANDS on, in degrees (0-90).
1835
+ *
1836
+ * At or under it the ground is ground: the character holds its place with no
1837
+ * slide at all, however long you leave it there. Over it the ground stops
1838
+ * counting as ground — no hover spring, no `grounded`, no jump reset — and
1839
+ * the body slips off the face, which is what a slope limit means in every
1840
+ * engine that has one.
1841
+ *
1842
+ * 55° is a person. A mountain goat is 80; something on wheels is 25. `90`
1843
+ * turns the limit off — which is what this controller did before it had one,
1844
+ * and why it slid down a 7° hillside.
1845
+ */
1846
+ slopeLimitDeg: number;
1564
1847
  mouseLook: boolean;
1565
1848
  zoomMin: number;
1566
1849
  zoomMax: number;
1567
1850
  pitchMin: number;
1568
1851
  pitchMax: number;
1852
+ pitchStart: number;
1569
1853
  /** Spring-arm: pull the orbit camera in front of walls/ground instead of clipping. */
1570
1854
  cameraCollision: boolean;
1855
+ camera: "drive" | "none";
1571
1856
  /**
1572
1857
  * Ride whatever you are standing on.
1573
1858
  *
@@ -1592,6 +1877,8 @@ declare class CharacterController3D extends Node3D {
1592
1877
  * would fling the player on every step onto one.
1593
1878
  */
1594
1879
  private groundCarry;
1880
+ private carriedRot;
1881
+ private carriedMe;
1595
1882
  turnSpeed: number;
1596
1883
  /** Camera smoothing rate (1-exp(-rate·dt)); side view uses 5. */
1597
1884
  camLerp: number;
@@ -1606,6 +1893,19 @@ declare class CharacterController3D extends Node3D {
1606
1893
  * character facing away from the camera until it first moves).
1607
1894
  */
1608
1895
  skinYawOffset: number;
1896
+ lockOnAction: string;
1897
+ lockGroup: string;
1898
+ lockRange: number;
1899
+ /** What the character is locked onto, or null. */
1900
+ lockTarget: Node | null;
1901
+ /** Lock onto `node`: the camera yaw and the skin follow it from now on. */
1902
+ lock(node: Node): void;
1903
+ /** Let go. */
1904
+ unlock(): void;
1905
+ /** Lock the nearest live, visible member of `lockGroup` within `lockRange` — or let go if locked. */
1906
+ toggleLock(): void;
1907
+ /** Player → target on the ground plane, unit; null when not locked (and lets go when the lock is stale). */
1908
+ private lockDirection;
1609
1909
  /**
1610
1910
  * Movement-state → animation asset map, applied to the `skinPath` model
1611
1911
  * directly — no CharacterAnimator behavior needed:
@@ -1628,9 +1928,33 @@ declare class CharacterController3D extends Node3D {
1628
1928
  /** Camera yaw/pitch in radians (mouse look mutates these). */
1629
1929
  yaw: number;
1630
1930
  pitch: number;
1631
- /** idle | walk | run | fastRun | airborne — drive animations from this. */
1931
+ /** idle | walk | run | fastRun | airborne | swim — drive animations from this. */
1632
1932
  state: MovementState;
1633
1933
  grounded: boolean;
1934
+ /** Slope of the ground under the character, degrees — 0 when there is none. */
1935
+ groundSlopeDeg: number;
1936
+ /** Its outward normal, or null. `[0, 1, 0]` is flat ground. */
1937
+ groundNormal: [number, number, number] | null;
1938
+ /** In water too deep to stand in — held at the surface by the swim spring. */
1939
+ swimming: boolean;
1940
+ /** The head is under the surface (diving, or a wave over it). */
1941
+ submerged: boolean;
1942
+ /** Pulling up onto a ledge out of the water (see the climb in fixedUpdate). */
1943
+ climbing: boolean;
1944
+ /** On a ladder — an `Area3D` in `ladderGroup` the body overlaps and pushed up into. */
1945
+ onLadder: boolean;
1946
+ private ladders;
1947
+ private ladderScanIn;
1948
+ private ladderLockout;
1949
+ /** Every Water3D in the tree, re-scanned every {@link WATER_SCAN_STEPS}. */
1950
+ private waters;
1951
+ private waterScanIn;
1952
+ /**
1953
+ * A hop out of the water is in progress: the spring may catch a ledge on
1954
+ * the way down (the mantle) — a jump from land never gets that, or the
1955
+ * spring would cushion every landing.
1956
+ */
1957
+ private swimHop;
1634
1958
  /** @internal Feel timers — see the jump block for what each one buys. */
1635
1959
  private coyoteLeft;
1636
1960
  private bufferLeft;
@@ -1640,6 +1964,12 @@ declare class CharacterController3D extends Node3D {
1640
1964
  private jumping;
1641
1965
  /** Seconds of air control still suppressed after a wall kick. */
1642
1966
  private wallLockout;
1967
+ /** What the anti-slide has learned it must keep paying to hold a slope. */
1968
+ private slideHold;
1969
+ /** Where the body stood last step, for the anti-slide's position error. */
1970
+ private slideFrom;
1971
+ /** Seconds since the player last asked this character to move. */
1972
+ private stillFor;
1643
1973
  /** The outward normal of the wall being pressed into this step, if any. */
1644
1974
  private wallNormal;
1645
1975
  private get body();
@@ -1648,6 +1978,25 @@ declare class CharacterController3D extends Node3D {
1648
1978
  /** Actions already reported missing, so a per-frame read says it once. */
1649
1979
  private readonly reportedActions;
1650
1980
  /**
1981
+ * The water over a world point — `[wave surface, still level]` — or null
1982
+ * when no `Water3D`'s footprint covers it. The tree is re-scanned once a
1983
+ * second, so a lake streamed in later is found; the wave height is the CPU
1984
+ * twin of what is drawn, so a swimmer rides the swell the way a raft does.
1985
+ */
1986
+ /** The ladder the body is overlapping, if any — an Area3D in `ladderGroup`. */
1987
+ private ladderUnder;
1988
+ /**
1989
+ * Swap the capsule's height and keep the feet where they are: the body
1990
+ * drops (or rises) by half the difference in the same step, the skin's
1991
+ * authored offset moves the other way so the model's feet stay on the
1992
+ * floor, and the adapter rebuilds the body at the new place next step.
1993
+ */
1994
+ private setHeight;
1995
+ private sceneGravity;
1996
+ /** `swimDepth`, or what it is derived to be for this body (see the prop). */
1997
+ private effectiveSwimDepth;
1998
+ private surfaceAt;
1999
+ /**
1651
2000
  * Read an input action this controller is allowed to be missing.
1652
2001
  *
1653
2002
  * Tolerated because a game may declare no sprint key and no jump; reported
@@ -1663,6 +2012,221 @@ declare class CharacterController3D extends Node3D {
1663
2012
  private findCamera;
1664
2013
  }
1665
2014
  //#endregion
2015
+ //#region src/3d/nodes/mesh-instance-3d.d.ts
2016
+ type MeshKind = "box" | "sphere" | "capsule" | "plane" | "cylinder" | "gem";
2017
+ interface MeshMaterialProps {
2018
+ color?: string;
2019
+ metalness?: number;
2020
+ roughness?: number;
2021
+ emissive?: string;
2022
+ emissiveIntensity?: number;
2023
+ wireframe?: boolean;
2024
+ /** Flat per-face shading — gives faceted meshes (gem!) crisp glints that
2025
+ * sparkle as the mesh turns, instead of a smoothed surface. */
2026
+ flatShading?: boolean;
2027
+ /** 0..1 surface opacity; below 1 turns on transparency (glassy gems, etc.). */
2028
+ opacity?: number;
2029
+ /** Depth-test against the scene (default true). Set false so the mesh ALWAYS
2030
+ * draws on top of geometry behind it — the standard trick for flat ground
2031
+ * decals (AoE telegraphs, selection rings) that must never z-fight with or be
2032
+ * occluded by bumpy terrain. Pair with a high `renderOrder`. */
2033
+ depthTest?: boolean;
2034
+ /** Write to the depth buffer (default true). Set false for additive/overlay
2035
+ * decals so they don't occlude effects drawn after them. */
2036
+ depthWrite?: boolean;
2037
+ /** 0..1 lacquer layer over the base — THE car-paint look: a glossy clear
2038
+ * coat with its own reflection on top of a metallic base. */
2039
+ clearcoat?: number;
2040
+ /** 0..1 roughness of the clearcoat layer alone (default 0 = mirror lacquer). */
2041
+ clearcoatRoughness?: number;
2042
+ /** Reflection strength from the scene's image-based lighting (the PMREM'd
2043
+ * sky / HDRI). 1 = scene default; 2+ makes glass visibly mirror the sky. */
2044
+ envMapIntensity?: number;
2045
+ /** Color texture URL (sampled sRGB, tinted by `color`). */
2046
+ map?: string;
2047
+ /** Tangent-space normal map URL (sampled linear). */
2048
+ normalMap?: string;
2049
+ /** Texture tiling [u, v] across the mesh's UVs (both maps; default [1,1]).
2050
+ * Box/plane UVs span 0–1 per face — scale repeat by the face's world size
2051
+ * for worldspace-ish texel density (e.g. a 6×2.5 m wall at one tile per
2052
+ * meter wants `repeat: [6, 2.5]`). */
2053
+ repeat?: [number, number];
2054
+ }
2055
+ /**
2056
+ * A primitive mesh. Intent-level props only:
2057
+ * - `mesh`: box | sphere | capsule | plane | cylinder | gem
2058
+ * - `size [x,y,z]`: box extents; sphere/gem radius = x; capsule radius = x,
2059
+ * height = y; plane = x·z ground plane (laid flat); cylinder radius = x, height = y
2060
+ * - `material`: `{color, metalness, roughness, wireframe, flatShading, opacity,
2061
+ * clearcoat, clearcoatRoughness, envMapIntensity, depthTest, depthWrite,
2062
+ * emissive, emissiveIntensity, map, normalMap, repeat}` —
2063
+ * `map`/`normalMap` are texture URLs loaded lazily (headless-safe: geometry and
2064
+ * physics never wait on them), tiled by `repeat: [u, v]`. `gem` + `flatShading:
2065
+ * true` + a low `roughness` reads as a sparkling faceted jewel (spin it for the
2066
+ * sparkle). `depthTest: false` makes a flat `plane`/`cylinder` an always-on-top
2067
+ * ground decal (AoE telegraph, selection ring) — no z-fighting with terrain.
2068
+ */
2069
+ declare class MeshInstance3D extends Node3D {
2070
+ static override readonly typeName: string;
2071
+ static override readonly props: PropSchema;
2072
+ mesh: string;
2073
+ size: number[];
2074
+ material: MeshMaterialProps;
2075
+ castShadow: boolean;
2076
+ receiveShadow: boolean;
2077
+ /** Loader hook: malformed materials fail at LOAD time, not at render. */
2078
+ static validateJson(node: Node): void;
2079
+ private geometryKey;
2080
+ private materialKey;
2081
+ /** True while this node is using a cache entry it must not dispose. */
2082
+ private sharedGeometry;
2083
+ private sharedMaterial;
2084
+ private textureKey;
2085
+ private loadedTextures;
2086
+ protected override _createObject3D(): Object3D;
2087
+ override _syncObject3D(alpha?: number, assets?: AssetStore3D | null): void;
2088
+ /** Lazy texture dressing — same headless guard as Tree3D bark: without a
2089
+ * DOM there is no Image decode, so geometry/colors apply and the maps are
2090
+ * skipped. Re-loads only when a texture-shaping key changes. */
2091
+ private syncTextures;
2092
+ /**
2093
+ * A private CLONE: `repeat` is per-node here (a 1x1 crate and a 8x8 floor
2094
+ * share one tile image), and that is the one field a shared master cannot
2095
+ * carry. The clone shares `texture.source`, so the fetch and the decode are
2096
+ * still done once per url for the whole scene.
2097
+ */
2098
+ private loadTexture;
2099
+ /**
2100
+ * Give the GPU back everything this node OWNS.
2101
+ *
2102
+ * It used to dispose textures and nothing else, so every freed mesh leaked its
2103
+ * geometry and its material — a wave spawner that kills and respawns fifty
2104
+ * enemies a minute leaks fifty of each, forever, and nothing in the game says
2105
+ * so. Both are built here (`_createObject3D` news them, `_syncObject3D`
2106
+ * rebuilds the geometry on a shape change), so both are ours to release.
2107
+ *
2108
+ * The textures are NOT: since the shared texture cache they belong to the
2109
+ * asset store, except the per-node clones this node asked for, which are what
2110
+ * `loadedTextures` holds.
2111
+ */
2112
+ override free(): void;
2113
+ }
2114
+ //#endregion
2115
+ //#region src/3d/nodes/instanced-mesh-3d.d.ts
2116
+ /**
2117
+ * Hundreds of copies of one mesh in ONE draw call — scattered rocks, fence
2118
+ * posts, pillars, crates, gravestones. Same `mesh`/`size`/`material` surface
2119
+ * as MeshInstance3D; `transforms` places the copies:
2120
+ *
2121
+ * { "name": "Rocks", "type": "InstancedMesh3D",
2122
+ * "props": { "mesh": "gem", "size": [0.7, 0.5, 0.7],
2123
+ * "material": { "color": "#8a8f98", "roughness": 0.9, "flatShading": true },
2124
+ * "transforms": [[12, 0.2, -8, 40, 1.3], [-6, 0.1, 14, 210, 0.8]] } }
2125
+ *
2126
+ * Each row is `[x, y, z, yawDeg?, scale?]` (yaw 0 and scale 1 when omitted) —
2127
+ * dense enough to generate hundreds inline. REPLACE the array to update
2128
+ * (mutations are not watched).
2129
+ */
2130
+ declare class InstancedMesh3D extends Node3D {
2131
+ static override readonly typeName: string;
2132
+ static override readonly props: PropSchema;
2133
+ mesh: string;
2134
+ size: number[];
2135
+ material: MeshMaterialProps;
2136
+ castShadow: boolean;
2137
+ receiveShadow: boolean;
2138
+ /**
2139
+ * Make the scatter SOLID: `{}` gives every row a collider shaped like the
2140
+ * mesh itself — a faceted `gem` stone collides as that stone's own hull, not
2141
+ * as a ball tucked inside it — sized from `size` × the row's scale.
2142
+ *
2143
+ * A field of fifty boulders a player walks straight through is the tell that
2144
+ * a world is a picture. One fixed body carries all the colliders, so the cost
2145
+ * is a handful of shapes and no per-frame work — these never move.
2146
+ *
2147
+ * `{ shape: 'sphere' | 'box' }` (with `radius`/`size`) opts out into a
2148
+ * cheaper approximation when a rough blocker is genuinely all you want.
2149
+ */
2150
+ collider: {
2151
+ shape?: string;
2152
+ radius?: number;
2153
+ size?: number[];
2154
+ } | null;
2155
+ private _transforms;
2156
+ private transformsDirty;
2157
+ get transforms(): number[][];
2158
+ set transforms(value: number[][]);
2159
+ private instanced;
2160
+ private builtKey;
2161
+ static validateJson(node: Node): void;
2162
+ /** Test hook: the live instance count. */
2163
+ get instanceCount(): number;
2164
+ override _syncObject3D(alpha?: number): void;
2165
+ override free(): void;
2166
+ }
2167
+ //#endregion
2168
+ //#region src/3d/nodes/flock-3d.d.ts
2169
+ /** One bird, in WORLD metres and m/s — what `boids()` answers. */
2170
+ interface Boid {
2171
+ x: number;
2172
+ y: number;
2173
+ z: number;
2174
+ vx: number;
2175
+ vy: number;
2176
+ vz: number;
2177
+ }
2178
+ /**
2179
+ * A flock — gulls over a harbor, fish under a boat, bats out of a cave — as
2180
+ * ONE instanced mesh that flies its own instances.
2181
+ *
2182
+ * ```json
2183
+ * { "name": "Gulls", "type": "Flock3D",
2184
+ * "props": { "position": [0, 14, 0], "count": 40, "radius": 30, "height": 6,
2185
+ * "mesh": "box", "size": [0.5, 0.08, 0.3], "follow": "/root/Boat" } }
2186
+ * ```
2187
+ *
2188
+ * Boids (Reynolds 1987): each instance steers away from neighbours closer
2189
+ * than `separation`, toward the average heading of those within `sight`
2190
+ * (`alignment`) and toward their centre (`cohesion`), turns back inside an
2191
+ * ellipsoid `radius` wide and `height` tall around this node — or around the
2192
+ * node at `follow`, offset by this node's position — and never turns faster
2193
+ * than `turn`. Every instance flies at `speed`, +z forward, so a mesh that is
2194
+ * longer along z reads as a body with a beak.
2195
+ *
2196
+ * No physics bodies, no behaviour per bird: forty birds cost one draw call
2197
+ * and forty-squared distance checks a frame. Seeded from `engine.rng`, so a
2198
+ * seeded run flies the same flight. `boids()` answers headless.
2199
+ */
2200
+ declare class Flock3D extends InstancedMesh3D {
2201
+ static override readonly typeName: string;
2202
+ static override readonly props: PropSchema;
2203
+ count: number;
2204
+ radius: number;
2205
+ height: number;
2206
+ speed: number;
2207
+ turn: number;
2208
+ separation: number;
2209
+ sight: number;
2210
+ alignment: number;
2211
+ cohesion: number;
2212
+ follow: string;
2213
+ private px;
2214
+ private py;
2215
+ private pz;
2216
+ private vx;
2217
+ private vy;
2218
+ private vz;
2219
+ private rows;
2220
+ static override validateJson(node: Node): void;
2221
+ /** Every bird right now, in world metres and m/s. */
2222
+ boids(): Boid[];
2223
+ override update(dt: number): void;
2224
+ /** The roam centre in this node's local frame: the followed node's offset, or the origin. */
2225
+ private centre;
2226
+ /** Scatter `n` birds through the bound with random headings — engine-seeded. */
2227
+ private seed;
2228
+ }
2229
+ //#endregion
1666
2230
  //#region src/3d/vegetation/flower-geometry.d.ts
1667
2231
  /**
1668
2232
  * Procedural flower PLANTS for Flowers3D — real plants like the reference
@@ -1999,161 +2563,8 @@ declare class Foliage3D extends Node3D implements SunConsumer3D {
1999
2563
  override free(): void;
2000
2564
  }
2001
2565
  //#endregion
2002
- //#region src/3d/nodes/mesh-instance-3d.d.ts
2003
- type MeshKind = "box" | "sphere" | "capsule" | "plane" | "cylinder" | "gem";
2004
- interface MeshMaterialProps {
2005
- color?: string;
2006
- metalness?: number;
2007
- roughness?: number;
2008
- emissive?: string;
2009
- emissiveIntensity?: number;
2010
- wireframe?: boolean;
2011
- /** Flat per-face shading — gives faceted meshes (gem!) crisp glints that
2012
- * sparkle as the mesh turns, instead of a smoothed surface. */
2013
- flatShading?: boolean;
2014
- /** 0..1 surface opacity; below 1 turns on transparency (glassy gems, etc.). */
2015
- opacity?: number;
2016
- /** Depth-test against the scene (default true). Set false so the mesh ALWAYS
2017
- * draws on top of geometry behind it — the standard trick for flat ground
2018
- * decals (AoE telegraphs, selection rings) that must never z-fight with or be
2019
- * occluded by bumpy terrain. Pair with a high `renderOrder`. */
2020
- depthTest?: boolean;
2021
- /** Write to the depth buffer (default true). Set false for additive/overlay
2022
- * decals so they don't occlude effects drawn after them. */
2023
- depthWrite?: boolean;
2024
- /** 0..1 lacquer layer over the base — THE car-paint look: a glossy clear
2025
- * coat with its own reflection on top of a metallic base. */
2026
- clearcoat?: number;
2027
- /** 0..1 roughness of the clearcoat layer alone (default 0 = mirror lacquer). */
2028
- clearcoatRoughness?: number;
2029
- /** Reflection strength from the scene's image-based lighting (the PMREM'd
2030
- * sky / HDRI). 1 = scene default; 2+ makes glass visibly mirror the sky. */
2031
- envMapIntensity?: number;
2032
- /** Color texture URL (sampled sRGB, tinted by `color`). */
2033
- map?: string;
2034
- /** Tangent-space normal map URL (sampled linear). */
2035
- normalMap?: string;
2036
- /** Texture tiling [u, v] across the mesh's UVs (both maps; default [1,1]).
2037
- * Box/plane UVs span 0–1 per face — scale repeat by the face's world size
2038
- * for worldspace-ish texel density (e.g. a 6×2.5 m wall at one tile per
2039
- * meter wants `repeat: [6, 2.5]`). */
2040
- repeat?: [number, number];
2041
- }
2042
- /**
2043
- * A primitive mesh. Intent-level props only:
2044
- * - `mesh`: box | sphere | capsule | plane | cylinder | gem
2045
- * - `size [x,y,z]`: box extents; sphere/gem radius = x; capsule radius = x,
2046
- * height = y; plane = x·z ground plane (laid flat); cylinder radius = x, height = y
2047
- * - `material`: `{color, metalness, roughness, wireframe, flatShading, opacity,
2048
- * clearcoat, clearcoatRoughness, envMapIntensity, depthTest, depthWrite,
2049
- * emissive, emissiveIntensity, map, normalMap, repeat}` —
2050
- * `map`/`normalMap` are texture URLs loaded lazily (headless-safe: geometry and
2051
- * physics never wait on them), tiled by `repeat: [u, v]`. `gem` + `flatShading:
2052
- * true` + a low `roughness` reads as a sparkling faceted jewel (spin it for the
2053
- * sparkle). `depthTest: false` makes a flat `plane`/`cylinder` an always-on-top
2054
- * ground decal (AoE telegraph, selection ring) — no z-fighting with terrain.
2055
- */
2056
- declare class MeshInstance3D extends Node3D {
2057
- static override readonly typeName: string;
2058
- static override readonly props: PropSchema;
2059
- mesh: string;
2060
- size: number[];
2061
- material: MeshMaterialProps;
2062
- castShadow: boolean;
2063
- receiveShadow: boolean;
2064
- /** Loader hook: malformed materials fail at LOAD time, not at render. */
2065
- static validateJson(node: Node): void;
2066
- private geometryKey;
2067
- private materialKey;
2068
- /** True while this node is using a cache entry it must not dispose. */
2069
- private sharedGeometry;
2070
- private sharedMaterial;
2071
- private textureKey;
2072
- private loadedTextures;
2073
- protected override _createObject3D(): Object3D;
2074
- override _syncObject3D(alpha?: number, assets?: AssetStore3D | null): void;
2075
- /** Lazy texture dressing — same headless guard as Tree3D bark: without a
2076
- * DOM there is no Image decode, so geometry/colors apply and the maps are
2077
- * skipped. Re-loads only when a texture-shaping key changes. */
2078
- private syncTextures;
2079
- /**
2080
- * A private CLONE: `repeat` is per-node here (a 1x1 crate and a 8x8 floor
2081
- * share one tile image), and that is the one field a shared master cannot
2082
- * carry. The clone shares `texture.source`, so the fetch and the decode are
2083
- * still done once per url for the whole scene.
2084
- */
2085
- private loadTexture;
2086
- /**
2087
- * Give the GPU back everything this node OWNS.
2088
- *
2089
- * It used to dispose textures and nothing else, so every freed mesh leaked its
2090
- * geometry and its material — a wave spawner that kills and respawns fifty
2091
- * enemies a minute leaks fifty of each, forever, and nothing in the game says
2092
- * so. Both are built here (`_createObject3D` news them, `_syncObject3D`
2093
- * rebuilds the geometry on a shape change), so both are ours to release.
2094
- *
2095
- * The textures are NOT: since the shared texture cache they belong to the
2096
- * asset store, except the per-node clones this node asked for, which are what
2097
- * `loadedTextures` holds.
2098
- */
2099
- override free(): void;
2100
- }
2101
- //#endregion
2102
- //#region src/3d/nodes/instanced-mesh-3d.d.ts
2103
- /**
2104
- * Hundreds of copies of one mesh in ONE draw call — scattered rocks, fence
2105
- * posts, pillars, crates, gravestones. Same `mesh`/`size`/`material` surface
2106
- * as MeshInstance3D; `transforms` places the copies:
2107
- *
2108
- * { "name": "Rocks", "type": "InstancedMesh3D",
2109
- * "props": { "mesh": "gem", "size": [0.7, 0.5, 0.7],
2110
- * "material": { "color": "#8a8f98", "roughness": 0.9, "flatShading": true },
2111
- * "transforms": [[12, 0.2, -8, 40, 1.3], [-6, 0.1, 14, 210, 0.8]] } }
2112
- *
2113
- * Each row is `[x, y, z, yawDeg?, scale?]` (yaw 0 and scale 1 when omitted) —
2114
- * dense enough to generate hundreds inline. REPLACE the array to update
2115
- * (mutations are not watched).
2116
- */
2117
- declare class InstancedMesh3D extends Node3D {
2118
- static override readonly typeName: string;
2119
- static override readonly props: PropSchema;
2120
- mesh: string;
2121
- size: number[];
2122
- material: MeshMaterialProps;
2123
- castShadow: boolean;
2124
- receiveShadow: boolean;
2125
- /**
2126
- * Make the scatter SOLID: `{}` gives every row a collider shaped like the
2127
- * mesh itself — a faceted `gem` stone collides as that stone's own hull, not
2128
- * as a ball tucked inside it — sized from `size` × the row's scale.
2129
- *
2130
- * A field of fifty boulders a player walks straight through is the tell that
2131
- * a world is a picture. One fixed body carries all the colliders, so the cost
2132
- * is a handful of shapes and no per-frame work — these never move.
2133
- *
2134
- * `{ shape: 'sphere' | 'box' }` (with `radius`/`size`) opts out into a
2135
- * cheaper approximation when a rough blocker is genuinely all you want.
2136
- */
2137
- collider: {
2138
- shape?: string;
2139
- radius?: number;
2140
- size?: number[];
2141
- } | null;
2142
- private _transforms;
2143
- private transformsDirty;
2144
- get transforms(): number[][];
2145
- set transforms(value: number[][]);
2146
- private instanced;
2147
- private builtKey;
2148
- static validateJson(node: Node): void;
2149
- /** Test hook: the live instance count. */
2150
- get instanceCount(): number;
2151
- override _syncObject3D(alpha?: number): void;
2152
- override free(): void;
2153
- }
2154
- //#endregion
2155
2566
  //#region src/3d/nodes/joint-3d.d.ts
2156
- type JointType3D = "fixed" | "spherical" | "rope" | "spring";
2567
+ type JointType3D = "fixed" | "spherical" | "revolute" | "prismatic" | "rope" | "spring";
2157
2568
  /**
2158
2569
  * A physics joint linking its PARENT body to `target` (a node path to the
2159
2570
  * other body). Godot-style placement: the joint lives as a child of body A.
@@ -2162,14 +2573,42 @@ type JointType3D = "fixed" | "spherical" | "rope" | "spring";
2162
2573
  * "props": { "type": "spherical", "target": "%Anchor", "anchor": [0, -20] } }
2163
2574
  *
2164
2575
  * Types: `fixed` welds the bodies rigidly · `spherical` is a ball joint at the
2165
- * anchors · `rope` caps the anchor distance at `length` (m; 0 = measured at
2166
- * creation) · `spring` pulls toward `length` with `stiffness`/`damping`.
2167
- * Anchors are LOCAL meter offsets on each body.
2576
+ * anchors · `revolute` is a HINGE about `axis` (a door, a wheel, a lever) with
2577
+ * optional `limits` (degrees) and a `motorSpeed` (degrees a second) ·
2578
+ * `prismatic` is a SLIDE along `axis` (a portcullis, an elevator, a piston)
2579
+ * with `limits` in metres, a `motorSpeed` in metres a second and `travel`
2580
+ * read back · `rope`
2581
+ * caps the anchor distance at `length` (m; 0 = measured at creation) ·
2582
+ * `spring` pulls toward `length` with `stiffness`/`damping`. Anchors are LOCAL
2583
+ * meter offsets on each body; `angle` reads the hinge back.
2168
2584
  */
2169
2585
  declare class Joint3D extends Node3D {
2170
2586
  static override readonly typeName: string;
2171
2587
  static override readonly props: PropSchema;
2172
2588
  type: JointType3D;
2589
+ /** Hinge axis of a `revolute`, or the line of a `prismatic` (local to this body). */
2590
+ axis: number[];
2591
+ /**
2592
+ * `[min, max]` in degrees for a `revolute` swing; `[]` = no stops. Measured
2593
+ * as `angle` is: this body turned about `axis`, relative to the target.
2594
+ */
2595
+ limits: number[];
2596
+ /** Degrees a second the motor drives the hinge at; `0` = no motor (free). */
2597
+ motorSpeed: number;
2598
+ /** The motor's gain — how quickly it reaches and holds `motorSpeed`. */
2599
+ motorStrength: number;
2600
+ /**
2601
+ * The hinge's current angle in degrees — this body's rotation about `axis`
2602
+ * relative to the target, written by physics every step. `0` when the joint
2603
+ * is not a `revolute` or is not simulated yet.
2604
+ */
2605
+ angle: number;
2606
+ /**
2607
+ * A `prismatic` slide's current travel in metres — this body's anchor along
2608
+ * `axis` relative to the target's, written by physics every step. `0` where
2609
+ * the two anchors coincide, which is where the scene loaded it.
2610
+ */
2611
+ travel: number;
2173
2612
  target: string;
2174
2613
  anchor: number[];
2175
2614
  targetAnchor: number[];
@@ -2558,6 +2997,10 @@ declare class Particles3D extends Node3D {
2558
2997
  maxParticles: number;
2559
2998
  static validateJson(node: Node): void;
2560
2999
  private sim;
3000
+ emitBox: number[];
3001
+ drift: number[];
3002
+ /** @internal Forget the sim so the next update rebuilds it from the props (a preset or kind that changed). */
3003
+ _rebuildSim(): void;
2561
3004
  private bursted;
2562
3005
  private finishedEmitted;
2563
3006
  /** @internal */
@@ -2597,6 +3040,17 @@ declare class Particles3D extends Node3D {
2597
3040
  * list changes (zero per-frame allocation); null when empty so the caller
2598
3041
  * uses the colorStart→colorEnd ramp. */
2599
3042
  private refreshPalette;
3043
+ /**
3044
+ * The stamp every particle is drawn with — a soft round dot.
3045
+ *
3046
+ * A subclass overrides it to look like the thing it IS: `Weather3D`'s rain is
3047
+ * a vertical STREAK, because 2,600 round dots read as a dirty lens and not as
3048
+ * a downpour (measured in a browser at intensity 1). The material is rebuilt
3049
+ * when this changes, so a node may return a different stamp over its life.
3050
+ */
3051
+ protected sprite(): DataTexture;
3052
+ /** The stamp the current material was built with, so a change can rebuild it. */
3053
+ private builtSprite;
2600
3054
  private syncParticles;
2601
3055
  /**
2602
3056
  * Hand the GPU back.
@@ -2609,6 +3063,65 @@ declare class Particles3D extends Node3D {
2609
3063
  override free(): void;
2610
3064
  }
2611
3065
  //#endregion
3066
+ //#region src/3d/nodes/ragdoll-3d.d.ts
3067
+ /** A bone name → world position, metres — what `activate` takes headless. */
3068
+ type RagdollPose = Record<string, [number, number, number]>;
3069
+ /** A built limb: its name in {@link RIG} and the RigidBody3D that is it. */
3070
+ interface Limb {
3071
+ name: string;
3072
+ body: Node;
3073
+ }
3074
+ /**
3075
+ * A ragdoll for a skinned model — the death that falls.
3076
+ *
3077
+ * ```json
3078
+ * { "name": "Body", "type": "CharacterBody3D", "children": [
3079
+ * { "name": "Skin", "type": "ModelInstance3D", "props": { "model": "$base" } },
3080
+ * { "name": "Ragdoll", "type": "Ragdoll3D", "props": { "target": "../Skin", "lifetime": 8 } }
3081
+ * ] }
3082
+ * ```
3083
+ *
3084
+ * Idle it is nothing. `activate()` reads the skeleton at `target` (Mixamo
3085
+ * names, forgiving lookup), builds a capsule `RigidBody3D` per limb where the
3086
+ * bones are, joins each to its parent with a spherical `Joint3D`, gives them
3087
+ * the velocity of the body they fell out of, and from then on writes the
3088
+ * bones' world matrices from the limbs every frame — the model crumples,
3089
+ * slides, hangs off the ledge. Wire it from `Health.died`; turn the body's
3090
+ * collider and its controller off beside it. `reset()` puts the skeleton
3091
+ * back under the animation; `lifetime` does that on its own.
3092
+ *
3093
+ * Headless there is no skeleton: `activate(pose)` takes bone positions, so a
3094
+ * test can ask the same questions the game will.
3095
+ */
3096
+ declare class Ragdoll3D extends Node3D {
3097
+ static override readonly typeName: string;
3098
+ static override readonly props: PropSchema;
3099
+ static override readonly signals: readonly string[];
3100
+ target: string;
3101
+ mass: number;
3102
+ damping: number;
3103
+ lifetime: number;
3104
+ collide: boolean;
3105
+ /** Read-only: built and driving the bones. */
3106
+ active: boolean;
3107
+ private driven;
3108
+ private limbNodes;
3109
+ private left;
3110
+ /** Every limb built right now, empty while idle. */
3111
+ limbs(): Limb[];
3112
+ /**
3113
+ * Build the ragdoll where the skeleton is (or where `pose` says the bones
3114
+ * are, headless). Returns false when there is nothing to build from.
3115
+ */
3116
+ activate(pose?: RagdollPose | undefined): boolean;
3117
+ /** Free the limbs and hand the skeleton back to the animation. */
3118
+ reset(): void;
3119
+ override update(dt: number): void;
3120
+ override free(): void;
3121
+ /** The velocity of the body this ragdoll falls out of — the first body up the tree. */
3122
+ private hostVelocity;
3123
+ }
3124
+ //#endregion
2612
3125
  //#region src/3d/terrain/heightmap.d.ts
2613
3126
  /**
2614
3127
  * A smooth radial depression carved into the terrain — a lake/pond bowl. x/z
@@ -3023,15 +3536,27 @@ declare class River3D extends Node3D implements SunConsumer3D {
3023
3536
  private rockScanAt;
3024
3537
  private world;
3025
3538
  private readonly _bodies;
3539
+ private cubeTarget;
3540
+ private cubeCamera;
3541
+ private lastSkyAt;
3542
+ private rippleFft;
3543
+ private rippleSpectrum;
3544
+ private fftUnsupported;
3026
3545
  /** Loader hook: a malformed river fails at LOAD, not as an invisible ribbon. */
3027
3546
  static validateJson(node: Node): void;
3028
3547
  protected override _createObject3D(): Object3D;
3029
3548
  override onReady(): void;
3030
3549
  override onExitTree(): void;
3550
+ /** Said once when the node has no course to be a river along. */
3551
+ private warnedNoPath;
3031
3552
  override update(dt: number): void;
3032
3553
  override _syncObject3D(): void;
3033
3554
  /** Rocks are picked per FRAME (nearest to the eye), scanned on a timer. */
3034
3555
  _onRender3D(ctx: RenderContext3D): void;
3556
+ /** The sky cube: the world beyond the banks, from above the channel's middle. */
3557
+ private syncSky;
3558
+ /** The ripple FFT, advected by the fragment in the ribbon's own frame. */
3559
+ private syncRipple;
3035
3560
  /**
3036
3561
  * @internal Water surface height over a world point, or null where this
3037
3562
  * river does not run — the question scattered vegetation asks before it
@@ -3263,7 +3788,10 @@ declare class Terrain3D extends Node3D {
3263
3788
  * `heightAt`, the drape and the mesh all pick it up on their next rebuild.
3264
3789
  */
3265
3790
  _setExternalChannel(owner: Node, specs: ChannelSpec[] | null): void;
3266
- /** Authored trenches plus the ones other nodes cut. */
3791
+ /**
3792
+ * Authored trenches plus the ones other nodes cut — every course flattened to
3793
+ * `[x, z]`, whichever spelling it was written in (see `course`).
3794
+ */
3267
3795
  private allChannels;
3268
3796
  /** @internal The pure heightfield (lazily built — physics pulls this). */
3269
3797
  _heightmap(): Heightmap;
@@ -3407,6 +3935,19 @@ declare class Tree3D extends Node3D {
3407
3935
  * it (keeps retrying until a terrain appears), false disables draping.
3408
3936
  */
3409
3937
  drape: boolean | null;
3938
+ /**
3939
+ * Keep trunks out of standing and running water (default true) — a valley a
3940
+ * rill runs through has a grove on its banks, not trees in the current.
3941
+ *
3942
+ * `Foliage3D` and `Flowers3D` have had this since they were written, with the
3943
+ * same default and the same reason; the TREE — the tallest thing in a
3944
+ * landscape and the one that most obviously does not grow midstream — did
3945
+ * not. Measured on a river-valley game: two of twenty-two trunks stood in the
3946
+ * rill.
3947
+ *
3948
+ * `false` is the mangrove, the cypress swamp and the flooded forest.
3949
+ */
3950
+ avoidWater: boolean;
3410
3951
  /** Node path to the Terrain3D to drape onto ('' = auto-find the first one). */
3411
3952
  terrain: string;
3412
3953
  /** Resolved drape target + the grove's world position, set per rebuild. */
@@ -3465,6 +4006,18 @@ declare class Tree3D extends Node3D {
3465
4006
  private buildBarkMaterial;
3466
4007
  private loadBarkTexture;
3467
4008
  /** Per-instance placement + jitter — ONE rng draw order for both paths. */
4009
+ /**
4010
+ * Where every trunk of this grove stands, in WORLD coordinates.
4011
+ *
4012
+ * `drape` is the prop that puts a grove on the ground it grows from, and
4013
+ * nothing could ask whether it had. `UiMinimap.markers()` and
4014
+ * `HudLayer.focusables()` are the same accessor for the same reason: a
4015
+ * feature whose effect no harness can read is a feature nobody checks.
4016
+ *
4017
+ * Recomputed from the seed rather than read back off the InstancedMesh, so it
4018
+ * answers headless too — where there is no mesh at all.
4019
+ */
4020
+ trunks(): Array<[number, number, number]>;
3468
4021
  private scatter;
3469
4022
  private disposeMeshes;
3470
4023
  override free(): void;
@@ -3613,6 +4166,14 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
3613
4166
  * as a calm sea; 0.08 = the noisier ported-source look. */
3614
4167
  waveHeight: number;
3615
4168
  waveSpeed: number;
4169
+ /**
4170
+ * How hard this water holds a floating body back, per second at full
4171
+ * submersion — a pushed crate loses `drag` of its velocity each second (and
4172
+ * its spin likewise), scaled by how much of it is under. `0` is ice; `1.5`
4173
+ * lets a boat coast a few lengths; `6` is syrup. Only bodies with `buoyancy`
4174
+ * feel it (see `RigidBody3D.buoyancy`).
4175
+ */
4176
+ drag: number;
3616
4177
  /** 'fancy' shader water (default) | 'simple' cheap CPU sine material. */
3617
4178
  quality: string;
3618
4179
  /** Fancy ramp overrides: `{trough?, surface?, peak?}` hex strings. */
@@ -3628,6 +4189,16 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
3628
4189
  */
3629
4190
  mirror: boolean;
3630
4191
  /**
4192
+ * Screen-space reflections (fancy only, default on): the scene ABOVE the
4193
+ * waterline is rendered once at half resolution and every water pixel
4194
+ * marches its reflected ray through that image — so a rock, a pier, a
4195
+ * wader or a boat is mirrored on the water WITHOUT the planar `mirror`
4196
+ * pass. What the screen cannot see (behind the camera, off its edges)
4197
+ * falls back to the sky cube, which is why a mirror-calm lake still wants
4198
+ * `mirror: true`; with `mirror` on this is skipped as redundant.
4199
+ */
4200
+ ssr: boolean;
4201
+ /**
3631
4202
  * Milliseconds between planar-mirror re-renders. A reflection seen through a
3632
4203
  * rippling surface does not need 60 Hz — 33 ms halves the pass's cost and
3633
4204
  * nobody can tell. 0 re-renders every frame.
@@ -3688,6 +4259,30 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
3688
4259
  /** Primary swell wavelength in meters (the two crossing trains derive). */
3689
4260
  swellWavelength: number;
3690
4261
  /**
4262
+ * The bottom under this water: a node path to a Terrain3D (fancy only). With
4263
+ * it the swell KNOWS the beach — waves slow and rise as the bottom comes up
4264
+ * (Green's law), their crests peak, and at 0.78× the depth they BREAK into
4265
+ * foam, with spray sprites bursting where the foam fronts pass. Sampled
4266
+ * once into a 256² depth grid over the terrain's footprint; beyond it the
4267
+ * water is assumed deep. Pair with `swell` (the waves that shoal) and
4268
+ * `shoreWaves` (the foam fronts the spray rides).
4269
+ */
4270
+ bed: string;
4271
+ /**
4272
+ * Wind speed in m/s — THE realistic-sea dial (fancy only; 0 = off). Above 0
4273
+ * the surface stops being a noise field and becomes a JONSWAP spectrum:
4274
+ * thousands of waves at every wavelength and heading, each moving at the
4275
+ * speed deep water gives it, inverse-FFT'd on the GPU every frame into a
4276
+ * tiling patch with real crest shapes, choppy horizontal drag and whitecaps
4277
+ * born where the surface folds. The wind blows toward `swellDirectionDeg`.
4278
+ * 4 = a light breeze (ripples, 20 cm), 7 = a fresh breeze (1 m waves),
4279
+ * 12 = rough. The CPU twin (buoyancy, waders) sums the same spectrum.
4280
+ */
4281
+ wind: number;
4282
+ /** Horizontal drag of the FFT sea toward its crests, 0..~1.5 — narrow
4283
+ * crests, broad troughs; past ~1.2 the surface folds and foams everywhere. */
4284
+ choppiness: number;
4285
+ /**
3691
4286
  * GERSTNER pinch on the swell: 0 keeps the legacy height-only trains, 1 is
3692
4287
  * maximum trochoid (crests narrow to a peak, troughs go broad). It also
3693
4288
  * decides where whitecaps break — the surface's Jacobian collapses on a
@@ -3750,6 +4345,26 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
3750
4345
  /** Per-frame scene pre-pass: depth (absorption/foam) + color grab (refraction). */
3751
4346
  private scenePassTarget;
3752
4347
  private mirrorTarget;
4348
+ /** Half-res render of the scene ABOVE the waterline — what SSR reflects. */
4349
+ private aboveTarget;
4350
+ private readonly ssrViewProj;
4351
+ /** The FFT sea (wind > 0): the GPU passes, and the CPU spectrum they share. */
4352
+ private seaFft;
4353
+ private seaFftUnsupported;
4354
+ private spectrum;
4355
+ private spectrumKey;
4356
+ private uploadedSpectrumKey;
4357
+ private rippleFft;
4358
+ private rippleSpectrum;
4359
+ private rippleKey;
4360
+ private uploadedRippleKey;
4361
+ /** The bed depth grid (see `bed`), its footprint, and the spray that rides it. */
4362
+ private bedTexture;
4363
+ private bedRect;
4364
+ private bedKey;
4365
+ private spray;
4366
+ /** Vertex spacing of the fancy grid — the mip the vertex shader reads at. */
4367
+ private fancySegments;
3753
4368
  private readonly mirrorCamera;
3754
4369
  private readonly mirrorMatrix;
3755
4370
  private lastMirrorAt;
@@ -3864,6 +4479,11 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
3864
4479
  private renderCutoutMasks;
3865
4480
  private syncFancy;
3866
4481
  /**
4482
+ * The bed depth grid and the spray that rides it. Built once per `bed`
4483
+ * (terrain heights are static); torn down when the path is cleared.
4484
+ */
4485
+ private syncBed;
4486
+ /**
3867
4487
  * @internal Environment-sky sun hand-off (see {@link SunConsumer3D}): the
3868
4488
  * sky's sun drives the glint ONLY while the `sunDirection` prop sits at its
3869
4489
  * schema default — an explicitly authored prop always wins. Runs right
@@ -3884,6 +4504,22 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
3884
4504
  */
3885
4505
  heightAt(x: number, z: number): number;
3886
4506
  /**
4507
+ * How far below THIS water's surface a world point is, in metres — `0` when
4508
+ * it is above the surface, and `0` when it is outside the pool's footprint.
4509
+ *
4510
+ * `heightAt` answers the surface height everywhere, including a hundred
4511
+ * metres inland, so the obvious spelling of "am I under water"
4512
+ * (`y < water.heightAt(x, z)`) says yes for a player standing in a cellar
4513
+ * well below sea level. The footprint-aware version lived inside
4514
+ * `CharacterController3D`'s swim spring and nothing else could reach it —
4515
+ * and every underwater game needs it for a breath meter, muffled audio, a
4516
+ * "you surfaced" banner, or an enemy that only strikes below.
4517
+ *
4518
+ * The WAVE is included: a point just under a trough is out, and the same
4519
+ * point under a crest is in, which is what a swimmer feels.
4520
+ */
4521
+ depthAt(x: number, y: number, z: number): number;
4522
+ /**
3887
4523
  * @internal Water surface height over a world point, or null outside this
3888
4524
  * surface's footprint — what scattered vegetation asks before it plants a
3889
4525
  * blade (see {@link WetSurface}). The STILL level, not the wave: grass does
@@ -3892,6 +4528,19 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
3892
4528
  _waterYAt(x: number, z: number): number | null;
3893
4529
  /** Base wave height at world (x, z) — the CPU twin of the active shader.
3894
4530
  * Ripples are excluded on purpose: a splash must not re-trigger itself. */
4531
+ /**
4532
+ * The sea's spectrum, or null when there is no wind (or the tier collapsed
4533
+ * the surface to the simple shader, whose CPU waves are its own). Built on
4534
+ * the CPU once per (wind, heading, choppiness) and shared by the GPU upload
4535
+ * and the headless twin — one sea, two evaluators.
4536
+ */
4537
+ private seaSpectrum;
4538
+ /**
4539
+ * The ripple layer's spectrum: the sea's own wind when it blows, else the
4540
+ * breeze that puts wind ripple on a pool — its ENERGY is irrelevant (the
4541
+ * layer is normalized to `detailStrength` by rms slope), only its shape.
4542
+ */
4543
+ private rippleSpectrumFor;
3895
4544
  private waveOffsetAt;
3896
4545
  }
3897
4546
  //#endregion
@@ -3948,6 +4597,29 @@ declare class WaterCutout3D extends Node3D {
3948
4597
  /** How many cutouts one Water3D reads (the nearest ones win). */
3949
4598
  declare const WATER_CUTOUT_MAX = 4;
3950
4599
  //#endregion
4600
+ //#region src/3d/nodes/weather-3d.d.ts
4601
+ declare class Weather3D extends Particles3D {
4602
+ static override readonly typeName: string;
4603
+ static override readonly props: PropSchema;
4604
+ kind: string;
4605
+ intensity: number;
4606
+ radius: number;
4607
+ height: number;
4608
+ wind: number[];
4609
+ follow: string;
4610
+ /** Rain is a streak; snow keeps the soft flake the particle system draws. */
4611
+ protected override sprite(): DataTexture;
4612
+ private tunedFor;
4613
+ private camera;
4614
+ private cameraScanIn;
4615
+ override onEnterTree(): void;
4616
+ /** Set the inherited particle props from `kind` — once, and again if it changes. */
4617
+ private tune;
4618
+ private tuning;
4619
+ private followed;
4620
+ override update(dt: number): void;
4621
+ }
4622
+ //#endregion
3951
4623
  //#region src/3d/register.d.ts
3952
4624
  /**
3953
4625
  * Register the 3D node taxonomy (and the core nodes). Call once in your game
@@ -4287,9 +4959,31 @@ interface SyncScratch {
4287
4959
  parent: Object3D;
4288
4960
  }>;
4289
4961
  state: WalkState;
4962
+ blend: CameraBlend;
4963
+ }
4964
+ /**
4965
+ * The picture's flight from one camera to the next. `lastPos/Quat/Fov` is the
4966
+ * pose the picture was LAST drawn from — a blend already flying included, so a
4967
+ * second switch mid-blend starts from where the picture is, not from a camera
4968
+ * it never reached.
4969
+ */
4970
+ interface CameraBlend {
4971
+ cam: PerspectiveCamera;
4972
+ active: boolean;
4973
+ t: number;
4974
+ seconds: number;
4975
+ fromPos: Vector3;
4976
+ fromQuat: Quaternion;
4977
+ fromFov: number;
4978
+ lastNode: Camera3D | null;
4979
+ lastPos: Vector3;
4980
+ lastQuat: Quaternion;
4981
+ lastFov: number;
4290
4982
  }
4291
4983
  interface SyncOptions {
4292
4984
  assets?: AssetStore3D;
4985
+ /** Real seconds since the last frame — what a camera blend flies on. */
4986
+ dt?: number;
4293
4987
  /** Sync `static: true` subtrees every frame anyway (the editor's edit mode). */
4294
4988
  ignoreStatic?: boolean;
4295
4989
  /**
@@ -4400,4 +5094,4 @@ declare function createNavDebugNode(nav: TerrainNav, opts?: {
4400
5094
  name?: string;
4401
5095
  }): InstancedMesh3D;
4402
5096
  //#endregion
4403
- export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type FrameDiff, type FrameSignature, type FrameStats, type FrameStatsOptions, type Game3D, type GridCell, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, type ModelFacts, ModelInstance3D, type ModelVerdict, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type Rig, type RigView, RigidBody3D, type Ripple, River3D, type RiverCarveOptions, type RiverCoverageGap, type RiverHit, type RiverRing, type RiverRingOptions, SIGNATURE_GRID, type SceneJson, type ShadowsEnvironment, type SkyEnvironment, SpotLight3D, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, TextureCache3D, type TextureSpec, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, isWebGLAvailable, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, showBootFailure, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };
5097
+ export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, type Boid, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flock3D, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type FrameDiff, type FrameSignature, type FrameStats, type FrameStatsOptions, type Game3D, type GridCell, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, type Limb, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, type ModelFacts, ModelInstance3D, type ModelVerdict, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, Ragdoll3D, type RagdollPose, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type Rig, type RigView, RigidBody3D, type Ripple, River3D, type RiverCarveOptions, type RiverCoverageGap, type RiverHit, type RiverRing, type RiverRingOptions, SIGNATURE_GRID, type SceneJson, type ShadowsEnvironment, type SkyEnvironment, SpotLight3D, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, TextureCache3D, type TextureSpec, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, Vehicle3D, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, Weather3D, type WheelSpec, type WheelState, acquireOwnTexture, acquireTexture, applyQualityTier3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, diffSignatures, diffText, enablePhysics3D, findRiverCoverageGaps, frameSignature, frameStats, frameText, horizonColorFromSky, isWebGLAvailable, keyboardIntensity, modelVerdict, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, showBootFailure, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath, verdictText };