incanto 0.4.3 → 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.
Files changed (44) hide show
  1. package/dist/2d.d.ts +80 -6
  2. package/dist/2d.js +4 -4
  3. package/dist/3d.d.ts +200 -15
  4. package/dist/3d.js +4 -4
  5. package/dist/{audio-player-DqUR3XFs.d.ts → audio-player-DkBqRTs4.d.ts} +1 -1
  6. package/dist/{behavior-BAQq7HGM.d.ts → behavior-CWhW3oa6.d.ts} +54 -0
  7. package/dist/{create-game-CZHROKcT.js → create-game-B_sW_eiE.js} +59 -27
  8. package/dist/{create-game-DiTq3-fQ.js → create-game-DkbZCNaV.js} +292 -14
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/{duplicate-DP2WPYom.js → duplicate-BEvGBtb_.js} +1 -1
  11. package/dist/{gameplay-Ccruc3Wd.js → gameplay-CDFgSG6z.js} +293 -24
  12. package/dist/gameplay.d.ts +113 -2
  13. package/dist/gameplay.js +2 -2
  14. package/dist/index.d.ts +139 -5
  15. package/dist/index.js +59 -6
  16. package/dist/{loader-CGs_G-r0.js → loader-B4OEXDZ8.js} +60 -0
  17. package/dist/{loader-Mo0KghCv.d.ts → loader-D2u0fVW2.d.ts} +1 -1
  18. package/dist/net.d.ts +1 -1
  19. package/dist/net.js +1 -1
  20. package/dist/{particle-sim-DYuSUxvK.js → particle-sim-CFkILGwh.js} +84 -3
  21. package/dist/{particle-sim-CbN4YUuH.d.ts → particle-sim-CwJ5rI_P.d.ts} +3 -0
  22. package/dist/{physics-2d-KuMWPTf6.js → physics-2d-Cgli1aju.js} +95 -10
  23. package/dist/{physics-3d-DmNCeh58.js → physics-3d-CBAQ12LY.js} +121 -43
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +1 -1
  26. package/dist/{register-DPEV9_9t.js → register-BJCfuuZ2.js} +79 -5
  27. package/dist/{register-B0gq63VW.js → register-C35HDZpm.js} +2 -2
  28. package/dist/{register-BRy8FNox.js → register-Dd7Juujf.js} +389 -49
  29. package/dist/{register-BuUV1_KB.js → register-Dl_ixIJe.js} +275 -2
  30. package/dist/test.d.ts +2 -2
  31. package/dist/test.js +10 -10
  32. package/editor/assets/{agent8-DryTXVd4.js → agent8-MciFwn0v.js} +1 -1
  33. package/editor/assets/index-mofVSmuA.js +7417 -0
  34. package/editor/index.html +1 -1
  35. package/package.json +1 -1
  36. package/schemas/scene.schema.json +980 -126
  37. package/skills/incanto-3d-models.md +24 -1
  38. package/skills/incanto-building-2d-games.md +6 -0
  39. package/skills/incanto-building-3d-games.md +46 -1
  40. package/skills/incanto-gameplay-behaviors.md +60 -0
  41. package/skills/incanto-hud.md +58 -0
  42. package/skills/incanto-node-reference.md +148 -0
  43. package/skills/incanto-physics-and-input.md +47 -0
  44. package/editor/assets/index-N8se-PhP.js +0 -7365
