incanto 0.9.0 → 0.11.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
@@ -311,6 +311,20 @@ declare class Physics3D {
311
311
  normal: [number, number, number];
312
312
  node: PhysicsBody3D | null;
313
313
  } | null;
314
+ /**
315
+ * Sweep a sphere of `radius` from `origin` along `dir` — a THICK ray. The
316
+ * spring-arm camera probe uses this instead of `castRay`: a thin ray that
317
+ * skims 10 cm over a wall reports "clear" while the camera's near plane
318
+ * still clips the wall; the ball's radius covers that band. Same filters
319
+ * as `castRay` (sensors never hit; `staticOnly` ignores dynamic/kinematic).
320
+ * Returns the travel distance of the sphere CENTER, or null when clear.
321
+ */
322
+ castSphere(origin: [number, number, number], dir: [number, number, number], radius: number, maxLen: number, exclude?: PhysicsBody3D, opts?: {
323
+ staticOnly?: boolean;
324
+ }): {
325
+ distance: number;
326
+ node: PhysicsBody3D | null;
327
+ } | null;
314
328
  }
315
329
  //#endregion
316
330
  //#region src/3d/create-game.d.ts
@@ -637,6 +651,35 @@ declare class Billboard3D extends Node3D {
637
651
  _onRender3D(ctx: RenderContext3D): void;
638
652
  }
639
653
  //#endregion
654
+ //#region src/3d/nodes/bone-attachment-3d.d.ts
655
+ /**
656
+ * Rides a skeleton bone of an animated model — swords in hands, hats on
657
+ * heads, particles on wingtips. Children inherit the bone's live animated
658
+ * transform; the node's own position/rotation/scale become a BONE-SPACE
659
+ * offset on top.
660
+ *
661
+ * { "name": "SwordMount", "type": "BoneAttachment3D",
662
+ * "props": { "target": "../Skin", "bone": "RightHand" },
663
+ * "children": [ ...blade mesh, trail... ] }
664
+ *
665
+ * `bone` lookup is forgiving: "RightHand" also matches the Mixamo spellings
666
+ * (mixamorigRightHand / mixamorig:RightHand). Purely visual: it follows the
667
+ * RENDERED skeleton, so headless (no renderer) the node stays where its
668
+ * props put it — keep gameplay checks range-based, not bone-based.
669
+ */
670
+ declare class BoneAttachment3D extends Node3D {
671
+ static override readonly typeName: string;
672
+ static override readonly props: PropSchema;
673
+ target: string;
674
+ bone: string;
675
+ private boneObj;
676
+ private lookedUpFor;
677
+ /** Test hook: the resolved bone (null until found on a rendered frame). */
678
+ get attachedBone(): Object3D | null;
679
+ _onRender3D(_ctx: RenderContext3D): void;
680
+ override free(): void;
681
+ }
682
+ //#endregion
640
683
  //#region src/3d/nodes/camera-3d.d.ts
641
684
  /**
642
685
  * A perspective camera. Mark exactly one camera `current: true`; with none
@@ -1278,6 +1321,15 @@ declare class ModelInstance3D extends Node3D {
1278
1321
  */
1279
1322
  poseNode(name: string, rotation: [number, number, number]): void;
1280
1323
  protected override _createObject3D(): Object3D;
1324
+ /**
1325
+ * Find a skeleton bone (or any named object) inside the mounted model —
1326
+ * the hook BoneAttachment3D rides. Forgiving lookup: exact name, then the
1327
+ * Mixamo spellings (`mixamorigRightHand` / `mixamorig:RightHand`), then a
1328
+ * case-insensitive suffix match. Null until the model is mounted.
1329
+ */
1330
+ findBone(name: string): Object3D | null;
1331
+ /** Names of all bones in the mounted model (debug / editor pickers). */
1332
+ boneNames(): string[];
1281
1333
  /** Embedded clip names + the scene's animation `$key`s (editor dropdown). */
1282
1334
  availableAnimations(): string[];
