incanto 0.62.0 → 0.63.1

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 (53) hide show
  1. package/bin/incanto-playtest.mjs +14 -5
  2. package/bin/incanto-verify.mjs +9 -4
  3. package/dist/2d.js +3 -3
  4. package/dist/3d.d.ts +22 -0
  5. package/dist/3d.js +5 -5
  6. package/dist/{create-game-BpunnGPX.js → create-game-BsqKOAFN.js} +6 -6
  7. package/dist/{create-game-Caut3bqN.js → create-game-CSnlQDkz.js} +6 -6
  8. package/dist/{duplicate-E4FUs5Bn.js → duplicate-BOOKmkQ7.js} +1 -1
  9. package/dist/{environment-presets-BAWeOeqf.js → environment-presets-BKRo-HXg.js} +3 -3
  10. package/dist/{gameplay-CaHqDiQD.js → gameplay-Dqm4LyKR.js} +30 -4
  11. package/dist/gameplay.js +1 -1
  12. package/dist/index.js +5 -5
  13. package/dist/{loader-DEe272nY.js → loader-zDynoew_.js} +57 -2
  14. package/dist/net.js +1 -1
  15. package/dist/{physics-2d-BXmu2i7W.js → physics-2d-BUD-MADM.js} +5 -5
  16. package/dist/{physics-3d-ClxP6Uv7.js → physics-3d-BsutZCtX.js} +5 -3
  17. package/dist/react.js +1 -1
  18. package/dist/{register-CNh4FlbD.js → register-BSA93acW.js} +3 -3
  19. package/dist/{register-D3yx8D4r.js → register-Btm7_Emq.js} +1 -1
  20. package/dist/{replay-DlgHItNv.js → replay-DKRmiVjk.js} +42 -2
  21. package/dist/{split-screen-CBM9wcMX.js → split-screen-Dx0LvzqS.js} +2 -2
  22. package/dist/{src-CH00_JsR.js → src-DQzZD_gx.js} +1 -1
  23. package/dist/{teardown-C7qP-dcC.js → teardown-BwhkcNt8.js} +1 -1
  24. package/dist/{test-BMkg8zMV.js → test-wPUuuJpP.js} +50 -18
  25. package/dist/test.d.ts +4 -0
  26. package/dist/test.js +2 -2
  27. package/dist/vite.js +2 -2
  28. package/editor/assets/{agent8-m5mtAO_A.js → agent8-CfacwdPM.js} +1 -1
  29. package/editor/assets/{debug-CPhzCT8f.js → debug-D0kX3cGf.js} +1 -1
  30. package/editor/assets/{index-D422P4kW.js → index-BrG3srCa.js} +92 -92
  31. package/editor/index.html +1 -1
  32. package/package.json +1 -1
  33. package/schemas/scene.schema.json +4 -0
  34. package/skills/README.md +4 -0
  35. package/skills/incanto-game-feel.md +3 -2
  36. package/skills/incanto-gameplay-behaviors.md +37 -6
  37. package/skills/incanto-node-reference.md +1 -0
  38. package/skills/incanto-physics-and-input.md +69 -7
  39. package/skills/incanto-scene-json-authoring.md +15 -1
  40. package/skills/incanto-your-first-game.md +242 -0
  41. package/templates-app/beacon-isle-3d/package.json +1 -1
  42. package/templates-app/platformer-2d/package.json +1 -1
  43. package/templates-app/star-survivor/package.json +1 -1
  44. package/templates-app/star-survivor/src/game.scene.json +4 -2
  45. package/templates-app/tps-3d/PROJECT/Requirements.md +3 -1
  46. package/templates-app/tps-3d/index.html +3 -1
  47. package/templates-app/tps-3d/package.json +1 -1
  48. package/templates-app/tps-3d/src/behaviors.ts +4 -2
  49. package/templates-app/tps-3d/src/game.scene.json +3 -2
  50. package/templates-app/tps-3d/verify.ts +38 -0
  51. package/templates-app/village-quest-3d/package.json +1 -1
  52. package/templates-app/village-quest-3d/src/grove.scene.json +6 -2
  53. package/templates-app/village-quest-3d/src/village.scene.json +8 -4
