incanto 0.5.0 → 0.6.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/2d.d.ts CHANGED
@@ -52,6 +52,8 @@ declare class Node2D extends Node {
52
52
  position: number[];
53
53
  /** Degrees, clockwise. */
54
54
  rotation: number;
55
+ /** Frozen after first sync — see Node3D.static (same semantics in 2D). */
56
+ static: boolean;
55
57
  scale: number[];
56
58
  /** Draw order among 2D drawables (higher = on top); matches 3D `renderOrder`. */
57
59
  renderOrder: number;
@@ -636,6 +638,8 @@ declare class Renderer2D {
636
638
  private readonly webgl;
637
639
  private readonly worldScene;
638
640
  private readonly syncScratch;
641
+ /** Editors set this: keep syncing `static: true` subtrees every frame. */
642
+ ignoreStatic: boolean;
639
643
  private readonly uiScene;
640
644
  private readonly worldCam;
641
645
  private readonly uiCam;
@@ -705,6 +709,8 @@ interface Sync2DScratch {
705
709
  visited: Set<Object3D>;
706
710
  cameras: Camera2D[];
707
711
  }
708
- declare function syncTree2D(root: Node, world: Scene, ui: Scene, assets: AssetStore2D | null, uiSize?: UiSize, scratch?: Sync2DScratch): Sync2DResult;
712
+ declare function syncTree2D(root: Node, world: Scene, ui: Scene, assets: AssetStore2D | null, uiSize?: UiSize, scratch?: Sync2DScratch, opts?: {
713
+ ignoreStatic?: boolean;
714
+ }): Sync2DResult;
709
715
  //#endregion
710
716
  export { AnimatedSprite2D, type AnimationDef, Area2D, type AssetStatus, AssetStore2D, Camera2D, CharacterBody2D, CharacterController2D, ColorRect2D, type CreateGame2DOptions, type Game2D, Joint2D, type JointType2D, Label, Node2D, Particles2D, Physics2D, type Physics2DOptions, PhysicsBody2D, Renderer2D, type Renderer2DOptions, type ResolvedSpriteTexture, RigidBody2D, type SheetInfo, Sprite2D, type SpriteFromLibraryResult, StaticBody2D, type Sync2DResult, type TextureLoadCallbacks, type UIAnchor, UILayer, createGame2D, enablePhysics2D, registerNodes2D, spriteFromLibraryMeta, syncTree2D };
package/dist/2d.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
- import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-BFESHv1X.js";
3
- import { a as ColorRect2D, c as AnimatedSprite2D, d as Area2D, f as CharacterBody2D, g as Node2D, h as StaticBody2D, i as Label, l as Sprite2D, m as RigidBody2D, n as UILayer, o as CharacterController2D, p as PhysicsBody2D, r as Particles2D, s as Camera2D, t as registerNodes2D, u as Joint2D } from "./register-D71C4rDC.js";
4
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-KXn1N1jF.js";
2
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-B_sW_eiE.js";
3
+ import { a as ColorRect2D, c as AnimatedSprite2D, d as Area2D, f as CharacterBody2D, g as Node2D, h as StaticBody2D, i as Label, l as Sprite2D, m as RigidBody2D, n as UILayer, o as CharacterController2D, p as PhysicsBody2D, r as Particles2D, s as Camera2D, t as registerNodes2D, u as Joint2D } from "./register-BJCfuuZ2.js";
4
+ import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-Cgli1aju.js";
5
5
  //#region src/2d/library-sprite.ts
6
6
  /**
7
7
  * Turn a library sprite-animation JSON into a scene-ready spritesheet asset
package/dist/3d.d.ts CHANGED
@@ -137,6 +137,15 @@ declare class Node3D extends Node {
137
137
  position: number[];
138
138
  /** Euler XYZ in degrees. */
139
139
  rotation: number[];
140
+ /**
141
+ * This subtree never changes after load: the renderer syncs it ONCE and
142
+ * skips it every frame after (transforms, materials, animations frozen).
143
+ * The per-frame walk is O(nodes) — marking terrain/buildings/decor static
144
+ * removes most of a big scene from it. Set `static = false` to resume
145
+ * live syncing. Do NOT mark animated nodes (Water3D, Particles3D,
146
+ * AnimatedSprite3D, ModelInstance3D with clips) or cameras static.
147
+ */
148
+ static: boolean;
140
149
  scale: number[];
141
150
  visible: boolean;
142
151
  /** Draw-order priority (higher = drawn later / on top). Only affects TRANSPARENT
@@ -412,6 +421,14 @@ interface ShadowsEnvironment {
412
421
  mapSize: 1024 | 2048;
413
422
  /** Shadow edge softening radius (PCF blur, default 1). */
414
423
  radius: number;
424
+ /**
425
+ * `true` = shadow casters never move or animate: the shadow pass renders
426
+ * ONCE (and again on scene/environment change) instead of every frame —
427
+ * measured ~42% of a dense-forest frame. Characters under a static-shadow
428
+ * scene should use blob/sprite shadows. `Renderer3D.refreshShadows()`
429
+ * forces a re-render after a one-off world edit.
430
+ */
431
+ static: boolean;
415
432
  }
416
433
  interface CloudsEnvironment {
417
434
  /** How much of the sky is cloudy, 0..1. */
@@ -1914,6 +1931,8 @@ declare class Renderer3D {
1914
1931
  private compiledScene;
1915
1932
  /** Reused per-frame walk scratch (instance-scoped — never a module global). */
1916
1933
  private readonly syncScratch;
1934
+ /** Editors set this: keep syncing `static: true` subtrees every frame. */
1935
+ ignoreStatic: boolean;
1917
1936
  /** Reused render-hook context — gl/scene are stable, camera reassigned/frame. */
1918
1937
  private renderCtx;
1919
1938
  /** Underwater caustics pass (lazy — only created the first time submerged). */
@@ -2000,6 +2019,11 @@ declare class Renderer3D {
2000
2019
  up: Vector3;
2001
2020
  forward: Vector3;
2002
2021
  };
2022
+ /**
2023
+ * Re-render the shadow pass once (static-shadow scenes,
2024
+ * `environment.shadows.static: true`) after a one-off world edit.
2025
+ */
2026
+ refreshShadows(): void;
2003
2027
  dispose(): void;
2004
2028
  }
2005
2029
  //#endregion
@@ -2043,6 +2067,8 @@ interface SyncScratch {
2043
2067
  }
2044
2068
  interface SyncOptions {
2045
2069
  assets?: AssetStore3D;
2070
+ /** Sync `static: true` subtrees every frame anyway (the editor's edit mode). */
2071
+ ignoreStatic?: boolean;
2046
2072
  /**
2047
2073
  * The environment sky's unit sun direction. Pushed to every
2048
2074
  * `_applySunDirection` node (Water3D, Foliage3D) right after its own sync —
@@ -2079,6 +2105,7 @@ interface WalkState {
2079
2105
  sunDirection: readonly [number, number, number] | null;
2080
2106
  sunLight: DirectionalLight3D | null;
2081
2107
  alpha: number;
2108
+ ignoreStatic: boolean;
2082
2109
  }
2083
2110
  //#endregion
2084
2111
  export { Area3D, AssetStore3D, Billboard3D, type BillboardGroupMode, 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, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, 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 };
package/dist/3d.js CHANGED
@@ -1,5 +1,5 @@
1
- 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-D8UvwXme.js";
2
- import { A as StaticBody3D, C as TERRAIN_THEMES, D as CharacterBody3D, E as Area3D, F as keyboardIntensity, I as movementState, L as rigPose, N as QUARTER_PITCH, O as PhysicsBody3D, P as cameraRelative, S as DEFAULT_TERRAIN_TEXTURE_BASE, T as Joint3D, _ as FLOWER_VARIETIES, a as VoxelGrid3D, b as Billboard3D, c as ModelInstance3D, d as DirectionalLight3D, f as OmniLight3D, g as resolveFlowerDensity, h as Flowers3D, i as VOXEL_PALETTE, j as Node3D, k as RigidBody3D, l as LoftMesh3D, m as DENSITY_PRESETS, n as Water3D, o as Tree3D, p as Foliage3D, r as WATER_MAX_RIPPLES, s as Particles3D, t as registerNodes3D, u as MeshInstance3D, v as CharacterController3D, w as terrainThemeLayers, x as Terrain3D, y as Camera3D } from "./register-DvPHJdVj.js";
1
+ 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-DkbZCNaV.js";
2
+ import { A as StaticBody3D, C as TERRAIN_THEMES, D as CharacterBody3D, E as Area3D, F as keyboardIntensity, I as movementState, L as rigPose, N as QUARTER_PITCH, O as PhysicsBody3D, P as cameraRelative, S as DEFAULT_TERRAIN_TEXTURE_BASE, T as Joint3D, _ as FLOWER_VARIETIES, a as VoxelGrid3D, b as Billboard3D, c as ModelInstance3D, d as DirectionalLight3D, f as OmniLight3D, g as resolveFlowerDensity, h as Flowers3D, i as VOXEL_PALETTE, j as Node3D, k as RigidBody3D, l as LoftMesh3D, m as DENSITY_PRESETS, n as Water3D, o as Tree3D, p as Foliage3D, r as WATER_MAX_RIPPLES, s as Particles3D, t as registerNodes3D, u as MeshInstance3D, v as CharacterController3D, w as terrainThemeLayers, x as Terrain3D, y as Camera3D } from "./register-Dd7Juujf.js";
3
3
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CroQPEER.js";
4
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-C58oQzTX.js";
4
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-CBAQ12LY.js";
5
5
  export { Area3D, AssetStore3D, Billboard3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, StaticBody3D, TERRAIN_THEMES, Terrain3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_MAX_RIPPLES, Water3D, buildHeightmap, cameraRelative, createGame3D, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers };
@@ -5,9 +5,9 @@ import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { s as AudioPlayer } from "./register-Dl_ixIJe.js";
6
6
  import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
7
7
  import { n as registerGameplayBehaviors } from "./gameplay-CDFgSG6z.js";
8
- import { g as Node2D, n as UILayer, p as PhysicsBody2D, s as Camera2D, t as registerNodes2D } from "./register-D71C4rDC.js";
8
+ import { g as Node2D, n as UILayer, p as PhysicsBody2D, s as Camera2D, t as registerNodes2D } from "./register-BJCfuuZ2.js";
9
9
  import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
10
- import { n as enablePhysics2D } from "./physics-2d-KXn1N1jF.js";
10
+ import { n as enablePhysics2D } from "./physics-2d-Cgli1aju.js";
11
11
  import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
12
12
  //#region src/2d/assets.ts
13
13
  /**
@@ -108,11 +108,11 @@ function createSync2DScratch() {
108
108
  cameras: []
109
109
  };
110
110
  }
111
- function syncTree2D(root, world, ui, assets, uiSize, scratch) {
111
+ function syncTree2D(root, world, ui, assets, uiSize, scratch, opts) {
112
112
  const s = scratch ?? createSync2DScratch();
113
113
  s.visited.clear();
114
114
  s.cameras.length = 0;
115
- walk(root, world, ui, false, s.visited, s.cameras, assets, uiSize);
115
+ walk(root, world, ui, false, s.visited, s.cameras, assets, uiSize, opts?.ignoreStatic === true);
116
116
  prune(world, s.visited);
117
117
  prune(ui, s.visited);
118
118
  let current = null;
@@ -126,9 +126,17 @@ function syncTree2D(root, world, ui, assets, uiSize, scratch) {
126
126
  if (!current) current = s.cameras[0] ?? null;
127
127
  return { activeCamera: current };
128
128
  }
129
- function walk(node, parentObj, ui, inUi, visited, cameras, assets, uiSize) {
129
+ function walk(node, parentObj, ui, inUi, visited, cameras, assets, uiSize, ignoreStatic) {
130
130
  let nextParent = parentObj;
131
131
  let nextInUi = inUi;
132
+ if (node instanceof Node2D && !(node instanceof UILayer)) {
133
+ const frozen = node._ensureObject2D();
134
+ if (node.static && !ignoreStatic && frozen.userData.incantoStaticSynced === true) {
135
+ frozen.userData.incantoStatic = true;
136
+ visited.add(frozen);
137
+ return;
138
+ }
139
+ }
132
140
  if (node instanceof UILayer) {
133
141
  const group = node._ensureGroup();
134
142
  if (group.parent !== ui) ui.add(group);
@@ -148,14 +156,24 @@ function walk(node, parentObj, ui, inUi, visited, cameras, assets, uiSize) {
148
156
  if (node instanceof Camera2D && !inUi) cameras.push(node);
149
157
  nextParent = obj;
150
158
  }
151
- for (const child of node.children) walk(child, nextParent, ui, nextInUi, visited, cameras, assets, uiSize);
159
+ for (const child of node.children) walk(child, nextParent, ui, nextInUi, visited, cameras, assets, uiSize, ignoreStatic);
160
+ if (node instanceof Node2D && !(node instanceof UILayer)) {
161
+ const obj = node._ensureObject2D();
162
+ if (node.static && !ignoreStatic) {
163
+ obj.userData.incantoStaticSynced = true;
164
+ obj.userData.incantoStatic = true;
165
+ } else if (obj.userData.incantoStaticSynced === true) {
166
+ obj.userData.incantoStaticSynced = false;
167
+ obj.userData.incantoStatic = false;
168
+ }
169
+ }
152
170
  }
153
171
  function prune(obj, visited) {
154
172
  const ch = obj.children;
155
173
  for (let i = ch.length - 1; i >= 0; i--) {
156
174
  const child = ch[i];
157
175
  if (child.userData.incantoNode && !visited.has(child)) obj.remove(child);
158
- else prune(child, visited);
176
+ else if (child.userData.incantoStatic !== true) prune(child, visited);
159
177
  }
160
178
  }
161
179
  //#endregion
@@ -175,6 +193,8 @@ var Renderer2D = class {
175
193
  webgl;
176
194
  worldScene = new Scene();
177
195
  syncScratch = createSync2DScratch();
196
+ /** Editors set this: keep syncing `static: true` subtrees every frame. */
197
+ ignoreStatic = false;
178
198
  uiScene = new Scene();
179
199
  worldCam = new OrthographicCamera(-1, 1, 1, -1, -1e3, 1e3);
180
200
  uiCam = new OrthographicCamera(-1, 1, 1, -1, -1e3, 1e3);
@@ -6,8 +6,8 @@ import { s as AudioPlayer } from "./register-Dl_ixIJe.js";
6
6
  import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
7
7
  import { n as registerGameplayBehaviors } from "./gameplay-CDFgSG6z.js";
8
8
  import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
9
- import { O as PhysicsBody3D, c as ModelInstance3D, d as DirectionalLight3D, j as Node3D, t as registerNodes3D, y as Camera3D } from "./register-DvPHJdVj.js";
10
- import { n as enablePhysics3D } from "./physics-3d-C58oQzTX.js";
9
+ import { O as PhysicsBody3D, c as ModelInstance3D, d as DirectionalLight3D, j as Node3D, t as registerNodes3D, y as Camera3D } from "./register-Dd7Juujf.js";
10
+ import { n as enablePhysics3D } from "./physics-3d-CBAQ12LY.js";
11
11
  import { ACESFilmicToneMapping, AmbientLight, 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";
12
12
  import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
13
13
  import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
@@ -563,7 +563,11 @@ const CLOUD_KEYS = [
563
563
  "scale"
564
564
  ];
565
565
  const BLOOM_KEYS = ["threshold", "strength"];
566
- const SHADOW_KEYS = ["mapSize", "radius"];
566
+ const SHADOW_KEYS = [
567
+ "mapSize",
568
+ "radius",
569
+ "static"
570
+ ];
567
571
  const SHADOW_MAP_SIZES = [1024, 2048];
568
572
  const DEFAULT_TURBIDITY = 2;
569
573
  const DEFAULT_RAYLEIGH = 1;
@@ -756,7 +760,8 @@ function parseShadows(value) {
756
760
  if (value === false) return false;
757
761
  if (value === true) return {
758
762
  mapSize: 2048,
759
- radius: 1
763
+ radius: 1,
764
+ static: false
760
765
  };
761
766
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new IncantoError("BAD_FORMAT", `environment.shadows must be true, false or an object ({ mapSize?, radius? }), got ${JSON.stringify(value)}.`, { prop: "shadows" });
762
767
  const shadows = value;
@@ -771,9 +776,12 @@ function parseShadows(value) {
771
776
  });
772
777
  const radius = numberOr(shadows.radius, 1, "shadows.radius");
773
778
  if (radius < 0) throw new IncantoError("BAD_FORMAT", `environment.shadows.radius must be >= 0, got ${radius}.`, { prop: "shadows" });
779
+ const staticShadows = shadows.static === void 0 ? false : shadows.static;
780
+ if (typeof staticShadows !== "boolean") throw new IncantoError("BAD_FORMAT", `environment.shadows.static must be a boolean, got ${JSON.stringify(shadows.static)}.`, { prop: "shadows" });
774
781
  return {
775
782
  mapSize,
776
- radius
783
+ radius,
784
+ static: staticShadows
777
785
  };
778
786
  }
779
787
  function numberOr(value, fallback, at) {
@@ -887,6 +895,12 @@ var Environment3D = class {
887
895
  if (gl) {
888
896
  gl.toneMappingExposure = cfg.exposure;
889
897
  gl.shadowMap.enabled = cfg.shadows !== false;
898
+ if (cfg.shadows !== false && cfg.shadows !== null && cfg.shadows.static) {
899
+ if (gl.shadowMap.autoUpdate) {
900
+ gl.shadowMap.autoUpdate = false;
901
+ gl.shadowMap.needsUpdate = true;
902
+ }
903
+ } else if (!gl.shadowMap.autoUpdate) gl.shadowMap.autoUpdate = true;
890
904
  }
891
905
  this.applyHdri(env);
892
906
  this.applySky(cfg.sky, gl);
@@ -1081,7 +1095,8 @@ function createSyncScratch() {
1081
1095
  assets: void 0,
1082
1096
  sunDirection: null,
1083
1097
  sunLight: null,
1084
- alpha: 1
1098
+ alpha: 1,
1099
+ ignoreStatic: false
1085
1100
  }
1086
1101
  };
1087
1102
  }
@@ -1105,6 +1120,7 @@ function syncTree(root, threeScene, assets, opts, scratch) {
1105
1120
  const state = s.state;
1106
1121
  state.assets = assets;
1107
1122
  state.sunDirection = opts?.sunDirection ?? null;
1123
+ state.ignoreStatic = opts?.ignoreStatic === true;
1108
1124
  state.sunLight = null;
1109
1125
  state.alpha = opts?.alpha ?? 1;
1110
1126
  walk(root, threeScene, state);
@@ -1176,6 +1192,11 @@ function walk(node, parentObj, state) {
1176
1192
  let nextParent = parentObj;
1177
1193
  if (node instanceof Node3D) {
1178
1194
  const obj = node._ensureObject3D();
1195
+ if (node.static && !state.ignoreStatic && obj.userData.incantoStaticSynced === true) {
1196
+ obj.userData.incantoStatic = true;
1197
+ state.visited.add(obj);
1198
+ return;
1199
+ }
1179
1200
  if (obj.parent !== parentObj) parentObj.add(obj);
1180
1201
  node._syncObject3D(state.alpha);
1181
1202
  if (state.assets && node instanceof ModelInstance3D) node._syncModel(state.assets);
@@ -1195,13 +1216,37 @@ function walk(node, parentObj, state) {
1195
1216
  parent: nextParent
1196
1217
  });
1197
1218
  for (const child of node.children) walk(child, nextParent, state);
1219
+ if (node instanceof Node3D) {
1220
+ const obj = node._ensureObject3D();
1221
+ if (node.static && !state.ignoreStatic) {
1222
+ obj.userData.incantoStaticSynced = true;
1223
+ obj.userData.incantoStatic = true;
1224
+ warnStaticHazards(node);
1225
+ } else if (obj.userData.incantoStaticSynced === true) {
1226
+ obj.userData.incantoStaticSynced = false;
1227
+ obj.userData.incantoStatic = false;
1228
+ }
1229
+ }
1230
+ }
1231
+ const warnedStatic = /* @__PURE__ */ new WeakSet();
1232
+ /** One-time warning when animated machinery is frozen under a static root. */
1233
+ function warnStaticHazards(root) {
1234
+ if (warnedStatic.has(root)) return;
1235
+ warnedStatic.add(root);
1236
+ const hazards = [];
1237
+ const scan = (n) => {
1238
+ if (typeof n._onRender3D === "function" || n instanceof Camera3D) hazards.push(`${n.name} (${n.constructor.typeName ?? "?"})`);
1239
+ for (const c of n.children) scan(c);
1240
+ };
1241
+ scan(root);
1242
+ if (hazards.length > 0) console.warn(`[incanto] static subtree '${root.name}' freezes animated/per-frame nodes: ${hazards.join(", ")} — they will stop updating. Unmark static or move them out.`);
1198
1243
  }
1199
1244
  function prune(obj, visited) {
1200
1245
  const ch = obj.children;
1201
1246
  for (let i = ch.length - 1; i >= 0; i--) {
1202
1247
  const child = ch[i];
1203
1248
  if (child.userData.incantoNode && !visited.has(child)) obj.remove(child);
1204
- else prune(child, visited);
1249
+ else if (child.userData.incantoStatic !== true) prune(child, visited);
1205
1250
  }
1206
1251
  }
1207
1252
  //#endregion
@@ -1348,6 +1393,8 @@ var Renderer3D = class {
1348
1393
  compiledScene = null;
1349
1394
  /** Reused per-frame walk scratch (instance-scoped — never a module global). */
1350
1395
  syncScratch = createSyncScratch();
1396
+ /** Editors set this: keep syncing `static: true` subtrees every frame. */
1397
+ ignoreStatic = false;
1351
1398
  /** Reused render-hook context — gl/scene are stable, camera reassigned/frame. */
1352
1399
  renderCtx = null;
1353
1400
  /** Underwater caustics pass (lazy — only created the first time submerged). */
@@ -1425,7 +1472,8 @@ var Renderer3D = class {
1425
1472
  this.environment.apply(scene.environment, this.webgl);
1426
1473
  const { activeCamera: sceneCamera, renderHooks, sunLight } = syncTree(scene.root, this.threeScene, this.assets, {
1427
1474
  sunDirection: this.environment.sunDirection,
1428
- alpha: this.engine.interpolationAlpha
1475
+ alpha: this.engine.interpolationAlpha,
1476
+ ignoreStatic: this.ignoreStatic
1429
1477
  }, this.syncScratch);
1430
1478
  let activeCamera = sceneCamera;
1431
1479
  if (this.viewOverride) {
@@ -1763,6 +1811,13 @@ var Renderer3D = class {
1763
1811
  forward: new Vector3(0, 0, -1).applyQuaternion(q)
1764
1812
  };
1765
1813
  }
1814
+ /**
1815
+ * Re-render the shadow pass once (static-shadow scenes,
1816
+ * `environment.shadows.static: true`) after a one-off world edit.
1817
+ */
1818
+ refreshShadows() {
1819
+ this.webgl.shadowMap.needsUpdate = true;
1820
+ }
1766
1821
  dispose() {
1767
1822
  this.disconnect();
1768
1823
  this.threeScene.traverse((obj) => {
package/dist/index.js CHANGED
@@ -110,6 +110,6 @@ function newUid() {
110
110
  //#endregion
111
111
  //#region src/index.ts
112
112
  /** Engine version. Kept in sync with package.json by the release pipeline. */
113
- const VERSION = "0.5.0";
113
+ const VERSION = "0.6.0";
114
114
  //#endregion
115
115
  export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, fadeGain, getBehavior, getNodeSchema, getNodeSignals, getNodeType, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, synthSfx };
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
- import { _ as validateCollider2D, d as Area2D, f as CharacterBody2D, g as Node2D, m as RigidBody2D, p as PhysicsBody2D, u as Joint2D } from "./register-D71C4rDC.js";
3
+ import { _ as validateCollider2D, d as Area2D, f as CharacterBody2D, g as Node2D, m as RigidBody2D, p as PhysicsBody2D, u as Joint2D } from "./register-BJCfuuZ2.js";
4
4
  import { n as registerDebugSource } from "./debug-draw-CZmOYjL2.js";
5
5
  //#region src/2d/physics/physics-2d.ts
6
6
  var physics_2d_exports = /* @__PURE__ */ __exportAll({
@@ -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 { D as CharacterBody3D, E as Area3D, M as validateCollider3D, O as PhysicsBody3D, T as Joint3D, j as Node3D, k as RigidBody3D, x as Terrain3D } from "./register-DvPHJdVj.js";
4
+ import { D as CharacterBody3D, E as Area3D, M as validateCollider3D, O as PhysicsBody3D, T as Joint3D, j as Node3D, k as RigidBody3D, x as Terrain3D } from "./register-Dd7Juujf.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-D8UvwXme.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-BFESHv1X.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-DkbZCNaV.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-B_sW_eiE.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -42,6 +42,7 @@ var Node2D = class extends Node {
42
42
  static props = {
43
43
  position: { default: [0, 0] },
44
44
  rotation: { default: 0 },
45
+ static: { default: false },
45
46
  scale: { default: [1, 1] },
46
47
  renderOrder: { default: 0 },
47
48
  visible: { default: true }
@@ -54,6 +55,8 @@ var Node2D = class extends Node {
54
55
  position = [0, 0];
55
56
  /** Degrees, clockwise. */
56
57
  rotation = 0;
58
+ /** Frozen after first sync — see Node3D.static (same semantics in 2D). */
59
+ static = false;
57
60
  scale = [1, 1];
58
61
  /** Draw order among 2D drawables (higher = on top); matches 3D `renderOrder`. */
59
62
  renderOrder = 0;
@@ -203,6 +203,7 @@ var Node3D = class extends Node {
203
203
  0,
204
204
  0
205
205
  ] },
206
+ static: { default: false },
206
207
  scale: { default: [
207
208
  1,
208
209
  1,
@@ -222,6 +223,15 @@ var Node3D = class extends Node {
222
223
  0,
223
224
  0
224
225
  ];
226
+ /**
227
+ * This subtree never changes after load: the renderer syncs it ONCE and
228
+ * skips it every frame after (transforms, materials, animations frozen).
229
+ * The per-frame walk is O(nodes) — marking terrain/buildings/decor static
230
+ * removes most of a big scene from it. Set `static = false` to resume
231
+ * live syncing. Do NOT mark animated nodes (Water3D, Particles3D,
232
+ * AnimatedSprite3D, ModelInstance3D with clips) or cameras static.
233
+ */
234
+ static = false;
225
235
  scale = [
226
236
  1,
227
237
  1,
package/dist/test.js CHANGED
@@ -4,8 +4,8 @@ import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
5
5
  import { i as getNodeSchema, s as mergeStaticProps } from "./registry-BVJ2HbCn.js";
6
6
  import { n as registerGameplayBehaviors } from "./gameplay-CDFgSG6z.js";
7
- import { t as registerNodes2D } from "./register-D71C4rDC.js";
8
- import { t as registerNodes3D } from "./register-DvPHJdVj.js";
7
+ import { t as registerNodes2D } from "./register-BJCfuuZ2.js";
8
+ import { t as registerNodes3D } from "./register-Dd7Juujf.js";
9
9
  import { t as registerNodesNet } from "./register-C35HDZpm.js";
10
10
  //#region src/test/index.ts
11
11
  /**
@@ -129,10 +129,10 @@ async function runScript(json, opts) {
129
129
  engine.setScene(scene);
130
130
  const physics = opts.physics ?? "auto";
131
131
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
132
- const { enablePhysics2D } = await import("./physics-2d-KXn1N1jF.js").then((n) => n.r);
132
+ const { enablePhysics2D } = await import("./physics-2d-Cgli1aju.js").then((n) => n.r);
133
133
  await enablePhysics2D(engine);
134
134
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
135
- const { enablePhysics3D } = await import("./physics-3d-C58oQzTX.js").then((n) => n.r);
135
+ const { enablePhysics3D } = await import("./physics-3d-CBAQ12LY.js").then((n) => n.r);
136
136
  await enablePhysics3D(engine);
137
137
  }
138
138
  const failures = [];
@@ -234,10 +234,10 @@ async function createPlaySession(json, opts = {}) {
234
234
  engine.setScene(scene);
235
235
  const physics = opts.physics ?? "auto";
236
236
  if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
237
- const { enablePhysics2D } = await import("./physics-2d-KXn1N1jF.js").then((n) => n.r);
237
+ const { enablePhysics2D } = await import("./physics-2d-Cgli1aju.js").then((n) => n.r);
238
238
  await enablePhysics2D(engine);
239
239
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
240
- const { enablePhysics3D } = await import("./physics-3d-C58oQzTX.js").then((n) => n.r);
240
+ const { enablePhysics3D } = await import("./physics-3d-CBAQ12LY.js").then((n) => n.r);
241
241
  await enablePhysics3D(engine);
242
242
  }
243
243
  const stepMs = 1e3 / (opts.fixedHz ?? 60);
@@ -1 +1 @@
1
- import{t as e}from"./index-D2qufqUo.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-mofVSmuA.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};