incanto 0.11.0 → 0.13.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 (83) hide show
  1. package/README.md +22 -0
  2. package/bin/incanto-new.mjs +87 -0
  3. package/dist/2d.d.ts +2 -2
  4. package/dist/2d.js +3 -3
  5. package/dist/3d.d.ts +246 -71
  6. package/dist/3d.js +143 -4
  7. package/dist/{behavior-Bod1AyJS.d.ts → behavior-CPibUfnH.d.ts} +4 -2
  8. package/dist/{create-game-CdWhP6ve.js → create-game-Ct86zczq.js} +5 -5
  9. package/dist/{create-game-DZ_4Nfq8.js → create-game-J6g4QInv.js} +75 -24
  10. package/dist/debug.d.ts +1 -1
  11. package/dist/{duplicate-BEvGBtb_.js → duplicate-BINwP0WG.js} +1 -1
  12. package/dist/{gameplay-Dn3yidJw.js → gameplay-sSqrUcnz.js} +75 -3
  13. package/dist/gameplay.d.ts +37 -2
  14. package/dist/gameplay.js +2 -2
  15. package/dist/index.d.ts +3 -30
  16. package/dist/index.js +4 -4
  17. package/dist/{loader-B4OEXDZ8.js → loader-BZqOKfI2.js} +3 -1
  18. package/dist/{loader-BcUDfNHn.d.ts → loader-DILt9PGC.d.ts} +1 -1
  19. package/dist/net.d.ts +1 -1
  20. package/dist/net.js +3 -3
  21. package/dist/{audio-player-Dyjg5k92.d.ts → pathfinding-RWYkNKx9.d.ts} +29 -2
  22. package/dist/{physics-2d-DweTURFt.js → physics-2d-BiIdl51r.js} +8 -4
  23. package/dist/{physics-3d-Br97Ans2.js → physics-3d-DhaAaKJo.js} +8 -4
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +1 -1
  26. package/dist/{register-Cwc-yCYx.js → register-BFFE1Mh1.js} +2 -2
  27. package/dist/{register-ysLR3WQS.js → register-BFw4c83z.js} +508 -123
  28. package/dist/{register-BD6zgrv_.js → register-Djwckzgx.js} +2 -2
  29. package/dist/{register-DJZXxwAn.js → register-nObreUQR.js} +1 -1
  30. package/dist/test.d.ts +2 -2
  31. package/dist/test.js +10 -10
  32. package/editor/assets/{agent8-BxNvAcLA.js → agent8-BF8Jfbkl.js} +1 -1
  33. package/editor/assets/{index-DMCQXowF.js → index-DSq0gdhE.js} +53 -41
  34. package/editor/index.html +1 -1
  35. package/package.json +4 -2
  36. package/schemas/scene.schema.json +263 -0
  37. package/skills/incanto-3d-character.md +37 -1
  38. package/skills/incanto-building-3d-games.md +83 -0
  39. package/skills/incanto-editor.md +3 -3
  40. package/skills/incanto-gameplay-behaviors.md +23 -1
  41. package/skills/incanto-node-reference.md +51 -0
  42. package/templates-app/beacon-isle-3d/PROJECT/Context.md +44 -0
  43. package/templates-app/beacon-isle-3d/PROJECT/Requirements.md +16 -0
  44. package/templates-app/beacon-isle-3d/PROJECT/Status.md +38 -0
  45. package/templates-app/beacon-isle-3d/PROJECT/Structure.md +24 -0
  46. package/templates-app/beacon-isle-3d/docs/project-3d-rules.md +38 -0
  47. package/templates-app/beacon-isle-3d/generate-world.ts +814 -0
  48. package/templates-app/beacon-isle-3d/index.html +68 -0
  49. package/templates-app/beacon-isle-3d/package.json +26 -0
  50. package/templates-app/beacon-isle-3d/src/behaviors.ts +570 -0
  51. package/templates-app/beacon-isle-3d/src/game.scene.json +2992 -0
  52. package/templates-app/beacon-isle-3d/src/main.ts +33 -0
  53. package/templates-app/beacon-isle-3d/tsconfig.json +13 -0
  54. package/templates-app/beacon-isle-3d/verify.ts +285 -0
  55. package/templates-app/beacon-isle-3d/vite.config.ts +5 -0
  56. package/templates-app/tps-3d/PROJECT/Context.md +49 -0
  57. package/templates-app/tps-3d/PROJECT/Requirements.md +38 -0
  58. package/templates-app/tps-3d/PROJECT/Status.md +41 -0
  59. package/templates-app/tps-3d/PROJECT/Structure.md +44 -0
  60. package/templates-app/tps-3d/docs/project-3d-rules.md +38 -0
  61. package/templates-app/tps-3d/index.html +244 -0
  62. package/templates-app/tps-3d/package.json +25 -0
  63. package/templates-app/tps-3d/src/behaviors.ts +405 -0
  64. package/templates-app/tps-3d/src/game.scene.json +677 -0
  65. package/templates-app/tps-3d/src/main.ts +64 -0
  66. package/templates-app/tps-3d/tsconfig.json +13 -0
  67. package/templates-app/tps-3d/verify.ts +167 -0
  68. package/templates-app/tps-3d/vite.config.ts +5 -0
  69. package/templates-app/village-quest-3d/PROJECT/Context.md +39 -0
  70. package/templates-app/village-quest-3d/PROJECT/Requirements.md +36 -0
  71. package/templates-app/village-quest-3d/PROJECT/Status.md +26 -0
  72. package/templates-app/village-quest-3d/PROJECT/Structure.md +30 -0
  73. package/templates-app/village-quest-3d/docs/project-3d-rules.md +38 -0
  74. package/templates-app/village-quest-3d/index.html +68 -0
  75. package/templates-app/village-quest-3d/package.json +25 -0
  76. package/templates-app/village-quest-3d/src/behaviors.ts +529 -0
  77. package/templates-app/village-quest-3d/src/grove.scene.json +826 -0
  78. package/templates-app/village-quest-3d/src/main.ts +35 -0
  79. package/templates-app/village-quest-3d/src/quest.ts +18 -0
  80. package/templates-app/village-quest-3d/src/village.scene.json +680 -0
  81. package/templates-app/village-quest-3d/tsconfig.json +13 -0
  82. package/templates-app/village-quest-3d/verify.ts +254 -0
  83. package/templates-app/village-quest-3d/vite.config.ts +5 -0