@@ -86,7 +86,15 @@ if (args.behaviors) {
86
86
  // it, and this bin's shebang is node, so `--behaviors` failed on the
87
87
  // engine's own flagship template. Hand the work to bun rather than telling
88
88
  // the author their file is wrong: it is not.
89
- const nodeCannotReadIt = /import attribute|Unknown file extension/i.test(why);
89
+ // …and the same for an EXTENSIONLESS relative import. `import { quest } from
90
+ // './quest'` is what every tsconfig this package scaffolds asks for, and
91
+ // node's resolver refuses it (`Cannot find module .../src/quest`) while bun
92
+ // and vite resolve it. It broke `incanto verify` on the shipped
93
+ // `village-quest-3d` starter, and it breaks for any author who splits their
94
+ // behaviours across files — which is most of them, eventually.
95
+ const nodeCannotReadIt = /import attribute|Unknown file extension|Cannot find module/i.test(
96
+ why,
97
+ );
90
98
  if (nodeCannotReadIt && !process.versions.bun && !process.env.INCANTO_BUN_REEXEC) {
91
99
  const { spawnSync } = await import('node:child_process');
92
100
  const hasBun = spawnSync('bun', ['--version'], { stdio: 'ignore' }).status === 0;
@@ -102,10 +110,11 @@ if (args.behaviors) {
102
110
  console.error(
103
111
  `could not load --behaviors '${args.behaviors}' (${why}).` +
104
112
  (nodeCannotReadIt
105
- ? '\n\nThis is node refusing your file, not a problem with it: node cannot' +
106
- "\nimport JSON from a .ts module without `with { type: 'json' }`, and every" +
107
- '\nincanto template imports its scene that way. Run it with bun, which' +
108
- '\nreads the file as written: `bunx incanto playtest <scene> --behaviors <file>`.'
113
+ ? '\n\nThis is usually node refusing your file rather than a problem with it:' +
114
+ "\nnode cannot import JSON from a .ts module without `with { type: 'json' }`," +
115
+ '\nand it will not resolve an extensionless relative import. Both are what' +
116
+ '\nthe scaffolded tsconfig asks you to write. Run it with bun, which reads' +
117
+ '\nthe file as written: `bunx incanto playtest <scene> --behaviors <file>`.'
109
118
  : '\nPass the file that exports your Behavior subclasses, e.g. src/behaviors.ts.'),
110
119
  );
111
120
  process.exit(1);
@@ -342,16 +342,21 @@ if (rungs[0].status === 'pass') {
342
342
  fix: 'connect them — a signal to `play` on an AudioPlayer, or `replay()` on a one-shot; `engine.audio.recent()` and `engine.effects.recent()` say what did fire',
343
343
  });
344
344
  } else {
345
- const silent = [...(fb.declaredAudio ?? []), ...(fb.declaredEffects ?? [])].filter(
346
- (path) => !fired.has(path),
347
- );
345
+ const declaredPaths = [...(fb.declaredAudio ?? []), ...(fb.declaredEffects ?? [])];
346
+ const silent = declaredPaths.filter((path) => !fired.has(path));
347
+ // Of the nodes this rung is ABOUT, how many fired. `fired` also holds paths
348
+ // nobody declared — a spawned clone sounds from its own path, a procedural
349
+ // preset from wherever a behaviour played it — so using its size printed
350
+ // arithmetic that cannot be true: `✓ feels — 12 of 9 fired`. The number has
351
+ // to add up with the `silent` list beside it.
352
+ const firedDeclared = declaredPaths.length - silent.length;
348
353
  rungs.push({
349
354
  name: 'feels',
350
355
  status: 'pass',
351
356
  summary:
352
357
  silent.length === 0
353
358
  ? `every one of the ${declared} sound/effect nodes fired`
354
- : `${fired.size} of ${declared} fired — silent: ${silent.slice(0, 3).join(', ')}${silent.length > 3 ? ` +${silent.length - 3}` : ''}`,
359
+ : `${firedDeclared} of ${declared} fired — silent: ${silent.slice(0, 3).join(', ')}${silent.length > 3 ? ` +${silent.length - 3}` : ''}`,
355
360
  });
356
361
  }
357
362
  }
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-BpunnGPX.js";
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-CNh4FlbD.js";
4
- import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-BXmu2i7W.js";
2
+ import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-BsqKOAFN.js";
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-BSA93acW.js";
4
+ import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-BUD-MADM.js";
5
5
  //#region src/2d/library-sprite.ts
6
6
  /**
7
7
  * What a `CharacterController2D`/`3D` will ask a skin to play, and the clip in
package/dist/3d.d.ts CHANGED
@@ -399,6 +399,28 @@ declare class CharacterBody3D extends PhysicsBody3D {
399
399
  /** Max climbable slope angle. */
400
400
  slopeLimitDeg: number;
401
401
  /**
402
+ * How high a step this body walks UP without jumping (0 = none).
403
+ *
404
+ * Rapier's character controller does not autostep unless asked, and nothing
405
+ * asked — so every `CharacterBody3D` was stopped dead by any ledge at all.
406
+ * The player never showed it, because `CharacterController3D` rides a hover
407
+ * spring rather than the KCC; enemies are the ones that walk.
408
+ *
409
+ * Measured on the shipped `tps-3d` template, whose arena has a 0.6 m `Ramp`
410
+ * between the spawn and the player. Standing still for 45 seconds:
411
+ *
412
+ * ```
413
+ * hits=0 hp=100 closest an enemy ever got = 33.91m
414
+ * ```
415
+ *
416
+ * They chase at the right speed the whole time and pile up against the near
417
+ * face of a knee-high box, because a chaser walks a straight line and cannot
418
+ * climb. The template's own playtest had been printing
419
+ * `danger: the player took damage 0 times in 4 runs — nothing here can hurt
420
+ * you` all along, and no rung failed on it.
421
+ */
422
+ stepHeight: number;
423
+ /**
402
424
  * `snapToGround` on a character body is ambiguous, so it is refused.
403
425
  *
404
426
  * A boolean here used to mean the ground-stick, and as placement it means
package/dist/3d.js CHANGED
@@ -1,10 +1,10 @@
1
- import { c as parseEnvironment3D, l as sunDirectionFromElevationAzimuth, s as horizonColorFromSky, u as sunDirectionFromSky } from "./loader-DEe272nY.js";
1
+ import { c as parseEnvironment3D, l as sunDirectionFromElevationAzimuth, s as horizonColorFromSky, u as sunDirectionFromSky } from "./loader-zDynoew_.js";
2
2
  import { a as frameSignature, n as diffSignatures, o as frameStats, r as diffText, s as frameText, t as SIGNATURE_GRID } from "./frame-report-BSMny7oe.js";
3
- import { B as CharacterBody3D, F as Water3D, H as RigidBody3D, I as WATER_CUTOUT_MAX, L as WaterCutout3D, U as StaticBody3D, V as PhysicsBody3D, W as Node3D, q as WATER_MAX_RIPPLES, z as Area3D } from "./gameplay-CaHqDiQD.js";
4
- import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-BAWeOeqf.js";
5
- import { a as Environment3D, i as syncTree, o as setEnvironment3D, r as Renderer3D, s as AssetStore3D, t as createGame3D } from "./create-game-Caut3bqN.js";
3
+ import { B as CharacterBody3D, F as Water3D, H as RigidBody3D, I as WATER_CUTOUT_MAX, L as WaterCutout3D, U as StaticBody3D, V as PhysicsBody3D, W as Node3D, q as WATER_MAX_RIPPLES, z as Area3D } from "./gameplay-Dqm4LyKR.js";
4
+ import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-BKRo-HXg.js";
5
+ import { a as Environment3D, i as syncTree, o as setEnvironment3D, r as Renderer3D, s as AssetStore3D, t as createGame3D } from "./create-game-CSnlQDkz.js";
6
6
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
7
- import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-ClxP6Uv7.js";
7
+ import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-BsutZCtX.js";
8
8
  //#region src/3d/model-verdict.ts
9
9
  /** Mixamo exports every bone as `mixamorigX`; the retargeter binds by that name. */
