incanto 0.13.0 → 0.14.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.
package/dist/3d.d.ts CHANGED
@@ -888,9 +888,13 @@ declare class Flowers3D extends Node3D {
888
888
  clustering: number;
889
889
  /** Gentle head-bob wind strength — 0 holds still. */
890
890
  sway: number;
891
- /** Drape each plant onto a Terrain3D's surface so flowers root on the ground
892
- * instead of a flat plane (lets ONE bed cover rolling terrain). */
893
- drape: boolean;
891
+ /**
892
+ * null (the default) is AUTO: whenever the scene has a Terrain3D, instances
893
+ * root on it — flat ground behaves as before, rolling terrain just works.
894
+ * true forces it (keeps retrying until a terrain appears), false disables
895
+ * draping entirely.
896
+ */
897
+ drape: boolean | null;
894
898
  /** Drape target: a node path to the Terrain3D. Empty = auto-find the first
895
899
  * Terrain3D in the tree (the zero-config default when `drape` is on). */
896
900
  terrain: string;
@@ -1025,9 +1029,14 @@ declare class Foliage3D extends Node3D implements SunConsumer3D {
1025
1029
  flowers: number;
1026
1030
  /** Placement seed — the field is identical across runs and machines. */
1027
1031
  seed: number;
1028
- /** Drape each instance onto a Terrain3D's surface so blades root on the
1029
- * ground instead of a flat plane (lets ONE field cover rolling terrain). */
1030
- drape: boolean;
1032
+ /**
1033
+ * Drape each instance onto a Terrain3D's surface so blades root on the
1034
+ * ground instead of a flat plane. null (the default) is AUTO: whenever the
1035
+ * scene has a Terrain3D, instances root on it — flat ground behaves as
1036
+ * before, rolling terrain just works. true forces it (keeps retrying until
1037
+ * a terrain appears), false disables draping entirely.
1038
+ */
1039
+ drape: boolean | null;
1031
1040
  /** Drape target: a node path to the Terrain3D. Empty = auto-find the first
1032
1041
  * Terrain3D in the tree (the zero-config default when `drape` is on). */
1033
1042
  terrain: string;
@@ -1061,6 +1070,8 @@ declare class Foliage3D extends Node3D implements SunConsumer3D {
1061
1070
  /** @internal Every InstancedMesh of the field: [quads] or [quads, quads90°]. */
1062
1071
  _fields(): InstancedMesh[];
1063
1072
  override update(dt: number): void;
1073
+ /** Wind time/strength + the ≤4 character-bend slots (no-op before first build). */
1074
+ private writeAnimationUniforms;
1064
1075
  override _syncObject3D(): void;
1065
1076
  /**
1066
1077
  * @internal Environment-sky sun hand-off (see {@link SunConsumer3D}): the
@@ -1821,11 +1832,14 @@ declare class Tree3D extends Node3D {
1821
1832
  /** Let leaves cast shadows. false keeps trunk/branch shadows but drops the
1822
1833
  * expensive alpha-cutout leaf shadow pass (the dappled floor) for big FPS. */
1823
1834
  leafShadows: boolean;
1824
- /** Drape the grove over a Terrain3D: each scattered instance roots at the
1825
- * ground height under it (not the node's single Y), so a patch on rolling or
1826
- * carved terrain never floats/buries. No effect on a count:1 tree placed by
1827
- * hand unless it sits off the ground. */
1828
- drape: boolean;
1835
+ /**
1836
+ * Drape the grove over a Terrain3D: each scattered instance roots at the
1837
+ * ground height under it (not the node's single Y), so a patch on rolling
1838
+ * or carved terrain never floats/buries. null (the default) is AUTO:
1839
+ * whenever the scene has a Terrain3D the grove drapes onto it. true forces
1840
+ * it (keeps retrying until a terrain appears), false disables draping.
1841
+ */
1842
+ drape: boolean | null;
1829
1843
  /** Node path to the Terrain3D to drape onto ('' = auto-find the first one). */
1830
1844
  terrain: string;
1831
1845
  /** Resolved drape target + the grove's world position, set per rebuild. */
package/dist/3d.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
- import { a as Environment3D, c as sunDirectionFromElevationAzimuth, i as syncTree, l as sunDirectionFromSky, o as horizonColorFromSky, r as Renderer3D, s as parseEnvironment3D, t as createGame3D, u as AssetStore3D } from "./create-game-J6g4QInv.js";
3
- import { A as Area3D, B as movementState, C as BoneAttachment3D, D as TERRAIN_THEMES, E as DEFAULT_TERRAIN_TEXTURE_BASE, F as Node3D, L as QUARTER_PITCH, M as PhysicsBody3D, N as RigidBody3D, O as terrainThemeLayers, P as StaticBody3D, R as cameraRelative, S as BoneLookAt3D, T as Terrain3D, V as rigPose, _ as Flowers3D, a as VoxelGrid3D, b as CharacterController3D, c as Particles3D, d as DirectionalLight3D, f as OmniLight3D, g as DENSITY_PRESETS, h as Foliage3D, i as VOXEL_PALETTE, j as CharacterBody3D, k as Joint3D, l as ModelInstance3D, m as MeshInstance3D, n as Water3D, o as Tree3D, p as InstancedMesh3D, r as WATER_MAX_RIPPLES, s as Trail3D, t as registerNodes3D, u as LoftMesh3D, v as resolveFlowerDensity, w as Billboard3D, x as Camera3D, y as FLOWER_VARIETIES, z as keyboardIntensity } from "./register-BFw4c83z.js";
2
+ import { a as Environment3D, c as sunDirectionFromElevationAzimuth, i as syncTree, l as sunDirectionFromSky, o as horizonColorFromSky, r as Renderer3D, s as parseEnvironment3D, t as createGame3D, u as AssetStore3D } from "./create-game-OSntcIiM.js";
3
+ import { A as Area3D, B as movementState, C as BoneAttachment3D, D as TERRAIN_THEMES, E as DEFAULT_TERRAIN_TEXTURE_BASE, F as Node3D, L as QUARTER_PITCH, M as PhysicsBody3D, N as RigidBody3D, O as terrainThemeLayers, P as StaticBody3D, R as cameraRelative, S as BoneLookAt3D, T as Terrain3D, V as rigPose, _ as Flowers3D, a as VoxelGrid3D, b as CharacterController3D, c as Particles3D, d as DirectionalLight3D, f as OmniLight3D, g as DENSITY_PRESETS, h as Foliage3D, i as VOXEL_PALETTE, j as CharacterBody3D, k as Joint3D, l as ModelInstance3D, m as MeshInstance3D, n as Water3D, o as Tree3D, p as InstancedMesh3D, r as WATER_MAX_RIPPLES, s as Trail3D, t as registerNodes3D, u as LoftMesh3D, v as resolveFlowerDensity, w as Billboard3D, x as Camera3D, y as FLOWER_VARIETIES, z as keyboardIntensity } from "./register-C9-16x_t.js";
4
4
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CroQPEER.js";
5
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-DhaAaKJo.js";
5
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-D_kUdOjr.js";
6
6
  //#region src/3d/environment-runtime.ts
7
7
  /**
8
8
  * Live environment editing — the renderer re-applies `scene.environment`
@@ -5,8 +5,8 @@ import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
6
6
  import { n as registerGameplayBehaviors } from "./gameplay-sSqrUcnz.js";
7
7
  import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
8
- import { F as Node3D, M as PhysicsBody3D, d as DirectionalLight3D, l as ModelInstance3D, t as registerNodes3D, x as Camera3D } from "./register-BFw4c83z.js";
9
- import { n as enablePhysics3D } from "./physics-3d-DhaAaKJo.js";
8
+ import { F as Node3D, M as PhysicsBody3D, d as DirectionalLight3D, l as ModelInstance3D, t as registerNodes3D, x as Camera3D } from "./register-C9-16x_t.js";
9
+ import { n as enablePhysics3D } from "./physics-3d-D_kUdOjr.js";
10
10
  import { ACESFilmicToneMapping, AmbientLight, Box3, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, HalfFloatType, LineBasicMaterial, LineSegments, Matrix4, Mesh, PCFShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, Quaternion, Raycaster, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
11
11
  import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
12
12
  import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
package/dist/index.js CHANGED
@@ -295,6 +295,6 @@ function newUid() {
295
295
  //#endregion
296
296
  //#region src/index.ts
297
297
  /** Engine version. Kept in sync with package.json by the release pipeline. */
298
- const VERSION = "0.13.0";
298
+ const VERSION = "0.14.0";
299
299
  //#endregion
300
300
  export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, synthSfx };
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
3
  import { n as registerDebugSource } from "./debug-draw-CZmOYjL2.js";
4
- import { A as Area3D, F as Node3D, I as validateCollider3D, M as PhysicsBody3D, N as RigidBody3D, T as Terrain3D, j as CharacterBody3D, k as Joint3D } from "./register-BFw4c83z.js";
4
+ import { A as Area3D, F as Node3D, I as validateCollider3D, M as PhysicsBody3D, N as RigidBody3D, T as Terrain3D, j as CharacterBody3D, k as Joint3D } from "./register-C9-16x_t.js";
5
5
  import { Euler, Quaternion } from "three";
6
6
  //#region src/3d/physics/physics-3d.ts
7
7
  var physics_3d_exports = /* @__PURE__ */ __exportAll({
package/dist/react.js CHANGED
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
156
156
  pointer: latest.pointer,
157
157
  ...keyboard !== void 0 ? { keyboard } : {}
158
158
  };
159
- const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-J6g4QInv.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-Ct86zczq.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-OSntcIiM.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-Ct86zczq.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -3066,7 +3066,7 @@ var Flowers3D = class extends Node3D {
3066
3066
  height: { default: .45 },
3067
3067
  clustering: { default: .6 },
3068
3068
  sway: { default: .5 },
3069
- drape: { default: false },
3069
+ drape: { default: null },
3070
3070
  terrain: { default: "" }
3071
3071
  };
3072
3072
  /** 'lush' | 'sparse' | 'none' | plants per m² (load-time check + budget). */
@@ -3087,9 +3087,13 @@ var Flowers3D = class extends Node3D {
3087
3087
  clustering = .6;
3088
3088
  /** Gentle head-bob wind strength — 0 holds still. */
3089
3089
  sway = .5;
3090
- /** Drape each plant onto a Terrain3D's surface so flowers root on the ground
3091
- * instead of a flat plane (lets ONE bed cover rolling terrain). */
3092
- drape = false;
3090
+ /**
3091
+ * null (the default) is AUTO: whenever the scene has a Terrain3D, instances
3092
+ * root on it — flat ground behaves as before, rolling terrain just works.
3093
+ * true forces it (keeps retrying until a terrain appears), false disables
3094
+ * draping entirely.
3095
+ */
3096
+ drape = null;
3093
3097
  /** Drape target: a node path to the Terrain3D. Empty = auto-find the first
3094
3098
  * Terrain3D in the tree (the zero-config default when `drape` is on). */
3095
3099
  terrain = "";
@@ -3119,7 +3123,7 @@ var Flowers3D = class extends Node3D {
3119
3123
  if (!(f.height > 0)) throw new IncantoError("BAD_FORMAT", `Flowers3D '${node.name}' height must be > 0 meters, got ${f.height}.`, { prop: "height" });
3120
3124
  if (!(f.clustering >= 0) || !(f.clustering <= 1)) throw new IncantoError("BAD_FORMAT", `Flowers3D '${node.name}' clustering must be in [0, 1] (0 uniform … 1 tight patches), got ${f.clustering}.`, { prop: "clustering" });
3121
3125
  if (!(f.sway >= 0)) throw new IncantoError("BAD_FORMAT", `Flowers3D '${node.name}' sway must be >= 0, got ${f.sway}.`, { prop: "sway" });
3122
- if (typeof f.drape !== "boolean") throw new IncantoError("BAD_FORMAT", `Flowers3D '${node.name}' drape must be a boolean, got ${JSON.stringify(f.drape)}.`, { prop: "drape" });
3126
+ if (f.drape !== null && typeof f.drape !== "boolean") throw new IncantoError("BAD_FORMAT", `Flowers3D '${node.name}' drape must be true, false or null (null = auto: drape whenever the scene has a Terrain3D), got ${JSON.stringify(f.drape)}.`, { prop: "drape" });
3123
3127
  if (typeof f.terrain !== "string") throw new IncantoError("BAD_FORMAT", `Flowers3D '${node.name}' terrain must be a node-path string (empty = auto-find), got ${JSON.stringify(f.terrain)}.`, { prop: "terrain" });
3124
3128
  }
3125
3129
  time = 0;
@@ -3172,7 +3176,7 @@ var Flowers3D = class extends Node3D {
3172
3176
  /** Resolve the drape target + cache the bed's world position for `bedY`. */
3173
3177
  resolveDrape() {
3174
3178
  this.drapeTerrain = null;
3175
- if (!this.drape) return;
3179
+ if (this.drape === false) return;
3176
3180
  const terrain = findDrapeTerrain(this, this.terrain);
3177
3181
  if (terrain) {
3178
3182
  this.drapeTerrain = terrain;
@@ -4449,7 +4453,7 @@ var Foliage3D = class Foliage3D extends Node3D {
4449
4453
  coverage: { default: .85 },
4450
4454
  flowers: { default: 0 },
4451
4455
  seed: { default: 1 },
4452
- drape: { default: false },
4456
+ drape: { default: null },
4453
4457
  terrain: { default: "" },
4454
4458
  renderOrder: { default: 2 }
4455
4459
  };
@@ -4508,9 +4512,14 @@ var Foliage3D = class Foliage3D extends Node3D {
4508
4512
  flowers = 0;
4509
4513
  /** Placement seed — the field is identical across runs and machines. */
4510
4514
  seed = 1;
4511
- /** Drape each instance onto a Terrain3D's surface so blades root on the
4512
- * ground instead of a flat plane (lets ONE field cover rolling terrain). */
4513
- drape = false;
4515
+ /**
4516
+ * Drape each instance onto a Terrain3D's surface so blades root on the
4517
+ * ground instead of a flat plane. null (the default) is AUTO: whenever the
4518
+ * scene has a Terrain3D, instances root on it — flat ground behaves as
4519
+ * before, rolling terrain just works. true forces it (keeps retrying until
4520
+ * a terrain appears), false disables draping entirely.
4521
+ */
4522
+ drape = null;
4514
4523
  /** Drape target: a node path to the Terrain3D. Empty = auto-find the first
4515
4524
  * Terrain3D in the tree (the zero-config default when `drape` is on). */
4516
4525
  terrain = "";
@@ -4544,7 +4553,7 @@ var Foliage3D = class Foliage3D extends Node3D {
4544
4553
  if (!Array.isArray(sun) || sun.length !== 3 || !sun.every((v) => Number.isFinite(v)) || Math.hypot(sun[0] ?? 0, sun[1] ?? 0, sun[2] ?? 0) === 0) throw new IncantoError("BAD_FORMAT", `Foliage3D '${node.name}' sunDirection must be a non-zero [x, y, z] vector, got ${JSON.stringify(sun)}.`, { prop: "sunDirection" });
4545
4554
  if (!(f.coverage > 0) || !(f.coverage <= 1)) throw new IncantoError("BAD_FORMAT", `Foliage3D '${node.name}' coverage must be in (0, 1] (carpet fill — 1 is solid, lower carves dirt patches), got ${f.coverage}.`, { prop: "coverage" });
4546
4555
  if (!(f.flowers >= 0) || !(f.flowers <= .2)) throw new IncantoError("BAD_FORMAT", `Foliage3D '${node.name}' flowers must be in [0, 0.2] (fraction of instances rendered as flower heads — for a whole flower FIELD use kind: 'flowers'), got ${f.flowers}.`, { prop: "flowers" });
4547
- if (typeof f.drape !== "boolean") throw new IncantoError("BAD_FORMAT", `Foliage3D '${node.name}' drape must be a boolean, got ${JSON.stringify(f.drape)}.`, { prop: "drape" });
4556
+ if (f.drape !== null && typeof f.drape !== "boolean") throw new IncantoError("BAD_FORMAT", `Foliage3D '${node.name}' drape must be true, false or null (null = auto: drape whenever the scene has a Terrain3D), got ${JSON.stringify(f.drape)}.`, { prop: "drape" });
4548
4557
  if (typeof f.terrain !== "string") throw new IncantoError("BAD_FORMAT", `Foliage3D '${node.name}' terrain must be a node-path string (empty = auto-find), got ${JSON.stringify(f.terrain)}.`, { prop: "terrain" });
4549
4558
  }
4550
4559
  time = 0;
@@ -4590,16 +4599,33 @@ var Foliage3D = class Foliage3D extends Node3D {
4590
4599
  update(dt) {
4591
4600
  this.time += dt;
4592
4601
  this.pickedBenders = this.computeBenders();
4602
+ this.writeAnimationUniforms();
4603
+ }
4604
+ /** Wind time/strength + the ≤4 character-bend slots (no-op before first build). */
4605
+ writeAnimationUniforms() {
4606
+ if (this.isShaderField && this.bladesMaterial) {
4607
+ const u = this.bladesMaterial.uniforms;
4608
+ if (u.time) u.time.value = this.time;
4609
+ if (u.windStrength) u.windStrength.value = this.sway * (this.isMesh ? MESH_SWAY_TO_WIND : SWAY_TO_WIND);
4610
+ const slots = this.benderSlots;
4611
+ if (slots) for (let i = 0; i < 4; i++) {
4612
+ const b = this.pickedBenders[i];
4613
+ if (b) slots[i]?.set(b.x, b.y, b.z, b.radius);
4614
+ else slots[i]?.set(0, 0, 0, 0);
4615
+ }
4616
+ } else if (this.isTufts) {
4617
+ if (this.tuftTime) this.tuftTime.value = this.time;
4618
+ if (this.tuftStrength) this.tuftStrength.value = this.sway * TUFT_SWAY_TO_WIND;
4619
+ }
4593
4620
  }
4594
4621
  _syncObject3D() {
4595
4622
  super._syncObject3D();
4596
4623
  this.rebuildIfNeeded();
4597
4624
  const pivot = this.pivot;
4625
+ this.writeAnimationUniforms();
4598
4626
  if (this.isShaderField) {
4599
4627
  pivot.matrix.identity();
4600
4628
  const u = this.bladesMaterial.uniforms;
4601
- if (u.time) u.time.value = this.time;
4602
- if (u.windStrength) u.windStrength.value = this.sway * (this.isMesh ? MESH_SWAY_TO_WIND : SWAY_TO_WIND);
4603
4629
  (u.bottomColor?.value).set(this.colorA);
4604
4630
  (u.topColor?.value).set(this.colorB);
4605
4631
  if (this.isMesh) {
@@ -4608,17 +4634,8 @@ var Foliage3D = class Foliage3D extends Node3D {
4608
4634
  if (u.fadeStart) u.fadeStart.value = this.fadeStart;
4609
4635
  if (u.fadeEnd) u.fadeEnd.value = this.fadeEnd;
4610
4636
  }
4611
- const slots = this.benderSlots;
4612
- for (let i = 0; i < 4; i++) {
4613
- const b = this.pickedBenders[i];
4614
- if (b) slots[i]?.set(b.x, b.y, b.z, b.radius);
4615
- else slots[i]?.set(0, 0, 0, 0);
4616
- }
4617
- } else if (this.isTufts) {
4618
- pivot.matrix.identity();
4619
- if (this.tuftTime) this.tuftTime.value = this.time;
4620
- if (this.tuftStrength) this.tuftStrength.value = this.sway * TUFT_SWAY_TO_WIND;
4621
- } else if (this.sway > 0) {
4637
+ } else if (this.isTufts) pivot.matrix.identity();
4638
+ else if (this.sway > 0) {
4622
4639
  const leanX = Math.sin(this.time * 1.7) * SWAY_LEAN * this.sway;
4623
4640
  const leanZ = Math.sin(this.time * 1.3 + 1.7) * SWAY_LEAN * this.sway * .6;
4624
4641
  pivot.matrix.makeShear(0, 0, leanX, leanZ, 0, 0);
@@ -4645,7 +4662,7 @@ var Foliage3D = class Foliage3D extends Node3D {
4645
4662
  computeBenders() {
4646
4663
  if (!this.interaction || !this.isShaderField) return [];
4647
4664
  const [fx, fy, fz] = worldXZY(this);
4648
- if (this.drape && !this.drapeTerrain) this.resolveDrape();
4665
+ if (this.drape === true && !this.drapeTerrain) this.resolveDrape();
4649
4666
  const terrain = this.drapeTerrain;
4650
4667
  const candidates = [];
4651
4668
  for (const b of movingBodiesFor(this)) {
@@ -4706,7 +4723,7 @@ var Foliage3D = class Foliage3D extends Node3D {
4706
4723
  /** Resolve the drape target + cache the field's world position for `bedY`. */
4707
4724
  resolveDrape() {
4708
4725
  this.drapeTerrain = null;
4709
- if (!this.drape) return;
4726
+ if (this.drape === false) return;
4710
4727
  const terrain = findDrapeTerrain(this, this.terrain);
4711
4728
  if (terrain) {
4712
4729
  this.drapeTerrain = terrain;
@@ -8411,7 +8428,7 @@ var Tree3D = class Tree3D extends Node3D {
8411
8428
  leafFadeStart: { default: 0 },
8412
8429
  leafFadeEnd: { default: 0 },
8413
8430
  leafShadows: { default: true },
8414
- drape: { default: false },
8431
+ drape: { default: null },
8415
8432
  terrain: { default: "" }
8416
8433
  };
8417
8434
  /** 'simple' (primitive low-poly) | 'medium' (light ez-trees) | 'high' (full). */
@@ -8440,11 +8457,14 @@ var Tree3D = class Tree3D extends Node3D {
8440
8457
  /** Let leaves cast shadows. false keeps trunk/branch shadows but drops the
8441
8458
  * expensive alpha-cutout leaf shadow pass (the dappled floor) for big FPS. */
8442
8459
  leafShadows = true;
8443
- /** Drape the grove over a Terrain3D: each scattered instance roots at the
8444
- * ground height under it (not the node's single Y), so a patch on rolling or
8445
- * carved terrain never floats/buries. No effect on a count:1 tree placed by
8446
- * hand unless it sits off the ground. */
8447
- drape = false;
8460
+ /**
8461
+ * Drape the grove over a Terrain3D: each scattered instance roots at the
8462
+ * ground height under it (not the node's single Y), so a patch on rolling
8463
+ * or carved terrain never floats/buries. null (the default) is AUTO:
8464
+ * whenever the scene has a Terrain3D the grove drapes onto it. true forces
8465
+ * it (keeps retrying until a terrain appears), false disables draping.
8466
+ */
8467
+ drape = null;
8448
8468
  /** Node path to the Terrain3D to drape onto ('' = auto-find the first one). */
8449
8469
  terrain = "";
8450
8470
  /** Resolved drape target + the grove's world position, set per rebuild. */
@@ -8463,7 +8483,7 @@ var Tree3D = class Tree3D extends Node3D {
8463
8483
  if (!((t.area[0] ?? 0) > 0) || !((t.area[1] ?? 0) > 0)) throw new IncantoError("BAD_FORMAT", `Tree3D '${node.name}' area must be positive [x, z] meters, got ${JSON.stringify(t.area)}.`, { prop: "area" });
8464
8484
  if (t.leafFadeEnd !== 0 && !(t.leafFadeEnd > t.leafFadeStart && t.leafFadeStart >= 0)) throw new IncantoError("BAD_FORMAT", `Tree3D '${node.name}' needs leafFadeEnd > leafFadeStart >= 0 (the leaf LOD window) or 0/0 to disable, got start ${t.leafFadeStart}, end ${t.leafFadeEnd}.`, { prop: "leafFadeStart" });
8465
8485
  if (typeof t.leafShadows !== "boolean") throw new IncantoError("BAD_FORMAT", `Tree3D '${node.name}' leafShadows must be a boolean, got ${JSON.stringify(t.leafShadows)}.`, { prop: "leafShadows" });
8466
- if (typeof t.drape !== "boolean") throw new IncantoError("BAD_FORMAT", `Tree3D '${node.name}' drape must be a boolean, got ${JSON.stringify(t.drape)}.`, { prop: "drape" });
8486
+ if (t.drape !== null && typeof t.drape !== "boolean") throw new IncantoError("BAD_FORMAT", `Tree3D '${node.name}' drape must be true, false or null (null = auto: drape whenever the scene has a Terrain3D), got ${JSON.stringify(t.drape)}.`, { prop: "drape" });
8467
8487
  if (typeof t.terrain !== "string") throw new IncantoError("BAD_FORMAT", `Tree3D '${node.name}' terrain must be a node-path string (empty = auto-find), got ${JSON.stringify(t.terrain)}.`, { prop: "terrain" });
8468
8488
  if (t.tier === "simple") return;
8469
8489
  const perTree = maxVariantTriangles(t.tier, t.type);
@@ -8540,7 +8560,7 @@ var Tree3D = class Tree3D extends Node3D {
8540
8560
  /** Resolve the drape target + cache the grove's world position for `bedY`. */
8541
8561
  resolveDrape() {
8542
8562
  this.drapeTerrain = null;
8543
- if (!this.drape) return;
8563
+ if (this.drape === false) return;
8544
8564
  const terrain = findDrapeTerrain(this, this.terrain);
8545
8565
  if (terrain) {
8546
8566
  this.drapeTerrain = terrain;
package/dist/test.js CHANGED
@@ -6,7 +6,7 @@ import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
6
6
  import { i as getNodeSchema, s as mergeStaticProps } from "./registry-BVJ2HbCn.js";
7
7
  import { n as registerGameplayBehaviors } from "./gameplay-sSqrUcnz.js";
8
8
  import { t as registerNodes2D } from "./register-Djwckzgx.js";
9
- import { t as registerNodes3D } from "./register-BFw4c83z.js";
9
+ import { t as registerNodes3D } from "./register-C9-16x_t.js";
10
10
  import { t as registerNodesNet } from "./register-BFFE1Mh1.js";
11
11
  //#region src/test/index.ts
12
12
  /**
@@ -127,7 +127,7 @@ async function runScript(json, opts) {
127
127
  const { enablePhysics2D } = await import("./physics-2d-BiIdl51r.js").then((n) => n.r);
128
128
  await enablePhysics2D(engine);
129
129
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
130
- const { enablePhysics3D } = await import("./physics-3d-DhaAaKJo.js").then((n) => n.r);
130
+ const { enablePhysics3D } = await import("./physics-3d-D_kUdOjr.js").then((n) => n.r);
131
131
  await enablePhysics3D(engine);
132
132
  }
133
133
  const failures = [];
@@ -232,7 +232,7 @@ async function createPlaySession(json, opts = {}) {
232
232
  const { enablePhysics2D } = await import("./physics-2d-BiIdl51r.js").then((n) => n.r);
233
233
  await enablePhysics2D(engine);
234
234
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
235
- const { enablePhysics3D } = await import("./physics-3d-DhaAaKJo.js").then((n) => n.r);
235
+ const { enablePhysics3D } = await import("./physics-3d-D_kUdOjr.js").then((n) => n.r);
236
236
  await enablePhysics3D(engine);
237
237
  }
238
238
  const stepMs = 1e3 / (opts.fixedHz ?? 60);
@@ -1 +1 @@
1
- import{t as e}from"./index-DSq0gdhE.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
1
+ import{t as e}from"./index-7qOYg7t8.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};