incanto 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/2d.js CHANGED
@@ -1,5 +1,5 @@
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-C7Zsqbko.js";
2
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-CdWhP6ve.js";
3
3
  import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-BD6zgrv_.js";
4
4
  import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-DweTURFt.js";
5
5
  //#region src/2d/library-sprite.ts
package/dist/3d.d.ts CHANGED
@@ -637,6 +637,35 @@ declare class Billboard3D extends Node3D {
637
637
  _onRender3D(ctx: RenderContext3D): void;
638
638
  }
639
639
  //#endregion
640
+ //#region src/3d/nodes/bone-attachment-3d.d.ts
641
+ /**
642
+ * Rides a skeleton bone of an animated model — swords in hands, hats on
643
+ * heads, particles on wingtips. Children inherit the bone's live animated
644
+ * transform; the node's own position/rotation/scale become a BONE-SPACE
645
+ * offset on top.
646
+ *
647
+ * { "name": "SwordMount", "type": "BoneAttachment3D",
648
+ * "props": { "target": "../Skin", "bone": "RightHand" },
649
+ * "children": [ ...blade mesh, trail... ] }
650
+ *
651
+ * `bone` lookup is forgiving: "RightHand" also matches the Mixamo spellings
652
+ * (mixamorigRightHand / mixamorig:RightHand). Purely visual: it follows the
653
+ * RENDERED skeleton, so headless (no renderer) the node stays where its
654
+ * props put it — keep gameplay checks range-based, not bone-based.
655
+ */
656
+ declare class BoneAttachment3D extends Node3D {
657
+ static override readonly typeName: string;
658
+ static override readonly props: PropSchema;
659
+ target: string;
660
+ bone: string;
661
+ private boneObj;
662
+ private lookedUpFor;
663
+ /** Test hook: the resolved bone (null until found on a rendered frame). */
664
+ get attachedBone(): Object3D | null;
665
+ _onRender3D(_ctx: RenderContext3D): void;
666
+ override free(): void;
667
+ }
668
+ //#endregion
640
669
  //#region src/3d/nodes/camera-3d.d.ts
641
670
  /**
642
671
  * A perspective camera. Mark exactly one camera `current: true`; with none
@@ -1278,6 +1307,15 @@ declare class ModelInstance3D extends Node3D {
1278
1307
  */
1279
1308
  poseNode(name: string, rotation: [number, number, number]): void;
1280
1309
  protected override _createObject3D(): Object3D;
1310
+ /**
1311
+ * Find a skeleton bone (or any named object) inside the mounted model —
1312
+ * the hook BoneAttachment3D rides. Forgiving lookup: exact name, then the
1313
+ * Mixamo spellings (`mixamorigRightHand` / `mixamorig:RightHand`), then a
1314
+ * case-insensitive suffix match. Null until the model is mounted.
1315
+ */
1316
+ findBone(name: string): Object3D | null;
1317
+ /** Names of all bones in the mounted model (debug / editor pickers). */
1318
+ boneNames(): string[];
1281
1319
  /** Embedded clip names + the scene's animation `$key`s (editor dropdown). */
1282
1320
  availableAnimations(): string[];
1283
1321
  /** Called by the 3D sync pass with the renderer's asset store. */
@@ -2155,4 +2193,4 @@ interface WalkState {
2155
2193
  ignoreStatic: boolean;
2156
2194
  }
2157
2195
  //#endregion
