incanto 0.1.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 (138) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +36 -0
  3. package/THIRD-PARTY-NOTICES.md +88 -0
  4. package/assets/audio/attacked.mp3 +0 -0
  5. package/assets/audio/explosion.mp3 +0 -0
  6. package/assets/audio/gold_loot.mp3 +0 -0
  7. package/assets/audio/heal.mp3 +0 -0
  8. package/assets/audio/hit_metal_bang.mp3 +0 -0
  9. package/assets/audio/ice_spear.mp3 +0 -0
  10. package/assets/audio/monster_died.mp3 +0 -0
  11. package/assets/audio/slash.mp3 +0 -0
  12. package/assets/audio/smite.mp3 +0 -0
  13. package/assets/audio/spells_cast.mp3 +0 -0
  14. package/assets/audio/ui_click.wav +0 -0
  15. package/assets/audio/walk.mp3 +0 -0
  16. package/assets/catalog.json +390 -0
  17. package/assets/characters/2dbasic.json +41 -0
  18. package/assets/characters/2dbasic.png +0 -0
  19. package/assets/characters/ghost.json +46 -0
  20. package/assets/characters/ghost.png +0 -0
  21. package/assets/characters/goblin.json +40 -0
  22. package/assets/characters/goblin.png +0 -0
  23. package/assets/characters/medieval-knight.json +41 -0
  24. package/assets/characters/medieval-knight.png +0 -0
  25. package/assets/effects/swoosh.png +0 -0
  26. package/assets/items/box.png +0 -0
  27. package/assets/items/buff_potion.png +0 -0
  28. package/assets/items/coin.png +0 -0
  29. package/assets/items/gem.png +0 -0
  30. package/assets/items/gold.png +0 -0
  31. package/assets/items/hp_potion.png +0 -0
  32. package/assets/items/locked_item_box.png +0 -0
  33. package/assets/items/map.png +0 -0
  34. package/assets/items/resurrection_potion.png +0 -0
  35. package/assets/items/super_box.png +0 -0
  36. package/assets/items/trap.png +0 -0
  37. package/assets/tiles/floor00.jpg +0 -0
  38. package/assets/tiles/minecraft-tiles.png +0 -0
  39. package/assets/tiles/wall00.jpg +0 -0
  40. package/assets/vegetation/ash_color.png +0 -0
  41. package/assets/vegetation/aspen_color.png +0 -0
  42. package/assets/vegetation/bark/birch_color_1k.jpg +0 -0
  43. package/assets/vegetation/bark/birch_normal_1k.jpg +0 -0
  44. package/assets/vegetation/bark/birch_roughness_1k.jpg +0 -0
  45. package/assets/vegetation/bark/oak_color_1k.jpg +0 -0
  46. package/assets/vegetation/bark/oak_normal_1k.jpg +0 -0
  47. package/assets/vegetation/bark/oak_roughness_1k.jpg +0 -0
  48. package/assets/vegetation/bark/pine_color_1k.jpg +0 -0
  49. package/assets/vegetation/bark/pine_normal_1k.jpg +0 -0
  50. package/assets/vegetation/bark/pine_roughness_1k.jpg +0 -0
  51. package/assets/vegetation/ground/dirt_color.jpg +0 -0
  52. package/assets/vegetation/ground/dirt_normal.jpg +0 -0
  53. package/assets/vegetation/ground/grass.jpg +0 -0
  54. package/assets/vegetation/oak_color.png +0 -0
  55. package/assets/vegetation/pine_color.png +0 -0
  56. package/bin/incanto-assets.mjs +107 -0
  57. package/bin/incanto-check.mjs +107 -0
  58. package/bin/incanto-editor.mjs +343 -0
  59. package/bin/incanto-env.mjs +144 -0
  60. package/bin/incanto-model.mjs +296 -0
  61. package/bin/incanto-play.mjs +219 -0
  62. package/bin/incanto-skills.mjs +71 -0
  63. package/dist/2d.d.ts +642 -0
  64. package/dist/2d.js +44 -0
  65. package/dist/3d.d.ts +1860 -0
  66. package/dist/3d.js +5 -0
  67. package/dist/agent8-DzU2fFyH.js +129 -0
  68. package/dist/audio-player-DqUR3XFs.d.ts +110 -0
  69. package/dist/behavior-BAQq7HGM.d.ts +851 -0
  70. package/dist/create-game-BdjpTHrW.js +1725 -0
  71. package/dist/create-game-CZHROKcT.js +527 -0
  72. package/dist/debug-draw-CZmOYjL2.js +13 -0
  73. package/dist/debug.d.ts +66 -0
  74. package/dist/debug.js +658 -0
  75. package/dist/duplicate-DP2WPYom.js +22 -0
  76. package/dist/env.d.ts +430 -0
  77. package/dist/env.js +3152 -0
  78. package/dist/errors-BMFaY68Q.d.ts +33 -0
  79. package/dist/errors-BpWbnbb_.js +13 -0
  80. package/dist/gameplay-Ccruc3Wd.js +1501 -0
  81. package/dist/gameplay.d.ts +543 -0
  82. package/dist/gameplay.js +2 -0
  83. package/dist/heightmap-CroQPEER.js +185 -0
  84. package/dist/index.d.ts +305 -0
  85. package/dist/index.js +62 -0
  86. package/dist/json-BLk7H2Qa.js +30 -0
  87. package/dist/loader-CGs_G-r0.js +919 -0
  88. package/dist/loader-Mo0KghCv.d.ts +41 -0
  89. package/dist/net.d.ts +427 -0
  90. package/dist/net.js +772 -0
  91. package/dist/noise-CGUMx44x.js +82 -0
  92. package/dist/particle-sim-CbN4YUuH.d.ts +63 -0
  93. package/dist/particle-sim-DYuSUxvK.js +1319 -0
  94. package/dist/physics-2d-KuMWPTf6.js +288 -0
  95. package/dist/physics-3d-Dl67vOLT.js +434 -0
  96. package/dist/react.d.ts +65 -0
  97. package/dist/react.js +209 -0
  98. package/dist/register-BuUV1_KB.js +561 -0
  99. package/dist/register-CNlYAS1_.js +10634 -0
  100. package/dist/register-DPEV9_9t.js +851 -0
  101. package/dist/register-Dasmnurl.js +374 -0
  102. package/dist/registry-BVJ2HbCn.js +132 -0
  103. package/dist/rng-DP-SR7eg.js +38 -0
  104. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  105. package/dist/schema-CcoWb32N.d.ts +104 -0
  106. package/dist/test.d.ts +158 -0
  107. package/dist/test.js +275 -0
  108. package/dist/touch-031PxtCR.js +208 -0
  109. package/dist/vite.d.ts +26 -0
  110. package/dist/vite.js +57 -0
  111. package/editor/assets/GameServer-C56iOUgF.js +1 -0
  112. package/editor/assets/agent8-Bp7QFI7v.js +1 -0
  113. package/editor/assets/index-DF3tMeKJ.css +1 -0
  114. package/editor/assets/index-Dl2pjA8e.js +7365 -0
  115. package/editor/assets/rapier-CEuLKeCu.js +1 -0
  116. package/editor/assets/rapier-DE6a0vmv.js +1 -0
  117. package/editor/index.html +169 -0
  118. package/package.json +97 -0
  119. package/schemas/scene.schema.json +4254 -0
  120. package/skills/README.md +9 -0
  121. package/skills/incanto-3d-character.md +229 -0
  122. package/skills/incanto-3d-models.md +151 -0
  123. package/skills/incanto-assets.md +118 -0
  124. package/skills/incanto-audio.md +309 -0
  125. package/skills/incanto-behaviors-and-scripts.md +169 -0
  126. package/skills/incanto-building-2d-games.md +242 -0
  127. package/skills/incanto-building-3d-games.md +245 -0
  128. package/skills/incanto-editor.md +163 -0
  129. package/skills/incanto-environment.md +743 -0
  130. package/skills/incanto-gameplay-behaviors.md +707 -0
  131. package/skills/incanto-multiplayer.md +264 -0
  132. package/skills/incanto-node-reference.md +797 -0
  133. package/skills/incanto-physics-and-input.md +164 -0
  134. package/skills/incanto-scene-json-authoring.md +325 -0
  135. package/skills/incanto-verifying-your-game.md +191 -0
  136. package/skills/incanto-web-integration.md +96 -0
  137. package/templates/agent8-server.js +84 -0
  138. package/templates/agent8-server.ts +138 -0