10
10
  const MIXAMO = /^mixamorig[:_]?/i;
@@ -1,14 +1,14 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { d as computeViewport, f as resolveViewport, k as diagnose, n as loadScene, z as registerBehavior } from "./loader-DEe272nY.js";
3
- import { _ as AudioPlayer, v as Engine } from "./register-D3yx8D4r.js";
2
+ import { d as computeViewport, f as resolveViewport, k as diagnose, n as loadScene, z as registerBehavior } from "./loader-zDynoew_.js";
3
+ import { _ as AudioPlayer, v as Engine } from "./register-Btm7_Emq.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { i as resolveRendering, n as attachTouchControls } from "./touch-DESwnpOc.js";
6
- import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-C7qP-dcC.js";
6
+ import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-BwhkcNt8.js";
7
7
  import { o as frameStats } from "./frame-report-BSMny7oe.js";
8
- import { n as registerGameplayBehaviors } from "./gameplay-CaHqDiQD.js";
9
- import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-CNh4FlbD.js";
8
+ import { n as registerGameplayBehaviors } from "./gameplay-Dqm4LyKR.js";
9
+ import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-BSA93acW.js";
10
10
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
11
- import { n as enablePhysics2D } from "./physics-2d-BXmu2i7W.js";
11
+ import { n as enablePhysics2D } from "./physics-2d-BUD-MADM.js";
12
12
  import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
13
13
  //#region src/2d/assets.ts