2158
- 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, Trail3D, 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 };
2196
+ export { Area3D, AssetStore3D, Billboard3D, type BillboardGroupMode, BoneAttachment3D, 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, Trail3D, 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-Bydey3CZ.js";
2
- import { A as RigidBody3D, C as DEFAULT_TERRAIN_TEXTURE_BASE, D as Area3D, E as Joint3D, F as cameraRelative, I as keyboardIntensity, L as movementState, M as Node3D, O as CharacterBody3D, P as QUARTER_PITCH, R as rigPose, S as Terrain3D, T as terrainThemeLayers, _ 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 StaticBody3D, k as PhysicsBody3D, 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 TERRAIN_THEMES, x as Billboard3D, y as CharacterController3D } from "./register-iUPtts7T.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-Cq0Z41EQ.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-BP-kbijm.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-Clv1uFzk.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, 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 };
4
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-D8m2Hd-q.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 };
@@ -3,7 +3,7 @@ import { _ as registerBehavior, n as loadScene, o as computeViewport, s as resol
3
3
  import { l as AudioPlayer, u as Engine } from "./register-DJZXxwAn.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-DPMgZk9W.js";
6
+ import { n as registerGameplayBehaviors } from "./gameplay-Dn3yidJw.js";
7
7
  import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-BD6zgrv_.js";
8
8
  import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
9
9
  import { n as enablePhysics2D } from "./physics-2d-DweTURFt.js";
@@ -3,10 +3,10 @@ import { _ as registerBehavior, n as loadScene } from "./loader-B4OEXDZ8.js";
3
3
  import { l as AudioPlayer, u as Engine } from "./register-DJZXxwAn.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-DPMgZk9W.js";
6
+ import { n as registerGameplayBehaviors } from "./gameplay-Dn3yidJw.js";
7
7
  import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
8
- import { M as Node3D, b as Camera3D, f as DirectionalLight3D, k as PhysicsBody3D, l as ModelInstance3D, t as registerNodes3D } from "./register-iUPtts7T.js";
9
- import { n as enablePhysics3D } from "./physics-3d-Clv1uFzk.js";
8
+ import { A as PhysicsBody3D, N as Node3D, b as Camera3D, f as DirectionalLight3D, l as ModelInstance3D, t as registerNodes3D } from "./register-BP-kbijm.js";
9
+ import { n as enablePhysics3D } from "./physics-3d-D8m2Hd-q.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";
@@ -321,6 +321,9 @@ function onTriggerEnter(behavior, fn) {
321
321
  * and each enemy's contact hitbox `targetGroup:'player'`.
322
322
  * - `oncePerTarget` (default) prevents re-hitting the same node (a stationary
323
323
  * hazard would otherwise drain a resting body every frame).
324
+ * - `repeatEvery` (seconds, 0 = off) re-damages targets that STAY overlapped —
325
+ * lava pools, poison clouds, an enemy standing on you. Contact events fire
326
+ * only on entry/exit; this is the "and it keeps hurting" knob.
324
327
  * - `destroySelf` frees the hazard after a hit (single-use projectiles).
325
328
  *
326
329
  * SCORING PATTERN (clone-safe): wire the KILLER's `dealtDamage` →
@@ -336,6 +339,8 @@ var DamageOnContact = class extends Behavior {
336
339
  targetGroup: { default: "" },
337
340
  /** Damage each distinct target at most once. */
338
341
  oncePerTarget: { default: true },
342
+ /** Re-damage targets still overlapping every N seconds (0 = entry only). */
343
+ repeatEvery: { default: 0 },
339
344
  /** queueFree() this node after the first successful hit. */
340
345
  destroySelf: { default: false }
341
346
  };
@@ -343,13 +348,37 @@ var DamageOnContact = class extends Behavior {
343
348
  amount = 10;
344
349
  targetGroup = "";
345
350
  oncePerTarget = true;
351
+ repeatEvery = 0;
346
352
  destroySelf = false;
347
353
  hit = /* @__PURE__ */ new WeakSet();
354
+ /** Targets currently overlapping → seconds until their next repeat tick. */
355
+ inside = /* @__PURE__ */ new Map();
348
356
  onReady() {
349
- onTriggerEnter(this, (other) => this.tryDamage(other));
357
+ onTriggerEnter(this, (other) => {
358
+ this.inside.set(other, this.repeatEvery);
359
+ this.tryDamage(other);
360
+ });
361
+ this.node.on("triggerExit", (other) => {
362
+ this.inside.delete(other);
363
+ });
364
+ }
365
+ update(dt) {
366
+ if (this.repeatEvery <= 0 || this.inside.size === 0) return;
367
+ for (const [target, left] of this.inside) {
368
+ if (!target.tree) {
369
+ this.inside.delete(target);
370
+ continue;
371
+ }
372
+ const next = left - dt;
373
+ if (next > 0) this.inside.set(target, next);
374
+ else {
375
+ this.inside.set(target, this.repeatEvery);
376
+ this.tryDamage(target, { repeat: true });
377
+ }
378
+ }
350
379
  }
351
- tryDamage(other) {
352
- if (this.oncePerTarget && this.hit.has(other)) return;
380
+ tryDamage(other, opts) {
381
+ if (!opts?.repeat && this.oncePerTarget && this.hit.has(other)) return;
353
382
  const found = findHealth(other);
354
383
  if (!found) return;
355
384
  if (this.targetGroup !== "" && !found.owner.isInGroup(this.targetGroup)) return;
@@ -113,6 +113,9 @@ declare class Health extends Behavior {
113
113
  * and each enemy's contact hitbox `targetGroup:'player'`.
114
114
  * - `oncePerTarget` (default) prevents re-hitting the same node (a stationary
115
115
  * hazard would otherwise drain a resting body every frame).
116
+ * - `repeatEvery` (seconds, 0 = off) re-damages targets that STAY overlapped —
117
+ * lava pools, poison clouds, an enemy standing on you. Contact events fire
118
+ * only on entry/exit; this is the "and it keeps hurting" knob.
116
119
  * - `destroySelf` frees the hazard after a hit (single-use projectiles).
117
120
  *
118
121
  * SCORING PATTERN (clone-safe): wire the KILLER's `dealtDamage` →
@@ -126,9 +129,13 @@ declare class DamageOnContact extends Behavior {
126
129
  amount: number;
127
130
  targetGroup: string;
128
131
  oncePerTarget: boolean;
132
+ repeatEvery: number;
129
133
  destroySelf: boolean;
130
134
  private readonly hit;
135
+ /** Targets currently overlapping → seconds until their next repeat tick. */
136
+ private readonly inside;
131
137
  override onReady(): void;
138
+ override update(dt: number): void;
132
139
  private tryDamage;
133
140
  }
134
141
  //#endregion
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-DPMgZk9W.js";
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
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 };
package/dist/index.js CHANGED
@@ -295,6 +295,6 @@ function newUid() {
295
295
  //#endregion
296
296
  //#region src/index.ts
297
297
  /** Engine version. Kept in sync with package.json by the release pipeline. */
298
- const VERSION = "0.9.0";
298
+ const VERSION = "0.10.0";
299
299
  //#endregion
300
300
  export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, synthSfx };
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
3
  import { n as registerDebugSource } from "./debug-draw-CZmOYjL2.js";
4
- import { A as RigidBody3D, D as Area3D, E as Joint3D, M as Node3D, N as validateCollider3D, O as CharacterBody3D, S as Terrain3D, k as PhysicsBody3D } from "./register-iUPtts7T.js";
4
+ import { A as PhysicsBody3D, C as Terrain3D, D as Joint3D, N as Node3D, O as Area3D, P as validateCollider3D, j as RigidBody3D, k as CharacterBody3D } from "./register-BP-kbijm.js";
5
5
  import { Euler, Quaternion } from "three";
6
6
  //#region src/3d/physics/physics-3d.ts
7
7
  var physics_3d_exports = /* @__PURE__ */ __exportAll({
package/dist/react.js CHANGED
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
156
156
  pointer: latest.pointer,
157
157
  ...keyboard !== void 0 ? { keyboard } : {}
158
158
  };
159
- const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-Bydey3CZ.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-C7Zsqbko.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-Cq0Z41EQ.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-CdWhP6ve.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -1901,6 +1901,73 @@ var Billboard3D = class extends Node3D {
1901
1901
  }
1902
1902
  };
1903
1903
  //#endregion
1904
+ //#region src/3d/nodes/bone-attachment-3d.ts
1905
+ /**
1906
+ * Rides a skeleton bone of an animated model — swords in hands, hats on
1907
+ * heads, particles on wingtips. Children inherit the bone's live animated
1908
+ * transform; the node's own position/rotation/scale become a BONE-SPACE
1909
+ * offset on top.
1910
+ *
1911
+ * { "name": "SwordMount", "type": "BoneAttachment3D",
1912
+ * "props": { "target": "../Skin", "bone": "RightHand" },
1913
+ * "children": [ ...blade mesh, trail... ] }
1914
+ *
1915
+ * `bone` lookup is forgiving: "RightHand" also matches the Mixamo spellings
1916
+ * (mixamorigRightHand / mixamorig:RightHand). Purely visual: it follows the
1917
+ * RENDERED skeleton, so headless (no renderer) the node stays where its
1918
+ * props put it — keep gameplay checks range-based, not bone-based.
1919
+ */
1920
+ var BoneAttachment3D = class extends Node3D {
1921
+ static typeName = "BoneAttachment3D";
1922
+ static props = {
1923
+ /** Node path to the ModelInstance3D whose skeleton to ride. */
1924
+ target: { default: "" },
1925
+ /** Bone name ("RightHand", "Head", …). Empty = inert. */
1926
+ bone: { default: "" }
1927
+ };
1928
+ target = "";
1929
+ bone = "";
1930
+ boneObj = null;
1931
+ lookedUpFor = "";
1932
+ /** Test hook: the resolved bone (null until found on a rendered frame). */
1933
+ get attachedBone() {
1934
+ return this.boneObj;
1935
+ }
1936
+ _onRender3D(_ctx) {
1937
+ if (this.target === "" || this.bone === "") return;
1938
+ const key = `${this.target} ${this.bone}`;
1939
+ if (this.lookedUpFor !== key || this.boneObj === null || !this.boneObj.parent) {
1940
+ this.lookedUpFor = key;
1941
+ this.boneObj = null;
1942
+ const source = this.getNodeOrNull(this.target);
1943
+ if (source && typeof source.findBone === "function") this.boneObj = source.findBone(this.bone);
1944
+ }
1945
+ const boneObj = this.boneObj;
1946
+ if (!boneObj) return;
1947
+ const obj = this._ensureObject3D();
1948
+ const parent = obj.parent;
1949
+ if (!parent) return;
1950
+ boneObj.updateWorldMatrix(true, false);
1951
+ parent.updateWorldMatrix(true, false);
1952
+ obj.matrixAutoUpdate = false;
1953
+ boneObj.matrixWorld.decompose(bonePosScratch, boneQuatScratch, boneScaleScratch);
1954
+ boneScratch.compose(bonePosScratch, boneQuatScratch, unitScale);
1955
+ obj.updateMatrix();
1956
+ offsetScratch.copy(obj.matrix);
1957
+ obj.matrix.copy(parent.matrixWorld).invert().multiply(boneScratch).multiply(offsetScratch);
1958
+ }
1959
+ free() {
1960
+ this.boneObj = null;
1961
+ super.free();
1962
+ }
1963
+ };
1964
+ const offsetScratch = new Matrix4();
1965
+ const boneScratch = new Matrix4();
1966
+ const bonePosScratch = new Vector3();
1967
+ const boneQuatScratch = new Quaternion();
1968
+ const boneScaleScratch = new Vector3();
1969
+ const unitScale = new Vector3(1, 1, 1);
1970
+ //#endregion
1904
1971
  //#region src/3d/nodes/camera-3d.ts
1905
1972
  /**
1906
1973
  * A perspective camera. Mark exactly one camera `current: true`; with none
@@ -5622,6 +5689,32 @@ var ModelInstance3D = class extends Node3D {
5622
5689
  _createObject3D() {
5623
5690
  return new Group();
5624
5691
  }
5692
+ /**
5693
+ * Find a skeleton bone (or any named object) inside the mounted model —
5694
+ * the hook BoneAttachment3D rides. Forgiving lookup: exact name, then the
5695
+ * Mixamo spellings (`mixamorigRightHand` / `mixamorig:RightHand`), then a
5696
+ * case-insensitive suffix match. Null until the model is mounted.
5697
+ */
5698
+ findBone(name) {
5699
+ const root = this.fitGroup;
5700
+ if (!root || name === "") return null;
5701
+ const exact = root.getObjectByName(name) ?? root.getObjectByName(`mixamorig${name}`) ?? root.getObjectByName(`mixamorig:${name}`);
5702
+ if (exact) return exact;
5703
+ const lower = name.toLowerCase();
5704
+ let found = null;
5705
+ root.traverse((obj) => {
5706
+ if (!found && obj.name.toLowerCase().endsWith(lower)) found = obj;
5707
+ });
5708
+ return found;
5709
+ }
5710
+ /** Names of all bones in the mounted model (debug / editor pickers). */
5711
+ boneNames() {
5712
+ const out = [];
5713
+ this.fitGroup?.traverse((obj) => {
5714
+ if (obj.isBone) out.push(obj.name);
5715
+ });
5716
+ return out;
5717
+ }
5625
5718
  /** Embedded clip names + the scene's animation `$key`s (editor dropdown). */
5626
5719
  availableAnimations() {
5627
5720
  const embedded = this.entry?.animations.map((c) => c.name) ?? [];
@@ -11618,6 +11711,7 @@ function registerNodes3D() {
11618
11711
  registerNode(VoxelGrid3D);
11619
11712
  registerNode(Joint3D);
11620
11713
  registerNode(Trail3D);
11714
+ registerNode(BoneAttachment3D);
11621
11715
  registerNode(Terrain3D);
11622
11716
  registerNode(Water3D);
11623
11717
  registerNode(Foliage3D);
@@ -11633,4 +11727,4 @@ function registerNodes3D() {
11633
11727
  registerNode(CharacterBody3D);
11634
11728
  }
11635
11729
  //#endregion
11636
- export { RigidBody3D as A, DEFAULT_TERRAIN_TEXTURE_BASE as C, Area3D as D, Joint3D as E, cameraRelative as F, keyboardIntensity as I, movementState as L, Node3D as M, validateCollider3D as N, CharacterBody3D as O, QUARTER_PITCH as P, rigPose as R, Terrain3D as S, terrainThemeLayers as T, resolveFlowerDensity as _, VoxelGrid3D as a, Camera3D as b, Particles3D as c, MeshInstance3D as d, DirectionalLight3D as f, Flowers3D as g, DENSITY_PRESETS as h, VOXEL_PALETTE as i, StaticBody3D as j, PhysicsBody3D as k, ModelInstance3D as l, Foliage3D as m, Water3D as n, Tree3D as o, OmniLight3D as p, WATER_MAX_RIPPLES as r, Trail3D as s, registerNodes3D as t, LoftMesh3D as u, FLOWER_VARIETIES as v, TERRAIN_THEMES as w, Billboard3D as x, CharacterController3D as y };
11730
+ export { PhysicsBody3D as A, Terrain3D as C, Joint3D as D, terrainThemeLayers as E, QUARTER_PITCH as F, cameraRelative as I, keyboardIntensity as L, StaticBody3D as M, Node3D as N, Area3D as O, validateCollider3D as P, movementState as R, Billboard3D as S, TERRAIN_THEMES as T, resolveFlowerDensity as _, VoxelGrid3D as a, Camera3D as b, Particles3D as c, MeshInstance3D as d, DirectionalLight3D as f, Flowers3D as g, DENSITY_PRESETS as h, VOXEL_PALETTE as i, RigidBody3D as j, CharacterBody3D as k, ModelInstance3D as l, Foliage3D as m, Water3D as n, Tree3D as o, OmniLight3D as p, WATER_MAX_RIPPLES as r, Trail3D as s, registerNodes3D as t, LoftMesh3D as u, FLOWER_VARIETIES as v, DEFAULT_TERRAIN_TEXTURE_BASE as w, BoneAttachment3D as x, CharacterController3D as y, rigPose as z };
package/dist/test.js CHANGED
@@ -4,9 +4,9 @@ import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as auditScene } from "./audit-C6rMyict.js";
5
5
  import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
6
6
  import { i as getNodeSchema, s as mergeStaticProps } from "./registry-BVJ2HbCn.js";
7
- import { n as registerGameplayBehaviors } from "./gameplay-DPMgZk9W.js";
7
+ import { n as registerGameplayBehaviors } from "./gameplay-Dn3yidJw.js";
8
8
  import { t as registerNodes2D } from "./register-BD6zgrv_.js";
9
- import { t as registerNodes3D } from "./register-iUPtts7T.js";
9
+ import { t as registerNodes3D } from "./register-BP-kbijm.js";
10
10
  import { t as registerNodesNet } from "./register-Cwc-yCYx.js";
11
11
  //#region src/test/index.ts
12
12
  /**
@@ -127,7 +127,7 @@ async function runScript(json, opts) {
127
127
  const { enablePhysics2D } = await import("./physics-2d-DweTURFt.js").then((n) => n.r);
128
128
  await enablePhysics2D(engine);
129
129
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
130
- const { enablePhysics3D } = await import("./physics-3d-Clv1uFzk.js").then((n) => n.r);
130
+ const { enablePhysics3D } = await import("./physics-3d-D8m2Hd-q.js").then((n) => n.r);
131
131
  await enablePhysics3D(engine);
132
132
  }
133
133
  const failures = [];
@@ -232,7 +232,7 @@ async function createPlaySession(json, opts = {}) {
232
232
  const { enablePhysics2D } = await import("./physics-2d-DweTURFt.js").then((n) => n.r);
233
233
  await enablePhysics2D(engine);
234
234
  } else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
235
- const { enablePhysics3D } = await import("./physics-3d-Clv1uFzk.js").then((n) => n.r);
235
+ const { enablePhysics3D } = await import("./physics-3d-D8m2Hd-q.js").then((n) => n.r);
236
236
  await enablePhysics3D(engine);
237
237
  }
238
238
  const stepMs = 1e3 / (opts.fixedHz ?? 60);
@@ -1 +1 @@
1
- import{t as e}from"./index-BLzp8moy.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
1
+ import{t as e}from"./index-Dp_cYQx1.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};