package/dist/2d.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { s as JsonObject } from "./schema-CcoWb32N.js";
2
- import { C as RendererStats, S as GameStats, T as Node, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-BAQq7HGM.js";
3
- import { t as LoadSceneOptions } from "./loader-Mo0KghCv.js";
4
- import { t as ParticleSim } from "./particle-sim-CbN4YUuH.js";
2
+ import { C as RendererStats, S as GameStats, T as Node, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-CWhW3oa6.js";
3
+ import { t as LoadSceneOptions } from "./loader-D2u0fVW2.js";
4
+ import { t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
5
5
  import { Group, Mesh, Object3D, Scene, Texture } from "three";
6
6
  import * as RapierNs from "@dimforge/rapier2d-compat";
7
7
 
@@ -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;
@@ -76,7 +78,16 @@ declare class PhysicsBody2D extends Node2D {
76
78
  static override readonly props: PropSchema;
77
79
  /** The unified collision model: every collider participant can emit these. */
78
80
  static override readonly signals: readonly string[];
79
- collider: JsonObject;
81
+ private _collider;
82
+ /** @internal Physics resync flag — raised when `collider` is REPLACED. */
83
+ _colliderChanged: boolean;
84
+ /**
85
+ * Collider shape prop. Change it by REPLACING the object
86
+ * (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
87
+ * body on replacement; in-place mutation of the old object is not watched.
88
+ */
89
+ get collider(): JsonObject;
90
+ set collider(value: JsonObject);
80
91
  /** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
81
92
  static validateJson(node: Node): void;
82
93
  /** @internal Set by Physics2D when the body is created. */
@@ -164,7 +175,27 @@ declare class Physics2D {
164
175
  moveAndSlide(node: CharacterBody2D): void;
165
176
  /** Rapier debug segments, scaled back to pixels. Null while debugDraw is off. */
166
177
  debugLines(): Float32Array | null;
178
+ /** Create newly-arrived joints; tear down departed ones. */
179
+ private syncJoints;
180
+ /**
181
+ * World-space raycast in PIXELS (y-down). `exclude` skips that body — a
182
+ * shooter probing from inside its own collider needs it. Sensors (Area2D)
183
+ * never block rays. Returns distance (px), the surface normal, and the hit
184
+ * body — or null.
185
+ */
186
+ castRay(origin: [number, number], dir: [number, number], maxLen: number, exclude?: PhysicsBody2D, opts?: {
187
+ staticOnly?: boolean;
188
+ }): {
189
+ distance: number;
190
+ normal: [number, number];
191
+ node: PhysicsBody2D | null;
192
+ } | null;
167
193
  dispose(): void;
194
+ private lastStructure;
195
+ private lastRoot;
196
+ private readonly jointSet;
197
+ private readonly joints;
198
+ private readonly bodySet;
168
199
  private syncBodies;
169
200
  private ensureEntry;
170
201
  }
@@ -409,6 +440,39 @@ declare class ColorRect2D extends Node2D {
409
440
  override _syncObject2D(assets: AssetStore2D | null): void;
410
441
  }
411
442
  //#endregion
443
+ //#region src/2d/nodes/joint-2d.d.ts
444
+ type JointType2D = "fixed" | "revolute" | "rope" | "spring";
445
+ /**
446
+ * A physics joint linking its PARENT body to `target` (a node path to the
447
+ * other body). Godot-style placement: the joint lives as a child of body A.
448
+ *
449
+ * { "name": "Hinge", "type": "Joint2D",
450
+ * "props": { "type": "revolute", "target": "%Anchor", "anchor": [0, -20] } }
451
+ *
452
+ * Types: `fixed` welds the bodies rigidly · `revolute` is a pin/hinge at the
453
+ * anchors · `rope` caps the anchor distance at `length` (px; 0 = measured at
454
+ * creation) · `spring` pulls toward `length` with `stiffness`/`damping`.
455
+ * Anchors are LOCAL pixel offsets on each body.
456
+ */
457
+ declare class Joint2D extends Node2D {
458
+ static override readonly typeName: string;
459
+ static override readonly props: PropSchema;
460
+ type: JointType2D;
461
+ target: string;
462
+ anchor: number[];
463
+ targetAnchor: number[];
464
+ /** rope/spring rest length in px; 0 = measure body distance at creation. */
465
+ length: number;
466
+ stiffness: number;
467
+ damping: number;
468
+ override onEnterTree(): void;
469
+ /** @internal The two bodies, resolved (throws on a bad target at step time). */
470
+ _resolveBodies(): {
471
+ a: PhysicsBody2D;
472
+ b: PhysicsBody2D;
473
+ };
474
+ }
475
+ //#endregion
412
476
  //#region src/2d/nodes/label.d.ts
413
477
  /**
414
478
  * CanvasTexture-backed text (zero deps, WebGL-safe). Re-rasterizes only when
@@ -573,6 +637,9 @@ declare class Renderer2D {
573
637
  } | null;
574
638
  private readonly webgl;
575
639
  private readonly worldScene;
640
+ private readonly syncScratch;
641
+ /** Editors set this: keep syncing `static: true` subtrees every frame. */
642
+ ignoreStatic: boolean;
576
643
  private readonly uiScene;
577
644
  private readonly worldCam;
578
645
  private readonly uiCam;
@@ -637,6 +704,13 @@ interface UiSize {
637
704
  *
638
705
  * Same dirty-push contract as the 3D sync; pure scene-graph math, headless-testable.
639
706
  */
640
- declare function syncTree2D(root: Node, world: Scene, ui: Scene, assets: AssetStore2D | null, uiSize?: UiSize): Sync2DResult;
707
+ /** Per-renderer reusable containers cleared each frame, never reallocated. */
708
+ interface Sync2DScratch {
709
+ visited: Set<Object3D>;
710
+ cameras: Camera2D[];
711
+ }
712
+ declare function syncTree2D(root: Node, world: Scene, ui: Scene, assets: AssetStore2D | null, uiSize?: UiSize, scratch?: Sync2DScratch, opts?: {
713
+ ignoreStatic?: boolean;
714
+ }): Sync2DResult;
641
715
  //#endregion
642
- export { AnimatedSprite2D, type AnimationDef, Area2D, type AssetStatus, AssetStore2D, Camera2D, CharacterBody2D, CharacterController2D, ColorRect2D, type CreateGame2DOptions, type Game2D, 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 };
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-CZHROKcT.js";
3
- import { a as ColorRect2D, c as AnimatedSprite2D, d as CharacterBody2D, f as PhysicsBody2D, h as Node2D, i as Label, l as Sprite2D, m as StaticBody2D, n as UILayer, o as CharacterController2D, p as RigidBody2D, r as Particles2D, s as Camera2D, t as registerNodes2D, u as Area2D } from "./register-DPEV9_9t.js";
4
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-KuMWPTf6.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
@@ -41,4 +41,4 @@ function spriteFromLibraryMeta(meta, opts) {
41
41
  };
42
42
  }
43
43
  //#endregion
44
- export { AnimatedSprite2D, Area2D, AssetStore2D, Camera2D, CharacterBody2D, CharacterController2D, ColorRect2D, Label, Node2D, Particles2D, Physics2D, PhysicsBody2D, Renderer2D, RigidBody2D, Sprite2D, StaticBody2D, UILayer, createGame2D, enablePhysics2D, registerNodes2D, spriteFromLibraryMeta, syncTree2D };
44
+ export { AnimatedSprite2D, Area2D, AssetStore2D, Camera2D, CharacterBody2D, CharacterController2D, ColorRect2D, Joint2D, Label, Node2D, Particles2D, Physics2D, PhysicsBody2D, Renderer2D, RigidBody2D, Sprite2D, StaticBody2D, UILayer, createGame2D, enablePhysics2D, registerNodes2D, spriteFromLibraryMeta, syncTree2D };
package/dist/3d.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { s as JsonObject } from "./schema-CcoWb32N.js";
2
- import { C as RendererStats, S as GameStats, T as Node, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-BAQq7HGM.js";
3
- import { t as LoadSceneOptions } from "./loader-Mo0KghCv.js";
4
- import { t as ParticleSim } from "./particle-sim-CbN4YUuH.js";
5
- import { r as SpatialPose } from "./audio-player-DqUR3XFs.js";
6
- import { AnimationClip, BufferGeometry, Color, DirectionalLight, InstancedMesh, Object3D, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
2
+ import { C as RendererStats, S as GameStats, T as Node, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-CWhW3oa6.js";
3
+ import { t as LoadSceneOptions } from "./loader-D2u0fVW2.js";
4
+ import { t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
5
+ import { r as SpatialPose } from "./audio-player-DkBqRTs4.js";
6
+ import { AnimationClip, BufferGeometry, Color, DirectionalLight, InstancedMesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
7
7
  import { VRM } from "@pixiv/three-vrm";
8
8
  import { Sky } from "three/examples/jsm/objects/Sky.js";
9
9
  import * as RapierNs from "@dimforge/rapier3d-compat";
@@ -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
@@ -173,7 +182,16 @@ declare class PhysicsBody3D extends Node3D {
173
182
  static override readonly props: PropSchema;
174
183
  /** The unified collision model: every collider participant can emit these. */
175
184
  static override readonly signals: readonly string[];
176
- collider: JsonObject;
185
+ private _collider;
186
+ /** @internal Physics resync flag — raised when `collider` is REPLACED. */
187
+ _colliderChanged: boolean;
188
+ /**
189
+ * Collider shape prop. Change it by REPLACING the object
190
+ * (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
191
+ * body on replacement; in-place mutation of the old object is not watched.
192
+ */
193
+ get collider(): JsonObject;
194
+ set collider(value: JsonObject);
177
195
  /** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
178
196
  static validateJson(node: Node): void;
179
197
  /** @internal Set by Physics3D when the body is created. */
@@ -260,6 +278,11 @@ declare class Physics3D {
260
278
  /** Rapier debug segments (meters). Null while debugDraw is off. */
261
279
  debugLines(): Float32Array | null;
262
280
  dispose(): void;
281
+ private lastStructure;
282
+ private lastRoot;
283
+ private readonly jointSet;
284
+ private readonly joints;
285
+ private readonly bodySet;
263
286
  private syncBodies;
264
287
  private ensureEntry;
265
288
  /** Apply a world-space impulse to a dynamic body (character controllers). */
@@ -268,6 +291,8 @@ declare class Physics3D {
268
291
  massOf(node: PhysicsBody3D): number;
269
292
  /** Current solver velocity (fresher than the node prop mid-step). */
270
293
  velocityOf(node: PhysicsBody3D): [number, number, number];
294
+ /** Create newly-arrived joints; tear down departed ones. */
295
+ private syncJoints;
271
296
  /**
272
297
  * World-space raycast (meters). `exclude` skips that body's collider —
273
298
  * ground probes from inside a capsule need it. `opts.staticOnly` hits ONLY the
@@ -396,6 +421,14 @@ interface ShadowsEnvironment {
396
421
  mapSize: 1024 | 2048;
397
422
  /** Shadow edge softening radius (PCF blur, default 1). */
398
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;
399
432
  }
400
433
  interface CloudsEnvironment {
401
434
  /** How much of the sky is cloudy, 0..1. */
@@ -415,6 +448,14 @@ interface CloudsEnvironment {
415
448
  /** Feature size in world units (bigger = larger, softer cloud masses). */
416
449
  scale: number;
417
450
  }
451
+ interface BloomEnvironment {
452
+ /** Linear-HDR luminance above which pixels bloom (the pass runs BEFORE tone
453
+ * mapping, so emissives/sun glints sit above 1). Default 1 = only
454
+ * brighter-than-white glows; ~0.8 pulls in bright paint, 1.5+ = emissives only. */
455
+ threshold: number;
456
+ /** How strongly the blurred glow is added back (0 = off, ~1.2 = neon). */
457
+ strength: number;
458
+ }
418
459
  interface Environment3DConfig {
419
460
  /** Tone-mapping exposure (ACES), default 1. */
420
461
  exposure: number;
@@ -424,6 +465,8 @@ interface Environment3DConfig {
424
465
  fog: FogEnvironment | null;
425
466
  /** Volumetric cloud layer (renderer raymarch composite), or null for none. */
426
467
  clouds: CloudsEnvironment | null;
468
+ /** Bloom post pass (bright-pass → blur → additive composite), or null. */
469
+ bloom: BloomEnvironment | null;
427
470
  /**
428
471
  * `null` = not declared (shadow maps stay available, free until a light
429
472
  * casts), `false` = force-disabled, object = declared ON with quality knobs
@@ -504,6 +547,8 @@ declare class Environment3D {
504
547
  get sunDirection(): [number, number, number] | null;
505
548
  /** Parsed volumetric-cloud config, or null when no cloud layer is declared. */
506
549
  get clouds(): CloudsEnvironment | null;
550
+ /** Parsed bloom config, or null when no bloom pass is declared. */
551
+ get bloom(): BloomEnvironment | null;
507
552
  /** The live scene fog (color/near/far) the cloud composite fades into, or null. */
508
553
  get sceneFog(): {
509
554
  color: Color;
@@ -965,6 +1010,39 @@ declare class Foliage3D extends Node3D implements SunConsumer3D {
965
1010
  override free(): void;
966
1011
  }
967
1012
  //#endregion
1013
+ //#region src/3d/nodes/joint-3d.d.ts
1014
+ type JointType3D = "fixed" | "spherical" | "rope" | "spring";
1015
+ /**
1016
+ * A physics joint linking its PARENT body to `target` (a node path to the
1017
+ * other body). Godot-style placement: the joint lives as a child of body A.
1018
+ *
1019
+ * { "name": "Hinge", "type": "Joint3D",
1020
+ * "props": { "type": "spherical", "target": "%Anchor", "anchor": [0, -20] } }
1021
+ *
1022
+ * Types: `fixed` welds the bodies rigidly · `spherical` is a ball joint at the
1023
+ * anchors · `rope` caps the anchor distance at `length` (m; 0 = measured at
1024
+ * creation) · `spring` pulls toward `length` with `stiffness`/`damping`.
1025
+ * Anchors are LOCAL meter offsets on each body.
1026
+ */
1027
+ declare class Joint3D extends Node3D {
1028
+ static override readonly typeName: string;
1029
+ static override readonly props: PropSchema;
1030
+ type: JointType3D;
1031
+ target: string;
1032
+ anchor: number[];
1033
+ targetAnchor: number[];
1034
+ /** rope/spring rest length in m; 0 = measure body distance at creation. */
1035
+ length: number;
1036
+ stiffness: number;
1037
+ damping: number;
1038
+ override onEnterTree(): void;
1039
+ /** @internal The two bodies, resolved (throws on a bad target at step time). */
1040
+ _resolveBodies(): {
1041
+ a: PhysicsBody3D;
1042
+ b: PhysicsBody3D;
1043
+ };
1044
+ }
1045
+ //#endregion
968
1046
  //#region src/3d/nodes/lights-3d.d.ts
969
1047
  /** Sun-style light. Direction comes from the node's rotation. */
970
1048
  declare class DirectionalLight3D extends Node3D {
@@ -1021,6 +1099,14 @@ interface MeshMaterialProps {
1021
1099
  /** Write to the depth buffer (default true). Set false for additive/overlay
1022
1100
  * decals so they don't occlude effects drawn after them. */
1023
1101
  depthWrite?: boolean;
1102
+ /** 0..1 lacquer layer over the base — THE car-paint look: a glossy clear
1103
+ * coat with its own reflection on top of a metallic base. */
1104
+ clearcoat?: number;
1105
+ /** 0..1 roughness of the clearcoat layer alone (default 0 = mirror lacquer). */
1106
+ clearcoatRoughness?: number;
1107
+ /** Reflection strength from the scene's image-based lighting (the PMREM'd
1108
+ * sky / HDRI). 1 = scene default; 2+ makes glass visibly mirror the sky. */
1109
+ envMapIntensity?: number;
1024
1110
  /** Color texture URL (sampled sRGB, tinted by `color`). */
1025
1111
  map?: string;
1026
1112
  /** Tangent-space normal map URL (sampled linear). */
@@ -1037,7 +1123,8 @@ interface MeshMaterialProps {
1037
1123
  * - `size [x,y,z]`: box extents; sphere/gem radius = x; capsule radius = x,
1038
1124
  * height = y; plane = x·z ground plane (laid flat); cylinder radius = x, height = y
1039
1125
  * - `material`: `{color, metalness, roughness, wireframe, flatShading, opacity,
1040
- * depthTest, depthWrite, emissive, emissiveIntensity, map, normalMap, repeat}` —
1126
+ * clearcoat, clearcoatRoughness, envMapIntensity, depthTest, depthWrite,
1127
+ * emissive, emissiveIntensity, map, normalMap, repeat}` —
1041
1128
  * `map`/`normalMap` are texture URLs loaded lazily (headless-safe: geometry and
1042
1129
  * physics never wait on them), tiled by `repeat: [u, v]`. `gem` + `flatShading:
1043
1130
  * true` + a low `roughness` reads as a sparkling faceted jewel (spin it for the
@@ -1067,6 +1154,56 @@ declare class MeshInstance3D extends Node3D {
1067
1154
  override free(): void;
1068
1155
  }
1069
1156
  //#endregion
1157
+ //#region src/3d/nodes/loft-mesh-3d.d.ts
1158
+ /**
1159
+ * One station of a loft: the hull's cross-section at local `z`.
1160
+ * Sections are interpolated with a Catmull-Rom spline, so the surface flows
1161
+ * SMOOTHLY through every station — this is how real car bodies are modelled.
1162
+ */
1163
+ interface LoftSection {
1164
+ /** Station along the hull's local Z (must ascend across `sections`). */
1165
+ z: number;
1166
+ /** Full width of the cross-section at this station. */
1167
+ width: number;
1168
+ /** Full height of the cross-section. */
1169
+ height: number;
1170
+ /** Height of the section CENTRE above the node origin. */
1171
+ y: number;
1172
+ /** 0 = boxy (slab-sided) … 1 = fully elliptical (default 0.7). The lower
1173
+ * half is always kept flatter — vehicle floors are flat. */
1174
+ corner?: number;
1175
+ }
1176
+ /**
1177
+ * A smooth swept hull ("loft"): superellipse cross-sections interpolated
1178
+ * along Z with rounded end caps and per-vertex normals — curved highlights
1179
+ * and sky reflections sweep across it like sheet metal, which no box/capsule
1180
+ * assembly can do. THE node for vehicle bodies, glass canopies, boat hulls,
1181
+ * fuselages.
1182
+ *
1183
+ * - `sections`: ≥2 stations front→back (see LoftSection)
1184
+ * - `slices`: vertices around each ring (8..64, default 24)
1185
+ * - `smooth`: interpolated rings per section gap (1..16, default 4)
1186
+ * - `material`: same object as MeshInstance3D **minus textures** (a loft has
1187
+ * no UVs — map/normalMap/repeat hard-fail). Pair `clearcoat` paint or a
1188
+ * high `envMapIntensity` glass with the smooth normals for the automotive
1189
+ * look.
1190
+ */
1191
+ declare class LoftMesh3D extends Node3D {
1192
+ static override readonly typeName: string;
1193
+ static override readonly props: PropSchema;
1194
+ sections: LoftSection[];
1195
+ slices: number;
1196
+ smooth: number;
1197
+ material: MeshMaterialProps;
1198
+ castShadow: boolean;
1199
+ receiveShadow: boolean;
1200
+ /** Loader hook: malformed sections/materials fail at LOAD time. */
1201
+ static validateJson(node: Node): void;
1202
+ private geometryKey;
1203
+ protected override _createObject3D(): Object3D;
1204
+ override _syncObject3D(): void;
1205
+ }
1206
+ //#endregion
1070
1207
  //#region src/3d/nodes/model-instance-3d.d.ts
1071
1208
  /**
1072
1209
  * A GLB/glTF/VRM model. `model` is a `$key` into the scene's assets header
@@ -1094,6 +1231,12 @@ declare class ModelInstance3D extends Node3D {
1094
1231
  /** Hex colour multiplied into every material — RESKIN one shared GLB into many
1095
1232
  * variants (e.g. a sickly-green zombie from the base human). '' = untinted. */
1096
1233
  tint: string;
1234
+ /** Override every material's metalness (0..1); -1 keeps the authored value.
1235
+ * Low-poly kits often ship fully rough — 0.5–0.8 turns flat paint into a
1236
+ * glinting clear-coat under the scene's IBL. Cloned per instance like tint. */
1237
+ metalness: number;
1238
+ /** Override every material's roughness (0..1); -1 keeps the authored value. */
1239
+ roughness: number;
1097
1240
  private store;
1098
1241
  private entry;
1099
1242
  private mountedRef;
@@ -1107,20 +1250,33 @@ declare class ModelInstance3D extends Node3D {
1107
1250
  * frames after `this.animation` already moved on). */
1108
1251
  private actionKeys;
1109
1252
  private warnedClaim;
1110
- /** Per-mesh original (shared) materials, captured at mount so `tint` can always
1111
- * re-derive from the source. Our clones are tracked for disposal. */
1253
+ /** Per-mesh original (shared) materials, captured at mount so tint/overrides
1254
+ * can always re-derive from the source. Our clones are tracked for disposal. */
1112
1255
  private tintTargets;
1113
1256
  private tintedClones;
1114
- private appliedTint;
1257
+ private appliedMods;
1258
+ /** Named sub-node poses (degrees), re-applied every sync — drive wheels,
1259
+ * turrets, doors from behaviors without touching three directly. */
1260
+ private nodePoses;
1261
+ /**
1262
+ * Rotate a named sub-node of the loaded model (local Euler XYZ, degrees) —
1263
+ * e.g. spin a GLB car's `wheel-front-left` every frame, aim a turret, swing a
1264
+ * door. Poses persist and re-apply on every sync (safe to set before the
1265
+ * model finishes loading). Unknown names are ignored. `npx incanto-model
1266
+ * <file>` lists a model's node names.
1267
+ */
1268
+ poseNode(name: string, rotation: [number, number, number]): void;
1115
1269
  protected override _createObject3D(): Object3D;
1116
1270
  /** Embedded clip names + the scene's animation `$key`s (editor dropdown). */
1117
1271
  availableAnimations(): string[];
1118
1272
  /** Called by the 3D sync pass with the renderer's asset store. */
1119
1273
  _syncModel(assets: AssetStore3D): void;
1120
- /** Multiply `tint` into every material a per-instance reskin of one shared
1121
- * GLB (e.g. base human green zombie). Clones the captured originals (never
1122
- * mutates the shared source), disposing prior clones on change. */
1123
- private applyTint;
1274
+ /** Apply per-instance material mods`tint` (multiplied into colour) and the
1275
+ * `metalness`/`roughness` overrides by cloning the captured originals
1276
+ * (never mutates the shared source), disposing prior clones on change. */
1277
+ private applyMaterialMods;
1278
+ /** Re-apply every queued named-node pose onto the mounted instance. */
1279
+ private applyPoses;
1124
1280
  private syncAnimation;
1125
1281
  override update(dt: number): void;
1126
1282
  override onExitTree(): void;
@@ -1775,6 +1931,8 @@ declare class Renderer3D {
1775
1931
  private compiledScene;
1776
1932
  /** Reused per-frame walk scratch (instance-scoped — never a module global). */
1777
1933
  private readonly syncScratch;
1934
+ /** Editors set this: keep syncing `static: true` subtrees every frame. */
1935
+ ignoreStatic: boolean;
1778
1936
  /** Reused render-hook context — gl/scene are stable, camera reassigned/frame. */
1779
1937
  private renderCtx;
1780
1938
  /** Underwater caustics pass (lazy — only created the first time submerged). */
@@ -1794,11 +1952,30 @@ declare class Renderer3D {
1794
1952
  private cloudsBlurUniforms;
1795
1953
  private cloudsCompositeScene;
1796
1954
  private cloudsCompositeUniforms;
1955
+ private bloomTarget;
1956
+ private bloomBrightTarget;
1957
+ private bloomBlurTarget;
1958
+ private bloomBrightScene;
1959
+ private bloomBrightUniforms;
1960
+ private bloomBlurScene;
1961
+ private bloomBlurUniforms;
1962
+ private bloomCompositeScene;
1963
+ private bloomCompositeUniforms;
1797
1964
  constructor(opts: Renderer3DOptions);
1798
1965
  private readonly debugLines;
1799
1966
  private syncDebugLines;
1800
1967
  private render;
1801
1968
  /**
1969
+ * Bloom post pass (only when `environment.bloom` is declared). LDR and safe:
1970
+ * 1. scene → full-res offscreen (tone-mapped exactly like direct output)
1971
+ * 2. bright-pass at HALF res (keep pixels above threshold)
1972
+ * 3. separable Gaussian blur (H, V) at half res
1973
+ * 4. composite scene + glow×strength → screen (pure ADD — never re-touches
1974
+ * the scene's tone; the clouds pipeline taught us that lesson)
1975
+ * Caustics/clouds paths take precedence (composite pipelines don't stack yet).
1976
+ */
1977
+ private renderWithBloom;
1978
+ /**
1802
1979
  * Volumetric cloud pass (only when `environment.clouds` is declared — scenes
1803
1980
  * without it render exactly as before, zero cost). Three steps:
1804
1981
  * 1. scene → full-res offscreen target (color + depth)
@@ -1842,6 +2019,11 @@ declare class Renderer3D {
1842
2019
  up: Vector3;
1843
2020
  forward: Vector3;
1844
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;
1845
2027
  dispose(): void;
1846
2028
  }
1847
2029
  //#endregion
@@ -1885,6 +2067,8 @@ interface SyncScratch {
1885
2067
  }
1886
2068
  interface SyncOptions {
1887
2069
  assets?: AssetStore3D;
2070
+ /** Sync `static: true` subtrees every frame anyway (the editor's edit mode). */
2071
+ ignoreStatic?: boolean;
1888
2072
  /**
1889
2073
  * The environment sky's unit sun direction. Pushed to every
1890
2074
  * `_applySunDirection` node (Water3D, Foliage3D) right after its own sync —
@@ -1921,6 +2105,7 @@ interface WalkState {
1921
2105
  sunDirection: readonly [number, number, number] | null;
1922
2106
  sunLight: DirectionalLight3D | null;
1923
2107
  alpha: number;
2108
+ ignoreStatic: boolean;
1924
2109
  }
1925
2110
  //#endregion
1926
- 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, 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 };
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-DiTq3-fQ.js";
2
- import { C as terrainThemeLayers, D as RigidBody3D, E as PhysicsBody3D, F as rigPose, M as cameraRelative, N as keyboardIntensity, O as StaticBody3D, P as movementState, S as TERRAIN_THEMES, T as CharacterBody3D, _ as CharacterController3D, a as VoxelGrid3D, b as Terrain3D, c as ModelInstance3D, d as OmniLight3D, f as Foliage3D, g as FLOWER_VARIETIES, h as resolveFlowerDensity, i as VOXEL_PALETTE, j as QUARTER_PITCH, k as Node3D, l as MeshInstance3D, m as Flowers3D, n as Water3D, o as Tree3D, p as DENSITY_PRESETS, r as WATER_MAX_RIPPLES, s as Particles3D, t as registerNodes3D, u as DirectionalLight3D, v as Camera3D, w as Area3D, x as DEFAULT_TERRAIN_TEXTURE_BASE, y as Billboard3D } from "./register-BRy8FNox.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-DmNCeh58.js";
5
- export { Area3D, AssetStore3D, Billboard3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, 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 };
4
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-CBAQ12LY.js";
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 };
@@ -1,4 +1,4 @@
1
- import { F as Listener, T as Node, l as PropSchema } from "./behavior-BAQq7HGM.js";
1
+ import { F as Listener, T as Node, l as PropSchema } from "./behavior-CWhW3oa6.js";
2
2
 
3
3
  //#region src/core/nodes/audio-player.d.ts
4
4
  /**
@@ -374,6 +374,34 @@ declare class InputMap {
374
374
  * wheel → wheelDelta. `lockOnClick` requests pointer lock on mousedown
375
375
  * (the FPS pattern).
376
376
  */
377
+ private readonly padAxesState;
378
+ private padButtonsDown;
379
+ /**
380
+ * Feed one polled gamepad snapshot (standard mapping). Buttons become
381
+ * codes `Pad0`..`Pad16` in the same space as keys — declare them in input
382
+ * actions like any key ("jump": ["Space", "Pad0"]). Axes land in
383
+ * `padAxes()` with a deadzone. Pass null when no pad is connected.
384
+ */
385
+ pollGamepad(pad: {
386
+ buttons: readonly {
387
+ pressed: boolean;
388
+ }[];
389
+ axes: readonly number[];
390
+ } | null): void;
391
+ /** Deadzoned analog sticks: 0 = left {x,y}, 1 = right. */
392
+ padAxes(stick?: 0 | 1): {
393
+ x: number;
394
+ y: number;
395
+ };
396
+ /**
397
+ * Poll the browser Gamepad API every frame (the first connected pad).
398
+ * Wire once at boot: `engine.input.attachGamepad(engine)`. Headless no-op.
399
+ */
400
+ attachGamepad(engine: {
401
+ updated: {
402
+ connect(fn: () => void): () => void;
403
+ };
404
+ }): () => void;
377
405
  attachPointer(target: HTMLElement, opts?: {
378
406
  lockOnClick?: boolean;
379
407
  }): () => void;
@@ -463,6 +491,18 @@ declare class SceneTree {
463
491
  private _root;
464
492
  private _engine;
465
493
  private readonly freeQueue;
494
+ /**
495
+ * name → attached nodes with that name. Maintained by Node enter/exit/rename
496
+ * so `%name` targeting and root-level getNodesByName are O(1) — behaviors
497
+ * resolve targets every frame and a full-tree walk per chaser melted horde
498
+ * scenes (N chasers × whole tree, 60×/s).
499
+ */
500
+ private readonly _nameIndex;
501
+ /**
502
+ * Bumped whenever ANY node attaches or detaches — cheap "did the tree
503
+ * change shape?" check for per-step body gathers and similar caches.
504
+ */
505
+ _structureVersion: number;
466
506
  private _frameId;
467
507
  /**
468
508
  * Monotonic update-frame counter — bumped once per `update()`. Per-frame
@@ -470,6 +510,12 @@ declare class SceneTree {
470
510
  * at most once a frame regardless of node visitation order.
471
511
  */
472
512
  get frameId(): number;
513
+ /** @internal Node attach hook. */
514
+ _indexName(node: Node): void;
515
+ /** @internal Node detach/rename hook (`name` = the entry to remove). */
516
+ _unindexName(node: Node, name?: string): void;
517
+ /** @internal Every ATTACHED node named `name` (undefined = none). */
518
+ _nodesNamed(name: string): ReadonlySet<Node> | undefined;
473
519
  /**
474
520
  * @internal Opaque per-frame scratch the 3d layer parks its shared moving-body
475
521
  * snapshot on (kept here so it is per-tree-instance, never a module global —
@@ -632,6 +678,8 @@ declare class Scene {
632
678
  /** Design-resolution viewport (consumed by renderers via resolveViewport). */
633
679
  readonly viewport: JsonObject | undefined;
634
680
  private readonly connections;
681
+ /** The full source JSON this scene was loaded from — `restartScene` fuel. */
682
+ readonly source: SceneJson;
635
683
  constructor(source: SceneJson, root: Node, tree: SceneTree);
636
684
  /** Lossless export. Note: expanded sub-scene instances serialize as full trees in M1. */
637
685
  toJSON(): SceneJson;
@@ -719,6 +767,12 @@ declare class Engine {
719
767
  private accumulator;
720
768
  private disposeScheduler;
721
769
  private readonly frameStats;
770
+ /**
771
+ * Game-time multiplier: 1 = realtime, 0.5 = slow motion, 0 = frozen
772
+ * (hit-stop / pause). Scales BOTH variable and fixed updates — physics,
773
+ * timers, behaviors all breathe together. See `gameplay` `hitStop()`.
774
+ */
775
+ timeScale: number;
722
776
  constructor(opts?: EngineOptions);
723
777
  get scene(): Scene | null;
724
778
  /** Replace the active scene. The previous scene's root is freed. */