package/dist/3d.d.ts ADDED
@@ -0,0 +1,1860 @@
1
+ import { s as JsonObject } from "./schema-CcoWb32N.js";
2
+ import { C as RendererStats, S as GameStats, T as Node, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-BAQq7HGM.js";
3
+ import { t as LoadSceneOptions } from "./loader-Mo0KghCv.js";
4
+ import { t as ParticleSim } from "./particle-sim-CbN4YUuH.js";
5
+ import { r as SpatialPose } from "./audio-player-DqUR3XFs.js";
6
+ import { AnimationClip, BufferGeometry, Color, DirectionalLight, InstancedMesh, Object3D, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
7
+ import { VRM } from "@pixiv/three-vrm";
8
+ import { Sky } from "three/examples/jsm/objects/Sky.js";
9
+ import * as RapierNs from "@dimforge/rapier3d-compat";
10
+
11
+ //#region src/3d/assets.d.ts
12
+ interface ModelEntry {
13
+ status: "loading" | "ready" | "error";
14
+ /** Source scene — GLB instances are CLONED from this; VRM mounts it directly. */
15
+ scene: Object3D | null;
16
+ animations: AnimationClip[];
17
+ isVrm: boolean;
18
+ /** The live three-vrm runtime (normalized rig, springbones) — VRM only. */
19
+ vrm: VRM | null;
20
+ /** VRM scenes mount directly: the node currently displaying this avatar. */
21
+ claimedBy: object | null;
22
+ /** Retargeted clips cached per animation-asset reference. */
23
+ retargeted: Map<string, AnimationClip>;
24
+ error?: string;
25
+ }
26
+ interface AnimationEntry {
27
+ status: "loading" | "ready" | "error";
28
+ clips: AnimationClip[];
29
+ /** The animation file's own scene — its rest pose drives VRM retargeting. */
30
+ scene: Object3D | null;
31
+ /** Optional clip selector from the asset declaration. */
32
+ clip?: string;
33
+ error?: string;
34
+ }
35
+ /**
36
+ * GLB/glTF/VRM store for the 3D adapter. Scene `assets` entries:
37
+ *
38
+ * {type:"model", url} → ModelInstance3D `model: "$key"`
39
+ * {type:"animation", url, clip?} → ModelInstance3D `animation: "$key"`
40
+ * (clips live in memory, never drawn)
41
+ *
42
+ * Raw URLs also work anywhere a `$key` does. VRM files load through
43
+ * @pixiv/three-vrm (VRM 0.x facing fixed via rotateVRM0).
44
+ */
45
+ declare class AssetStore3D {
46
+ private readonly models;
47
+ private readonly animations;
48
+ private readonly modelKeys;
49
+ private readonly animationKeys;
50
+ private readonly loader;
51
+ constructor();
52
+ /** Map a scene's `assets` header. */
53
+ load(assets: JsonObject): void;
54
+ /** Animation `$key`s declared by the scene (editor dropdowns). */
55
+ animationRefs(): string[];
56
+ modelRefs(): string[];
57
+ /** Resolve `$key` or URL; kicks off the load on first sight. */
58
+ getModel(ref: string): ModelEntry;
59
+ /** Resolve an animation `$key` or URL; clips load into memory only. */
60
+ getAnimation(ref: string): AnimationEntry;
61
+ dispose(): void;
62
+ }
63
+ //#endregion
64
+ //#region src/3d/camera-rig.d.ts
65
+ /**
66
+ * Camera rig math for the four classic character views — pure functions so
67
+ * every magic number stays testable. All values mirror vibe-starter-3d's
68
+ * controllers exactly (the agent8 template stack):
69
+ *
70
+ * free third-person orbit: pivot = player + eye, cam at -distance
71
+ * along the yaw/pitch direction, lookAt pivot
72
+ * firstPerson cam AT the eye pivot, looking along yaw/pitch
73
+ * quarter fixed isometric pitch atan(1/√2) ≈ 35.264°, offset
74
+ * (0, d·sinX, d·cosX), rotation hard-set (no lookAt)
75
+ * side cam at (player.x, eye.y, player.z + distance), facing -z
76
+ */
77
+ type RigView = "free" | "firstPerson" | "quarter" | "side";
78
+ interface RigPose {
79
+ position: [number, number, number];
80
+ /** Euler XYZ in RADIANS (Camera3D's rotation prop is degrees — convert). */
81
+ rotation: [number, number, number];
82
+ }
83
+ declare const QUARTER_PITCH: number;
84
+ declare function rigPose(view: RigView, player: [number, number, number], eyeHeight: number, yaw: number, pitch: number, distance: number): RigPose;
85
+ /** Camera-relative movement: input (x right, y forward) rotated by camera yaw. */
86
+ declare function cameraRelative(x: number, y: number, yaw: number): [number, number, number];
87
+ /** Keyboard intensity model: any direction = 0.6, sprint adds 0.4 (cap 1). */
88
+ declare function keyboardIntensity(moving: boolean, sprinting: boolean): number;
89
+ /** intensity → movement state (the animation driver). */
90
+ declare function movementState(grounded: boolean, intensity: number): "idle" | "walk" | "run" | "fastRun" | "airborne";
91
+ //#endregion
92
+ //#region src/3d/nodes/node-3d.d.ts
93
+ /**
94
+ * Per-frame hand-off from `Renderer3D` to nodes that need the LIVE WebGL
95
+ * context (cube-camera reflections, depth pre-passes…). `syncTree` collects
96
+ * `_onRender3D` hooks while it walks the tree; the renderer invokes them just
97
+ * before the main render. Headless runs (no Renderer3D) never invoke them —
98
+ * GPU-only features must stay dormant, not crash.
99
+ */
100
+ interface RenderContext3D {
101
+ gl: WebGLRenderer;
102
+ scene: Scene;
103
+ camera: PerspectiveCamera;
104
+ }
105
+ /**
106
+ * Implement on a Node3D subclass to receive {@link RenderContext3D} once per
107
+ * rendered frame — `syncTree` detects `_onRender3D` structurally, so plain
108
+ * headless trees pay nothing and never invoke it.
109
+ */
110
+ interface RenderHook3D {
111
+ /** @internal Called by Renderer3D right before the main render. */
112
+ _onRender3D(ctx: RenderContext3D): void;
113
+ }
114
+ /**
115
+ * Implement on a Node3D subclass to receive the environment sky's UNIT sun
116
+ * direction once per sync (after the node's own `_syncObject3D`). Contract:
117
+ * apply it ONLY while the node's own sun prop is at its schema default — an
118
+ * explicitly authored prop always wins over the sky.
119
+ */
120
+ interface SunConsumer3D {
121
+ /** @internal Called by syncTree when `environment.sky` declares a sun. */
122
+ _applySunDirection(dir: readonly [number, number, number]): void;
123
+ }
124
+ /**
125
+ * Base of every 3D node: a JSON-prop transform backed by a `THREE.Object3D`.
126
+ *
127
+ * Props stay plain JSON (registry/serializer compatible); the renderer layer
128
+ * pushes them onto the backing object once per frame (`_syncObject3D`).
129
+ * Rotation is in DEGREES in JSON — friendlier for agents and humans.
130
+ */
131
+ declare class Node3D extends Node {
132
+ static override readonly typeName: string;
133
+ static readonly props: PropSchema;
134
+ position: number[];
135
+ /** Euler XYZ in degrees. */
136
+ rotation: number[];
137
+ scale: number[];
138
+ visible: boolean;
139
+ /** Draw-order priority (higher = drawn later / on top). Only affects TRANSPARENT
140
+ * materials — three sorts the transparent pass by renderOrder, then depth.
141
+ * Pairs with named scene constants (e.g. `{"@const": "UI"}`) for sorting tiers. */
142
+ renderOrder: number;
143
+ private _object3D;
144
+ /**
145
+ * @internal Fixed-step render interpolation. A physics body fills these with
146
+ * its previous and current fixed-step LOCAL positions; `_syncObject3D(alpha)`
147
+ * lerps the rendered transform between them so motion is smooth between fixed
148
+ * steps. `null` means "not physics-driven" — render `position` directly.
149
+ */
150
+ _interpPrev: number[] | null;
151
+ _interpCurr: number[] | null;
152
+ /** @internal The backing three object (lazily created). */
153
+ _ensureObject3D(): Object3D;
154
+ /** @internal Override point for subclasses (mesh, camera, light…). */
155
+ protected _createObject3D(): Object3D;
156
+ /** @internal Push JSON props onto the backing object. Called every frame.
157
+ * `alpha` (0..1) interpolates a physics body between its last two fixed-step
158
+ * positions; non-physics nodes ignore it and render `position` directly. */
159
+ _syncObject3D(alpha?: number): void;
160
+ override free(): void;
161
+ }
162
+ //#endregion
163
+ //#region src/3d/nodes/bodies-3d.d.ts
164
+ /**
165
+ * Shared base for physics-backed 3D nodes. Colliders are NODE PROPS:
166
+ * `{shape:'box', size:[x,y,z]}` · `{shape:'sphere', radius}` ·
167
+ * `{shape:'capsule', radius, height}` (meters, y-up).
168
+ */
169
+ declare class PhysicsBody3D extends Node3D {
170
+ static override readonly props: PropSchema;
171
+ /** The unified collision model: every collider participant can emit these. */
172
+ static override readonly signals: readonly string[];
173
+ collider: JsonObject;
174
+ /** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
175
+ static validateJson(node: Node): void;
176
+ /** @internal Set by Physics3D when the body is created. */
177
+ _physics: Physics3D | null;
178
+ }
179
+ /** Immovable collider (ground, walls, platforms). */
180
+ declare class StaticBody3D extends PhysicsBody3D {
181
+ static override readonly typeName: string;
182
+ }
183
+ /** Sensor volume emitting `triggerEnter(other)` / `triggerExit(other)`. */
184
+ declare class Area3D extends PhysicsBody3D {
185
+ static override readonly typeName: string;
186
+ }
187
+ /** Dynamic simulated body. */
188
+ declare class RigidBody3D extends PhysicsBody3D {
189
+ static override readonly typeName: string;
190
+ static override readonly props: PropSchema;
191
+ mass: number;
192
+ gravityScale: number;
193
+ fixedRotation: boolean;
194
+ friction: number;
195
+ restitution: number;
196
+ /** m/s, y-up. Read back every step; write to launch. */
197
+ linearVelocity: number[];
198
+ /** @internal set by Physics3D */
199
+ _physics3d: Physics3D | null;
200
+ /** World-space impulse (kg·m/s) — the floating-capsule controller's verb. */
201
+ applyImpulse(impulse: [number, number, number]): void;
202
+ }
203
+ /**
204
+ * Kinematic capsule on Rapier's character controller. Gravity is NOT applied
205
+ * automatically — integrate `velocity` yourself, then `moveAndSlide()` from
206
+ * `fixedUpdate`.
207
+ */
208
+ declare class CharacterBody3D extends PhysicsBody3D {
209
+ static override readonly typeName: string;
210
+ static override readonly props: PropSchema;
211
+ /** m/s, y-up (up = +y). */
212
+ velocity: number[];
213
+ snapToGround: boolean;
214
+ /** Max climbable slope angle. */
215
+ slopeLimitDeg: number;
216
+ /** @internal Updated by Physics3D.moveAndSlide. */
217
+ _grounded: boolean;
218
+ moveAndSlide(): void;
219
+ isOnFloor(): boolean;
220
+ }
221
+ //#endregion
222
+ //#region src/3d/physics/physics-3d.d.ts
223
+ type Rapier = typeof RapierNs;
224
+ interface Physics3DOptions {
225
+ /** m/s², y-up. Default: scene `physics.gravity`, else [0, -9.81, 0]. */
226
+ gravity?: [number, number, number];
227
+ }
228
+ /**
229
+ * Enable 3D physics for an engine. Dynamically imports Rapier (compat build)
230
+ * so games without physics never pay its bundle cost. 1 unit = 1 meter, y-up —
231
+ * three's native space, no conversion.
232
+ */
233
+ declare function enablePhysics3D(engine: Engine, opts?: Physics3DOptions): Promise<Physics3D>;
234
+ /** Per-engine 3D physics world. Same contract as Physics2D. */
235
+ declare class Physics3D {
236
+ private readonly R;
237
+ private readonly engine;
238
+ /** Render collider outlines in the GAME view (renderers pick this up). */
239
+ debugDraw: boolean;
240
+ readonly dimension = "3d";
241
+ private readonly unregisterDebug;
242
+ private readonly warnedNoCollider;
243
+ private readonly entries;
244
+ private readonly byColliderHandle;
245
+ private readonly world;
246
+ private readonly events;
247
+ private readonly kcc;
248
+ private readonly disconnect;
249
+ private lastDt;
250
+ private readonly optsGravity;
251
+ private lastScene;
252
+ constructor(R: Rapier, engine: Engine, opts?: Physics3DOptions);
253
+ /** @internal Driven by engine.fixedUpdated. */
254
+ step(dt: number): void;
255
+ /** @internal Called by CharacterBody3D.moveAndSlide (during tree fixedUpdate). */
256
+ moveAndSlide(node: CharacterBody3D): void;
257
+ /** Rapier debug segments (meters). Null while debugDraw is off. */
258
+ debugLines(): Float32Array | null;
259
+ dispose(): void;
260
+ private syncBodies;
261
+ private ensureEntry;
262
+ /** Apply a world-space impulse to a dynamic body (character controllers). */
263
+ applyImpulse(node: PhysicsBody3D, impulse: [number, number, number]): void;
264
+ /** Mass the solver actually uses (collider-derived unless overridden). */
265
+ massOf(node: PhysicsBody3D): number;
266
+ /** Current solver velocity (fresher than the node prop mid-step). */
267
+ velocityOf(node: PhysicsBody3D): [number, number, number];
268
+ /**
269
+ * World-space raycast (meters). `exclude` skips that body's collider —
270
+ * ground probes from inside a capsule need it. `opts.staticOnly` hits ONLY the
271
+ * fixed/static world (excludes both dynamic AND kinematic bodies): the camera
272
+ * spring arm uses it so neither a dynamic projectile nor a KINEMATIC enemy
273
+ * (CharacterBody3D) passing behind the player yanks the camera in.
274
+ */
275
+ castRay(origin: [number, number, number], dir: [number, number, number], maxLen: number, exclude?: PhysicsBody3D, opts?: {
276
+ staticOnly?: boolean;
277
+ }): {
278
+ distance: number;
279
+ normal: [number, number, number];
280
+ node: PhysicsBody3D | null;
281
+ } | null;
282
+ }
283
+ //#endregion
284
+ //#region src/3d/create-game.d.ts
285
+ interface KeyboardTarget {
286
+ addEventListener: (t: string, cb: (e: KeyboardEvent) => void) => void;
287
+ removeEventListener: (t: string, cb: (e: KeyboardEvent) => void) => void;
288
+ }
289
+ interface CreateGame3DOptions {
290
+ canvas: HTMLCanvasElement;
291
+ /** Scene JSON (cloned internally — the same object can boot many games). */
292
+ scene: unknown;
293
+ behaviors?: Record<string, BehaviorCtor>;
294
+ /**
295
+ * Auto-register the built-in `incanto/gameplay` behaviors (Health, Pickup,
296
+ * ScoreKeeper, …) — default true. They register BEFORE `behaviors`, so a
297
+ * same-named user behavior always wins. Set false to ship none of them.
298
+ */
299
+ gameplay?: boolean;
300
+ /** Keyboard source (default: window when available). `false` disables. */
301
+ keyboard?: KeyboardTarget | false;
302
+ /** Pointer-look on the canvas (FPS pattern). Default false. */
303
+ pointer?: boolean | {
304
+ lockOnClick?: boolean;
305
+ };
306
+ /** 'auto' (default): enable Rapier iff the tree has physics bodies. */
307
+ physics?: "auto" | boolean;
308
+ /**
309
+ * On-screen touch controls for the scene's `"touch"` input declarations:
310
+ * 'auto' (default) shows them on coarse-pointer devices, true forces them,
311
+ * false disables. They overlay `touchContainer` (default: the canvas's
312
+ * parent element — give it position: relative).
313
+ */
314
+ touch?: "auto" | boolean;
315
+ touchContainer?: HTMLElement;
316
+ /** @internal Test seam — replaces `document` for the touch overlay. */
317
+ _touchDoc?: {
318
+ createElement(tag: string): HTMLElement;
319
+ };
320
+ /**
321
+ * Mount the runtime debug overlay (`incanto/debug` — explorer/inspector/
322
+ * logs). Default: false (off), and there is NO URL/query toggle — a deployed
323
+ * build must never be switchable on by end users. Opt in via `debug`, gated
324
+ * to development, e.g. `debug: import.meta.env.VITE_INCANTO_DEBUG === '1'`.
325
+ */
326
+ debug?: boolean;
327
+ /** @internal Test seam — replaces `document` for the debug overlay. */
328
+ _debugDoc?: {
329
+ createElement(tag: string): HTMLElement;
330
+ };
331
+ seed?: number;
332
+ fixedHz?: number;
333
+ pixelRatio?: number;
334
+ resolveScene?: LoadSceneOptions["resolveScene"];
335
+ /** @internal Test seam — replaces the Renderer3D construction. */
336
+ _rendererFactory?: (engine: Engine, canvas: HTMLCanvasElement) => GameRenderer;
337
+ /** @internal Test seam — replaces the rAF scheduler. */
338
+ _scheduler?: Scheduler;
339
+ }
340
+ /** What createGame needs from a renderer (stats is optional for test stubs). */
341
+ interface GameRenderer {
342
+ dispose(): void;
343
+ stats?(): RendererStats;
344
+ }
345
+ interface Game3D {
346
+ engine: Engine;
347
+ scene: Scene$1;
348
+ renderer: GameRenderer;
349
+ physics: Physics3D | null;
350
+ /** Engine + renderer perf counters in one read (fps/nodes/triangles/…). */
351
+ stats(): GameStats;
352
+ dispose(): void;
353
+ }
354
+ /**
355
+ * One-call 3D boot/teardown — the 3D twin of createGame2D: register → load
356
+ * (engine attached for onReady) → physics (auto) → input → renderer → start.
357
+ */
358
+ declare function createGame3D(opts: CreateGame3DOptions): Promise<Game3D>;
359
+ //#endregion
360
+ //#region src/3d/environment.d.ts
361
+ /**
362
+ * The 3D `environment` header, parsed and validated — the renderer's
363
+ * "rendering stage". Pure data in, plain data out (NO three imports), so the
364
+ * whole contract tests headlessly:
365
+ *
366
+ * - `sky`: a physical atmosphere (three Sky shader) that also feeds
367
+ * image-based lighting and the unified sun direction
368
+ * - `fog`: linear distance fog (color defaults to the sky's horizon haze)
369
+ * - `shadows`: shadow-map master switch + quality knobs
370
+ * - `exposure`: ACES tone-mapping exposure dial
371
+ *
372
+ * Everything is renderer-interpreted: scenes without these keys parse to the
373
+ * exact pre-atmosphere defaults (no sky, no fog, exposure 1).
374
+ */
375
+ interface SkyEnvironment {
376
+ /** Sun direction (not necessarily unit length — see {@link sunDirectionFromSky}). */
377
+ sunPosition: [number, number, number];
378
+ /** Atmospheric haze 0.1–20 (three Sky semantics; 2 ≈ clear day). */
379
+ turbidity: number;
380
+ /** Rayleigh scattering ≥ 0 (1 ≈ earth-like blue). */
381
+ rayleigh: number;
382
+ }
383
+ interface FogEnvironment {
384
+ /** Hex fog color. */
385
+ color: string;
386
+ /** Distance where fog starts, meters. */
387
+ near: number;
388
+ /** Distance of full fog, meters. */
389
+ far: number;
390
+ }
391
+ interface ShadowsEnvironment {
392
+ /** Shadow map resolution per side. */
393
+ mapSize: 1024 | 2048;
394
+ /** Shadow edge softening radius (PCF blur, default 1). */
395
+ radius: number;
396
+ }
397
+ interface CloudsEnvironment {
398
+ /** How much of the sky is cloudy, 0..1. */
399
+ coverage: number;
400
+ /** Optical thickness multiplier (higher = more opaque/dramatic). */
401
+ density: number;
402
+ /** Cloud-base altitude, world Y. */
403
+ base: number;
404
+ /** Cloud-top altitude, world Y (> base). */
405
+ top: number;
406
+ /** Sunlit cloud color (hex). */
407
+ color: string;
408
+ /** Shaded underside color (hex). */
409
+ shadeColor: string;
410
+ /** Wind drift speed. */
411
+ speed: number;
412
+ /** Feature size in world units (bigger = larger, softer cloud masses). */
413
+ scale: number;
414
+ }
415
+ interface Environment3DConfig {
416
+ /** Tone-mapping exposure (ACES), default 1. */
417
+ exposure: number;
418
+ /** Physical sky, or null when the scene keeps a flat `background`. */
419
+ sky: SkyEnvironment | null;
420
+ /** Linear fog, or null for none. */
421
+ fog: FogEnvironment | null;
422
+ /** Volumetric cloud layer (renderer raymarch composite), or null for none. */
423
+ clouds: CloudsEnvironment | null;
424
+ /**
425
+ * `null` = not declared (shadow maps stay available, free until a light
426
+ * casts), `false` = force-disabled, object = declared ON with quality knobs
427
+ * (the renderer then makes the scene's main DirectionalLight cast).
428
+ */
429
+ shadows: ShadowsEnvironment | false | null;
430
+ }
431
+ /** Parse + hard-validate the 3D slice of a scene's `environment` header. */
432
+ declare function parseEnvironment3D(env: JsonObject | undefined): Environment3DConfig;
433
+ /**
434
+ * elevation/azimuth (degrees) → unit direction. Matches three's Sky example
435
+ * (`Vector3.setFromSphericalCoords`): azimuth 0 = +Z, 90 = +X; elevation 90 =
436
+ * straight up.
437
+ */
438
+ declare function sunDirectionFromElevationAzimuth(elevationDeg: number, azimuthDeg: number): [number, number, number];
439
+ /** The sky's sun as a UNIT vector — what Water3D/Foliage3D uniforms consume. */
440
+ declare function sunDirectionFromSky(sky: SkyEnvironment): [number, number, number];
441
+ /**
442
+ * A horizon-ish haze color derived from the sky config — the default fog
443
+ * color. Cheap model, judged by eye: clear skies haze blue-grey, turbid skies
444
+ * whiten, and a low sun warms the band toward amber.
445
+ */
446
+ declare function horizonColorFromSky(sky: SkyEnvironment): string;
447
+ //#endregion
448
+ //#region src/3d/water/underwater.d.ts
449
+ /** Animated caustics — the dancing refracted-light pattern on submerged surfaces. */
450
+ interface CausticsConfig {
451
+ enabled: boolean;
452
+ /** Hex tint of the caustic highlights (a bright pale aqua by default). */
453
+ color: string;
454
+ /** Brightness of the additive caustic light. */
455
+ intensity: number;
456
+ /** World-space frequency: bigger = smaller, busier cells. */
457
+ scale: number;
458
+ /** Animation speed multiplier. */
459
+ speed: number;
460
+ }
461
+ /** Resolved underwater look: fog color, view distance, and the caustics layer. */
462
+ interface UnderwaterConfig {
463
+ enabled: boolean;
464
+ /** Hex color of the underwater fog + background (the murk you're submerged in). */
465
+ color: string;
466
+ /** Visibility in meters — the fog's far plane (near is fixed tiny). */
467
+ visibility: number;
468
+ /** Caustics projected onto submerged surfaces (the floor especially). */
469
+ caustics: CausticsConfig;
470
+ }
471
+ //#endregion
472
+ //#region src/3d/environment-3d.d.ts
473
+ /**
474
+ * Applies the scene `environment` header to a three scene — the rendering
475
+ * stage Renderer3D delegates to. One instance per renderer; `apply` runs once
476
+ * per frame but re-parses/rebuilds only when the env JSON actually changed.
477
+ *
478
+ * Headless-safe: `gl` is nullable, and everything GPU-bound (PMREM
479
+ * image-based lighting, exposure, shadow-map switches) is skipped without it
480
+ * while the scene-graph side (Sky object, fog, background, ambient) still
481
+ * applies — that's what the node-environment tests assert against.
482
+ */
483
+ declare class Environment3D {
484
+ private readonly scene;
485
+ private readonly ambient;
486
+ private envKey;
487
+ private config;
488
+ private hdriUrl;
489
+ private hdriTexture;
490
+ /** @internal The Sky backdrop object (tests reach in). */
491
+ _sky: Sky | null;
492
+ private skyKey;
493
+ private skyEnvKey;
494
+ private skyEnvTarget;
495
+ private readonly fog;
496
+ /** Underwater fog/tint, created lazily the first time the camera submerges. */
497
+ private underwaterFog;
498
+ private readonly underwaterBg;
499
+ constructor(scene: Scene);
500
+ /** The sky's unit sun direction, or null when no sky is declared. */
501
+ get sunDirection(): [number, number, number] | null;
502
+ /** Parsed volumetric-cloud config, or null when no cloud layer is declared. */
503
+ get clouds(): CloudsEnvironment | null;
504
+ /** The live scene fog (color/near/far) the cloud composite fades into, or null. */
505
+ get sceneFog(): {
506
+ color: Color;
507
+ near: number;
508
+ far: number;
509
+ } | null;
510
+ /** Apply the env header. Cheap when unchanged; hard-validates on change. */
511
+ apply(env: JsonObject | undefined, gl: WebGLRenderer | null): void;
512
+ /**
513
+ * Per-frame hand-off for the scene's MAIN DirectionalLight (the renderer
514
+ * passes the brightest one after the sync pass — never auto-created):
515
+ *
516
+ * - sky declared → the light's backing object is re-aimed along the sun
517
+ * direction (its distance from origin is kept, so the node's `position`
518
+ * keeps meaning "how far out the sun sits")
519
+ * - `shadows` declared truthy → the light casts; env mapSize/radius win,
520
+ * and lights that configured nothing themselves get the default ortho box
521
+ */
522
+ applySunLight(light: DirectionalLight | null, focus?: {
523
+ x: number;
524
+ y: number;
525
+ z: number;
526
+ } | null): void;
527
+ /**
528
+ * Underwater override (the renderer passes the resolved config from whichever
529
+ * water the camera is submerged in, or null). When submerged, the scene
530
+ * switches to a short-range underwater fog + matching background AND the sky
531
+ * dome is hidden — without that you'd see the bright sky through the surface
532
+ * and the murk would never read. `null` is a no-op: the per-frame `apply`
533
+ * already (re)established the normal fog/background/sky, so surfacing restores
534
+ * the look for free.
535
+ */
536
+ applyUnderwater(config: UnderwaterConfig | null): void;
537
+ private applyHdri;
538
+ private applySky;
539
+ private applyFog;
540
+ dispose(): void;
541
+ }
542
+ //#endregion
543
+ //#region src/3d/nodes/camera-3d.d.ts
544
+ /**
545
+ * A perspective camera. Mark exactly one camera `current: true`; with none
546
+ * marked, the renderer falls back to the first camera in tree order.
547
+ * Aspect ratio is managed by the renderer on resize.
548
+ */
549
+ declare class Camera3D extends Node3D {
550
+ static override readonly typeName: string;
551
+ static override readonly props: PropSchema;
552
+ fov: number;
553
+ near: number;
554
+ far: number;
555
+ current: boolean;
556
+ protected override _createObject3D(): Object3D;
557
+ override _syncObject3D(): void;
558
+ }
559
+ //#endregion
560
+ //#region src/3d/nodes/character-controller-3d.d.ts
561
+ /**
562
+ * The floating-capsule character controller behind every agent8 3D template
563
+ * (vibe-starter-3d parity, same numbers): put it UNDER a dynamic RigidBody3D
564
+ * with a capsule collider and it gives you impulse-based movement with
565
+ * sprint, jump, snappy falls, hover-float ground suspension, an
566
+ * intensity-driven movement state (drives animations), and a camera rig for
567
+ * the four classic views — free orbit, first person, quarter, side.
568
+ */
569
+ declare class CharacterController3D extends Node3D {
570
+ static override readonly typeName: string;
571
+ static override readonly signals: readonly string[];
572
+ static override readonly props: PropSchema;
573
+ view: RigView;
574
+ camDistance: number;
575
+ eyeHeight: number;
576
+ maxSpeed: number;
577
+ sprintMultiplier: number;
578
+ jumpVelocity: number;
579
+ sprintJumpMultiplier: number;
580
+ floatHeight: number;
581
+ mouseLook: boolean;
582
+ zoomMin: number;
583
+ zoomMax: number;
584
+ pitchMin: number;
585
+ pitchMax: number;
586
+ /** Spring-arm: pull the orbit camera in front of walls/ground instead of clipping. */
587
+ cameraCollision: boolean;
588
+ turnSpeed: number;
589
+ /** Camera smoothing rate (1-exp(-rate·dt)); side view uses 5. */
590
+ camLerp: number;
591
+ moveAction: string;
592
+ jumpAction: string;
593
+ sprintAction: string;
594
+ skinPath: string;
595
+ /**
596
+ * Extra yaw added to the move-facing target (degrees) for models whose
597
+ * native forward is not +z. agent8's base-model IS +z-forward, so the
598
+ * default is 0; the skin merely STARTS at 180° (vibe-starter mounts the
599
+ * character facing away from the camera until it first moves).
600
+ */
601
+ skinYawOffset: number;
602
+ /** Camera yaw/pitch in radians (mouse look mutates these). */
603
+ yaw: number;
604
+ pitch: number;
605
+ /** idle | walk | run | fastRun | airborne — drive animations from this. */
606
+ state: "idle" | "walk" | "run" | "fastRun" | "airborne";
607
+ grounded: boolean;
608
+ private get body();
609
+ override onEnterTree(): void;
610
+ override fixedUpdate(dt: number): void;
611
+ override update(dt: number): void;
612
+ private pivot;
613
+ private cameraCache;
614
+ private findCamera;
615
+ }
616
+ //#endregion
617
+ //#region src/3d/vegetation/flower-geometry.d.ts
618
+ /**
619
+ * Procedural flower PLANTS for Flowers3D — real plants like the reference
620
+ * meadow flowers, not confetti quads: a curved stem strip, 2-3 leaf blades
621
+ * low on the stem, and a multi-petal head (5-8 rounded-diamond petals in a
622
+ * shallow cup around a contrasting center disc), with 1-3 blooms clustered
623
+ * per plant at staggered heights.
624
+ *
625
+ * Per VARIETY the geometry is built ONCE (deterministic — no rng; topology
626
+ * and positions are pure functions of the spec) and split into TWO buffers:
627
+ *
628
+ * - `structure`: stems + leaves + center discs, vertex-colored (greens + the
629
+ * variety's center color) — instanced with a light per-plant jitter tint
630
+ * - `petals`: the petal fans only, white — the per-plant `instanceColor`
631
+ * carries the palette pick, so one variety renders patches of different
632
+ * head colors without splitting draw calls
633
+ *
634
+ * The template is ~unit height: the instance matrix's uniform scale supplies
635
+ * the plant height in meters (petal/leaf sizes stay proportionate).
636
+ */
637
+ declare const FLOWER_VARIETIES: readonly ["daisy", "cosmos", "bellflower"];
638
+ type FlowerVariety = (typeof FLOWER_VARIETIES)[number];
639
+ //#endregion
640
+ //#region src/3d/nodes/flowers-3d.d.ts
641
+ /** Named density presets, plants per m² — the vibe-coding dial (풍성하게 /
642
+ * 듬성듬성 / 없게). Numbers are accepted too. */
643
+ declare const DENSITY_PRESETS: Record<string, number>;
644
+ /** Map a density prop value to plants/m² — presets by name, numbers as-is. */
645
+ declare function resolveFlowerDensity(density: string | number): number;
646
+ /**
647
+ * A first-class flower field: instanced procedural flower PLANTS — curved
648
+ * stems, leaf blades, multi-petal heads with contrasting centers, 1-3 blooms
649
+ * per plant (see vegetation/flower-geometry.ts) — scattered over an XZ
650
+ * `area`, deterministic from the `seed` PROP. Placement is cluster-gathered:
651
+ * whole Voronoi patches bloom together (`clustering`), each patch coherent
652
+ * in variety AND head color, like real meadow flowers.
653
+ *
654
+ * `density` is the vibe dial: `'lush'` (풍성하게), `'sparse'` (듬성듬성,
655
+ * default), `'none'` (없게) or a number in plants/m². Up to 3 `varieties`
656
+ * render as TWO InstancedMeshes each (green structure + palette-tinted
657
+ * petals) — ≤6 draw calls per node. `sway` bobs the heads in a gentle wind
658
+ * via a vertex-shader time uniform; roots never move.
659
+ *
660
+ * Like Foliage3D, the bed lies flat on the node's local XZ plane by default —
661
+ * on rolling terrain either set `drape: true` (each plant roots on a Terrain3D's
662
+ * surface per-instance) or place small patches at flat spots (the meadow theme
663
+ * does the latter for you).
664
+ */
665
+ declare class Flowers3D extends Node3D {
666
+ static override readonly typeName: string;
667
+ static override readonly props: PropSchema;
668
+ /** 'lush' | 'sparse' | 'none' | plants per m² (load-time check + budget). */
669
+ density: string | number;
670
+ /** [x, z] extent in meters, centered on the node. */
671
+ area: number[];
672
+ /** Placement seed — the field is identical across runs and machines. */
673
+ seed: number;
674
+ /** Unique subset of ['daisy', 'cosmos', 'bellflower'] — ≤6 draw calls
675
+ * total. `[]` (default) = all three varieties. */
676
+ varieties: string[];
677
+ /** Head colors (hex) — patches pick one each. `[]` (default) = the
678
+ * reference white/yellow/violet trio. */
679
+ palette: string[];
680
+ /** Plant height in meters (±25% per-plant jitter; petals scale along). */
681
+ height: number;
682
+ /** 0 = uniform scatter … 1 = tight Voronoi patches (default 0.6). */
683
+ clustering: number;
684
+ /** Gentle head-bob wind strength — 0 holds still. */
685
+ sway: number;
686
+ /** Drape each plant onto a Terrain3D's surface so flowers root on the ground
687
+ * instead of a flat plane (lets ONE bed cover rolling terrain). */
688
+ drape: boolean;
689
+ /** Drape target: a node path to the Terrain3D. Empty = auto-find the first
690
+ * Terrain3D in the tree (the zero-config default when `drape` is on). */
691
+ terrain: string;
692
+ /** Resolved drape target + the bed's world position, set per rebuild. */
693
+ private drapeTerrain;
694
+ private drapeWorld;
695
+ /** Loader hook: bad densities/varieties/palettes fail at LOAD, not render. */
696
+ static validateJson(node: Node): void;
697
+ private time;
698
+ private field;
699
+ private varietyMeshes;
700
+ private fieldKey;
701
+ /** Shared by every material — update(dt) writes, the vertex sway reads. */
702
+ private readonly timeUniform;
703
+ private readonly swayUniform;
704
+ protected override _createObject3D(): Object3D;
705
+ /** @internal Every variety's mesh pair (tests and tools reach in here). */
706
+ _varietyMeshes(): {
707
+ variety: FlowerVariety;
708
+ structure: InstancedMesh;
709
+ petals: InstancedMesh;
710
+ }[];
711
+ override update(dt: number): void;
712
+ override _syncObject3D(): void;
713
+ /** Re-scatter only when a field-shaping prop changed (the JSON IS the field). */
714
+ private rebuildIfNeeded;
715
+ /** Resolve the drape target + cache the bed's world position for `bedY`. */
716
+ private resolveDrape;
717
+ /** Bed-local Y that roots a plant on the draped terrain (0 = flat). */
718
+ private bedY;
719
+ private buildField;
720
+ private disposeMeshes;
721
+ override free(): void;
722
+ }
723
+ //#endregion
724
+ //#region src/3d/nodes/foliage-3d.d.ts
725
+ type FoliageKind = "grass" | "flowers" | "reeds";
726
+ type FoliageStyle = "mesh" | "tufts" | "blades" | "simple";
727
+ /**
728
+ * An instanced vegetation field — grass (잔디밭), flowers or reeds — scattered
729
+ * over an XZ `area`, deterministic from the `seed` PROP (never the engine
730
+ * rng) so the same scene JSON grows the identical field on every machine.
731
+ *
732
+ * Four looks, picked by `style`:
733
+ *
734
+ * - `'tufts'` (kind 'grass' only): the ez-tree-demo mechanism — ONE
735
+ * InstancedMesh of 3-crossed-quad cards (12 vertices each) carrying a
736
+ * baked multi-blade alpha-cutout texture, per-instance green tints
737
+ * (`colorA`→`colorB`, clump-coherent, dry patches) and the ported sin·cos
738
+ * tuft sway. Dozens of overlapping textured fans read as a DENSE meadow
739
+ * (잔디밭) at a fraction of the instances — the look that per-blade
740
+ * geometry never reaches at sane budgets.
741
+ *
742
+ * - `'mesh'` (default, kind 'grass' only): the per-blade path — ONE
743
+ * InstancedMesh where every instance is a REAL tapered blade (7-vertex
744
+ * strip) curved along a vertex-shader quadratic bezier. 8× the instances
745
+ * of the other styles (one blade each, still capped at `maxInstances`),
746
+ * Voronoi-clump shared lean/hue, soil-`groundColor` roots, Blinn-Phong tip
747
+ * sheen vs `sunDirection`, 2-octave rolling wind gusts, and a
748
+ * camera-distance LOD (`fadeStart`/`fadeEnd`). Fully opaque — no alpha
749
+ * sorting. Best up close; pairs with tufts (sparse mesh over a tuft body).
750
+ *
751
+ * - `'blades'` (kind 'grass' only): the ported shader look — TWO
752
+ * InstancedMeshes of crossed bottom-anchored quads (same instances, second
753
+ * rotated 90°) whose fragment shader draws 8 procedural SDF blades per
754
+ * quad with per-blade wind phases, plus a vertex wind ride.
755
+ *
756
+ * - `'simple'`: the legacy look (and the ONLY look for flowers/reeds) — one
757
+ * InstancedMesh of crossed quads with per-instance colorA→colorB lerp and a
758
+ * whole-field shear sway (one matrix write per frame).
759
+ *
760
+ * mesh + blades share placement (cluster-noise passes) and the character
761
+ * bend: with `interaction: true` the nearest ≤4 moving bodies
762
+ * (CharacterBody3D / RigidBody3D) push blade tops radially aside. Tufts
763
+ * skip the bend (a card fan has no per-blade tops to push).
764
+ */
765
+ declare class Foliage3D extends Node3D implements SunConsumer3D {
766
+ static override readonly typeName: string;
767
+ static override readonly props: PropSchema;
768
+ /** Transparent blades draw after water (1) + opaque ground (overrides Node3D's 0). */
769
+ override renderOrder: number;
770
+ /** 'grass' | 'flowers' | 'reeds' — picks the blade silhouette. */
771
+ kind: string;
772
+ /** 'mesh' (real curved blades) | 'tufts' (ez-tree textured cards) |
773
+ * 'blades' (SDF quads) | 'simple' — mesh/tufts/blades are grass only. */
774
+ style: string;
775
+ /** Tufts style only: which silhouette the card texture bakes — 'grass'
776
+ * (the dense blade fan, default) or 'fern' (fewer, wider, arching fronds
777
+ * with leaflet notches — forest undergrowth). */
778
+ tuftStyle: string;
779
+ /** [x, z] extent in meters, centered on the node. */
780
+ area: number[];
781
+ /** Instances per square meter (mesh style plants 8× — capped at `maxInstances`). */
782
+ density: number;
783
+ /** Instance cap (hard ceiling 200_000). */
784
+ maxInstances: number;
785
+ /** Base blade height in meters — mesh style additionally scales it 0.55–1.7×
786
+ * regionally (low-frequency noise: tall patches and short worn patches). */
787
+ height: number;
788
+ /** colorA = bottom/lerp-start, colorB = top/lerp-end. Refinement pass 3:
789
+ * lifted toward the reference's warm sunlit green (the old olives read
790
+ * dusky under the brighter meadow stage). */
791
+ colorA: string;
792
+ colorB: string;
793
+ /** Mesh style: soil tint at the blade ROOT — pair with the terrain/ground color. */
794
+ groundColor: string;
795
+ /** Mesh style: fixed sun for the specular sheen — match the scene's key light. */
796
+ sunDirection: number[];
797
+ /** Mesh style LOD: blades start shrinking at this camera distance (meters)… */
798
+ fadeStart: number;
799
+ /** …and are gone by this one. */
800
+ fadeEnd: number;
801
+ /** Wind strength — 0 disables wind entirely. */
802
+ sway: number;
803
+ /** mesh/blades styles: let nearby moving bodies bend the grass. */
804
+ interaction: boolean;
805
+ /** Mesh style: carpet fill in (0, 1] — below 1, low-frequency noise carves
806
+ * dirt patches into the field (default 0.85 ≈ a real meadow's break-up;
807
+ * 1 = the old wall-to-wall carpet). */
808
+ coverage: number;
809
+ /** Mesh style: fraction of instances rendered as small flower heads
810
+ * (white/yellow/violet), 0–0.2. Default 0.
811
+ * @deprecated The 5-vertex heads read as confetti — use a `Flowers3D`
812
+ * node for real flower plants (stems, leaves, multi-petal heads, density
813
+ * presets). Kept working for existing scenes; generators emit Flowers3D. */
814
+ flowers: number;
815
+ /** Placement seed — the field is identical across runs and machines. */
816
+ seed: number;
817
+ /** Drape each instance onto a Terrain3D's surface so blades root on the
818
+ * ground instead of a flat plane (lets ONE field cover rolling terrain). */
819
+ drape: boolean;
820
+ /** Drape target: a node path to the Terrain3D. Empty = auto-find the first
821
+ * Terrain3D in the tree (the zero-config default when `drape` is on). */
822
+ terrain: string;
823
+ /** Resolved drape target + the field's world position, set per rebuild. */
824
+ private drapeTerrain;
825
+ private drapeWorld;
826
+ /** Loader hook: bad kinds/styles and degenerate areas fail at LOAD, not at render. */
827
+ static validateJson(node: Node): void;
828
+ private time;
829
+ private pivot;
830
+ private meshes;
831
+ private bladesMaterial;
832
+ private benderSlots;
833
+ private pickedBenders;
834
+ private fieldKey;
835
+ /** Tufts style: the wind uniforms + the baked card texture (disposed here). */
836
+ private tuftTime;
837
+ private tuftStrength;
838
+ private tuftTexture;
839
+ protected override _createObject3D(): Object3D;
840
+ /** Whether this field renders the ported procedural-blades shader. */
841
+ private get isBlades();
842
+ /** Whether this field renders real bezier-curved blade meshes (the default). */
843
+ private get isMesh();
844
+ /** Whether this field renders the ez-tree-demo textured tuft cards. */
845
+ private get isTufts();
846
+ /** Mesh/blades share the shader-driven path (uniform wind + benders). */
847
+ private get isShaderField();
848
+ /** @internal The primary InstancedMesh (lazily built — tests reach in here). */
849
+ _field(): InstancedMesh;
850
+ /** @internal Every InstancedMesh of the field: [quads] or [quads, quads90°]. */
851
+ _fields(): InstancedMesh[];
852
+ override update(dt: number): void;
853
+ override _syncObject3D(): void;
854
+ /**
855
+ * @internal Environment-sky sun hand-off (see {@link SunConsumer3D}): the
856
+ * sky drives the blade sheen ONLY while the `sunDirection` prop sits at its
857
+ * schema default — an explicit prop wins. Mesh style only (the other
858
+ * styles have no sun uniform).
859
+ */
860
+ _applySunDirection(dir: readonly [number, number, number]): void;
861
+ /**
862
+ * Character bend: collect moving bodies in the tree, keep the nearest ≤4
863
+ * whose reach touches this field. `_syncObject3D` writes them into the
864
+ * shader's vec4 slots (xyz = world position, w = radius; w 0 = inactive).
865
+ */
866
+ private computeBenders;
867
+ /** Re-scatter only when a field-shaping prop changed (the JSON IS the field). */
868
+ private rebuildIfNeeded;
869
+ /** Resolve the drape target + cache the field's world position for `bedY`. */
870
+ private resolveDrape;
871
+ /** Field-local Y that roots an instance on the draped terrain (0 = flat). */
872
+ private bedY;
873
+ /**
874
+ * The mesh-style field: ONE InstancedMesh of real tapered blade strips,
875
+ * cluster-noise placement, clump-coherent per-instance attributes —
876
+ * `aBlade` (leanX, leanZ, curve, rand), `aClumpHue` and `aDry` — feeding
877
+ * the bezier vertex shader. Fully opaque: no sorting, no alpha test.
878
+ *
879
+ * Refinement pass: `coverage` < 1 drops blades in noise-carved patches
880
+ * (dirt breaks the carpet), a low-frequency height field scales blades
881
+ * 0.55–1.7× regionally (tall blades lean/curve harder — droop), `aDry`
882
+ * mixes dry yellow-green hues in noise patches, and `flowers` > 0 splits a
883
+ * fraction of instances into a SECOND InstancedMesh of 5-vertex flower
884
+ * heads (white/yellow/violet per instance).
885
+ */
886
+ private buildMeshField;
887
+ /**
888
+ * The meadow's flower sprinkle: ONE extra InstancedMesh of 5-vertex flower
889
+ * heads riding the grass canopy (the matrix y-scale is the stem height; the
890
+ * head's local y ≈ 1). A standard material keeps fog/shadows/tonemapping
891
+ * for free, instance colors pick white/yellow/violet deterministically.
892
+ */
893
+ private buildFlowerMesh;
894
+ /**
895
+ * The tufts field — the ez-tree demo's grass verbatim in spirit: ONE
896
+ * InstancedMesh of 3-crossed-quad cards, a baked multi-blade alpha-cutout
897
+ * texture (see tuft-texture.ts for the recon), per-instance green tints
898
+ * (clump-coherent + dry patches) and the ported sin·cos tuft sway.
899
+ * MeshStandardMaterial keeps fog/shadows/tonemapping for free.
900
+ */
901
+ private buildTuftField;
902
+ /**
903
+ * The ported procedural grass: cluster-noise placement, then the SAME
904
+ * placements into two InstancedMeshes — the second yawed 90° so every slot
905
+ * renders as a crossed pair — sharing ONE ShaderMaterial (one uniform set).
906
+ */
907
+ private buildBladesField;
908
+ /** The legacy field: uniform scatter, instance colors, one draw call. */
909
+ private buildSimpleField;
910
+ private disposeMeshes;
911
+ override free(): void;
912
+ }
913
+ //#endregion
914
+ //#region src/3d/nodes/lights-3d.d.ts
915
+ /** Sun-style light. Direction comes from the node's rotation. */
916
+ declare class DirectionalLight3D extends Node3D {
917
+ static override readonly typeName: string;
918
+ static override readonly props: PropSchema;
919
+ color: string;
920
+ intensity: number;
921
+ castShadow: boolean;
922
+ /** Half-extent of the directional shadow frustum (template default ±75). */
923
+ shadowArea: number;
924
+ shadowMapSize: number;
925
+ /**
926
+ * Re-center the shadow frustum on the camera every frame. A directional sun's
927
+ * shadow map covers a fixed ortho box; in a big roaming world that box must be
928
+ * huge (and so low-resolution that the player's own shadow nearly vanishes).
929
+ * With this on, a SMALL `shadowArea` tracks the camera, so contact shadows stay
930
+ * crisp wherever you go. The renderer supplies the camera position.
931
+ */
932
+ shadowFollowsCamera: boolean;
933
+ protected override _createObject3D(): Object3D;
934
+ override _syncObject3D(): void;
935
+ }
936
+ /** Point light with a range (Godot's OmniLight3D). */
937
+ declare class OmniLight3D extends Node3D {
938
+ static override readonly typeName: string;
939
+ static override readonly props: PropSchema;
940
+ color: string;
941
+ intensity: number;
942
+ /** 0 = unlimited (three `distance` semantics). */
943
+ range: number;
944
+ protected override _createObject3D(): Object3D;
945
+ override _syncObject3D(): void;
946
+ }
947
+ //#endregion
948
+ //#region src/3d/nodes/mesh-instance-3d.d.ts
949
+ type MeshKind = "box" | "sphere" | "capsule" | "plane" | "cylinder" | "gem";
950
+ interface MeshMaterialProps {
951
+ color?: string;
952
+ metalness?: number;
953
+ roughness?: number;
954
+ emissive?: string;
955
+ emissiveIntensity?: number;
956
+ wireframe?: boolean;
957
+ /** Flat per-face shading — gives faceted meshes (gem!) crisp glints that
958
+ * sparkle as the mesh turns, instead of a smoothed surface. */
959
+ flatShading?: boolean;
960
+ /** 0..1 surface opacity; below 1 turns on transparency (glassy gems, etc.). */
961
+ opacity?: number;
962
+ /** Color texture URL (sampled sRGB, tinted by `color`). */
963
+ map?: string;
964
+ /** Tangent-space normal map URL (sampled linear). */
965
+ normalMap?: string;
966
+ /** Texture tiling [u, v] across the mesh's UVs (both maps; default [1,1]).
967
+ * Box/plane UVs span 0–1 per face — scale repeat by the face's world size
968
+ * for worldspace-ish texel density (e.g. a 6×2.5 m wall at one tile per
969
+ * meter wants `repeat: [6, 2.5]`). */
970
+ repeat?: [number, number];
971
+ }
972
+ /**
973
+ * A primitive mesh. Intent-level props only:
974
+ * - `mesh`: box | sphere | capsule | plane | cylinder | gem
975
+ * - `size [x,y,z]`: box extents; sphere/gem radius = x; capsule radius = x,
976
+ * height = y; plane = x·z ground plane (laid flat); cylinder radius = x, height = y
977
+ * - `material`: `{color, metalness, roughness, wireframe, flatShading, opacity,
978
+ * emissive, emissiveIntensity, map, normalMap, repeat}` — `map`/`normalMap` are
979
+ * texture URLs loaded lazily (headless-safe: geometry and physics never
980
+ * wait on them), tiled by `repeat: [u, v]`. `gem` + `flatShading: true` + a low
981
+ * `roughness` reads as a sparkling faceted jewel (spin it for the sparkle).
982
+ */
983
+ declare class MeshInstance3D extends Node3D {
984
+ static override readonly typeName: string;
985
+ static override readonly props: PropSchema;
986
+ mesh: string;
987
+ size: number[];
988
+ material: MeshMaterialProps;
989
+ castShadow: boolean;
990
+ receiveShadow: boolean;
991
+ /** Loader hook: malformed materials fail at LOAD time, not at render. */
992
+ static validateJson(node: Node): void;
993
+ private geometryKey;
994
+ private textureKey;
995
+ private loadedTextures;
996
+ protected override _createObject3D(): Object3D;
997
+ override _syncObject3D(): void;
998
+ /** Lazy texture dressing — same headless guard as Tree3D bark: without a
999
+ * DOM there is no Image decode, so geometry/colors apply and the maps are
1000
+ * skipped. Re-loads only when a texture-shaping key changes. */
1001
+ private syncTextures;
1002
+ private loadTexture;
1003
+ override free(): void;
1004
+ }
1005
+ //#endregion
1006
+ //#region src/3d/nodes/model-instance-3d.d.ts
1007
+ /**
1008
+ * A GLB/glTF/VRM model. `model` is a `$key` into the scene's assets header
1009
+ * ({type:"model", url}) or a direct URL. `targetHeight` (>0) uniformly scales
1010
+ * the model to stand that many units tall (`npx incanto-model` reports the raw
1011
+ * size). `animation` plays either an EMBEDDED clip name or an animation-asset
1012
+ * reference (`"$run"` → {type:"animation"} entry); Mixamo-rigged assets are
1013
+ * retargeted automatically onto VRM humanoids (vibe-starter rigmap).
1014
+ *
1015
+ * GLB instances are skeleton-aware clones (one asset → many nodes). A VRM
1016
+ * mounts its SOURCE scene so the humanoid rig and springbones stay live —
1017
+ * one node per VRM asset (additional nodes warn and stay empty).
1018
+ */
1019
+ declare class ModelInstance3D extends Node3D {
1020
+ static override readonly typeName: string;
1021
+ static override readonly signals: readonly string[];
1022
+ static override props: PropSchema;
1023
+ model: string;
1024
+ animation: string;
1025
+ targetHeight: number;
1026
+ castShadow: boolean;
1027
+ /** false = play the clip once, then emit `animationFinished`. */
1028
+ animationLoop: boolean;
1029
+ receiveShadow: boolean;
1030
+ /** Hex colour multiplied into every material — RESKIN one shared GLB into many
1031
+ * variants (e.g. a sickly-green zombie from the base human). '' = untinted. */
1032
+ tint: string;
1033
+ private store;
1034
+ private entry;
1035
+ private mountedRef;
1036
+ private fittedHeight;
1037
+ private fitGroup;
1038
+ private mixer;
1039
+ private playing;
1040
+ private currentAction;
1041
+ /** action → the `animation` key that played it, so `animationFinished` reports
1042
+ * the clip that ACTUALLY finished (a crossfaded-away one-shot can finish a few
1043
+ * frames after `this.animation` already moved on). */
1044
+ private actionKeys;
1045
+ private warnedClaim;
1046
+ /** Per-mesh original (shared) materials, captured at mount so `tint` can always
1047
+ * re-derive from the source. Our clones are tracked for disposal. */
1048
+ private tintTargets;
1049
+ private tintedClones;
1050
+ private appliedTint;
1051
+ protected override _createObject3D(): Object3D;
1052
+ /** Embedded clip names + the scene's animation `$key`s (editor dropdown). */
1053
+ availableAnimations(): string[];
1054
+ /** Called by the 3D sync pass with the renderer's asset store. */
1055
+ _syncModel(assets: AssetStore3D): void;
1056
+ /** Multiply `tint` into every material — a per-instance reskin of one shared
1057
+ * GLB (e.g. base human → green zombie). Clones the captured originals (never
1058
+ * mutates the shared source), disposing prior clones on change. */
1059
+ private applyTint;
1060
+ private syncAnimation;
1061
+ override update(dt: number): void;
1062
+ override onExitTree(): void;
1063
+ private unmount;
1064
+ }
1065
+ //#endregion
1066
+ //#region src/3d/nodes/particles-3d.d.ts
1067
+ /**
1068
+ * Point-sprite particle emitter — same presets and deterministic sim as
1069
+ * Particles2D, in meters (preset distances auto-scale ÷100). `rate: 0` +
1070
+ * `burst` one-shots emit `finished`. Spread covers a sphere (`spreadDeg`
1071
+ * tilts emission out of the plane too).
1072
+ */
1073
+ declare class Particles3D extends Node3D {
1074
+ static override readonly typeName: string;
1075
+ static override readonly signals: readonly string[];
1076
+ static override readonly props: PropSchema;
1077
+ preset: string;
1078
+ emitting: boolean;
1079
+ rate: number;
1080
+ burst: number;
1081
+ lifetime: number[];
1082
+ speed: number[];
1083
+ directionDeg: number;
1084
+ spreadDeg: number;
1085
+ gravity: number[];
1086
+ drag: number;
1087
+ sizeStart: number;
1088
+ sizeEnd: number;
1089
+ colorStart: string;
1090
+ colorEnd: string;
1091
+ /** Per-particle base colours sampled by the particle's stable seed (multi-
1092
+ * colour confetti). Empty → the colorStart→colorEnd ramp is used unchanged. */
1093
+ paletteColors: string[];
1094
+ alphaStart: number;
1095
+ alphaEnd: number;
1096
+ /** Per-particle alpha twinkle frequency in Hz (0 = off) — a seed-phased sine
1097
+ * over the particle's age, so each sparkle glitters on its own rhythm. */
1098
+ shimmer: number;
1099
+ blend: string;
1100
+ /** Depth-test against scene geometry (default true). Set false so the effect
1101
+ * isn't depth-occluded by the scene — pair with a high `renderOrder` to also
1102
+ * win the TRANSPARENT draw-order against alpha scenery (e.g. grass blades,
1103
+ * which write depth AND draw after a default-order particle, hiding it). */
1104
+ depthTest: boolean;
1105
+ maxParticles: number;
1106
+ static validateJson(node: Node): void;
1107
+ private sim;
1108
+ private bursted;
1109
+ private finishedEmitted;
1110
+ /** @internal */
1111
+ _ensureSim(): ParticleSim;
1112
+ override update(dt: number): void;
1113
+ /** Restart a one-shot (re-burst + allow finished to fire again). */
1114
+ replay(): void;
1115
+ private points;
1116
+ private positions;
1117
+ private colors;
1118
+ /** Parsed `paletteColors`, reused; rebuilt only when the hex list changes. */
1119
+ private paletteCache;
1120
+ private paletteCacheKey;
1121
+ override _syncObject3D(): void;
1122
+ /** Parse `paletteColors` into a reused `Color[]`, rebuilt only when the hex
1123
+ * list changes (zero per-frame allocation); null when empty so the caller
1124
+ * uses the colorStart→colorEnd ramp. */
1125
+ private refreshPalette;
1126
+ private syncParticles;
1127
+ }
1128
+ //#endregion
1129
+ //#region src/3d/terrain/heightmap.d.ts
1130
+ /**
1131
+ * A smooth radial depression carved into the terrain — a lake/pond bowl. x/z
1132
+ * are terrain-LOCAL centered meters (the same space `heightAt`/`slopeAt` use);
1133
+ * `radius`/`depth` are meters. The carve is a smoothstep bowl (0 slope at the
1134
+ * rim), so banks blend into the surrounding ground with no cliff.
1135
+ */
1136
+ interface BasinSpec {
1137
+ x: number;
1138
+ z: number;
1139
+ radius: number;
1140
+ depth: number;
1141
+ }
1142
+ interface HeightmapOptions {
1143
+ /** Terrain extent in meters along x. */
1144
+ width: number;
1145
+ /** Terrain extent in meters along z. */
1146
+ depth: number;
1147
+ /** Grid segments along x (vertices = segsX+1). */
1148
+ segsX: number;
1149
+ /** Grid segments along z (vertices = segsZ+1). */
1150
+ segsZ: number;
1151
+ /** Height scale — realized heights land at roughly 3.5–8× this value. */
1152
+ maxHeight: number;
1153
+ /** Integer seed — same seed, same terrain, every machine. */
1154
+ seed: number;
1155
+ /** Detail persistence (amplitude falloff per detail octave). */
1156
+ roughness?: number;
1157
+ /** Detail octave count. */
1158
+ detail?: number;
1159
+ /** Snap-flatten heights within ±0.6 m of maxHeight·flatThreshold. */
1160
+ flatThreshold?: number;
1161
+ /** Curl the border down a quarter circle and floor it at edgeBottom. */
1162
+ islandEdge?: boolean;
1163
+ /** Quarter-circle radius in meters (clamped to [0, 45]). */
1164
+ edgeWrapRadius?: number;
1165
+ /** Floor y at the outermost ring once the wrap completes. */
1166
+ edgeBottom?: number;
1167
+ /** 0..1 — 1 rounds island corners fully, 0 keeps them square-ish. */
1168
+ edgeCornerSmoothness?: number;
1169
+ /** Smooth radial lake/pond bowls carved into the surface (interior basins). */
1170
+ basins?: BasinSpec[];
1171
+ }
1172
+ interface Heightmap {
1173
+ readonly width: number;
1174
+ readonly depth: number;
1175
+ readonly segsX: number;
1176
+ readonly segsZ: number;
1177
+ /**
1178
+ * Vertex heights INCLUDING edge wrap, `(segsX+1)·(segsZ+1)` long, indexed
1179
+ * `iz·(segsX+1)+ix` with x = -width/2 + ix·cell and z = -depth/2 + iz·cell —
1180
+ * exactly a rotateX(-π/2) PlaneGeometry's vertex order.
1181
+ */
1182
+ readonly heights: Float32Array;
1183
+ /** Realized height range EXCLUDING edge wrap — normalizes splat heights. */
1184
+ readonly minHeight: number;
1185
+ readonly maxHeight: number;
1186
+ /** Bilinear height lookup in terrain-local centered meters (clamps outside). */
1187
+ heightAt(x: number, z: number): number;
1188
+ /** Analytic pipeline height WITHOUT edge wrap (splat domain), same coords. */
1189
+ baseHeight(x: number, z: number): number;
1190
+ /** Splat slope at a point: atan(|∇ baseHeight|) via central differences. */
1191
+ slopeAt(x: number, z: number): number;
1192
+ }
1193
+ /** One splat layer: a texture plus the trapezoid ranges that place it. */
1194
+ interface TerrainLayer {
1195
+ /** Base color texture URL. */
1196
+ texture: string;
1197
+ /** Optional tangent-space normal map URL. */
1198
+ normalMap?: string;
1199
+ /** World-UV tiling factor (UV = worldXZ · repeat · 0.1). Default 2. */
1200
+ repeat?: number;
1201
+ /** Normalized [min, max] over the realized height span. Default [0, 1]. */
1202
+ heightRange?: [number, number];
1203
+ /** [min, max] slope in radians (atan of gradient). Default [0, π/2]. */
1204
+ slopeRange?: [number, number];
1205
+ /** Linear falloff width OUTSIDE heightRange (fraction of span). Default 0.1. */
1206
+ heightBlendRange?: number;
1207
+ /** Linear falloff width outside slopeRange (radians). Default 0.1. */
1208
+ slopeBlendRange?: number;
1209
+ /** PBR roughness scalar for this layer. Default 1. */
1210
+ roughness?: number;
1211
+ /** PBR metalness scalar for this layer. Default 0. */
1212
+ metalness?: number;
1213
+ /** Anti-tiling UV jitter strength. Default 0.15. */
1214
+ uvNoiseIntensity?: number;
1215
+ }
1216
+ /**
1217
+ * Build the deterministic heightfield. Heavy (one noise eval per vertex) —
1218
+ * call once and keep the result.
1219
+ */
1220
+ declare function buildHeightmap(opts: HeightmapOptions): Heightmap;
1221
+ /**
1222
+ * Per-vertex splat weights for up to 4 layers. `height01` is the vertex
1223
+ * height normalized over the heightmap's realized (no-wrap) span, `slopeRad`
1224
+ * is `atan(|∇h|)`. Weight = heightTrapezoid · slopeTrapezoid, sharpened by
1225
+ * `pow(w, 1/blendingStrength)`, then normalized to sum 1 — when nothing
1226
+ * matches, layer 0 takes everything. Always returns exactly 4 entries
1227
+ * (missing layers weigh 0).
1228
+ */
1229
+ declare function splatWeights(height01: number, slopeRad: number, layers: readonly TerrainLayer[], blendingStrength?: number): [number, number, number, number];
1230
+ //#endregion
1231
+ //#region src/3d/terrain/themes.d.ts
1232
+ /** Valid `Terrain3D.theme` values — drives the prop options AND validateJson. */
1233
+ declare const TERRAIN_THEMES: readonly ["island", "alpine", "plains", "desert", "grassland", "forest", "savanna", "snow", "wetland", "volcanic", "custom"];
1234
+ type TerrainTheme = (typeof TERRAIN_THEMES)[number];
1235
+ /** Live CDN the agent8 starter terrains splat from — works without any setup.
1236
+ * (Lives here so themes can detect it; re-exported by Terrain3D.) */
1237
+ declare const DEFAULT_TERRAIN_TEXTURE_BASE = "https://agent8-games.verse8.io/assets/3D/default/textures/terrain";
1238
+ /**
1239
+ * Resolve a theme to its 3–4 splat layers against a texture CDN base
1240
+ * (`<base>/<name>.png` + `<base>/<name>_normal.png`). Height ranges are
1241
+ * NORMALIZED 0..1 over the terrain's realized (no-edge-wrap) height span,
1242
+ * slope ranges are radians of `atan(|∇h|)`. 'custom' has no preset — the
1243
+ * node hard-fails before ever calling this with it.
1244
+ */
1245
+ declare function terrainThemeLayers(theme: TerrainTheme, textureBase: string): TerrainLayer[];
1246
+ //#endregion
1247
+ //#region src/3d/nodes/terrain-3d.d.ts
1248
+ /**
1249
+ * Procedural heightfield terrain with biome texture splatting — seeded
1250
+ * simplex octaves displace a PlaneGeometry once on the CPU, per-vertex
1251
+ * height/slope trapezoids pick between 4 biome textures (vertex-color
1252
+ * weights, world-UV anti-tiled sampling in a patched MeshStandardMaterial).
1253
+ * Ported from the agent8 `vibe-starter-3d-environment` terrain stack.
1254
+ *
1255
+ * - `theme` presets the splat layers (and the island edge wrap); 'custom'
1256
+ * takes your own `layers`.
1257
+ * - `heightAt(x, z)` answers the surface height at WORLD coords — placement,
1258
+ * spawning, AI all read it, headless included (no renderer needed).
1259
+ * - Physics: put the terrain UNDER a `StaticBody3D` with
1260
+ * `collider: { shape: 'heightfield' }` — the body pulls this node's grid.
1261
+ *
1262
+ * Heights realize at roughly 3.5–8× `maxHeight` (the ported pipeline sums
1263
+ * noise octaves above zero) — treat `maxHeight` as a scale knob, read the
1264
+ * real numbers off `heightAt`.
1265
+ */
1266
+ declare class Terrain3D extends Node3D {
1267
+ static override readonly typeName: string;
1268
+ static override readonly props: PropSchema;
1269
+ /** [width, depth] extent in meters, centered on the node. */
1270
+ size: number[];
1271
+ /** Height scale (realized heights land at ~3.5–8× this — see class docs). */
1272
+ maxHeight: number;
1273
+ /** Integer seed — the same seed grows the identical terrain everywhere. */
1274
+ seed: number;
1275
+ /** Grid segments per side (2–128). 128 ≈ 16k vertices, built once. */
1276
+ resolution: number;
1277
+ /** 'island' | 'alpine' | 'plains' | 'desert' | 'custom'. */
1278
+ theme: string;
1279
+ /** Detail-octave persistence — higher = more rugged. */
1280
+ roughness: number;
1281
+ /** Detail octave count. */
1282
+ detail: number;
1283
+ /** Snap-flatten a plateau within ±0.6 m of maxHeight·flatThreshold. */
1284
+ flatThreshold: number;
1285
+ /** Wrap edges down into an island rim. null = theme decides (island: on). */
1286
+ islandEdge: boolean | null;
1287
+ /** Custom splat layers (theme 'custom' only, 1–4 entries). */
1288
+ layers: TerrainLayer[];
1289
+ /** CDN base for theme textures: `<base>/<name>.png` (+`_normal`). */
1290
+ textureBase: string;
1291
+ /** Lake/pond bowls carved into the surface — smooth radial depressions the
1292
+ * collider + heightAt + dressing all see (place a Water3D in each). */
1293
+ basins: BasinSpec[];
1294
+ /** Loader hook: bad themes/layers/grids fail at LOAD, not at render. */
1295
+ static validateJson(node: Node): void;
1296
+ private heightmap;
1297
+ private heightmapKey;
1298
+ private builtKey;
1299
+ private loadedTextures;
1300
+ protected override _createObject3D(): Object3D;
1301
+ /**
1302
+ * Terrain surface height (world y) at WORLD x/z — works headless, before
1303
+ * any render. Ancestor transforms are translation-only (the same contract
1304
+ * physics bodies follow).
1305
+ */
1306
+ heightAt(x: number, z: number): number;
1307
+ /** @internal The pure heightfield (lazily built — physics pulls this). */
1308
+ _heightmap(): Heightmap;
1309
+ /** The splat layers in effect — theme preset, or `layers` for 'custom'. */
1310
+ resolvedLayers(): TerrainLayer[];
1311
+ override _syncObject3D(): void;
1312
+ private effectiveIslandEdge;
1313
+ private segments;
1314
+ /** Rebuild geometry+material only when a terrain-shaping prop changed. */
1315
+ private rebuildIfNeeded;
1316
+ private buildGeometry;
1317
+ private buildMaterial;
1318
+ private disposeTextures;
1319
+ override free(): void;
1320
+ }
1321
+ //#endregion
1322
+ //#region src/3d/vegetation/tree-blueprint.d.ts
1323
+ /**
1324
+ * Tree3D construction recipes — the pure half of the procedural trees.
1325
+ * A (tier, type) pair maps to exact segment/layer/branch counts; the
1326
+ * geometry builder consumes these so tests can pin the numbers without
1327
+ * touching three.
1328
+ */
1329
+ type TreeTier = "simple" | "medium" | "high";
1330
+ type TreeType = "conifer" | "broadleaf" | "dead" | "bush";
1331
+ //#endregion
1332
+ //#region src/3d/nodes/tree-3d.d.ts
1333
+ interface TreeVariant {
1334
+ branches: InstancedMesh;
1335
+ /** null for dead trees (leafless recipes). */
1336
+ leaves: InstancedMesh | null;
1337
+ }
1338
+ /**
1339
+ * Procedural trees grown by the ported ez-tree generator (recursive gnarled
1340
+ * branches + textured leaf billboards) — one node is a whole grove in ≤6
1341
+ * draw calls: up to 3 seed VARIANTS, each one branches-InstancedMesh plus
1342
+ * one leaves-InstancedMesh, with `count` instances scattered over an XZ
1343
+ * `area` and distributed among the variants.
1344
+ *
1345
+ * `type` picks the recipe family: 'conifer' → pine whorls, 'broadleaf' →
1346
+ * oak/ash/aspen rotation, 'dead' → bare branches, no leaves, 'bush' → the
1347
+ * ported bush_1 shrub (undergrowth — pair with height 2–4). `tier` picks
1348
+ * the cost: 'simple' keeps the original primitive low-poly trees (cheapest,
1349
+ * unchanged), 'medium' grows the light `*-forest` presets (≤3k tris/tree —
1350
+ * forests), 'high' the full presets (8–20k tris/tree — hero trees).
1351
+ * validateJson enforces count × tris/tree ≤ 1.5M at LOAD time.
1352
+ *
1353
+ * Everything is deterministic from the `seed` PROP (never the engine rng):
1354
+ * the same scene JSON grows the identical grove on every machine. Leaves are
1355
+ * alpha-cutout quads (MeshStandardMaterial) sampling the packaged ez-tree
1356
+ * textures, riding a ported simplex-wind vertex sway on medium+.
1357
+ */
1358
+ declare class Tree3D extends Node3D {
1359
+ static override readonly typeName: string;
1360
+ static override readonly props: PropSchema;
1361
+ /** 'simple' (primitive low-poly) | 'medium' (light ez-trees) | 'high' (full). */
1362
+ tier: string;
1363
+ /** 'conifer' | 'broadleaf' | 'dead' | 'bush' — the recipe family. */
1364
+ type: string;
1365
+ /** Construction seed — identical grove across runs and machines. */
1366
+ seed: number;
1367
+ /** Tree height in meters (each instance jitters ±20%). */
1368
+ height: number;
1369
+ /** Instances — count > 1 turns the node into a forest patch. */
1370
+ count: number;
1371
+ /** [x, z] scatter extent in meters when count > 1, centered on the node. */
1372
+ area: number[];
1373
+ trunkColor: string;
1374
+ /** Leaf/canopy tint — hue is kept, value is normalized over leaf textures. */
1375
+ canopyColor: string;
1376
+ /** Override the leaf texture URL ('' = packaged per-type ez-tree texture). */
1377
+ leafTexture: string;
1378
+ /** Leaf LOD: leaf cards begin collapsing toward their branch at this camera
1379
+ * distance (m). 0 (with leafFadeEnd 0) disables LOD — full leaves at any range. */
1380
+ leafFadeStart: number;
1381
+ /** Leaf LOD: cards are fully collapsed (zero overdraw) by here — tune near the
1382
+ * scene fog far so the thinning hides in fog. leafFadeEnd > leafFadeStart to enable. */
1383
+ leafFadeEnd: number;
1384
+ /** Let leaves cast shadows. false keeps trunk/branch shadows but drops the
1385
+ * expensive alpha-cutout leaf shadow pass (the dappled floor) for big FPS. */
1386
+ leafShadows: boolean;
1387
+ /** Drape the grove over a Terrain3D: each scattered instance roots at the
1388
+ * ground height under it (not the node's single Y), so a patch on rolling or
1389
+ * carved terrain never floats/buries. No effect on a count:1 tree placed by
1390
+ * hand unless it sits off the ground. */
1391
+ drape: boolean;
1392
+ /** Node path to the Terrain3D to drape onto ('' = auto-find the first one). */
1393
+ terrain: string;
1394
+ /** Resolved drape target + the grove's world position, set per rebuild. */
1395
+ private drapeTerrain;
1396
+ private drapeWorld;
1397
+ /** Loader hook: bad tiers/types/counts/budgets fail at LOAD, not at render. */
1398
+ static validateJson(node: Node): void;
1399
+ private time;
1400
+ private grove;
1401
+ private variants;
1402
+ private simpleTrunk;
1403
+ private simpleCanopy;
1404
+ private swayUniform;
1405
+ private leafFadeUniform;
1406
+ private loadedTextures;
1407
+ private groveKey;
1408
+ protected override _createObject3D(): Object3D;
1409
+ /** @internal First branches/trunk InstancedMesh (tests reach in here). */
1410
+ _trunk(): InstancedMesh;
1411
+ /** @internal First canopy/leaves InstancedMesh — null for dead trees. */
1412
+ _canopy(): InstancedMesh | null;
1413
+ /** @internal Every grown variant (medium/high tiers; empty for 'simple'). */
1414
+ _variantMeshes(): TreeVariant[];
1415
+ override update(dt: number): void;
1416
+ override _syncObject3D(): void;
1417
+ /** Re-grow only when a grove-shaping prop changed (the JSON IS the grove). */
1418
+ private rebuildIfNeeded;
1419
+ /** Resolve the drape target + cache the grove's world position for `bedY`. */
1420
+ private resolveDrape;
1421
+ /** Grove-local Y that roots an instance on the draped terrain (0 = flat). */
1422
+ private bedY;
1423
+ /** The original primitive trees — the cheap path, byte-for-byte unchanged. */
1424
+ private buildSimpleGrove;
1425
+ /** The ported ez-tree grove: K seed variants, instances dealt round-robin. */
1426
+ private buildEzGrove;
1427
+ /**
1428
+ * Trunk/branch dressing. Dead trees stay barkless weathered gray (flat
1429
+ * `trunkColor` — bark long gone from a snag); live medium/high trees
1430
+ * sample the packaged ez-tree bark set (color + normal + roughness, 1k)
1431
+ * tiled by the upstream per-preset repeat over the generator's ring UVs —
1432
+ * ridged bark up close instead of smooth plastic. The default `trunkColor`
1433
+ * hands the tint to the upstream preset (the maps carry the color); a
1434
+ * customized prop keeps its hue max-channel-normalized so it TINTS the map
1435
+ * instead of multiplying brown×brown into mud.
1436
+ */
1437
+ private buildBarkMaterial;
1438
+ private loadBarkTexture;
1439
+ /** Per-instance placement + jitter — ONE rng draw order for both paths. */
1440
+ private scatter;
1441
+ private disposeMeshes;
1442
+ override free(): void;
1443
+ }
1444
+ //#endregion
1445
+ //#region src/3d/nodes/voxel-grid-3d.d.ts
1446
+ /**
1447
+ * Face colors per tile (front,right,back,left,top,bottom) — the agent8
1448
+ * minecraft template's exact 26-entry palette (tile 1 = grass, 2 = dirt,
1449
+ * 5 = bedrock, …). Override per-scene via the `palette` prop.
1450
+ */
1451
+ declare const VOXEL_PALETTE: number[][][];
1452
+ interface VoxelBlock {
1453
+ x: number;
1454
+ y: number;
1455
+ z: number;
1456
+ tile: number;
1457
+ }
1458
+ /**
1459
+ * A whole voxel terrain in ONE draw call: an InstancedMesh of unit cubes
1460
+ * with per-face instanced colors, simple directional shading and darkened
1461
+ * edges — the minecraft-template look. Blocks are runtime data
1462
+ * (`setBlocks`/`addBlock`) or baked scene JSON via the `voxels` prop
1463
+ * (`[[x,y,z,tile], …]` — what `incanto-env terrain` emits), applied on the
1464
+ * next update; runtime edits take over from there. Colliders are the
1465
+ * GAME's job (chunked trimeshes near the player — see the minecraft
1466
+ * template's Terrain behavior).
1467
+ */
1468
+ declare class VoxelGrid3D extends Node3D {
1469
+ static override readonly typeName: string;
1470
+ static override readonly signals: readonly string[];
1471
+ static override readonly props: PropSchema;
1472
+ /** JSON-authored blocks as [x, y, z, tile] tuples (generated terrains bake these). */
1473
+ voxels: number[][];
1474
+ private appliedVoxels;
1475
+ private readonly map;
1476
+ private mesh;
1477
+ private dirty;
1478
+ blockCount(): number;
1479
+ tileAt(x: number, y: number, z: number): number | undefined;
1480
+ setBlocks(blocks: VoxelBlock[]): void;
1481
+ addBlock(b: VoxelBlock): void;
1482
+ removeBlockAt(x: number, y: number, z: number): boolean;
1483
+ /** All blocks (for chunk collider builders). */
1484
+ blocks(): VoxelBlock[];
1485
+ protected override _createObject3D(): Object3D;
1486
+ override update(): void;
1487
+ }
1488
+ //#endregion
1489
+ //#region src/3d/water/interaction.d.ts
1490
+ /**
1491
+ * Water3D character interaction — pure functions, no three, node-env testable.
1492
+ *
1493
+ * The node samples physics bodies each update, detects waterline crossings
1494
+ * (`detectCrossings`), and converts them into ripple impulses in a FIXED-SIZE
1495
+ * ring buffer that mirrors the shader's `uRipples[8]` uniform array
1496
+ * (`pushRipple` / `ageRipples`). `rippleHeight` is the exact ring-wave shape
1497
+ * the vertex shader evaluates — `quality: 'simple'` runs it on the CPU.
1498
+ */
1499
+ /** Ring-buffer capacity — MUST match `uRipples[8]` in the water shaders. */
1500
+ declare const WATER_MAX_RIPPLES = 8;
1501
+ /** One expanding ring wave, in world XZ. */
1502
+ interface Ripple {
1503
+ x: number;
1504
+ z: number;
1505
+ /** Seconds since impact (real time — waveSpeed does not stretch ripples). */
1506
+ age: number;
1507
+ /** Impulse amplitude in meters. */
1508
+ amp: number;
1509
+ /** Splash-foam strength 0..1 the shaders paint as surface froth (omitted = 0). */
1510
+ foam?: number;
1511
+ }
1512
+ //#endregion
1513
+ //#region src/3d/nodes/water-3d.d.ts
1514
+ /**
1515
+ * A water surface on XZ. `quality: 'fancy'` (default) is a commercial-grade
1516
+ * water shader — 9-iteration simplex FBM displacement with analytic normals,
1517
+ * two animated procedural detail-normal layers (`detailStrength`), a sun
1518
+ * specular glint+sheen (`sunDirection`/`sunColor`/`sunIntensity`), Beer's-law
1519
+ * depth `absorption` (clear shallows → dark depths), screen-space
1520
+ * `refraction` of the submerged scene, depth-texture shoreline `foam` (on by
1521
+ * default), a trough/surface/peak color ramp and fresnel-mixed CubeCamera
1522
+ * reflections (re-rendered every `reflectionInterval` ms). `quality:
1523
+ * 'simple'` is the cheap CPU sine-wave material (low-end fallback; also the
1524
+ * deterministic headless path).
1525
+ *
1526
+ * Per-frame GPU budget for fancy (beyond drawing the surface): ONE half-res
1527
+ * scene pre-pass that feeds depth (absorption/foam/soft intersections) AND
1528
+ * the refraction color grab; the cube reflection stays on its `1000 ms`
1529
+ * throttle.
1530
+ *
1531
+ * Character interaction (`interaction: true`): each update the node scans the
1532
+ * tree for CharacterBody3D/RigidBody3D, compares their world y against the
1533
+ * waterline at their xz (wave offset included, CPU twin of the shader), emits
1534
+ * `entered`/`exited` with the body node, and pushes ripple impulses into a
1535
+ * fixed 8-slot ring buffer both shaders turn into expanding ring waves. The
1536
+ * surface has no bottom — anything below the waterline inside the XZ
1537
+ * footprint counts as in-water (pair with `Area3D` for volumetric gameplay).
1538
+ *
1539
+ * GPU-only features (reflection/foam) run through the renderer's
1540
+ * `_onRender3D` hand-off — headless runs simply never invoke it.
1541
+ */
1542
+ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
1543
+ static override readonly typeName: string;
1544
+ static override readonly signals: readonly string[];
1545
+ static override readonly props: PropSchema;
1546
+ /** [width, depth] in meters (the surface lies on XZ). */
1547
+ size: number[];
1548
+ /** Legacy single hue: simple-quality color; tints the fancy ramp when set. */
1549
+ color: string;
1550
+ opacity: number;
1551
+ /** Draw after opaque geometry (overrides Node3D's 0 default). */
1552
+ override renderOrder: number;
1553
+ /** Wave intensity dial, meters-ish. 0 = mirror-flat; 0.04 (default) reads
1554
+ * as a calm sea; 0.08 = the noisier ported-source look. */
1555
+ waveHeight: number;
1556
+ waveSpeed: number;
1557
+ /** 'fancy' shader water (default) | 'simple' cheap CPU sine material. */
1558
+ quality: string;
1559
+ /** Fancy ramp overrides: `{trough?, surface?, peak?}` hex strings. */
1560
+ colors: JsonObject;
1561
+ /** CubeCamera reflections (fancy only; needs a live renderer). */
1562
+ reflection: boolean;
1563
+ /** ms between reflection (and foam depth) re-renders. */
1564
+ reflectionInterval: number;
1565
+ /** Depth-texture shoreline foam — rides the always-on fancy scene pass. */
1566
+ foam: boolean;
1567
+ /** Bodies crossing the surface emit signals and raise ripples. */
1568
+ interaction: boolean;
1569
+ /** Splash WHITEWATER: the shaders paint expanding surface foam where bodies
1570
+ * interact — a "풍덩" froth bloom on entry (scaled by fall speed) and a foam
1571
+ * trail ("물살") behind a body wading/running through. Needs `interaction`. */
1572
+ splash: boolean;
1573
+ /** Fixed sun for the specular glint — match the scene's key light. */
1574
+ sunDirection: number[];
1575
+ /** Sun glint/sheen tint. */
1576
+ sunColor: string;
1577
+ /** Sun glint/sheen strength — 0 disables the specular entirely. */
1578
+ sunIntensity: number;
1579
+ /** Animated detail-normal strength — the close-up liquid feel (0 = off). */
1580
+ detailStrength: number;
1581
+ /** Beer's-law absorption per meter of water depth (0 = no depth tinting).
1582
+ * Per-channel under the hood: red dies ~3× faster than blue, so shallows
1583
+ * over a bright bottom read turquoise before fading to deep blue. */
1584
+ absorption: number;
1585
+ /** Screen-space refraction of the submerged scene (fancy only). */
1586
+ refraction: boolean;
1587
+ /**
1588
+ * Submerged-camera look: when the camera dips below this surface (inside its
1589
+ * XZ footprint) the scene switches to an underwater fog + tint for that frame
1590
+ * — the murky, limited-visibility feel of being underwater. `true` (default)
1591
+ * derives the murk from `color`; `false` disables it; an object
1592
+ * `{ color?, visibility? }` overrides the fog hue / view distance (meters).
1593
+ */
1594
+ underwater: boolean | JsonObject;
1595
+ /** Loader hook: degenerate planes and bad enums fail at LOAD, not at render. */
1596
+ static validateJson(node: Node): void;
1597
+ private time;
1598
+ private surfaceKey;
1599
+ /** @internal Live ripple ring buffer (≤8) — tests and tools read this. */
1600
+ readonly _ripples: Ripple[];
1601
+ private inWater;
1602
+ private readonly bobTimers;
1603
+ /** Last position a wake ripple was dropped, per in-water body (for the trail). */
1604
+ private readonly wadeAnchors;
1605
+ private readonly _scratchA;
1606
+ private readonly _scratchB;
1607
+ private readonly _bodies;
1608
+ private cubeTarget;
1609
+ private cubeCamera;
1610
+ /** Reflection near plane (max half extent) — recreate the camera on resize. */
1611
+ private reflectionNear;
1612
+ private lastReflectionAt;
1613
+ /** Per-frame scene pre-pass: depth (absorption/foam) + color grab (refraction). */
1614
+ private scenePassTarget;
1615
+ protected override _createObject3D(): Object3D;
1616
+ override update(dt: number): void;
1617
+ override _syncObject3D(): void;
1618
+ /**
1619
+ * @internal Renderer hand-off (see {@link RenderContext3D}): re-render the
1620
+ * CubeCamera reflection every `reflectionInterval` ms, plus ONE per-frame
1621
+ * half-res scene pre-pass whose depth texture drives Beer's-law absorption,
1622
+ * shoreline foam and soft intersections, and whose color texture is the
1623
+ * refraction grab — one extra render feeds all three.
1624
+ */
1625
+ _onRender3D(ctx: RenderContext3D): void;
1626
+ /**
1627
+ * Camera-submersion query (the renderer asks every water node each frame): the
1628
+ * resolved underwater look + this surface's Y when the camera EYE is below
1629
+ * THIS surface inside its XZ footprint, else null. Deliberately gated to the
1630
+ * eye being underwater (not merely near the surface) so wading with the camera
1631
+ * above the water never tints the view. Cheap above water (geometric test first).
1632
+ */
1633
+ underwaterAt(camX: number, camY: number, camZ: number): (UnderwaterConfig & {
1634
+ surfaceY: number;
1635
+ }) | null;
1636
+ override free(): void;
1637
+ /** Swap geometry+material only when a surface-shaping prop changed. */
1638
+ private rebuildSurfaceIfNeeded;
1639
+ private buildFancyMaterial;
1640
+ /** The cheap lake ShaderMaterial — fresnel sky tint + ripple normals + sun
1641
+ * glint + edge fade, NO scene re-renders (see lake.ts). */
1642
+ private buildLakeMaterial;
1643
+ private syncFancy;
1644
+ /**
1645
+ * @internal Environment-sky sun hand-off (see {@link SunConsumer3D}): the
1646
+ * sky's sun drives the glint ONLY while the `sunDirection` prop sits at its
1647
+ * schema default — an explicitly authored prop always wins. Runs right
1648
+ * after `_syncObject3D` (which wrote the prop value), overwriting the
1649
+ * uniform for this frame.
1650
+ */
1651
+ _applySunDirection(dir: readonly [number, number, number]): void;
1652
+ /** The fancy three-color ramp; a customized legacy `color` tints it. */
1653
+ private resolvePalette;
1654
+ private syncSimple;
1655
+ private stepInteraction;
1656
+ /** Base wave height at world (x, z) — the CPU twin of the active shader.
1657
+ * Ripples are excluded on purpose: a splash must not re-trigger itself. */
1658
+ private waveOffsetAt;
1659
+ }
1660
+ //#endregion
1661
+ //#region src/3d/register.d.ts
1662
+ /**
1663
+ * Register the 3D node taxonomy (and the core nodes). Call once in your game
1664
+ * entry before loading a 3D scene. Explicit — never an import side effect.
1665
+ */
1666
+ declare function registerNodes3D(): void;
1667
+ //#endregion
1668
+ //#region src/3d/renderer.d.ts
1669
+ interface Renderer3DOptions {
1670
+ canvas: HTMLCanvasElement;
1671
+ engine: Engine;
1672
+ /** Bring your own model store (custom loader) — defaults to a GLTF+VRM one. */
1673
+ assets?: AssetStore3D;
1674
+ /** Defaults to `window.devicePixelRatio` capped at 2. */
1675
+ pixelRatio?: number;
1676
+ }
1677
+ /**
1678
+ * WebGL presentation layer: subscribes to `engine.updated`, mirrors the active
1679
+ * scene's node tree onto a three.js scene, applies the scene `environment`
1680
+ * header (ambient/background/sky/fog/shadows/exposure — see Environment3D),
1681
+ * and renders with the current Camera3D under ACES filmic tone mapping.
1682
+ *
1683
+ * Keep this class thin — everything testable lives in `syncTree`, the node
1684
+ * classes and `Environment3D`; this file is the only place that touches WebGL.
1685
+ */
1686
+ declare class Renderer3D {
1687
+ /**
1688
+ * When set, the render uses THIS free camera instead of the scene's active
1689
+ * Camera3D — tooling (the scene editor) orbits/pans without touching scene
1690
+ * data. `null` restores normal camera behavior.
1691
+ */
1692
+ viewOverride: {
1693
+ position: [number, number, number];
1694
+ target: [number, number, number];
1695
+ } | null;
1696
+ private readonly overrideCam;
1697
+ private lastCamera;
1698
+ private lastSize;
1699
+ private readonly webgl;
1700
+ private readonly threeScene;
1701
+ private readonly environment;
1702
+ private readonly engine;
1703
+ private readonly disconnect;
1704
+ private readonly canvas;
1705
+ private readonly assets;
1706
+ private readonly ownsAssets;
1707
+ private readonly loadedAssetScenes;
1708
+ /** The scene whose GPU programs have been pre-compiled (warm-up, see render). */
1709
+ private compiledScene;
1710
+ /** Reused per-frame walk scratch (instance-scoped — never a module global). */
1711
+ private readonly syncScratch;
1712
+ /** Reused render-hook context — gl/scene are stable, camera reassigned/frame. */
1713
+ private renderCtx;
1714
+ /** Underwater caustics pass (lazy — only created the first time submerged). */
1715
+ private causticsTarget;
1716
+ private causticsScene;
1717
+ private causticsUniforms;
1718
+ /** Volumetric cloud pass (lazy — only created when environment.clouds is set).
1719
+ * The raymarch runs at HALF resolution into `cloudsHalfTarget`, then a cheap
1720
+ * full-res composite blends it over the scene — keeps the heavy fullscreen
1721
+ * raymarch off the critical path so it holds 60fps at retina. */
1722
+ private cloudsTarget;
1723
+ private cloudsHalfTarget;
1724
+ private cloudsBlurTarget;
1725
+ private cloudsScene;
1726
+ private cloudsUniforms;
1727
+ private cloudsBlurScene;
1728
+ private cloudsBlurUniforms;
1729
+ private cloudsCompositeScene;
1730
+ private cloudsCompositeUniforms;
1731
+ constructor(opts: Renderer3DOptions);
1732
+ private readonly debugLines;
1733
+ private syncDebugLines;
1734
+ private render;
1735
+ /**
1736
+ * Volumetric cloud pass (only when `environment.clouds` is declared — scenes
1737
+ * without it render exactly as before, zero cost). Three steps:
1738
+ * 1. scene → full-res offscreen target (color + depth)
1739
+ * 2. raymarch the cloud slab → HALF-res target (the heavy fullscreen work,
1740
+ * run at ¼ the pixels so it holds 60fps even at retina)
1741
+ * 3. composite the half-res cloud buffer over the full-res scene → screen
1742
+ */
1743
+ private renderWithClouds;
1744
+ /**
1745
+ * Underwater caustics pass (only while submerged — normal rendering never
1746
+ * touches this). Render the scene to an offscreen color+depth target, then a
1747
+ * fullscreen composite that reconstructs each pixel's WORLD position from depth
1748
+ * and adds an animated caustic highlight below the water line. World-keyed, so
1749
+ * the pattern sticks to the floor/props instead of being a flat screen overlay.
1750
+ */
1751
+ private renderWithCaustics;
1752
+ /**
1753
+ * Project a world point through the LAST rendered camera to canvas CSS px.
1754
+ * `behind` is true when the point sits behind the camera (don't draw it).
1755
+ */
1756
+ screenFromWorld(wx: number, wy: number, wz: number): {
1757
+ x: number;
1758
+ y: number;
1759
+ behind: boolean;
1760
+ };
1761
+ /**
1762
+ * The node rendered under canvas pixel (sx, sy), or null — raycasts the last
1763
+ * rendered camera into the scene and resolves the hit up to its `incantoNode`
1764
+ * (every Node3D stamps that in `_ensureObject3D`). 3D depth-sorts, so the
1765
+ * nearest hit wins. Mirrors `Renderer2D.pick` for click-to-select in the editor.
1766
+ */
1767
+ pick(sx: number, sy: number): Node | null;
1768
+ /**
1769
+ * GPU counters for the LAST rendered frame, straight from three's
1770
+ * per-frame-maintained `renderer.info` (render pass + GPU memory).
1771
+ */
1772
+ stats(): RendererStats;
1773
+ /** The last rendered camera's world-space basis (for screen-plane dragging). */
1774
+ cameraBasis(): {
1775
+ right: Vector3;
1776
+ up: Vector3;
1777
+ forward: Vector3;
1778
+ };
1779
+ dispose(): void;
1780
+ }
1781
+ //#endregion
1782
+ //#region src/3d/sync.d.ts
1783
+ /** A node that accepts a per-frame spatial pose (AudioPlayer with spatial on). */
1784
+ interface SpatialConsumer {
1785
+ spatial: boolean;
1786
+ _setSpatialPose(pose: SpatialPose): void;
1787
+ }
1788
+ interface SyncResult {
1789
+ activeCamera: PerspectiveCamera | null;
1790
+ /**
1791
+ * Nodes that declared `_onRender3D`, in tree order — Renderer3D invokes
1792
+ * `node._onRender3D(ctx)` with the live WebGL context right before the main
1793
+ * render. Node refs (not bound closures) so the per-frame walk allocates
1794
+ * nothing; the renderer owns and reuses the context object.
1795
+ */
1796
+ renderHooks: RenderHook3D[];
1797
+ /**
1798
+ * The scene's MAIN directional light (highest intensity, tree order breaks
1799
+ * ties) — the environment sky aims it and `environment.shadows` makes it
1800
+ * cast. null when the scene declares none (never auto-created).
1801
+ */
1802
+ sunLight: DirectionalLight3D | null;
1803
+ }
1804
+ /**
1805
+ * Per-frame walk scratch — the caller (Renderer3D) owns ONE and reuses it every
1806
+ * frame so `syncTree` allocates nothing for its containers. Instance-scoped (a
1807
+ * field on the renderer), never a module global. Omit it and `syncTree` makes a
1808
+ * throwaway one (the test path).
1809
+ */
1810
+ interface SyncScratch {
1811
+ visited: Set<Object3D>;
1812
+ cameras: Camera3D[];
1813
+ renderHooks: RenderHook3D[];
1814
+ emitters: Array<{
1815
+ node: Node & SpatialConsumer;
1816
+ parent: Object3D;
1817
+ }>;
1818
+ state: WalkState;
1819
+ }
1820
+ interface SyncOptions {
1821
+ assets?: AssetStore3D;
1822
+ /**
1823
+ * The environment sky's unit sun direction. Pushed to every
1824
+ * `_applySunDirection` node (Water3D, Foliage3D) right after its own sync —
1825
+ * a per-frame cheap setter, chosen over sceneChanged-time wiring because the
1826
+ * walk already touches every node and uniforms are overwritten by node sync
1827
+ * each frame anyway. Nodes ignore it when their own prop was customized.
1828
+ */
1829
+ sunDirection?: readonly [number, number, number] | null;
1830
+ /** Render interpolation factor (0..1) for fixed-step physics bodies; see
1831
+ * Engine.interpolationAlpha. Defaults to 1 (render the current transform). */
1832
+ alpha?: number;
1833
+ }
1834
+ /**
1835
+ * Mirror an Incanto node tree onto a three.js scene (dirty-push, once per frame):
1836
+ *
1837
+ * - every Node3D's backing Object3D is parented under its nearest Node3D
1838
+ * ancestor's object (plain Nodes are transparent), falling back to the scene
1839
+ * - transforms/props are pushed via `_syncObject3D`
1840
+ * - backing objects whose nodes left the tree are pruned
1841
+ * - returns the active camera (`current: true` wins, else first in tree order)
1842
+ *
1843
+ * Pure scene-graph math — no WebGL — so it tests headlessly with real three.
1844
+ */
1845
+ declare function syncTree(root: Node, threeScene: Scene, assets?: AssetStore3D, opts?: SyncOptions, scratch?: SyncScratch): SyncResult;
1846
+ interface WalkState {
1847
+ visited: Set<Object3D>;
1848
+ cameras: Camera3D[];
1849
+ renderHooks: RenderHook3D[];
1850
+ emitters: Array<{
1851
+ node: Node & SpatialConsumer;
1852
+ parent: Object3D;
1853
+ }>;
1854
+ assets: AssetStore3D | undefined;
1855
+ sunDirection: readonly [number, number, number] | null;
1856
+ sunLight: DirectionalLight3D | null;
1857
+ alpha: number;
1858
+ }
1859
+ //#endregion
1860
+ export { Area3D, AssetStore3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type Game3D, type Heightmap, type HeightmapOptions, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type RigView, RigidBody3D, type Ripple, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainTheme, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_MAX_RIPPLES, Water3D, buildHeightmap, cameraRelative, createGame3D, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers };