incanto 0.4.2 → 0.5.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 (43) hide show
  1. package/dist/2d.d.ts +74 -6
  2. package/dist/2d.js +4 -4
  3. package/dist/3d.d.ts +174 -16
  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-BFESHv1X.js} +37 -25
  8. package/dist/{create-game-CMS7DiPi.js → create-game-D8UvwXme.js} +231 -8
  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-KXn1N1jF.js} +95 -10
  23. package/dist/{physics-3d-CXOBOUKG.js → physics-3d-C58oQzTX.js} +121 -43
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +1 -1
  26. package/dist/{register-B0gq63VW.js → register-C35HDZpm.js} +2 -2
  27. package/dist/{register-DPEV9_9t.js → register-D71C4rDC.js} +76 -5
  28. package/dist/{register-BuUV1_KB.js → register-Dl_ixIJe.js} +275 -2
  29. package/dist/{register-02aSywx2.js → register-DvPHJdVj.js} +390 -59
  30. package/dist/test.d.ts +2 -2
  31. package/dist/test.js +10 -10
  32. package/editor/assets/{agent8-DUVZGcuO.js → agent8-DruKhR22.js} +1 -1
  33. package/editor/assets/index-D2qufqUo.js +7417 -0
  34. package/editor/index.html +1 -1
  35. package/package.json +1 -1
  36. package/schemas/scene.schema.json +847 -133
  37. package/skills/incanto-3d-models.md +24 -1
  38. package/skills/incanto-building-3d-games.md +35 -3
  39. package/skills/incanto-gameplay-behaviors.md +60 -0
  40. package/skills/incanto-hud.md +58 -0
  41. package/skills/incanto-node-reference.md +113 -0
  42. package/skills/incanto-physics-and-input.md +47 -0
  43. package/editor/assets/index-DsM2Yp9F.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
 