1283
1335
  /** Called by the 3D sync pass with the renderer's asset store. */
@@ -2155,4 +2207,4 @@ interface WalkState {
2155
2207
  ignoreStatic: boolean;
2156
2208
  }
2157
2209
  //#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 };
2210
+ 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-DZ_4Nfq8.js";
2
+ import { A as PhysicsBody3D, C as Terrain3D, D as Joint3D, E as terrainThemeLayers, F as QUARTER_PITCH, I as cameraRelative, L as keyboardIntensity, M as StaticBody3D, N as Node3D, O as Area3D, R as movementState, S as Billboard3D, T as TERRAIN_THEMES, _ as resolveFlowerDensity, a as VoxelGrid3D, b as Camera3D, c as Particles3D, d as MeshInstance3D, f as DirectionalLight3D, g as Flowers3D, h as DENSITY_PRESETS, i as VOXEL_PALETTE, j as RigidBody3D, k as CharacterBody3D, l as ModelInstance3D, m as Foliage3D, n as Water3D, o as Tree3D, p as OmniLight3D, r as WATER_MAX_RIPPLES, s as Trail3D, t as registerNodes3D, u as LoftMesh3D, v as FLOWER_VARIETIES, w as DEFAULT_TERRAIN_TEXTURE_BASE, x as BoneAttachment3D, y as CharacterController3D, z as rigPose } from "./register-ysLR3WQS.js";
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-Br97Ans2.js";
5
+ export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, StaticBody3D, TERRAIN_THEMES, Terrain3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_MAX_RIPPLES, Water3D, buildHeightmap, cameraRelative, createGame3D, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers };
@@ -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-ysLR3WQS.js";
9
+ import { n as enablePhysics3D } from "./physics-3d-Br97Ans2.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.11.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-ysLR3WQS.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({
@@ -399,6 +399,38 @@ var Physics3D = class {
399
399
  node: this.byColliderHandle.get(hit.collider.handle) ?? null
400
400
  };
401
401
  }
402
+ /**
403
+ * Sweep a sphere of `radius` from `origin` along `dir` — a THICK ray. The
404
+ * spring-arm camera probe uses this instead of `castRay`: a thin ray that
405
+ * skims 10 cm over a wall reports "clear" while the camera's near plane
406
+ * still clips the wall; the ball's radius covers that band. Same filters
407
+ * as `castRay` (sensors never hit; `staticOnly` ignores dynamic/kinematic).
408
+ * Returns the travel distance of the sphere CENTER, or null when clear.
409
+ */
410
+ castSphere(origin, dir, radius, maxLen, exclude, opts) {
411
+ const R = this.R;
412
+ const excludeEntry = exclude ? this.entries.get(exclude) : void 0;
413
+ const flags = opts?.staticOnly ? R.QueryFilterFlags.EXCLUDE_DYNAMIC | R.QueryFilterFlags.EXCLUDE_KINEMATIC : void 0;
414
+ const hit = this.world.castShape({
415
+ x: origin[0],
416
+ y: origin[1],
417
+ z: origin[2]
418
+ }, {
419
+ x: 0,
420
+ y: 0,
421
+ z: 0,
422
+ w: 1
423
+ }, {
424
+ x: dir[0],
425
+ y: dir[1],
426
+ z: dir[2]
427
+ }, new R.Ball(radius), 0, maxLen, true, flags, void 0, excludeEntry?.collider, excludeEntry?.body, (collider) => !collider.isSensor());
428
+ if (!hit) return null;
429
+ return {
430
+ distance: hit.time_of_impact,
431
+ node: this.byColliderHandle.get(hit.collider.handle) ?? null
432
+ };
433
+ }
402
434
  };
403
435
  const quatScratch = new Quaternion();
404
436
  const eulerScratch = new Euler();
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-DZ_4Nfq8.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
@@ -1950,6 +2017,10 @@ const CAM_COLLISION_MARGIN = .3;
1950
2017
  const CAM_COLLISION_MIN_DIST = .4;