14
14
  /**
@@ -1,14 +1,14 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { c as parseEnvironment3D, k as diagnose, n as loadScene, s as horizonColorFromSky, u as sunDirectionFromSky, z as registerBehavior } from "./loader-DEe272nY.js";
3
- import { C as qualityRendering, _ as AudioPlayer, v as Engine, x as qualityCaps } from "./register-D3yx8D4r.js";
2
+ import { c as parseEnvironment3D, k as diagnose, n as loadScene, s as horizonColorFromSky, u as sunDirectionFromSky, z as registerBehavior } from "./loader-zDynoew_.js";
3
+ import { C as qualityRendering, _ as AudioPlayer, v as Engine, x as qualityCaps } from "./register-Btm7_Emq.js";
4
4
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
5
5
  import { i as resolveRendering, n as attachTouchControls } from "./touch-DESwnpOc.js";
6
- import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-C7qP-dcC.js";
6
+ import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-BwhkcNt8.js";
7
7
  import { o as frameStats } from "./frame-report-BSMny7oe.js";
8
- import { K as createCausticsQuad, V as PhysicsBody3D, W as Node3D, n as registerGameplayBehaviors } from "./gameplay-CaHqDiQD.js";
8
+ import { K as createCausticsQuad, V as PhysicsBody3D, W as Node3D, n as registerGameplayBehaviors } from "./gameplay-Dqm4LyKR.js";
9
9
  import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
10
- import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-BAWeOeqf.js";
11
- import { n as enablePhysics3D } from "./physics-3d-ClxP6Uv7.js";
10
+ import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-BKRo-HXg.js";
11
+ import { n as enablePhysics3D } from "./physics-3d-BsutZCtX.js";
12
12
  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";
13
13
  import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
14
14
  import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
@@ -1,4 +1,4 @@
1
- import { a as serializeNode, t as buildNodeJson } from "./loader-DEe272nY.js";
1
+ import { a as serializeNode, t as buildNodeJson } from "./loader-zDynoew_.js";
2
2
  //#region src/core/scene/duplicate.ts
3
3
  function stripUids(json) {
4
4
  const { uid: _uid, ...rest } = json;
@@ -1,11 +1,11 @@
1
- import { k as diagnose } from "./loader-DEe272nY.js";
2
- import { L as translateOn, t as registerCoreNodes } from "./register-D3yx8D4r.js";
1
+ import { k as diagnose } from "./loader-zDynoew_.js";
2
+ import { L as translateOn, t as registerCoreNodes } from "./register-Btm7_Emq.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { i as getNodeSchema, l as registerNode } from "./registry-CF70EArN.js";
6
6
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
7
7
  import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CY-mrr1L.js";
8
- import { B as CharacterBody3D, F as Water3D, H as RigidBody3D, J as colliderFootDrop, L as WaterCutout3D, R as rejectScale, U as StaticBody3D, V as PhysicsBody3D, W as Node3D, w as tolerateUnknownAction, z as Area3D } from "./gameplay-CaHqDiQD.js";
8
+ import { B as CharacterBody3D, F as Water3D, H as RigidBody3D, J as colliderFootDrop, L as WaterCutout3D, R as rejectScale, U as StaticBody3D, V as PhysicsBody3D, W as Node3D, w as tolerateUnknownAction, z as Area3D } from "./gameplay-Dqm4LyKR.js";
9
9
  import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
10
10
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
11
11
  import { AdditiveBlending, AnimationClip, AnimationMixer, Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, CapsuleGeometry, ClampToEdgeWrapping, Color, ConeGeometry, CylinderGeometry, DataTexture, DirectionalLight, DoubleSide, DynamicDrawUsage, Euler, Group, IcosahedronGeometry, ImageBitmapLoader, InstancedBufferAttribute, InstancedMesh, LinearFilter, LinearMipmapLinearFilter, LoopOnce, LoopRepeat, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshPhysicalMaterial, MeshStandardMaterial, NearestFilter, NoBlending, NoColorSpace, NormalBlending, PerspectiveCamera, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, QuaternionKeyframeTrack, RGBADepthPacking, RGBAFormat, RepeatWrapping, SRGBColorSpace, ShaderChunk, ShaderMaterial, SphereGeometry, Texture, TextureLoader, UniformsLib, UniformsUtils, Vector3, Vector4, VectorKeyframeTrack } from "three";
@@ -1,7 +1,7 @@
1
- import { O as Node, P as Behavior, _ as SaveSlots, k as diagnose, n as loadScene, w as effectiveOrder, z as registerBehavior } from "./loader-DEe272nY.js";
1
+ import { O as Node, P as Behavior, _ as SaveSlots, k as diagnose, n as loadScene, w as effectiveOrder, z as registerBehavior } from "./loader-zDynoew_.js";
2
2
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
3
3
  import { t as jsonClone } from "./json-CwwhxQgb.js";
4
- import { t as duplicateNode } from "./duplicate-E4FUs5Bn.js";
4
+ import { t as duplicateNode } from "./duplicate-BOOKmkQ7.js";
5
5
  import { Color, CubeCamera, DepthTexture, DoubleSide, Euler, FloatType, Frustum, HalfFloatType, LinearMipmapLinearFilter, MathUtils, Matrix4, Mesh, MeshDepthMaterial, Object3D, PerspectiveCamera, Plane, PlaneGeometry, Quaternion, ShaderMaterial, Sphere, Vector2, Vector3, Vector4, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
6
6
  //#region src/3d/frustum.ts
7
7
  const scratchFrustum = new Frustum();
@@ -2582,7 +2582,11 @@ var CharacterBody3D = class extends PhysicsBody3D {
2582
2582
  0
2583
2583
  ] },
2584
2584
  stickToGround: { default: true },
2585
- slopeLimitDeg: { default: 45 }
2585
+ slopeLimitDeg: { default: 45 },
2586
+ stepHeight: {
2587
+ default: .35,
2588
+ range: { min: 0 }
2589
+ }
2586
2590
  };
2587
2591
  /** m/s, y-up (up = +y). */