@@ -76,7 +76,16 @@ declare class PhysicsBody2D extends Node2D {
76
76
  static override readonly props: PropSchema;
77
77
  /** The unified collision model: every collider participant can emit these. */
78
78
  static override readonly signals: readonly string[];
79
- collider: JsonObject;
79
+ private _collider;
80
+ /** @internal Physics resync flag — raised when `collider` is REPLACED. */
81
+ _colliderChanged: boolean;
82
+ /**
83
+ * Collider shape prop. Change it by REPLACING the object
84
+ * (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
85
+ * body on replacement; in-place mutation of the old object is not watched.
86
+ */
87
+ get collider(): JsonObject;
88
+ set collider(value: JsonObject);
80
89
  /** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
81
90
  static validateJson(node: Node): void;
82
91
  /** @internal Set by Physics2D when the body is created. */
@@ -164,7 +173,27 @@ declare class Physics2D {
164
173
  moveAndSlide(node: CharacterBody2D): void;
165
174
  /** Rapier debug segments, scaled back to pixels. Null while debugDraw is off. */
166
175
  debugLines(): Float32Array | null;
176
+ /** Create newly-arrived joints; tear down departed ones. */
177
+ private syncJoints;
178
+ /**
179
+ * World-space raycast in PIXELS (y-down). `exclude` skips that body — a
180
+ * shooter probing from inside its own collider needs it. Sensors (Area2D)
181
+ * never block rays. Returns distance (px), the surface normal, and the hit
182
+ * body — or null.
183
+ */
184
+ castRay(origin: [number, number], dir: [number, number], maxLen: number, exclude?: PhysicsBody2D, opts?: {
185
+ staticOnly?: boolean;
186
+ }): {
187
+ distance: number;
188
+ normal: [number, number];
189
+ node: PhysicsBody2D | null;
190
+ } | null;
167
191
  dispose(): void;
192
+ private lastStructure;
193
+ private lastRoot;
194
+ private readonly jointSet;
195
+ private readonly joints;
196
+ private readonly bodySet;
168
197
  private syncBodies;
169
198
  private ensureEntry;
170
199
  }
@@ -409,6 +438,39 @@ declare class ColorRect2D extends Node2D {
409
438
  override _syncObject2D(assets: AssetStore2D | null): void;
410
439
  }
411
440
  //#endregion
441
+ //#region src/2d/nodes/joint-2d.d.ts
442
+ type JointType2D = "fixed" | "revolute" | "rope" | "spring";
443
+ /**
444
+ * A physics joint linking its PARENT body to `target` (a node path to the
445
+ * other body). Godot-style placement: the joint lives as a child of body A.
446
+ *
447
+ * { "name": "Hinge", "type": "Joint2D",
448
+ * "props": { "type": "revolute", "target": "%Anchor", "anchor": [0, -20] } }
449
+ *
450
+ * Types: `fixed` welds the bodies rigidly · `revolute` is a pin/hinge at the
451
+ * anchors · `rope` caps the anchor distance at `length` (px; 0 = measured at
452
+ * creation) · `spring` pulls toward `length` with `stiffness`/`damping`.
453
+ * Anchors are LOCAL pixel offsets on each body.
454
+ */
455
+ declare class Joint2D extends Node2D {
456
+ static override readonly typeName: string;
457
+ static override readonly props: PropSchema;
458
+ type: JointType2D;
459
+ target: string;
460
+ anchor: number[];
461
+ targetAnchor: number[];
462
+ /** rope/spring rest length in px; 0 = measure body distance at creation. */
463
+ length: number;
464
+ stiffness: number;
465
+ damping: number;
466
+ override onEnterTree(): void;
467
+ /** @internal The two bodies, resolved (throws on a bad target at step time). */
468
+ _resolveBodies(): {
469
+ a: PhysicsBody2D;
470
+ b: PhysicsBody2D;
471
+ };
472
+ }
473
+ //#endregion
412
474
  //#region src/2d/nodes/label.d.ts
413
475
  /**
414
476
  * CanvasTexture-backed text (zero deps, WebGL-safe). Re-rasterizes only when
@@ -573,6 +635,7 @@ declare class Renderer2D {
573
635
  } | null;
574
636
  private readonly webgl;
575
637
  private readonly worldScene;
638
+ private readonly syncScratch;
576
639
  private readonly uiScene;
577
640
  private readonly worldCam;
578
641
  private readonly uiCam;
@@ -637,6 +700,11 @@ interface UiSize {
637
700
  *
638
701
  * Same dirty-push contract as the 3D sync; pure scene-graph math, headless-testable.
639
702
  */
640
- declare function syncTree2D(root: Node, world: Scene, ui: Scene, assets: AssetStore2D | null, uiSize?: UiSize): Sync2DResult;
703
+ /** Per-renderer reusable containers cleared each frame, never reallocated. */
704
+ interface Sync2DScratch {
705
+ visited: Set<Object3D>;
706
+ cameras: Camera2D[];
707
+ }
708
+ declare function syncTree2D(root: Node, world: Scene, ui: Scene, assets: AssetStore2D | null, uiSize?: UiSize, scratch?: Sync2DScratch): Sync2DResult;
641
709
  //#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 };
710
+ 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-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";
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";
@@ -173,7 +173,16 @@ declare class PhysicsBody3D extends Node3D {
173
173
  static override readonly props: PropSchema;
174
174
  /** The unified collision model: every collider participant can emit these. */
175
175
  static override readonly signals: readonly string[];
176
- collider: JsonObject;
176
+ private _collider;
177
+ /** @internal Physics resync flag — raised when `collider` is REPLACED. */
178
+ _colliderChanged: boolean;
179
+ /**
180
+ * Collider shape prop. Change it by REPLACING the object
181
+ * (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
182
+ * body on replacement; in-place mutation of the old object is not watched.
183
+ */
184
+ get collider(): JsonObject;
185
+ set collider(value: JsonObject);
177
186
  /** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
178
187
  static validateJson(node: Node): void;
179
188
  /** @internal Set by Physics3D when the body is created. */
@@ -260,6 +269,11 @@ declare class Physics3D {
260
269
  /** Rapier debug segments (meters). Null while debugDraw is off. */
261
270
  debugLines(): Float32Array | null;
262
271
  dispose(): void;
272
+ private lastStructure;
273
+ private lastRoot;
274
+ private readonly jointSet;
275
+ private readonly joints;
276
+ private readonly bodySet;
263
277
  private syncBodies;
264
278
  private ensureEntry;
265
279
  /** Apply a world-space impulse to a dynamic body (character controllers). */
@@ -268,6 +282,8 @@ declare class Physics3D {
268
282
  massOf(node: PhysicsBody3D): number;
269
283
  /** Current solver velocity (fresher than the node prop mid-step). */
270
284
  velocityOf(node: PhysicsBody3D): [number, number, number];
285
+ /** Create newly-arrived joints; tear down departed ones. */
286
+ private syncJoints;
271
287
  /**
272
288
  * World-space raycast (meters). `exclude` skips that body's collider —
273
289
  * ground probes from inside a capsule need it. `opts.staticOnly` hits ONLY the
@@ -415,6 +431,14 @@ interface CloudsEnvironment {
415
431
  /** Feature size in world units (bigger = larger, softer cloud masses). */
416
432
  scale: number;
417
433
  }
434
+ interface BloomEnvironment {
435
+ /** Linear-HDR luminance above which pixels bloom (the pass runs BEFORE tone
436
+ * mapping, so emissives/sun glints sit above 1). Default 1 = only
437
+ * brighter-than-white glows; ~0.8 pulls in bright paint, 1.5+ = emissives only. */
438
+ threshold: number;
439
+ /** How strongly the blurred glow is added back (0 = off, ~1.2 = neon). */
440
+ strength: number;
441
+ }
418
442
  interface Environment3DConfig {
419
443
  /** Tone-mapping exposure (ACES), default 1. */
420
444
  exposure: number;
@@ -424,6 +448,8 @@ interface Environment3DConfig {
424
448
  fog: FogEnvironment | null;
425
449
  /** Volumetric cloud layer (renderer raymarch composite), or null for none. */
426
450
  clouds: CloudsEnvironment | null;
451
+ /** Bloom post pass (bright-pass → blur → additive composite), or null. */
452
+ bloom: BloomEnvironment | null;
427
453
  /**
428
454
  * `null` = not declared (shadow maps stay available, free until a light
429
455
  * casts), `false` = force-disabled, object = declared ON with quality knobs
@@ -504,6 +530,8 @@ declare class Environment3D {
504
530
  get sunDirection(): [number, number, number] | null;
505
531
  /** Parsed volumetric-cloud config, or null when no cloud layer is declared. */
506
532
  get clouds(): CloudsEnvironment | null;
533
+ /** Parsed bloom config, or null when no bloom pass is declared. */
534
+ get bloom(): BloomEnvironment | null;
507
535
  /** The live scene fog (color/near/far) the cloud composite fades into, or null. */
508
536
  get sceneFog(): {
509
537
  color: Color;
@@ -573,7 +601,7 @@ declare class Billboard3D extends Node3D {
573
601
  static override readonly typeName: string;
574
602
  static override readonly props: PropSchema;
575
603
  /** `"screen"` copy the camera orientation (screen-aligned) · `"y"` upright
576
- * yaw-to-camera · `"none"` billboarding off (authored rotation applies). */
604
+ * screen-aligned yaw (no off-center roll) · `"none"` off (authored rotation applies). */
577
605
  mode: BillboardGroupMode;
578
606
  /** Last successfully billboarded WORLD orientation — reapplied on the frames
579
607
  * where `"y"` has no answer (camera dead overhead), because `_syncObject3D`
@@ -965,6 +993,39 @@ declare class Foliage3D extends Node3D implements SunConsumer3D {
965
993
  override free(): void;
966
994
  }
967
995
  //#endregion
996
+ //#region src/3d/nodes/joint-3d.d.ts
997
+ type JointType3D = "fixed" | "spherical" | "rope" | "spring";
998
+ /**
999
+ * A physics joint linking its PARENT body to `target` (a node path to the
1000
+ * other body). Godot-style placement: the joint lives as a child of body A.
1001
+ *
1002
+ * { "name": "Hinge", "type": "Joint3D",
1003
+ * "props": { "type": "spherical", "target": "%Anchor", "anchor": [0, -20] } }
1004
+ *
1005
+ * Types: `fixed` welds the bodies rigidly · `spherical` is a ball joint at the
1006
+ * anchors · `rope` caps the anchor distance at `length` (m; 0 = measured at
1007
+ * creation) · `spring` pulls toward `length` with `stiffness`/`damping`.
1008
+ * Anchors are LOCAL meter offsets on each body.
1009
+ */
1010
+ declare class Joint3D extends Node3D {
1011
+ static override readonly typeName: string;
1012
+ static override readonly props: PropSchema;
1013
+ type: JointType3D;
1014
+ target: string;
1015
+ anchor: number[];
1016
+ targetAnchor: number[];
1017
+ /** rope/spring rest length in m; 0 = measure body distance at creation. */
1018
+ length: number;
1019
+ stiffness: number;
1020
+ damping: number;
1021
+ override onEnterTree(): void;
1022
+ /** @internal The two bodies, resolved (throws on a bad target at step time). */
1023
+ _resolveBodies(): {
1024
+ a: PhysicsBody3D;
1025
+ b: PhysicsBody3D;
1026
+ };
1027
+ }
1028
+ //#endregion
968
1029
  //#region src/3d/nodes/lights-3d.d.ts
969
1030
  /** Sun-style light. Direction comes from the node's rotation. */
970
1031
  declare class DirectionalLight3D extends Node3D {
@@ -1021,6 +1082,14 @@ interface MeshMaterialProps {
1021
1082
  /** Write to the depth buffer (default true). Set false for additive/overlay
1022
1083
  * decals so they don't occlude effects drawn after them. */
1023
1084
  depthWrite?: boolean;
1085
+ /** 0..1 lacquer layer over the base — THE car-paint look: a glossy clear
1086
+ * coat with its own reflection on top of a metallic base. */
1087
+ clearcoat?: number;
1088
+ /** 0..1 roughness of the clearcoat layer alone (default 0 = mirror lacquer). */
1089
+ clearcoatRoughness?: number;
1090
+ /** Reflection strength from the scene's image-based lighting (the PMREM'd
1091
+ * sky / HDRI). 1 = scene default; 2+ makes glass visibly mirror the sky. */
1092
+ envMapIntensity?: number;
1024
1093
  /** Color texture URL (sampled sRGB, tinted by `color`). */
1025
1094
  map?: string;
1026
1095
  /** Tangent-space normal map URL (sampled linear). */
@@ -1037,7 +1106,8 @@ interface MeshMaterialProps {
1037
1106
  * - `size [x,y,z]`: box extents; sphere/gem radius = x; capsule radius = x,
1038
1107
  * height = y; plane = x·z ground plane (laid flat); cylinder radius = x, height = y
1039
1108
  * - `material`: `{color, metalness, roughness, wireframe, flatShading, opacity,
1040
- * depthTest, depthWrite, emissive, emissiveIntensity, map, normalMap, repeat}` —
1109
+ * clearcoat, clearcoatRoughness, envMapIntensity, depthTest, depthWrite,
1110
+ * emissive, emissiveIntensity, map, normalMap, repeat}` —
1041
1111
  * `map`/`normalMap` are texture URLs loaded lazily (headless-safe: geometry and
1042
1112
  * physics never wait on them), tiled by `repeat: [u, v]`. `gem` + `flatShading:
1043
1113
  * true` + a low `roughness` reads as a sparkling faceted jewel (spin it for the
@@ -1067,6 +1137,56 @@ declare class MeshInstance3D extends Node3D {
1067
1137
  override free(): void;
1068
1138
  }
1069
1139
  //#endregion
1140
+ //#region src/3d/nodes/loft-mesh-3d.d.ts
1141
+ /**
1142
+ * One station of a loft: the hull's cross-section at local `z`.
1143
+ * Sections are interpolated with a Catmull-Rom spline, so the surface flows
1144
+ * SMOOTHLY through every station — this is how real car bodies are modelled.
1145
+ */
1146
+ interface LoftSection {
1147
+ /** Station along the hull's local Z (must ascend across `sections`). */
1148
+ z: number;
1149
+ /** Full width of the cross-section at this station. */
1150
+ width: number;
1151
+ /** Full height of the cross-section. */
1152
+ height: number;
1153
+ /** Height of the section CENTRE above the node origin. */
1154
+ y: number;
1155
+ /** 0 = boxy (slab-sided) … 1 = fully elliptical (default 0.7). The lower
1156
+ * half is always kept flatter — vehicle floors are flat. */
1157
+ corner?: number;
1158
+ }
1159
+ /**
1160
+ * A smooth swept hull ("loft"): superellipse cross-sections interpolated
1161
+ * along Z with rounded end caps and per-vertex normals — curved highlights
1162
+ * and sky reflections sweep across it like sheet metal, which no box/capsule
1163
+ * assembly can do. THE node for vehicle bodies, glass canopies, boat hulls,
1164
+ * fuselages.
1165
+ *
1166
+ * - `sections`: ≥2 stations front→back (see LoftSection)
1167
+ * - `slices`: vertices around each ring (8..64, default 24)
1168
+ * - `smooth`: interpolated rings per section gap (1..16, default 4)
1169
+ * - `material`: same object as MeshInstance3D **minus textures** (a loft has
1170
+ * no UVs — map/normalMap/repeat hard-fail). Pair `clearcoat` paint or a
1171
+ * high `envMapIntensity` glass with the smooth normals for the automotive
1172
+ * look.
1173
+ */
1174
+ declare class LoftMesh3D extends Node3D {
1175
+ static override readonly typeName: string;
1176
+ static override readonly props: PropSchema;
1177
+ sections: LoftSection[];
1178
+ slices: number;
1179
+ smooth: number;
1180
+ material: MeshMaterialProps;
1181
+ castShadow: boolean;
1182
+ receiveShadow: boolean;
1183
+ /** Loader hook: malformed sections/materials fail at LOAD time. */
1184
+ static validateJson(node: Node): void;
1185
+ private geometryKey;
1186
+ protected override _createObject3D(): Object3D;
1187
+ override _syncObject3D(): void;
1188
+ }
1189
+ //#endregion
1070
1190
  //#region src/3d/nodes/model-instance-3d.d.ts
1071
1191
  /**
1072
1192
  * A GLB/glTF/VRM model. `model` is a `$key` into the scene's assets header
@@ -1094,6 +1214,12 @@ declare class ModelInstance3D extends Node3D {
1094
1214
  /** Hex colour multiplied into every material — RESKIN one shared GLB into many
1095
1215
  * variants (e.g. a sickly-green zombie from the base human). '' = untinted. */
1096
1216
  tint: string;
1217
+ /** Override every material's metalness (0..1); -1 keeps the authored value.
1218
+ * Low-poly kits often ship fully rough — 0.5–0.8 turns flat paint into a
1219
+ * glinting clear-coat under the scene's IBL. Cloned per instance like tint. */
1220
+ metalness: number;
1221
+ /** Override every material's roughness (0..1); -1 keeps the authored value. */
1222
+ roughness: number;
1097
1223
  private store;
1098
1224
  private entry;
1099
1225
  private mountedRef;
@@ -1107,20 +1233,33 @@ declare class ModelInstance3D extends Node3D {
1107
1233
  * frames after `this.animation` already moved on). */
1108
1234
  private actionKeys;
1109
1235
  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. */
1236
+ /** Per-mesh original (shared) materials, captured at mount so tint/overrides
1237
+ * can always re-derive from the source. Our clones are tracked for disposal. */
1112
1238
  private tintTargets;
1113
1239
  private tintedClones;
1114
- private appliedTint;
1240
+ private appliedMods;
1241
+ /** Named sub-node poses (degrees), re-applied every sync — drive wheels,
1242
+ * turrets, doors from behaviors without touching three directly. */
1243
+ private nodePoses;
1244
+ /**
1245
+ * Rotate a named sub-node of the loaded model (local Euler XYZ, degrees) —
1246
+ * e.g. spin a GLB car's `wheel-front-left` every frame, aim a turret, swing a
1247
+ * door. Poses persist and re-apply on every sync (safe to set before the
1248
+ * model finishes loading). Unknown names are ignored. `npx incanto-model
1249
+ * <file>` lists a model's node names.
1250
+ */
1251
+ poseNode(name: string, rotation: [number, number, number]): void;
1115
1252
  protected override _createObject3D(): Object3D;
1116
1253
  /** Embedded clip names + the scene's animation `$key`s (editor dropdown). */
1117
1254
  availableAnimations(): string[];
1118
1255
  /** Called by the 3D sync pass with the renderer's asset store. */
1119
1256
  _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;
1257
+ /** Apply per-instance material mods`tint` (multiplied into colour) and the
1258
+ * `metalness`/`roughness` overrides by cloning the captured originals
1259
+ * (never mutates the shared source), disposing prior clones on change. */
1260
+ private applyMaterialMods;
1261
+ /** Re-apply every queued named-node pose onto the mounted instance. */
1262
+ private applyPoses;
1124
1263
  private syncAnimation;
1125
1264
  override update(dt: number): void;
1126
1265
  override onExitTree(): void;
@@ -1794,11 +1933,30 @@ declare class Renderer3D {
1794
1933
  private cloudsBlurUniforms;
1795
1934
  private cloudsCompositeScene;
1796
1935
  private cloudsCompositeUniforms;
1936
+ private bloomTarget;
1937
+ private bloomBrightTarget;
1938
+ private bloomBlurTarget;
1939
+ private bloomBrightScene;
1940
+ private bloomBrightUniforms;
1941
+ private bloomBlurScene;
1942
+ private bloomBlurUniforms;
1943
+ private bloomCompositeScene;
1944
+ private bloomCompositeUniforms;
1797
1945
  constructor(opts: Renderer3DOptions);
1798
1946
  private readonly debugLines;
1799
1947
  private syncDebugLines;
1800
1948
  private render;
1801
1949
  /**
1950
+ * Bloom post pass (only when `environment.bloom` is declared). LDR and safe:
1951
+ * 1. scene → full-res offscreen (tone-mapped exactly like direct output)
1952
+ * 2. bright-pass at HALF res (keep pixels above threshold)
1953
+ * 3. separable Gaussian blur (H, V) at half res
1954
+ * 4. composite scene + glow×strength → screen (pure ADD — never re-touches
1955
+ * the scene's tone; the clouds pipeline taught us that lesson)
1956
+ * Caustics/clouds paths take precedence (composite pipelines don't stack yet).
1957
+ */
1958
+ private renderWithBloom;
1959
+ /**
1802
1960
  * Volumetric cloud pass (only when `environment.clouds` is declared — scenes
1803
1961
  * without it render exactly as before, zero cost). Three steps:
1804
1962
  * 1. scene → full-res offscreen target (color + depth)
@@ -1923,4 +2081,4 @@ interface WalkState {
1923
2081
  alpha: number;
1924
2082
  }
1925
2083
  //#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 };
2084
+ 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-CMS7DiPi.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-02aSywx2.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-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";
3
3
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CroQPEER.js";
4
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-CXOBOUKG.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-C58oQzTX.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. */
@@ -1,13 +1,13 @@
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-CGs_G-r0.js";
3
- import { a as Engine } from "./particle-sim-DYuSUxvK.js";
2
+ import { _ as registerBehavior, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-B4OEXDZ8.js";
3
+ import { a as Engine } from "./particle-sim-CFkILGwh.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
- import { r as AudioPlayer } from "./register-BuUV1_KB.js";
5
+ import { s as AudioPlayer } from "./register-Dl_ixIJe.js";
6
6
  import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
7
- import { n as registerGameplayBehaviors } from "./gameplay-Ccruc3Wd.js";
8
- import { f as PhysicsBody2D, h as Node2D, n as UILayer, s as Camera2D, t as registerNodes2D } from "./register-DPEV9_9t.js";
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";
9
9
  import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
10
- import { n as enablePhysics2D } from "./physics-2d-KuMWPTf6.js";
10
+ import { n as enablePhysics2D } from "./physics-2d-KXn1N1jF.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
  /**
@@ -102,22 +102,29 @@ function worldFromScreen(view, sx, sy) {
102
102
  }
103
103
  //#endregion
104
104
  //#region src/2d/sync.ts
105
- /**
106
- * Mirror an Incanto node tree onto two three scenes:
107
- * - `world`: camera-space drawables
108
- * - `ui`: everything under a `UILayer` (screen space, ignores the camera);
109
- * each layer mounts through its own group, positioned by its `anchor`
110
- * against `uiSize` (origin when no size is known — headless).
111
- *
112
- * Same dirty-push contract as the 3D sync; pure scene-graph math, headless-testable.
113
- */
114
- function syncTree2D(root, world, ui, assets, uiSize) {
115
- const visited = /* @__PURE__ */ new Set();
116
- const cameras = [];
117
- walk(root, world, ui, false, visited, cameras, assets, uiSize);
118
- prune(world, visited);
119
- prune(ui, visited);
120
- return { activeCamera: cameras.find((c) => c.current) ?? cameras[0] ?? null };
105
+ function createSync2DScratch() {
106
+ return {
107
+ visited: /* @__PURE__ */ new Set(),
108
+ cameras: []
109
+ };
110
+ }
111
+ function syncTree2D(root, world, ui, assets, uiSize, scratch) {
112
+ const s = scratch ?? createSync2DScratch();
113
+ s.visited.clear();
114
+ s.cameras.length = 0;
115
+ walk(root, world, ui, false, s.visited, s.cameras, assets, uiSize);
116
+ prune(world, s.visited);
117
+ prune(ui, s.visited);
118
+ let current = null;
119
+ for (let i = 0; i < s.cameras.length; i++) {
120
+ const c = s.cameras[i];
121
+ if (c.current) {
122
+ current = c;
123
+ break;
124
+ }
125
+ }
126
+ if (!current) current = s.cameras[0] ?? null;
127
+ return { activeCamera: current };
121
128
  }
122
129
  function walk(node, parentObj, ui, inUi, visited, cameras, assets, uiSize) {
123
130
  let nextParent = parentObj;
@@ -144,8 +151,12 @@ function walk(node, parentObj, ui, inUi, visited, cameras, assets, uiSize) {
144
151
  for (const child of node.children) walk(child, nextParent, ui, nextInUi, visited, cameras, assets, uiSize);
145
152
  }
146
153
  function prune(obj, visited) {
147
- for (const child of [...obj.children]) if (child.userData.incantoNode && !visited.has(child)) obj.remove(child);
148
- else prune(child, visited);
154
+ const ch = obj.children;
155
+ for (let i = ch.length - 1; i >= 0; i--) {
156
+ const child = ch[i];
157
+ if (child.userData.incantoNode && !visited.has(child)) obj.remove(child);
158
+ else prune(child, visited);
159
+ }
149
160
  }
150
161
  //#endregion
151
162
  //#region src/2d/renderer.ts
@@ -163,6 +174,7 @@ var Renderer2D = class {
163
174
  viewOverride = null;
164
175
  webgl;
165
176
  worldScene = new Scene();
177
+ syncScratch = createSync2DScratch();
166
178
  uiScene = new Scene();
167
179
  worldCam = new OrthographicCamera(-1, 1, 1, -1, -1e3, 1e3);
168
180
  uiCam = new OrthographicCamera(-1, 1, 1, -1, -1e3, 1e3);
@@ -231,7 +243,7 @@ var Renderer2D = class {
231
243
  const { activeCamera } = syncTree2D(scene.root, this.worldScene, this.uiScene, this.assets, {
232
244
  width: uiW,
233
245
  height: uiH
234
- });
246
+ }, this.syncScratch);
235
247
  let center = vp ? {
236
248
  x: vp.design[0] / 2,
237
249
  y: vp.design[1] / 2