package/dist/3d.js CHANGED
@@ -1,5 +1,144 @@
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-DZ_4Nfq8.js";
2
- import { A as PhysicsBody3D, C as Terrain3D, D as Joint3D, E as terrainThemeLayers, F as QUARTER_PITCH, I as cameraRelative, L as keyboardIntensity, M as StaticBody3D, N as Node3D, O as Area3D, R as movementState, S as Billboard3D, T as TERRAIN_THEMES, _ as resolveFlowerDensity, a as VoxelGrid3D, b as Camera3D, c as Particles3D, d as MeshInstance3D, f as DirectionalLight3D, g as Flowers3D, h as DENSITY_PRESETS, i as VOXEL_PALETTE, j as RigidBody3D, k as CharacterBody3D, l as ModelInstance3D, m as Foliage3D, n as Water3D, o as Tree3D, p as OmniLight3D, r as WATER_MAX_RIPPLES, s as Trail3D, t as registerNodes3D, u as LoftMesh3D, v as FLOWER_VARIETIES, w as DEFAULT_TERRAIN_TEXTURE_BASE, x as BoneAttachment3D, y as CharacterController3D, z as rigPose } from "./register-ysLR3WQS.js";
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";
3
4
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CroQPEER.js";
4
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-Br97Ans2.js";
5
- export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, 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, Trail3D, 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
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-DhaAaKJo.js";
6
+ //#region src/3d/environment-runtime.ts
7
+ /**
8
+ * Live environment editing — the renderer re-applies `scene.environment`
9
+ * every frame (cheap when unchanged), so changing the LOOK at runtime is
10
+ * just writing that object. This helper does it safely:
11
+ *
12
+ * setEnvironment3D(engine, { sky: { elevationDeg: 4 }, exposure: 0.8 });
13
+ * setEnvironment3D(engine, { fog: null }); // null DELETES a key
14
+ *
15
+ * - one-level deep merge (patching `sky.elevationDeg` keeps the other sky keys)
16
+ * - hard-validates the merged result NOW (in your call stack), so a bad patch
17
+ * throws where you wrote it — never inside the render loop
18
+ */
19
+ function setEnvironment3D(engine, patch) {
20
+ const scene = engine.scene;
21
+ if (!scene) throw new IncantoError("BAD_FORMAT", "setEnvironment3D: the engine has no scene.");
22
+ const merged = { ...scene.environment ?? {} };
23
+ for (const [key, value] of Object.entries(patch)) if (value === null) delete merged[key];
24
+ else if (typeof value === "object" && !Array.isArray(value) && typeof merged[key] === "object" && merged[key] !== null && !Array.isArray(merged[key])) merged[key] = {
25
+ ...merged[key],
26
+ ...value
27
+ };
28
+ else merged[key] = value;
29
+ parseEnvironment3D(merged);
30
+ scene.environment = merged;
31
+ }
32
+ //#endregion
33
+ //#region src/3d/terrain-nav.ts
34
+ function buildTerrainNav(terrain, opts) {
35
+ const cellSize = opts?.cellSize ?? 2;
36
+ const maxSlopeDeg = opts?.maxSlopeDeg ?? 40;
37
+ const minHeight = opts?.minHeight ?? Number.NEGATIVE_INFINITY;
38
+ let ox = 0;
39
+ let oz = 0;
40
+ for (let n = terrain; n; n = n.parent) if (n instanceof Node3D) {
41
+ ox += n.position[0] ?? 0;
42
+ oz += n.position[2] ?? 0;
43
+ }
44
+ const sizeX = terrain.size[0] ?? 200;
45
+ const sizeZ = terrain.size[1] ?? 200;
46
+ const width = Math.max(1, Math.round(sizeX / cellSize));
47
+ const height = Math.max(1, Math.round(sizeZ / cellSize));
48
+ const minX = ox - sizeX / 2;
49
+ const minZ = oz - sizeZ / 2;
50
+ const centerX = (cx) => minX + (cx + .5) * cellSize;
51
+ const centerZ = (cy) => minZ + (cy + .5) * cellSize;
52
+ const tanLimit = Math.tan(maxSlopeDeg * Math.PI / 180);
53
+ const halfDiag = cellSize / 2 * Math.SQRT2;
54
+ const solidBits = new Uint8Array(width * height);
55
+ for (let cy = 0; cy < height; cy++) for (let cx = 0; cx < width; cx++) {
56
+ const i = cy * width + cx;
57
+ const x = centerX(cx);
58
+ const z = centerZ(cy);
59
+ const h = terrain.heightAt(x, z);
60
+ if (h < minHeight) {
61
+ solidBits[i] = 1;
62
+ continue;
63
+ }
64
+ const half = cellSize / 2;
65
+ for (const [dx, dz] of CORNERS) {
66
+ const hc = terrain.heightAt(x + dx * half, z + dz * half);
67
+ if (Math.abs(hc - h) / halfDiag > tanLimit) {
68
+ solidBits[i] = 1;
69
+ break;
70
+ }
71
+ }
72
+ }
73
+ return {
74
+ grid: {
75
+ width,
76
+ height,
77
+ solid: (x, y) => x < 0 || y < 0 || x >= width || y >= height || solidBits[y * width + x] === 1
78
+ },
79
+ cellSize,
80
+ toCell: (x, z) => [Math.floor((x - minX) / cellSize), Math.floor((z - minZ) / cellSize)],
81
+ toWorld: (cx, cy) => {
82
+ const x = centerX(cx);
83
+ const z = centerZ(cy);
84
+ return [
85
+ x,
86
+ terrain.heightAt(x, z),
87
+ z
88
+ ];
89
+ },
90
+ setSolid: (cx, cy) => {
91
+ if (cx >= 0 && cy >= 0 && cx < width && cy < height) solidBits[cy * width + cx] = 1;
92
+ }
93
+ };
94
+ }
95
+ const CORNERS = [
96
+ [1, 1],
97
+ [1, -1],
98
+ [-1, 1],
99
+ [-1, -1]
100
+ ];
101
+ //#endregion
102
+ //#region src/3d/terrain-nav-debug.ts
103
+ /**
104
+ * See what the enemies see: one node that paints every WALKABLE nav cell as a
105
+ * translucent quad hugging the terrain surface. Add it while tuning
106
+ * `cellSize`/`maxSlopeDeg`, drop it when the routes look right:
107
+ *
108
+ * if (DEBUG_NAV) this.node.addChild(createNavDebugNode(this.nav));
109
+ *
110
+ * One InstancedMesh — a few thousand cells render as a single draw call.
111
+ */
112
+ function createNavDebugNode(nav, opts) {
113
+ const node = new InstancedMesh3D(opts?.name ?? "__navDebug");
114
+ node.mesh = "plane";
115
+ node.size = [
116
+ nav.cellSize * .9,
117
+ 1,
118
+ nav.cellSize * .9
119
+ ];
120
+ node.material = {
121
+ color: opts?.color ?? "#37ff8b",
122
+ emissive: opts?.color ?? "#37ff8b",
123
+ emissiveIntensity: .5,
124
+ opacity: opts?.opacity ?? .3,
125
+ roughness: 1,
126
+ depthWrite: false
127
+ };
128
+ node.renderOrder = 50;
129
+ const lift = opts?.lift ?? .08;
130
+ const rows = [];
131
+ for (let cy = 0; cy < nav.grid.height; cy++) for (let cx = 0; cx < nav.grid.width; cx++) {
132
+ if (nav.grid.solid(cx, cy)) continue;
133
+ const [x, y, z] = nav.toWorld(cx, cy);
134
+ rows.push([
135
+ x,
136
+ y + lift,
137
+ z
138
+ ]);
139
+ }
140
+ node.transforms = rows;
141
+ return node;
142
+ }
143
+ //#endregion
144
+ export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, StaticBody3D, TERRAIN_THEMES, Terrain3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_MAX_RIPPLES, Water3D, buildHeightmap, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, setEnvironment3D, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers };
@@ -693,8 +693,10 @@ declare class Scene {
693
693
  readonly dimension: "2d" | "3d" | undefined;
694
694
  readonly root: Node;
695
695
  readonly tree: SceneTree;
696
- /** Scene-level rendering environment (read by renderer adapters). */
697
- readonly environment: JsonObject | undefined;
696
+ /** Scene-level rendering environment (read by renderer adapters EVERY
697
+ * frame, cheap when unchanged) — mutable so runtime look changes
698
+ * (setEnvironment3D, DayNight) are just writes to this object. */
699
+ environment: JsonObject | undefined;
698
700
  /** Asset declarations (consumed by renderer/asset layers, `$key` references). */
699
701
  readonly assets: Record<string, JsonObject> | undefined;
700
702
  /** Named constant values (`{"@const": "NAME"}` prop references resolve to these). */
@@ -1,12 +1,12 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { _ as registerBehavior, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-B4OEXDZ8.js";
3
- import { l as AudioPlayer, u as Engine } from "./register-DJZXxwAn.js";
2
+ import { _ as registerBehavior, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-BZqOKfI2.js";
3
+ import { l as AudioPlayer, u as Engine } from "./register-nObreUQR.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
6
- import { n as registerGameplayBehaviors } from "./gameplay-Dn3yidJw.js";
7
- import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-BD6zgrv_.js";
6
+ import { n as registerGameplayBehaviors } from "./gameplay-sSqrUcnz.js";
7
+ import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-Djwckzgx.js";
8
8
  import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
9
- import { n as enablePhysics2D } from "./physics-2d-DweTURFt.js";
9
+ import { n as enablePhysics2D } from "./physics-2d-BiIdl51r.js";
10
10
  import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
11
11
  //#region src/2d/assets.ts
12
12
  /**
@@ -1,12 +1,12 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { _ as registerBehavior, n as loadScene } from "./loader-B4OEXDZ8.js";
3
- import { l as AudioPlayer, u as Engine } from "./register-DJZXxwAn.js";
2
+ import { _ as registerBehavior, n as loadScene } from "./loader-BZqOKfI2.js";
3
+ import { l as AudioPlayer, u as Engine } from "./register-nObreUQR.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
6
- import { n as registerGameplayBehaviors } from "./gameplay-Dn3yidJw.js";
6
+ import { n as registerGameplayBehaviors } from "./gameplay-sSqrUcnz.js";
7
7
  import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
8
- import { A as PhysicsBody3D, N as Node3D, b as Camera3D, f as DirectionalLight3D, l as ModelInstance3D, t as registerNodes3D } from "./register-ysLR3WQS.js";
9
- import { n as enablePhysics3D } from "./physics-3d-Br97Ans2.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";
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";
@@ -182,6 +182,9 @@ varying vec2 vUv;
182
182
  uniform sampler2D tScene;
183
183
  uniform sampler2D tBloom;
184
184
  uniform float uStrength;
185
+ uniform float uVignette;
186
+ uniform float uSaturation;
187
+ uniform float uContrast;
185
188
  void main() {
186
189
  vec3 base = texture2D(tScene, vUv).rgb;
187
190
  vec3 glow = texture2D(tBloom, vUv).rgb;
@@ -192,6 +195,15 @@ void main() {
192
195
  // tail here (ACES then linear→sRGB); without it the frame comes out dark
193
196
  // and oversaturated (the volumetric-clouds pipeline bug, root-caused).
194
197
  #include <tonemapping_fragment>
198
+ // color grade AFTER tone mapping (values are display-referred now) and
199
+ // BEFORE the colorspace transform: saturation → contrast → vignette
200
+ vec3 graded = gl_FragColor.rgb;
201
+ float luma = dot(graded, vec3(0.2126, 0.7152, 0.0722));
202
+ graded = mix(vec3(luma), graded, uSaturation);
203
+ graded = (graded - 0.5) * uContrast + 0.5;
204
+ float dist = distance(vUv, vec2(0.5));
205
+ graded *= 1.0 - uVignette * smoothstep(0.3, 0.78, dist);
206
+ gl_FragColor.rgb = max(graded, 0.0);
195
207
  #include <colorspace_fragment>
196
208
  }
197
209
  `;
@@ -234,7 +246,10 @@ function createBloomCompositeQuad() {
234
246
  const mesh = quad(COMPOSITE_FRAG, {
235
247
  tScene: { value: null },
236
248
  tBloom: { value: null },
237
- uStrength: { value: .85 }
249
+ uStrength: { value: .85 },
250
+ uVignette: { value: 0 },
251
+ uSaturation: { value: 1 },
252
+ uContrast: { value: 1 }
238
253
  });
239
254
  return {
240
255
  mesh,
@@ -562,6 +577,11 @@ const CLOUD_KEYS = [
562
577
  "scale"
563
578
  ];
564
579
  const BLOOM_KEYS = ["threshold", "strength"];
580
+ const POST_KEYS = [
581
+ "vignette",
582
+ "saturation",
583
+ "contrast"
584
+ ];
565
585
  const SHADOW_KEYS = [
566
586
  "mapSize",
567
587
  "radius",
@@ -583,6 +603,7 @@ function parseEnvironment3D(env) {
583
603
  fog: parseFog(env?.fog, env?.sky !== void 0),
584
604
  clouds: parseClouds(env?.clouds),
585
605
  bloom: parseBloom(env?.bloom),
606
+ post: parsePost(env?.post),
586
607
  shadows: parseShadows(env?.shadows)
587
608
  };
588
609
  }
@@ -754,6 +775,26 @@ function parseBloom(value) {
754
775
  strength
755
776
  };
756
777
  }
778
+ function parsePost(value) {
779
+ if (value === void 0) return null;
780
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new IncantoError("BAD_FORMAT", `environment.post must be an object ({ vignette?, saturation?, contrast? }), got ${JSON.stringify(value)}.`, { prop: "post" });
781
+ const p = value;
782
+ for (const key of Object.keys(p)) if (!POST_KEYS.includes(key)) throw new IncantoError("BAD_FORMAT", `environment.post has unknown key '${key}'. Valid keys: [${POST_KEYS.join(", ")}].`, {
783
+ prop: "post",
784
+ validOptions: POST_KEYS
785
+ });
786
+ const vignette = numberOr(p.vignette, 0, "post.vignette");
787
+ if (vignette < 0 || vignette > 1) throw new IncantoError("BAD_FORMAT", `environment.post.vignette must be in [0, 1], got ${vignette}.`, { prop: "post" });
788
+ const saturation = numberOr(p.saturation, 1, "post.saturation");
789
+ if (saturation < 0 || saturation > 4) throw new IncantoError("BAD_FORMAT", `environment.post.saturation must be in [0, 4] (1 = neutral), got ${saturation}.`, { prop: "post" });
790
+ const contrast = numberOr(p.contrast, 1, "post.contrast");
791
+ if (contrast < .2 || contrast > 3) throw new IncantoError("BAD_FORMAT", `environment.post.contrast must be in [0.2, 3] (1 = neutral), got ${contrast}.`, { prop: "post" });
792
+ return {
793
+ vignette,
794
+ saturation,
795
+ contrast
796
+ };
797
+ }
757
798
  function parseShadows(value) {
758
799
  if (value === void 0) return null;
759
800
  if (value === false) return false;
@@ -876,6 +917,10 @@ var Environment3D = class {
876
917
  get bloom() {
877
918
  return this.config.bloom;
878
919
  }
920
+ /** Parsed color-grade/vignette config, or null when not declared. */
921
+ get post() {
922
+ return this.config.post;
923
+ }
879
924
  /** The live scene fog (color/near/far) the cloud composite fades into, or null. */
880
925
  get sceneFog() {
881
926
  return this.scene.fog instanceof Fog ? this.scene.fog : null;
@@ -1601,9 +1646,10 @@ var Renderer3D = class {
1601
1646
  this.environment.applyUnderwater(underwater);
1602
1647
  const clouds = this.environment.clouds;
1603
1648
  const bloom = this.environment.bloom;
1649
+ const post = this.environment.post;
1604
1650
  if (underwater?.caustics.enabled) this.renderWithCaustics(activeCamera, underwater);
1605
1651
  else if (clouds && !underwater) this.renderWithClouds(activeCamera, clouds, threeLight);
1606
- else if (bloom && !underwater) this.renderWithBloom(activeCamera, bloom);
1652
+ else if ((bloom || post) && !underwater) this.renderWithBloom(activeCamera, bloom, post);
1607
1653
  else this.webgl.render(this.threeScene, activeCamera);
1608
1654
  }
1609
1655
  /**
@@ -1615,7 +1661,7 @@ var Renderer3D = class {
1615
1661
  * the scene's tone; the clouds pipeline taught us that lesson)
1616
1662
  * Caustics/clouds paths take precedence (composite pipelines don't stack yet).
1617
1663
  */
1618
- renderWithBloom(camera, bloom) {
1664
+ renderWithBloom(camera, bloom, post = null) {
1619
1665
  const buf = this.webgl.getDrawingBufferSize(sizeScratch);
1620
1666
  const w = Math.max(1, buf.x);
1621
1667
  const h = Math.max(1, buf.y);
@@ -1652,24 +1698,29 @@ var Renderer3D = class {
1652
1698
  const blurTarget = this.bloomBlurTarget;
1653
1699
  this.webgl.setRenderTarget(this.bloomTarget);
1654
1700
  this.webgl.render(this.threeScene, camera);
1655
- const bu = this.bloomBrightUniforms;
1656
- bu.tScene.value = this.bloomTarget.texture;
1657
- bu.uThreshold.value = bloom.threshold;
1658
- this.webgl.setRenderTarget(brightTarget);
1659
- this.webgl.render(this.bloomBrightScene, camera);
1660
- const blu = this.bloomBlurUniforms;
1661
- blu.tTex.value = brightTarget.texture;
1662
- blu.uDir.value.set(1 / hw, 0);
1663
- this.webgl.setRenderTarget(blurTarget);
1664
- this.webgl.render(this.bloomBlurScene, camera);
1665
- blu.tTex.value = blurTarget.texture;
1666
- blu.uDir.value.set(0, 1 / hh);
1667
- this.webgl.setRenderTarget(brightTarget);
1668
- this.webgl.render(this.bloomBlurScene, camera);
1701
+ if (bloom) {
1702
+ const bu = this.bloomBrightUniforms;
1703
+ bu.tScene.value = this.bloomTarget.texture;
1704
+ bu.uThreshold.value = bloom.threshold;
1705
+ this.webgl.setRenderTarget(brightTarget);
1706
+ this.webgl.render(this.bloomBrightScene, camera);
1707
+ const blu = this.bloomBlurUniforms;
1708
+ blu.tTex.value = brightTarget.texture;
1709
+ blu.uDir.value.set(1 / hw, 0);
1710
+ this.webgl.setRenderTarget(blurTarget);
1711
+ this.webgl.render(this.bloomBlurScene, camera);
1712
+ blu.tTex.value = blurTarget.texture;
1713
+ blu.uDir.value.set(0, 1 / hh);
1714
+ this.webgl.setRenderTarget(brightTarget);
1715
+ this.webgl.render(this.bloomBlurScene, camera);
1716
+ }
1669
1717
  const cu = this.bloomCompositeUniforms;
1670
1718
  cu.tScene.value = this.bloomTarget.texture;
1671
- cu.tBloom.value = brightTarget.texture;
1672
- cu.uStrength.value = bloom.strength;
1719
+ cu.tBloom.value = bloom ? brightTarget.texture : this.bloomTarget.texture;
1720
+ cu.uStrength.value = bloom ? bloom.strength : 0;
1721
+ cu.uVignette.value = post?.vignette ?? 0;
1722
+ cu.uSaturation.value = post?.saturation ?? 1;
1723
+ cu.uContrast.value = post?.contrast ?? 1;
1673
1724
  this.webgl.setRenderTarget(null);
1674
1725
  this.webgl.render(this.bloomCompositeScene, camera);
1675
1726
  }
package/dist/debug.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as RendererStats, E as LogLevel, v as Engine } from "./behavior-Bod1AyJS.js";
1
+ import { C as RendererStats, E as LogLevel, v as Engine } from "./behavior-CPibUfnH.js";
2
2
 
3
3
  //#region src/debug/panel.d.ts
4
4
  /** Minimal document surface the overlay needs (injectable for tests). */
@@ -1,4 +1,4 @@
1
- import { i as serializeNode, t as buildNodeJson } from "./loader-B4OEXDZ8.js";
1
+ import { i as serializeNode, t as buildNodeJson } from "./loader-BZqOKfI2.js";
2
2
  //#region src/core/scene/duplicate.ts
3
3
  function stripUids(json) {
4
4
  const { uid: _uid, ...rest } = json;
@@ -1,7 +1,7 @@
1
- import { _ as registerBehavior, m as Behavior, n as loadScene } from "./loader-B4OEXDZ8.js";
1
+ import { _ as registerBehavior, m as Behavior, n as loadScene } from "./loader-BZqOKfI2.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
3
  import { t as jsonClone } from "./json-BLk7H2Qa.js";
4
- import { t as duplicateNode } from "./duplicate-BEvGBtb_.js";
4
+ import { t as duplicateNode } from "./duplicate-BINwP0WG.js";
5
5
  //#region src/gameplay/spatial.ts
6
6
  /** Duck-type test: every spatial node (Node2D/Node3D) exposes `position: number[]`. */
7
7
  function hasPosition$1(node) {
@@ -428,6 +428,77 @@ function findHealth(node) {
428
428
  return null;
429
429
  }
430
430
  //#endregion
431
+ //#region src/gameplay/day-night.ts
432
+ var DayNight = class extends Behavior {
433
+ static signals = ["dayPhaseChanged"];
434
+ static props = {
435
+ /** Real seconds for a full 24h cycle. */
436
+ daySeconds: { default: 240 },
437
+ /** Clock position at scene start (0-24; 12 = noon). */
438
+ startHour: { default: 10 },
439
+ /** Noon sun elevation, degrees. */
440
+ maxSunElevationDeg: { default: 55 },
441
+ /** How dark midnight gets: 0 = unchanged, 1 = black. */
442
+ nightDarkness: { default: .75 },
443
+ /** Freeze the clock (drive `hour` yourself). */
444
+ paused: { default: false }
445
+ };
446
+ daySeconds = 240;
447
+ startHour = 10;
448
+ maxSunElevationDeg = 55;
449
+ nightDarkness = .75;
450
+ paused = false;
451
+ /** Current clock hour (0-24). Write it to jump the time of day. */
452
+ hour = 0;
453
+ phase = "";
454
+ applyClock = 0;
455
+ baseAmbient = .3;
456
+ baseExposure = 1;
457
+ captured = false;
458
+ onReady() {
459
+ this.hour = this.startHour;
460
+ }
461
+ update(dt) {
462
+ if (!this.paused) this.hour = (this.hour + dt / this.daySeconds * 24) % 24;
463
+ this.applyClock -= dt;
464
+ if (this.applyClock > 0) return;
465
+ this.applyClock = .2;
466
+ this.applyHour();
467
+ const next = phaseOf(this.hour);
468
+ if (next !== this.phase) {
469
+ this.phase = next;
470
+ this.emit("dayPhaseChanged", next);
471
+ }
472
+ }
473
+ applyHour() {
474
+ const engine = this.node.tree?.engine;
475
+ const env = engine?.scene?.environment;
476
+ if (!engine || !env?.sky) return;
477
+ if (!this.captured) {
478
+ this.captured = true;
479
+ this.baseAmbient = env.ambient?.intensity ?? .3;
480
+ this.baseExposure = env.exposure ?? 1;
481
+ }
482
+ const t = (this.hour - 6) / 12 * Math.PI;
483
+ const sun = Math.sin(t);
484
+ env.sky.elevationDeg = sun * this.maxSunElevationDeg + (sun < 0 ? sun * 10 : 0);
485
+ env.sky.azimuthDeg = this.hour / 24 * 360 + 90;
486
+ const daylight = Math.max(0, sun);
487
+ const dark = this.nightDarkness;
488
+ env.ambient = {
489
+ ...env.ambient ?? {},
490
+ intensity: this.baseAmbient * (1 - dark + dark * daylight)
491
+ };
492
+ env.exposure = this.baseExposure * (1 - dark * .55 * (1 - daylight));
493
+ }
494
+ };
495
+ function phaseOf(hour) {
496
+ if (hour < 5 || hour >= 20) return "night";
497
+ if (hour < 7.5) return "dawn";
498
+ if (hour < 17.5) return "day";
499
+ return "dusk";
500
+ }
501
+ //#endregion
431
502
  //#region src/gameplay/follow-camera.ts
432
503
  /**
433
504
  * Make the node it sits on chase a target's position — THE camera-follow
@@ -1873,6 +1944,7 @@ var ZombieAI = class extends Behavior {
1873
1944
  /** Every built-in gameplay behavior, keyed by its registration name. */
1874
1945
  const GAMEPLAY_BEHAVIORS = {
1875
1946
  CameraShake,
1947
+ DayNight,
1876
1948
  GameFlow,
1877
1949
  PathFollow,
1878
1950
  Health,
@@ -1903,4 +1975,4 @@ function registerGameplayBehaviors(opts) {
1903
1975
  for (const [name, ctor] of Object.entries(GAMEPLAY_BEHAVIORS)) registerBehavior(name, ctor, { replace });
1904
1976
  }
1905
1977
  //#endregion
1906
- export { FollowCamera as C, Chase as D, Collector as E, restartScene as S, Health as T, hitStop as _, Wander as a, GameFlow as b, Projectile as c, PathFollow as d, Oscillate as f, Cooldown as g, CameraShake as h, WaveSpawner as i, Pickup as l, Lifetime as m, registerGameplayBehaviors as n, Spawner as o, MoveTo as p, ZombieAI as r, ScoreKeeper as s, GAMEPLAY_BEHAVIORS as t, Patrol as u, screenFlash as v, DamageOnContact as w, goToScene as x, Interactable as y };
1978
+ export { FollowCamera as C, Health as D, DamageOnContact as E, Collector as O, restartScene as S, phaseOf as T, hitStop as _, Wander as a, GameFlow as b, Projectile as c, PathFollow as d, Oscillate as f, Cooldown as g, CameraShake as h, WaveSpawner as i, Chase as k, Pickup as l, Lifetime as m, registerGameplayBehaviors as n, Spawner as o, MoveTo as p, ZombieAI as r, ScoreKeeper as s, GAMEPLAY_BEHAVIORS as t, Patrol as u, screenFlash as v, DayNight as w, goToScene as x, Interactable as y };
@@ -1,5 +1,5 @@
1
1
  import { c as JsonValue, s as JsonObject } from "./schema-CcoWb32N.js";
2
- import { $ as Node, l as PropSchema, n as BehaviorCtor, t as Behavior, v as Engine } from "./behavior-Bod1AyJS.js";
2
+ import { $ as Node, l as PropSchema, n as BehaviorCtor, t as Behavior, v as Engine } from "./behavior-CPibUfnH.js";
3
3
 
4
4
  //#region src/gameplay/chase.d.ts
5
5
  /**
@@ -540,6 +540,41 @@ declare class ZombieAI extends Behavior {
540
540
  private pickGoal;
541
541
  }
542
542
  //#endregion
543
+ //#region src/gameplay/day-night.d.ts
544
+ /**
545
+ * A live day/night cycle — drives the scene's `environment` (sun elevation +
546
+ * azimuth, ambient, exposure) through a full 24-hour loop. Attach anywhere
547
+ * (the scene root is natural); requires a 3D scene with an atmosphere sky.
548
+ *
549
+ * { "script": { "name": "DayNight", "props": { "daySeconds": 240, "startHour": 9 } } }
550
+ *
551
+ * `hour` (0-24, readable/writable) is the single source of truth — set it to
552
+ * jump to a time of day, or set `paused: true` and animate it yourself.
553
+ * Emits `dayPhaseChanged('dawn'|'day'|'dusk'|'night')` on transitions — hook
554
+ * spawners/lights/music to it.
555
+ */
556
+ type DayPhase = "dawn" | "day" | "dusk" | "night";
557
+ declare class DayNight extends Behavior {
558
+ static readonly signals: readonly string[];
559
+ static readonly props: PropSchema;
560
+ daySeconds: number;
561
+ startHour: number;
562
+ maxSunElevationDeg: number;
563
+ nightDarkness: number;
564
+ paused: boolean;
565
+ /** Current clock hour (0-24). Write it to jump the time of day. */
566
+ hour: number;
567
+ private phase;
568
+ private applyClock;
569
+ private baseAmbient;
570
+ private baseExposure;
571
+ private captured;
572
+ override onReady(): void;
573
+ override update(dt: number): void;
574
+ private applyHour;
575
+ }
576
+ declare function phaseOf(hour: number): DayPhase;
577
+ //#endregion
543
578
  //#region src/gameplay/game-flow.d.ts
544
579
  /**
545
580
  * Reload the CURRENT scene from its source JSON — fresh nodes, reset physics,
@@ -699,4 +734,4 @@ declare function registerGameplayBehaviors(opts?: {
699
734
  replace?: boolean;
700
735
  }): void;
701
736
  //#endregion
702
- export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, registerGameplayBehaviors, restartScene, screenFlash };
737
+ export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, type DayPhase, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { C as FollowCamera, D as Chase, E as Collector, S as restartScene, T as Health, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DamageOnContact, x as goToScene, y as Interactable } from "./gameplay-Dn3yidJw.js";
2
- export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, registerGameplayBehaviors, restartScene, screenFlash };
1
+ import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-sSqrUcnz.js";
2
+ export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { a as Rng, c as JsonValue, d as jsonKind, i as SceneJson, l as jsonClone, n as NodeJson, o as JsonKind, r as SCENE_FORMAT, s as JsonObject, t as ConnectionJson, u as jsonEquals } from "./schema-CcoWb32N.js";
2
- import { $ as Node, A as SfxPlayOptions, B as spatialPan, C as RendererStats, D as LogManager, E as LogLevel, F as ROLLOFF_MODELS, G as SynthOptions, H as SFX_PRESET_NAMES, I as RolloffModel, J as MusicManager, K as synthSfx, L as SpatialParams, M as Voice, N as VoicePreset, O as InputMap, P as Listener, Q as BusName, R as Vec3, S as GameStats, T as LogEntry, U as SfxParams, V as SFX_PRESETS, W as SfxWave, X as PlayMusicOptions, Y as MusicTrack, Z as AudioBuses, _ as registeredTypes, a as registerBehavior, b as Scheduler, c as PropDef, d as createNode, et as NodeLifecycle, f as getNodeSchema, g as registerNode, h as mergeStaticSignals, i as getBehavior, j as isAudioContextAvailable, k as SfxEngine, l as PropSchema, m as getNodeType, n as BehaviorCtor, nt as SignalListener, o as registeredBehaviors, p as getNodeSignals, q as MusicBackend, r as clearBehaviors, rt as SceneTree, s as NodeCtor, t as Behavior, tt as Signal, u as clearRegistry, v as Engine, w as Scene, x as EngineStats, y as EngineOptions, z as spatialGain } from "./behavior-Bod1AyJS.js";
3
- import { n as loadScene, t as LoadSceneOptions } from "./loader-BcUDfNHn.js";
2
+ import { $ as Node, A as SfxPlayOptions, B as spatialPan, C as RendererStats, D as LogManager, E as LogLevel, F as ROLLOFF_MODELS, G as SynthOptions, H as SFX_PRESET_NAMES, I as RolloffModel, J as MusicManager, K as synthSfx, L as SpatialParams, M as Voice, N as VoicePreset, O as InputMap, P as Listener, Q as BusName, R as Vec3, S as GameStats, T as LogEntry, U as SfxParams, V as SFX_PRESETS, W as SfxWave, X as PlayMusicOptions, Y as MusicTrack, Z as AudioBuses, _ as registeredTypes, a as registerBehavior, b as Scheduler, c as PropDef, d as createNode, et as NodeLifecycle, f as getNodeSchema, g as registerNode, h as mergeStaticSignals, i as getBehavior, j as isAudioContextAvailable, k as SfxEngine, l as PropSchema, m as getNodeType, n as BehaviorCtor, nt as SignalListener, o as registeredBehaviors, p as getNodeSignals, q as MusicBackend, r as clearBehaviors, rt as SceneTree, s as NodeCtor, t as Behavior, tt as Signal, u as clearRegistry, v as Engine, w as Scene, x as EngineStats, y as EngineOptions, z as spatialGain } from "./behavior-CPibUfnH.js";
3
+ import { n as loadScene, t as LoadSceneOptions } from "./loader-DILt9PGC.js";
4
4
  import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
5
- import { n as AudioPlayer, t as AudioElementLike } from "./audio-player-Dyjg5k92.js";
5
+ import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-RWYkNKx9.js";
6
6
  import { i as auditScene, n as IncantoErrorCode, r as IncantoErrorDetails, t as IncantoError } from "./errors-1dXlIwoR.js";
7
7
 
8
8
  //#region src/core/audio/crossfade.d.ts
@@ -336,33 +336,6 @@ declare const PARTICLE_PRESET_NAMES: readonly string[];
336
336
  */
337
337
  declare function applyParticlePreset(target: Record<string, unknown>, presetName: string, schema: PropSchema): void;
338
338
  //#endregion
339
- //#region src/core/pathfinding.d.ts
340
- /**
341
- * Grid A* — the pathfinding every top-down/RPG/tower-defense asks for and
342
- * agents keep re-inventing badly. Pure and dimension-free: cells in, cells
343
- * out; the caller maps cells to world units (px, meters, tiles).
344
- */
345
- interface PathGrid {
346
- width: number;
347
- height: number;
348
- /** True = impassable. Out-of-bounds is always solid. */
349
- solid(x: number, y: number): boolean;
350
- }
351
- interface FindPathOptions {
352
- /** Allow diagonal steps (blocked from cutting corners). Default true. */
353
- diagonal?: boolean;
354
- /** Abort guard for huge/impossible searches. Default 20000 nodes. */
355
- maxExpansions?: number;
356
- }
357
- /**
358
- * A* from `from` to `to` (inclusive cell coords). Returns the cell path
359
- * INCLUDING both endpoints, or null when unreachable. Straight steps cost
360
- * 1, diagonals √2; the heuristic is octile (admissible for both modes).
361
- */
362
- declare function findPath(grid: PathGrid, from: [number, number], to: [number, number], opts?: FindPathOptions): [number, number][] | null;
363
- /** Build a PathGrid from row-strings ('#' = solid) — tests and tile games. */
364
- declare function gridFromRows(rows: readonly string[]): PathGrid;
365
- //#endregion
366
339
  //#region src/core/preload.d.ts
367
340
  interface PreloadResult {
368
341
  /** Urls whose fetch failed — show an error UI instead of pretending success. */
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as registerBehavior, a as SCENE_FORMAT, c as SceneTree, d as resolveConstants, f as Node, g as getBehavior, h as clearBehaviors, i as serializeNode, l as CONST_REF_KEY, m as Behavior, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as registeredBehaviors, y as Signal } from "./loader-B4OEXDZ8.js";
2
- import { C as crossfadeGains, S as WebAudioMusicBackend, T as AudioBuses, _ as spatialPan, a as HudLayer, b as synthSfx, c as UiText, d as LogManager, f as InputMap, g as spatialGain, h as ROLLOFF_MODELS, i as UiDialogue, l as AudioPlayer, m as isAudioContextAvailable, n as Timer, o as UiBanner, p as SfxEngine, r as UiButton, s as UiBar, t as registerCoreNodes, u as Engine, v as SFX_PRESETS, w as fadeGain, x as MusicManager, y as SFX_PRESET_NAMES } from "./register-DJZXxwAn.js";
1
+ import { _ as registerBehavior, a as SCENE_FORMAT, c as SceneTree, d as resolveConstants, f as Node, g as getBehavior, h as clearBehaviors, i as serializeNode, l as CONST_REF_KEY, m as Behavior, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as registeredBehaviors, y as Signal } from "./loader-BZqOKfI2.js";
2
+ import { C as crossfadeGains, S as WebAudioMusicBackend, T as AudioBuses, _ as spatialPan, a as HudLayer, b as synthSfx, c as UiText, d as LogManager, f as InputMap, g as spatialGain, h as ROLLOFF_MODELS, i as UiDialogue, l as AudioPlayer, m as isAudioContextAvailable, n as Timer, o as UiBanner, p as SfxEngine, r as UiButton, s as UiBar, t as registerCoreNodes, u as Engine, v as SFX_PRESETS, w as fadeGain, x as MusicManager, y as SFX_PRESET_NAMES } from "./register-nObreUQR.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { t as auditScene } from "./audit-C6rMyict.js";
@@ -8,7 +8,7 @@ import { a as getNodeSignals, c as mergeStaticSignals, i as getNodeSchema, l as
8
8
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
9
9
  import { a as ORDER_GROUP_BASE, i as applyParticlePreset, n as PARTICLE_PRESETS, o as effectiveOrder, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-CyUU7HVU.js";
10
10
  import { i as resolveRendering, n as attachTouchControls, r as joystickVector, t as TouchControls } from "./touch-031PxtCR.js";
11
- import { t as duplicateNode } from "./duplicate-BEvGBtb_.js";
11
+ import { t as duplicateNode } from "./duplicate-BINwP0WG.js";
12
12
  //#region src/core/pathfinding.ts
13
13
  /**
14
14
  * A* from `from` to `to` (inclusive cell coords). Returns the cell path
@@ -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.11.0";
298
+ const VERSION = "0.13.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 };
@@ -751,7 +751,9 @@ var Scene = class {
751
751
  dimension;
752
752
  root;
753
753
  tree;
754
- /** Scene-level rendering environment (read by renderer adapters). */
754
+ /** Scene-level rendering environment (read by renderer adapters EVERY
755
+ * frame, cheap when unchanged) — mutable so runtime look changes
756
+ * (setEnvironment3D, DayNight) are just writes to this object. */
755
757
  environment;
756
758
  /** Asset declarations (consumed by renderer/asset layers, `$key` references). */
757
759
  assets;
@@ -1,5 +1,5 @@
1
1
  import { i as SceneJson } from "./schema-CcoWb32N.js";
2
- import { v as Engine, w as Scene } from "./behavior-Bod1AyJS.js";
2
+ import { v as Engine, w as Scene } from "./behavior-CPibUfnH.js";
3
3
 
4
4
  //#region src/core/scene/loader.d.ts
5
5
  interface LoadSceneOptions {
package/dist/net.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CcoWb32N.js";
2
- import { $ as Node, l as PropSchema, tt as Signal, v as Engine } from "./behavior-Bod1AyJS.js";
2
+ import { $ as Node, l as PropSchema, tt as Signal, v as Engine } from "./behavior-CPibUfnH.js";
3
3
 
4
4
  //#region src/net/types.d.ts
5
5
  type Unsubscribe = () => void;