2588
2592
  velocity = [
@@ -2604,6 +2608,28 @@ var CharacterBody3D = class extends PhysicsBody3D {
2604
2608
  /** Max climbable slope angle. */
2605
2609
  slopeLimitDeg = 45;
2606
2610
  /**
2611
+ * How high a step this body walks UP without jumping (0 = none).
2612
+ *
2613
+ * Rapier's character controller does not autostep unless asked, and nothing
2614
+ * asked — so every `CharacterBody3D` was stopped dead by any ledge at all.
2615
+ * The player never showed it, because `CharacterController3D` rides a hover
2616
+ * spring rather than the KCC; enemies are the ones that walk.
2617
+ *
2618
+ * Measured on the shipped `tps-3d` template, whose arena has a 0.6 m `Ramp`
2619
+ * between the spawn and the player. Standing still for 45 seconds:
2620
+ *
2621
+ * ```
2622
+ * hits=0 hp=100 closest an enemy ever got = 33.91m
2623
+ * ```
2624
+ *
2625
+ * They chase at the right speed the whole time and pile up against the near
2626
+ * face of a knee-high box, because a chaser walks a straight line and cannot
2627
+ * climb. The template's own playtest had been printing
2628
+ * `danger: the player took damage 0 times in 4 runs — nothing here can hurt
2629
+ * you` all along, and no rung failed on it.
2630
+ */
2631
+ stepHeight = .35;
2632
+ /**
2607
2633
  * `snapToGround` on a character body is ambiguous, so it is refused.
2608
2634
  *
2609
2635
  * A boolean here used to mean the ground-stick, and as placement it means
@@ -4905,7 +4931,7 @@ var DamageOnContact = class extends Behavior {
4905
4931
  if (!opts?.repeat && this.oncePerTarget && this.hit.has(other)) return;
4906
4932
  const found = findHealth(other);
4907
4933
  if (!found) return;
4908
- if (this.targetGroup !== "" && !found.owner.isInGroup(this.targetGroup)) return;
4934
+ if (this.targetGroup !== "" && !found.owner.isInGroup(this.targetGroup) && !other.isInGroup(this.targetGroup)) return;
4909
4935
  if (this.oncePerTarget) this.hit.add(other);
4910
4936
  found.health.damage(this.amount);
4911
4937
  this.emit("dealtDamage", this.amount, found.owner);
package/dist/gameplay.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as Health, C as restartScene, D as DayNight, E as FloatAway, M as Chase, O as phaseOf, S as goToScene, T as FollowCamera, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, j as Collector, k as DamageOnContact, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, x as GameFlow, y as screenFlash } from "./gameplay-CaHqDiQD.js";
1
+ import { A as Health, C as restartScene, D as DayNight, E as FloatAway, M as Chase, O as phaseOf, S as goToScene, T as FollowCamera, _ as Cooldown, a as Wander, b as Interactable, c as SavePoint, d as Patrol, f as PathFollow, g as CameraShake, h as Lifetime, i as WaveSpawner, j as Collector, k as DamageOnContact, l as Projectile, m as MoveTo, n as registerGameplayBehaviors, o as Spawner, p as Oscillate, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Pickup, v as hitStop, x as GameFlow, y as screenFlash } from "./gameplay-Dqm4LyKR.js";
2
2
  export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FloatAway, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, SavePoint, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
- import { A as describeRefProblem, B as registeredBehaviors, C as ORDER_GROUP_BASE, E as InputMap, F as behaviorSchema, I as behaviorSignals, L as clearBehaviors, M as resolveRefInJson, N as parseNodePath, O as Node, P as Behavior, R as getBehavior, S as createSaveStore, T as resolveOrderGroups, V as Signal, _ as SaveSlots, a as serializeNode, b as restoreBehaviors, d as computeViewport, f as resolveViewport, g as resolveConstants, h as isConstRef, i as Scene, j as nodeRefWarnings, m as CONST_REF_KEY, n as loadScene, o as SCENE_FORMAT, p as SceneTree, v as behaviorsWithoutSave, w as effectiveOrder, x as savesWithoutUid, y as captureBehaviors, z as registerBehavior } from "./loader-DEe272nY.js";
2
- import { A as UiBanner, B as isAudioContextAvailable, D as EffectLog, E as LogManager, F as T_PREFIX, G as SFX_PRESET_NAMES, H as spatialGain, I as suggestLocale, J as MusicManager, K as sfxDuration, M as UiText, N as BASE_LOCALE, O as HudLayer, P as Localization, Q as AudioBuses, R as translationKey, S as qualityEnvironment, T as suggestQuality, U as spatialPan, V as ROLLOFF_MODELS, W as SFX_PRESETS, X as crossfadeGains, Y as WebAudioMusicBackend, Z as fadeGain, _ as AudioPlayer, a as UiMuteToggle, b as Settings, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as UiDialogue, h as UiButton, i as UiLanguageSelect, j as UiBar, l as UiSelect, m as Respawn, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as synthSfx, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as Engine, w as readDeviceHints, z as SfxEngine } from "./register-D3yx8D4r.js";
1
+ import { A as describeRefProblem, B as registeredBehaviors, C as ORDER_GROUP_BASE, E as InputMap, F as behaviorSchema, I as behaviorSignals, L as clearBehaviors, M as resolveRefInJson, N as parseNodePath, O as Node, P as Behavior, R as getBehavior, S as createSaveStore, T as resolveOrderGroups, V as Signal, _ as SaveSlots, a as serializeNode, b as restoreBehaviors, d as computeViewport, f as resolveViewport, g as resolveConstants, h as isConstRef, i as Scene, j as nodeRefWarnings, m as CONST_REF_KEY, n as loadScene, o as SCENE_FORMAT, p as SceneTree, v as behaviorsWithoutSave, w as effectiveOrder, x as savesWithoutUid, y as captureBehaviors, z as registerBehavior } from "./loader-zDynoew_.js";
2
+ import { A as UiBanner, B as isAudioContextAvailable, D as EffectLog, E as LogManager, F as T_PREFIX, G as SFX_PRESET_NAMES, H as spatialGain, I as suggestLocale, J as MusicManager, K as sfxDuration, M as UiText, N as BASE_LOCALE, O as HudLayer, P as Localization, Q as AudioBuses, R as translationKey, S as qualityEnvironment, T as suggestQuality, U as spatialPan, V as ROLLOFF_MODELS, W as SFX_PRESETS, X as crossfadeGains, Y as WebAudioMusicBackend, Z as fadeGain, _ as AudioPlayer, a as UiMuteToggle, b as Settings, c as UiRenderScaleSelect, d as UiToggle, f as UiVolumeSlider, g as UiDialogue, h as UiButton, i as UiLanguageSelect, j as UiBar, l as UiSelect, m as Respawn, n as UiFrameCapSelect, o as UiPanel, p as Timer, q as synthSfx, r as UiImage, s as UiQualitySelect, t as registerCoreNodes, u as UiSlider, v as Engine, w as readDeviceHints, z as SfxEngine } from "./register-Btm7_Emq.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-CwwhxQgb.js";
6
6
  import { a as getNodeSignals, c as mergeStaticSignals, i as getNodeSchema, l as registerNode, n as clearRegistry, o as getNodeType, r as createNode, u as registeredTypes } from "./registry-CF70EArN.js";
7
- import { n as startRecording, r as auditScene, t as replay } from "./replay-DlgHItNv.js";
7
+ import { n as startRecording, r as auditScene, t as replay } from "./replay-DKRmiVjk.js";
8
8
  import { a as logReport, i as resolveRendering, n as attachTouchControls, o as logText, r as joystickVector, s as parseDrive, t as TouchControls } from "./touch-DESwnpOc.js";
9
9
  import { t as createNoise2D } from "./noise-CGUMx44x.js";
10
10
  import { a as PARTICLE_PRESETS, i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CY-mrr1L.js";
11
- import { a as preloadUrls, i as preloadSceneAssets, n as newUid, o as findPath, r as assetUrls, s as gridFromRows, t as VERSION } from "./src-CH00_JsR.js";
12
- import { t as duplicateNode } from "./duplicate-E4FUs5Bn.js";
11
+ import { a as preloadUrls, i as preloadSceneAssets, n as newUid, o as findPath, r as assetUrls, s as gridFromRows, t as VERSION } from "./src-DQzZD_gx.js";
12
+ import { t as duplicateNode } from "./duplicate-BOOKmkQ7.js";
13
13
  export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, EffectLog, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Respawn, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
@@ -2815,6 +2815,53 @@ function validateStrings(strings) {
2815
2815
  }
2816
2816
  }
2817
2817
  //#endregion
2818
+ //#region src/core/scene/wire-arity.ts
2819
+ /**
2820
+ * A wire that hands a handler fewer arguments than it needs.
2821
+ *
2822
+ * The engine hard-validates node types, prop kinds, every prop enum, node
2823
+ * paths, handler existence and signal declaration — and never that the signal's
2824
+ * ARGUMENTS fit the handler it is wired to. `died` carries none and
2825
+ * `ScoreKeeper.addScore(n)` wants one, so the most natural way to score a kill:
2826
+ *
2827
+ * ```json
2828
+ * { "signal": "died", "from": "Enemy", "to": "Keeper", "handler": "addScore" }
2829
+ * ```
2830
+ *
2831
+ * sets the score to `NaN` on the first kill and the win condition becomes
2832
+ * permanently unreachable. Measured:
2833
+ *
2834
+ * ```
2835
+ * score = NaN | Number.isNaN: true
2836
+ * HUD text = "Kills 0"
2837
+ * won emitted = false
2838
+ * engine errors = 0
2839
+ * incanto-check → 1/1 scene(s) valid
2840
+ * ```
2841
+ *
2842
+ * Two agents building different games from the published package hit it
2843
+ * independently, which is what makes it worth a rule rather than a footnote.
2844
+ *
2845
+ * **Why this is not a load error.** Nothing declares how many arguments a signal
2846
+ * carries — `static signals` is a list of names — so at load the pair cannot be
2847
+ * compared. At the moment the wire FIRES both halves are known exactly, and
2848
+ * that is where this lives.
2849
+ *
2850
+ * **Why `fn.length` is the right measure.** It counts leading parameters
2851
+ * without defaults, so it makes precisely the distinction the engine needs:
2852
+ * `addScore(n)` is 1 and gets reported; `GameFlow.win(text = 'YOU WIN')` is 0
2853
+ * and is silent, because that wire genuinely works — the banner defaults. The
2854
+ * same arg-less signal into a same-arity method is right in one case and wrong
2855
+ * in the other, and only the defaults tell them apart.
2856
+ */
2857
+ function reportShortWire(from, conn, fn, args) {
2858
+ diagnose(from.tree?.engine ?? null, "error", `[incanto] '${conn.signal}' from '${conn.from}' carries ${args.length} argument(s) and '${conn.to}.${conn.handler}' needs ${fn.length}. The missing one(s) arrive as undefined — a number handler gets NaN and never recovers. Give the handler a default, or wire a signal that carries what it needs.`);
2859
+ }
2860
+ /** Does this wire under-feed its handler? */
2861
+ function isShortWire(fn, args) {
2862
+ return args.length < fn.length;
2863
+ }
2864
+ //#endregion
2818
2865
  //#region src/core/scene/loader.ts
2819
2866
  var loader_exports = /* @__PURE__ */ __exportAll({
2820
2867
  buildNodeJson: () => buildNodeJson,
@@ -3023,17 +3070,25 @@ function wireConnections(root, connections, declareSignals = false) {
3023
3070
  const behaviorFn = toNode.behavior ? toNode.behavior[conn.handler] : void 0;
3024
3071
  if (typeof nodeFn !== "function" && typeof behaviorFn !== "function" && !(toNode.behavior instanceof StubBehavior)) throw new IncantoError("UNKNOWN_HANDLER", `Connection '${conn.signal}' → '${conn.to}' names handler '${conn.handler}', but '${toNode.name}' has no such method` + (toNode.behavior ? ` (nor does its behavior '${toNode.script?.name}')` : "") + `.`);
3025
3072
  if (ambiguousHandler(toNode, conn.handler)) throw ambiguousHandlerError(toNode, conn.handler, toNode.script?.name, conn.to);
3073
+ let saidShort = false;
3074
+ const feed = (fn, self, args) => {
3075
+ if (!saidShort && isShortWire(fn, args)) {
3076
+ saidShort = true;
3077
+ reportShortWire(fromNode, conn, fn, args);
3078
+ }
3079
+ fn.apply(self, args);
3080
+ };
3026
3081
  const listener = (...args) => {
3027
3082
  if (conn.filter && !matchesFilter(args[0], conn.filter)) return;
3028
3083
  if (conn.once) fromNode.off(conn.signal, listener);
3029
3084
  try {
3030
3085
  const fn = toNode[conn.handler];
3031
3086
  if (typeof fn === "function") {
3032
- fn.apply(toNode, args);
3087
+ feed(fn, toNode, args);
3033
3088
  return;
3034
3089
  }
3035
3090
  const bfn = toNode.behavior ? toNode.behavior[conn.handler] : void 0;
3036
- if (typeof bfn === "function") bfn.apply(toNode.behavior, args);
3091
+ if (typeof bfn === "function") feed(bfn, toNode.behavior, args);
3037
3092
  } catch (error) {
3038
3093
  quarantineWire(fromNode, toNode, conn, listener, error);
3039
3094
  }
package/dist/net.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
2
- import { a as NetworkManager, d as createLocalGameServer, f as LoopbackHub, l as LocalGameServer, n as registerNodesNet, o as applySyncPatch, p as LoopbackTransport, r as NetworkSpawner, t as createSplitScreen, u as LocalGameServerTransport } from "./split-screen-CBM9wcMX.js";
2
+ import { a as NetworkManager, d as createLocalGameServer, f as LoopbackHub, l as LocalGameServer, n as registerNodesNet, o as applySyncPatch, p as LoopbackTransport, r as NetworkSpawner, t as createSplitScreen, u as LocalGameServerTransport } from "./split-screen-Dx0LvzqS.js";
3
3
  export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { k as diagnose } from "./loader-DEe272nY.js";
2
+ import { k as diagnose } from "./loader-zDynoew_.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-CNh4FlbD.js";
4
+ import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-BSA93acW.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
6
  import { t as withoutRapierInitNoise } from "./quiet-rapier-BAJ4K94N.js";
7
7
  //#region src/2d/physics/physics-2d.ts
@@ -109,9 +109,9 @@ var Physics2D = class {
109
109
  e.lastVX = node.linearVelocity[0] ?? 0;
110
110
  e.lastVY = node.linearVelocity[1] ?? 0;
111
111
  }
112
- const [wx, wy] = worldPosition2D(node);
113
- e.lastX = wx;
114
- e.lastY = wy;
112
+ const t2 = e.body.translation();
113
+ e.lastX = t2.x * SCALE;
114
+ e.lastY = t2.y * SCALE;
115
115
  }
116
116
  this.events.drainCollisionEvents((h1, h2, started) => {
117
117
  const a = this.byColliderHandle.get(h1);
@@ -1,10 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { k as diagnose } from "./loader-DEe272nY.js";
2
+ import { k as diagnose } from "./loader-zDynoew_.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { B as CharacterBody3D, G as validateCollider3D, H as RigidBody3D, V as PhysicsBody3D, W as Node3D, z as Area3D } from "./gameplay-CaHqDiQD.js";
4
+ import { B as CharacterBody3D, G as validateCollider3D, H as RigidBody3D, V as PhysicsBody3D, W as Node3D, z as Area3D } from "./gameplay-Dqm4LyKR.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
6
  import { t as withoutRapierInitNoise } from "./quiet-rapier-BAJ4K94N.js";
7
- import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-BAWeOeqf.js";
7
+ import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-BKRo-HXg.js";
8
8
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
9
9
  //#region src/3d/physics/collider-lines.ts
10
10
  /**
@@ -384,6 +384,8 @@ var Physics3D = class {
384
384
  if (node.stickToGround && vy <= 0) this.kcc.enableSnapToGround(.1);
385
385
  else this.kcc.disableSnapToGround();
386
386
  this.kcc.setMaxSlopeClimbAngle(node.slopeLimitDeg * DEG);
387
+ if (node.stepHeight > 0) this.kcc.enableAutostep(node.stepHeight, node.stepHeight / 2, false);
388
+ else this.kcc.disableAutostep();
387
389
  let carryX = 0;
388
390
  let carryY = 0;
389
391
  let carryZ = 0;
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-Caut3bqN.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-BpunnGPX.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-CSnlQDkz.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-BsqKOAFN.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -1,10 +1,10 @@
1
- import { O as Node, k as diagnose, w as effectiveOrder } from "./loader-DEe272nY.js";
2
- import { L as translateOn, t as registerCoreNodes } from "./register-D3yx8D4r.js";
1
+ import { O as Node, k as diagnose, w as effectiveOrder } from "./loader-zDynoew_.js";
2
+ import { L as translateOn, t as registerCoreNodes } from "./register-Btm7_Emq.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { i as getNodeSchema, l as registerNode } from "./registry-CF70EArN.js";
6
6
  import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CY-mrr1L.js";
7
- import { N as localFromWorld, P as worldPosition } from "./gameplay-CaHqDiQD.js";
7
+ import { N as localFromWorld, P as worldPosition } from "./gameplay-Dqm4LyKR.js";
8
8
  import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
9
9
  import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry, SRGBColorSpace } from "three";
10
10
  //#region src/2d/physics/collider-validate.ts
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-DEe272nY.js";
2
+ import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-zDynoew_.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
5
  import { l as registerNode } from "./registry-CF70EArN.js";
@@ -1,5 +1,5 @@
1
- import { D as handlerShadowWarnings, j as nodeRefWarnings } from "./loader-DEe272nY.js";
2
- import { R as translationKey, k as HudWidgetBase } from "./register-D3yx8D4r.js";
1
+ import { D as handlerShadowWarnings, j as nodeRefWarnings } from "./loader-zDynoew_.js";
2
+ import { R as translationKey, k as HudWidgetBase } from "./register-Btm7_Emq.js";
3
3
  import { o as getNodeType } from "./registry-CF70EArN.js";
4
4
  //#region src/core/audit.ts
5
5
  const ANIMATED_TYPES = new Set([
@@ -115,6 +115,8 @@ function auditScene(scene, opts = {}) {
115
115
  const cameraDrivers = [];
116
116
  const followedCameras = [];
117
117
  const needUid = [];
118
+ /** Every `DamageOnContact.targetGroup` in the scene, with the node that set it. */
119
+ const damageGroups = [];
118
120
  const walk = (node, path, insideHud, staticRoot, transformed) => {
119
121
  const type = node.type ?? "";
120
122
  const props = node.props ?? {};
@@ -133,6 +135,13 @@ function auditScene(scene, opts = {}) {
133
135
  if (type === "CharacterController3D") cameraDrivers.push(path);
134
136
  if (LIGHT_TYPES.has(type)) lights += 1;
135
137
  if (BODY_TYPES.has(type) && transformed) warnings.push(`${path}: ${type} sits under '${transformed.path}', whose ${transformed.what} physics cannot compose — the collider will not line up with what is drawn. Keep body ancestors untransformed or translation-only, or put the body outside them.`);
138
+ if (script === "DamageOnContact") {
139
+ const group = (node.script?.props)?.targetGroup;
140
+ if (typeof group === "string" && group !== "") damageGroups.push({
141
+ path,
142
+ group
143
+ });
144
+ }
136
145
  if (WORLD_QUERY_TYPES.has(type) && transformed) warnings.push(`${path}: ${type} sits under '${transformed.path}', whose ${transformed.what} it cannot compose — the ground (or water) you can see would not be the ground you can stand on. Keep its ancestors untransformed or translation-only.`);
137
146
  if (BODY_TYPES.has(type)) {
138
147
  const collider = props.collider;
@@ -158,12 +167,43 @@ function auditScene(scene, opts = {}) {
158
167
  if (cameras > 0 && currentCameras === 0) warnings.push("no camera has \"current\": true — the screen renders from a default view, probably not what you framed.");
159
168
  if (currentCameras > 1) warnings.push(`${currentCameras} cameras claim "current": true — only the first found wins.`);
160
169
  if (scene.dimension === "3d" && lights === 0 && !isLit(scene.environment)) warnings.push("nothing lights this 3D scene — no light node, no environment sky/hdri, and no ambient intensity. It will render black. Add a DirectionalLight3D, or an \"environment\": { \"sky\": { \"type\": \"atmosphere\" } } / { \"preset\": \"...\" }.");
170
+ if (damageGroups.length > 0) {
171
+ const hurtable = hurtableGroups(root);
172
+ for (const { path, group } of damageGroups) {
173
+ if (hurtable.has(group)) continue;
174
+ warnings.push(`${path}: DamageOnContact targets group '${group}', and nothing in this scene both carries a Health and is in that group — so this hazard can never damage anything. Groups that CAN be hurt here: [${[...hurtable].join(", ") || "none"}].`);
175
+ }
176
+ }
161
177
  for (const warning of nodeRefWarnings(scene)) warnings.push(warning);
162
178
  for (const warning of handlerShadowWarnings(scene)) warnings.push(warning);
163
179
  for (const warning of auditStrings(scene, opts.declaredElsewhere)) warnings.push(warning);
164
180
  return warnings;
165
181
  }
166
182
  /**
183
+ * Every group that names something the engine could actually damage.
184
+ *
185
+ * `DamageOnContact` finds the nearest `Health` to the contacted node and then
186
+ * checks `targetGroup` against either end of that entity. So a group only means
187
+ * something if some node declares it AND that node's entity — itself, its
188
+ * descendants, or its ancestors — carries a `Health`.
189
+ *
190
+ * The shipped `star-survivor` template failed this for a subtler reason (its
191
+ * groups and its Health were on different nodes and the filter read only one
192
+ * end), but the shape generalises: a hazard whose `targetGroup` nothing can
193
+ * satisfy is wired, validated, and disarmed, and every check comes back green.
194
+ */
195
+ function hurtableGroups(root) {
196
+ const out = /* @__PURE__ */ new Set();
197
+ const hasHealth = (node) => node.script?.name === "Health" || (node.children ?? []).some(hasHealth);
198
+ const walk = (node, healthAbove) => {
199
+ const mine = node.script?.name === "Health" || healthAbove;
200
+ if ((node.groups ?? []).length > 0 && (mine || hasHealth(node))) for (const g of node.groups ?? []) out.add(g);
201
+ for (const child of node.children ?? []) walk(child, mine);
202
+ };
203
+ walk(root, false);
204
+ return out;
205
+ }
206
+ /**
167
207
  * Translation keys nothing declares — the ONE localization mistake worth
168
208
  * reporting.
169
209
  *
@@ -1,5 +1,5 @@
1
- import { O as Node, V as Signal, k as diagnose, n as loadScene, t as buildNodeJson } from "./loader-DEe272nY.js";
2
- import { t as registerCoreNodes, v as Engine } from "./register-D3yx8D4r.js";
1
+ import { O as Node, V as Signal, k as diagnose, n as loadScene, t as buildNodeJson } from "./loader-zDynoew_.js";
2
+ import { t as registerCoreNodes, v as Engine } from "./register-Btm7_Emq.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
5
5
  import { l as registerNode } from "./registry-CF70EArN.js";
@@ -214,6 +214,6 @@ function newUid() {
214
214
  //#endregion
215
215
  //#region src/index.ts
216
216
  /** Engine version. Kept in sync with package.json by the release pipeline. */
217
- const VERSION = "0.62.0";
217
+ const VERSION = "0.63.1";
218
218
  //#endregion
219
219
  export { preloadUrls as a, preloadSceneAssets as i, newUid as n, findPath as o, assetUrls as r, gridFromRows as s, VERSION as t };
@@ -1,4 +1,4 @@
1
- import { _ as AudioPlayer } from "./register-D3yx8D4r.js";
1
+ import { _ as AudioPlayer } from "./register-Btm7_Emq.js";
2
2
  import { a as logReport, s as parseDrive } from "./touch-DESwnpOc.js";
3
3
  import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-BSMny7oe.js";
4
4
  //#region src/core/audio-errors.ts