1951
2018
  /** Ground backstop: keep the camera at least this high above whatever is below it. */
1952
2019
  const CAM_GROUND_CLEARANCE = .3;
2020
+ /** Spring-arm probe thickness: a THIN ray that skims 10 cm above a wall reports
2021
+ * "clear" while the wall still fills the frame — the sphere's radius covers the
2022
+ * near-plane band around the boom (the Emberwood hedge-maze lesson). */
2023
+ const CAM_PROBE_RADIUS = .25;
1953
2024
  const eulerScratch = new Euler();
1954
2025
  const quatScratch = new Quaternion();
1955
2026
  const matrixScratch$3 = new Matrix4();
@@ -2218,7 +2289,7 @@ var CharacterController3D = class extends Node3D {
2218
2289
  if (this.cameraCollision && this.view !== "firstPerson") {
2219
2290
  const physics = body._physics3d;
2220
2291
  if (physics) {
2221
- target = springArmCamera(eye, target, (o, dr, len) => physics.castRay(o, dr, len, body, { staticOnly: true })?.distance ?? null, CAM_COLLISION_MARGIN, CAM_COLLISION_MIN_DIST);
2292
+ target = springArmCamera(eye, target, (o, dr, len) => physics.castSphere(o, dr, CAM_PROBE_RADIUS, len, body, { staticOnly: true })?.distance ?? null, CAM_COLLISION_MARGIN, CAM_COLLISION_MIN_DIST);
2222
2293
  target = clampCameraAboveGround(target, (o, len) => physics.castRay(o, [
2223
2294
  0,
2224
2295
  -1,
@@ -5622,6 +5693,32 @@ var ModelInstance3D = class extends Node3D {
5622
5693
  _createObject3D() {
5623
5694
  return new Group();
5624
5695
  }
5696
+ /**
5697
+ * Find a skeleton bone (or any named object) inside the mounted model —
5698
+ * the hook BoneAttachment3D rides. Forgiving lookup: exact name, then the
5699
+ * Mixamo spellings (`mixamorigRightHand` / `mixamorig:RightHand`), then a
5700
+ * case-insensitive suffix match. Null until the model is mounted.
5701
+ */
5702
+ findBone(name) {
5703
+ const root = this.fitGroup;
5704
+ if (!root || name === "") return null;
5705
+ const exact = root.getObjectByName(name) ?? root.getObjectByName(`mixamorig${name}`) ?? root.getObjectByName(`mixamorig:${name}`);
5706
+ if (exact) return exact;
5707
+ const lower = name.toLowerCase();
5708
+ let found = null;
5709
+ root.traverse((obj) => {
5710
+ if (!found && obj.name.toLowerCase().endsWith(lower)) found = obj;
5711
+ });
5712
+ return found;
5713
+ }
5714
+ /** Names of all bones in the mounted model (debug / editor pickers). */
5715
+ boneNames() {
5716
+ const out = [];
5717
+ this.fitGroup?.traverse((obj) => {
5718
+ if (obj.isBone) out.push(obj.name);
5719
+ });
5720
+ return out;
5721
+ }
5625
5722
  /** Embedded clip names + the scene's animation `$key`s (editor dropdown). */
5626
5723
  availableAnimations() {
5627
5724
  const embedded = this.entry?.animations.map((c) => c.name) ?? [];
@@ -11618,6 +11715,7 @@ function registerNodes3D() {
11618
11715
  registerNode(VoxelGrid3D);
11619
11716
  registerNode(Joint3D);
11620
11717
  registerNode(Trail3D);
11718
+ registerNode(BoneAttachment3D);
11621
11719
  registerNode(Terrain3D);
11622
11720
  registerNode(Water3D);
11623
11721
  registerNode(Foliage3D);
@@ -11633,4 +11731,4 @@ function registerNodes3D() {
11633
11731
  registerNode(CharacterBody3D);
11634
11732
  }
11635
11733
  //#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 };
11734
+ 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-ysLR3WQS.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-Br97Ans2.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-Br97Ans2.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-DMCQXowF.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};