incanto 0.69.0 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/bin/_behaviors-loader.mjs +22 -0
  2. package/bin/_read-json.mjs +28 -0
  3. package/bin/incanto-assets.mjs +5 -4
  4. package/bin/incanto-check.mjs +58 -31
  5. package/bin/incanto-editor.mjs +128 -5
  6. package/bin/incanto-env.mjs +3 -2
  7. package/bin/incanto-feel.mjs +24 -13
  8. package/bin/incanto-frame.mjs +8 -1
  9. package/bin/incanto-multiplay.mjs +11 -9
  10. package/bin/incanto-new.mjs +105 -10
  11. package/bin/incanto-play.mjs +158 -16
  12. package/bin/incanto-playtest.mjs +66 -23
  13. package/bin/incanto-skills.mjs +14 -2
  14. package/bin/incanto-verify.mjs +125 -44
  15. package/bin/incanto.mjs +3 -2
  16. package/dist/2d.d.ts +281 -34
  17. package/dist/2d.js +4 -4
  18. package/dist/3d.d.ts +157 -9
  19. package/dist/3d.js +7 -7
  20. package/dist/{agent8-CvsfVskX.js → agent8-CmNF01gA.js} +61 -8
  21. package/dist/{audio-player-C0j2k-yB.d.ts → audio-player-DaMxqfNE.d.ts} +2 -2
  22. package/dist/{behavior-rm8vVzu_.d.ts → behavior-DZExDn9o.d.ts} +744 -46
  23. package/dist/{create-game-CwxEDlCm.js → create-game-Bwvh6q8A.js} +142 -59
  24. package/dist/{create-game-CvJ1wSRP.js → create-game-C7ffQWW7.js} +97 -47
  25. package/dist/debug.d.ts +1 -1
  26. package/dist/debug.js +2 -3
  27. package/dist/diagnostics-Cu85N3tL.d.ts +12 -0
  28. package/dist/{editor-switch-CnIOiyNJ.d.ts → editor-switch-CFU9mCec.d.ts} +22 -13
  29. package/dist/editor.js +1065 -863
  30. package/dist/env.d.ts +1 -1
  31. package/dist/env.js +5 -3
  32. package/dist/{environment-presets-BO-7XoFV.js → environment-presets-D6Q5BxeE.js} +298 -45
  33. package/dist/{gameplay-D1KixWK8.js → gameplay-BfHkuzVb.js} +809 -211
  34. package/dist/gameplay.d.ts +187 -7
  35. package/dist/gameplay.js +1 -1
  36. package/dist/{heightmap-CRK0M4jT.js → heightmap-BYgD5Edk.js} +1 -1
  37. package/dist/index.d.ts +156 -13
  38. package/dist/index.js +10 -12
  39. package/dist/json-CfTjpvW8.js +67 -0
  40. package/dist/{loader-DadV2QCT.d.ts → loader-Cff09LMm.d.ts} +2 -2
  41. package/dist/net.d.ts +27 -3
  42. package/dist/net.js +2 -2
  43. package/dist/{noise-CGUMx44x.js → noise-D3nPpmFg.js} +1 -1
  44. package/dist/{physics-2d-CF5yyjbj.js → physics-2d-CE0Qvy3V.js} +136 -11
  45. package/dist/{physics-3d-DbYaEcZy.js → physics-3d-CpH-2gn5.js} +104 -23
  46. package/dist/{teardown-CMqFVxO9.js → picking-CQJ_PJKh.js} +103 -12
  47. package/dist/react.d.ts +2 -2
  48. package/dist/react.js +2 -2
  49. package/dist/{register-mmfugpaQ.js → register-6DYnKZcy.js} +546 -817
  50. package/dist/{register-DbVArsns.js → register-Bkk0wSDB.js} +332 -23
  51. package/dist/{replay-BEHLDcQV.js → replay-DjAkAzMq.js} +224 -14
  52. package/dist/{replay-BQfkX4x7.d.ts → replay-Dmw-PKQu.d.ts} +20 -3
  53. package/dist/{schema-B6ugCV1Q.d.ts → rng-Bb-IutXB.d.ts} +38 -21
  54. package/dist/{rng-DP-SR7eg.js → rng-CDOMybym.js} +22 -0
  55. package/dist/{loader-DZE7B07H.js → save-slots-BXVg148r.js} +4570 -2322
  56. package/dist/{split-screen-Cg76MCV5.d.ts → split-screen--k-XpBjr.d.ts} +36 -4
  57. package/dist/{split-screen-CauMwu6y.js → split-screen-PL78oVXP.js} +159 -26
  58. package/dist/{sprite-animation-CY-mrr1L.js → sprite-animation-CqR2o3SA.js} +39 -8
  59. package/dist/{src-CYEu-_S9.js → src-Cxfiv1Hg.js} +2 -17
  60. package/dist/test-iHYVUcDK.js +4036 -0
  61. package/dist/test.d.ts +531 -30
  62. package/dist/test.js +3 -3
  63. package/dist/touch-BnCyPA0G.js +519 -0
  64. package/dist/vite.d.ts +20 -37
  65. package/dist/vite.js +176 -6
  66. package/dist/{webgl-unavailable-Z5_g8x9H.js → webgl-unavailable-C8aDbGmR.js} +44 -0
  67. package/editor/assets/agent8-D0MS174y.js +1 -0
  68. package/editor/assets/{debug-CMULinBW.js → debug-BnXkKuYu.js} +2 -2
  69. package/editor/assets/index-CIu3uc3l.js +11046 -0
  70. package/editor/index.html +1 -1
  71. package/package.json +4 -14
  72. package/schemas/scene.schema.json +30 -3
  73. package/skills/incanto-3d-character.md +14 -1
  74. package/skills/incanto-3d-models.md +12 -0
  75. package/skills/incanto-assets.md +9 -1
  76. package/skills/incanto-audio.md +8 -2
  77. package/skills/incanto-behaviors-and-scripts.md +83 -6
  78. package/skills/incanto-building-2d-games.md +77 -8
  79. package/skills/incanto-building-3d-games.md +58 -4
  80. package/skills/incanto-editor.md +46 -7
  81. package/skills/incanto-environment.md +19 -1
  82. package/skills/incanto-game-feel.md +70 -0
  83. package/skills/incanto-gameplay-behaviors.md +109 -19
  84. package/skills/incanto-hud.md +128 -7
  85. package/skills/incanto-localization.md +13 -5
  86. package/skills/incanto-multiplayer.md +83 -3
  87. package/skills/incanto-node-reference.md +222 -58
  88. package/skills/incanto-performance.md +52 -0
  89. package/skills/incanto-physics-and-input.md +123 -24
  90. package/skills/incanto-playtesting.md +78 -2
  91. package/skills/incanto-save-slots.md +161 -6
  92. package/skills/incanto-scene-json-authoring.md +69 -12
  93. package/skills/incanto-verifying-your-game.md +196 -10
  94. package/skills/incanto-web-integration.md +28 -0
  95. package/skills/incanto-your-first-game.md +3 -1
  96. package/templates-app/beacon-isle-3d/generate-world.ts +77 -9
  97. package/templates-app/beacon-isle-3d/package.json +2 -2
  98. package/templates-app/beacon-isle-3d/src/behaviors.ts +22 -0
  99. package/templates-app/beacon-isle-3d/src/game.scene.json +103 -378
  100. package/templates-app/beacon-isle-3d/src/main.ts +11 -1
  101. package/templates-app/beacon-isle-3d/tsconfig.json +1 -1
  102. package/templates-app/beacon-isle-3d/verify.ts +3 -1
  103. package/templates-app/molehill-2d/.incanto/playtest/lost-seed1.json +4277 -0
  104. package/templates-app/molehill-2d/PROJECT/Context.md +58 -0
  105. package/templates-app/molehill-2d/PROJECT/Requirements.md +39 -0
  106. package/templates-app/molehill-2d/PROJECT/Status.md +27 -0
  107. package/templates-app/molehill-2d/PROJECT/Structure.md +48 -0
  108. package/templates-app/molehill-2d/docs/project-2d-rules.md +44 -0
  109. package/templates-app/molehill-2d/index.html +73 -0
  110. package/templates-app/molehill-2d/package.json +23 -0
  111. package/templates-app/molehill-2d/src/behaviors.ts +198 -0
  112. package/templates-app/molehill-2d/src/game.scene.json +1255 -0
  113. package/templates-app/molehill-2d/src/main.ts +41 -0
  114. package/templates-app/molehill-2d/tsconfig.json +13 -0
  115. package/templates-app/molehill-2d/verify.ts +247 -0
  116. package/templates-app/molehill-2d/vite.config.ts +12 -0
  117. package/templates-app/platformer-2d/package.json +2 -2
  118. package/templates-app/platformer-2d/src/game.scene.json +128 -600
  119. package/templates-app/platformer-2d/src/main.ts +11 -1
  120. package/templates-app/platformer-2d/tsconfig.json +1 -1
  121. package/templates-app/star-survivor/package.json +2 -2
  122. package/templates-app/star-survivor/src/game.scene.json +41 -195
  123. package/templates-app/star-survivor/src/main.ts +11 -1
  124. package/templates-app/star-survivor/tsconfig.json +1 -1
  125. package/templates-app/tps-3d/PROJECT/Context.md +1 -1
  126. package/templates-app/tps-3d/package.json +2 -2
  127. package/templates-app/tps-3d/src/behaviors.ts +19 -1
  128. package/templates-app/tps-3d/src/game.scene.json +78 -217
  129. package/templates-app/tps-3d/src/main.ts +13 -2
  130. package/templates-app/tps-3d/tsconfig.json +1 -1
  131. package/templates-app/village-quest-3d/.incanto/playtest/swapped-seed1.json +1735 -0
  132. package/templates-app/village-quest-3d/package.json +2 -2
  133. package/templates-app/village-quest-3d/src/behaviors.ts +21 -0
  134. package/templates-app/village-quest-3d/src/grove.scene.json +54 -221
  135. package/templates-app/village-quest-3d/src/main.ts +11 -1
  136. package/templates-app/village-quest-3d/src/village.scene.json +199 -838
  137. package/templates-app/village-quest-3d/tsconfig.json +1 -1
  138. package/templates-app/village-quest-3d/verify.ts +14 -1
  139. package/dist/duplicate-Dj4L-5AA.js +0 -22
  140. package/dist/json-CwwhxQgb.js +0 -36
  141. package/dist/registry-CF70EArN.js +0 -212
  142. package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
  143. package/dist/test-D6ISmuFE.js +0 -2383
  144. package/dist/touch-DEAmqGdf.js +0 -225
  145. package/editor/assets/agent8-BDvaZMQJ.js +0 -1
  146. package/editor/assets/index-CZIRr5EL.js +0 -10958
@@ -1,10 +1,10 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { k as diagnose } from "./loader-DZE7B07H.js";
1
+ import { o as __exportAll } from "./json-CfTjpvW8.js";
2
+ import { q as diagnose } from "./save-slots-BXVg148r.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { H as CharacterBody3D, K as Node3D, U as PhysicsBody3D, V as Area3D, W as RigidBody3D, q as validateCollider3D } from "./gameplay-D1KixWK8.js";
4
+ import { B as Area3D, G as Node3D, H as PhysicsBody3D, K as validateCollider3D, U as RigidBody3D, V as CharacterBody3D } from "./gameplay-BfHkuzVb.js";
5
5
  import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
6
6
  import { n as jointContacts, t as withoutRapierInitNoise } from "./quiet-rapier-C6fW4zcW.js";
7
- import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-BO-7XoFV.js";
7
+ import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-D6Q5BxeE.js";
8
8
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
9
9
  //#region src/3d/physics/collider-lines.ts
10
10
  /**
@@ -386,6 +386,21 @@ var Physics3D = class {
386
386
  this.world.propagateModifiedBodyPositionsToColliders();
387
387
  }
388
388
  /** @internal Called by CharacterBody3D.moveAndSlide (during tree fixedUpdate). */
389
+ /**
390
+ * May `candidate` carry `rider`? — see the 2D twin.
391
+ *
392
+ * "Never a CharacterBody3D" is the whole CLASS, and a CharacterBody3D
393
+ * animated by position writes is the documented moving-platform path. What
394
+ * the guard was really for is a carry CYCLE, so that is what it tests.
395
+ */
396
+ canCarry(rider, candidate) {
397
+ let at = candidate;
398
+ for (let depth = 0; depth < 8 && at; depth++) {
399
+ if (at === rider) return false;
400
+ at = this.entries.get(at)?.ground ?? null;
401
+ }
402
+ return true;
403
+ }
389
404
  moveAndSlide(node) {
390
405
  const e = this.ensureEntry(node);
391
406
  if (!e) return;
@@ -421,6 +436,20 @@ var Physics3D = class {
421
436
  z: nz
422
437
  });
423
438
  node._grounded = this.kcc.computedGrounded();
439
+ node._onWall = null;
440
+ node._onCeiling = false;
441
+ const touches = this.kcc.numComputedCollisions();
442
+ for (let i = 0; i < touches; i++) {
443
+ const normal = this.kcc.computedCollision(i)?.normal1;
444
+ if (!normal) continue;
445
+ if (normal.y < -.7) {
446
+ if (!node._grounded) node._onCeiling = true;
447
+ } else if (Math.hypot(normal.x, normal.z) > .7) node._onWall = [
448
+ normal.x,
449
+ normal.y,
450
+ normal.z
451
+ ];
452
+ }
424
453
  if (node._grounded) {
425
454
  const half = characterHalfHeight(node);
426
455
  const hit = this.castRay([
@@ -432,7 +461,7 @@ var Physics3D = class {
432
461
  -1,
433
462
  0
434
463
  ], .4, node);
435
- if (hit?.node && !(hit.node instanceof CharacterBody3D)) {
464
+ if (hit?.node && this.canCarry(node, hit.node)) {
436
465
  e.ground = hit.node;
437
466
  const at = worldPosition3D(hit.node);
438
467
  e.groundAt = [
@@ -499,9 +528,28 @@ var Physics3D = class {
499
528
  * off ONE fixed body — they never move, so there is nothing to step — and the
500
529
  * whole set is rebuilt only when the tree's shape changes.
501
530
  */
531
+ /** Where each live scatter field was when its colliders were built. */
532
+ scatterState = /* @__PURE__ */ new Map();
533
+ /**
534
+ * Has any scatter field moved, or had its rows replaced, since the build?
535
+ *
536
+ * `transforms` is compared by IDENTITY, which is the operation the skill
537
+ * teaches ("replace the whole array"); the world position catches the field
538
+ * itself or an ancestor being moved.
539
+ */
540
+ scattersMoved() {
541
+ for (const [field] of this.scatterBodies) {
542
+ const was = this.scatterState.get(field);
543
+ const [x, y, z] = worldPosition3D(field);
544
+ if (!was) return true;
545
+ if (was.rows !== field.transforms || was.x !== x || was.y !== y || was.z !== z) return true;
546
+ }
547
+ return false;
548
+ }
502
549
  syncScatters(root) {
503
550
  for (const [, entry] of this.scatterBodies) this.world.removeRigidBody(entry);
504
551
  this.scatterBodies.clear();
552
+ this.scatterState.clear();
505
553
  const fields = /* @__PURE__ */ new Set();
506
554
  collectScatters(root, fields);
507
555
  for (const field of fields) {
@@ -543,6 +591,12 @@ var Physics3D = class {
543
591
  this.world.createCollider(desc, body);
544
592
  }
545
593
  this.scatterBodies.set(field, body);
594
+ this.scatterState.set(field, {
595
+ rows: field.transforms,
596
+ x: ox,
597
+ y: oy,
598
+ z: oz
599
+ });
546
600
  }
547
601
  }
548
602
  syncBodies() {
@@ -577,6 +631,7 @@ var Physics3D = class {
577
631
  this.syncScatters(scene.root);
578
632
  }
579
633
  }
634
+ if (this.scatterBodies.size > 0 && this.scattersMoved()) this.syncScatters(scene.root);
580
635
  const seen = this.bodySet;
581
636
  for (const node of seen) {
582
637
  if (node.collider.shape === void 0) {
@@ -644,12 +699,13 @@ var Physics3D = class {
644
699
  if (node instanceof RigidBody3D) {
645
700
  desc.setGravityScale(node.gravityScale);
646
701
  desc.setLinvel(node.linearVelocity[0] ?? 0, node.linearVelocity[1] ?? 0, node.linearVelocity[2] ?? 0);
702
+ const locked = node.fixedRotation;
647
703
  desc.setAngvel({
648
- x: node.angularVelocity[0] ?? 0,
649
- y: node.angularVelocity[1] ?? 0,
650
- z: node.angularVelocity[2] ?? 0
704
+ x: locked ? 0 : node.angularVelocity[0] ?? 0,
705
+ y: locked ? 0 : node.angularVelocity[1] ?? 0,
706
+ z: locked ? 0 : node.angularVelocity[2] ?? 0
651
707
  });
652
- if (node.fixedRotation) desc.lockRotations();
708
+ if (locked) desc.lockRotations();
653
709
  }
654
710
  const body = this.world.createRigidBody(desc);
655
711
  colDesc.setActiveEvents(R.ActiveEvents.COLLISION_EVENTS);
@@ -674,7 +730,10 @@ var Physics3D = class {
674
730
  0,
675
731
  0,
676
732
  0
677
- ]
733
+ ],
734
+ lastMass: node instanceof RigidBody3D ? node.mass : 1,
735
+ lastFriction: node instanceof RigidBody3D ? node.friction : .5,
736
+ lastRestitution: node instanceof RigidBody3D ? node.restitution : 0
678
737
  };
679
738
  this.entries.set(node, e);
680
739
  this.byColliderHandle.set(collider.handle, node);
@@ -735,6 +794,18 @@ var Physics3D = class {
735
794
  e.lastV = v;
736
795
  }
737
796
  if (node.gravityScale !== e.body.gravityScale()) e.body.setGravityScale(node.gravityScale, true);
797
+ if (node.mass !== e.lastMass) {
798
+ e.collider.setMass(node.mass);
799
+ e.lastMass = node.mass;
800
+ }
801
+ if (node.friction !== e.lastFriction) {
802
+ e.collider.setFriction(node.friction);
803
+ e.lastFriction = node.friction;
804
+ }
805
+ if (node.restitution !== e.lastRestitution) {
806
+ e.collider.setRestitution(node.restitution);
807
+ e.lastRestitution = node.restitution;
808
+ }
738
809
  }
739
810
  return e;
740
811
  }
@@ -864,21 +935,24 @@ var Physics3D = class {
864
935
  return [...set];
865
936
  }
866
937
  /**
867
- * World-space raycast (meters). `exclude` skips that body's collider —
938
+ * World-space raycast (meters). `dir` may be any length (it is normalized),
939
+ * so `distance` and `maxLen` are metres. `exclude` skips that body's collider —
868
940
  * ground probes from inside a capsule need it. `opts.staticOnly` hits ONLY the
869
941
  * fixed/static world (excludes both dynamic AND kinematic bodies): the camera
870
942
  * spring arm uses it so neither a dynamic projectile nor a KINEMATIC enemy
871
943
  * (CharacterBody3D) passing behind the player yanks the camera in.
872
944
  */
873
945
  castRay(origin, dir, maxLen, exclude, opts) {
946
+ const len = Math.hypot(dir[0], dir[1], dir[2]);
947
+ if (!(len > 0)) return null;
874
948
  const ray = new this.R.Ray({
875
949
  x: origin[0],
876
950
  y: origin[1],
877
951
  z: origin[2]
878
952
  }, {
879
- x: dir[0],
880
- y: dir[1],
881
- z: dir[2]
953
+ x: dir[0] / len,
954
+ y: dir[1] / len,
955
+ z: dir[2] / len
882
956
  });
883
957
  const excludeCollider = exclude ? this.entries.get(exclude)?.collider : void 0;
884
958
  const flags = opts?.staticOnly ? this.R.QueryFilterFlags.EXCLUDE_DYNAMIC | this.R.QueryFilterFlags.EXCLUDE_KINEMATIC : void 0;
@@ -891,7 +965,12 @@ var Physics3D = class {
891
965
  hit.normal.y,
892
966
  hit.normal.z
893
967
  ],
894
- node: this.byColliderHandle.get(hit.collider.handle) ?? null
968
+ node: this.byColliderHandle.get(hit.collider.handle) ?? null,
969
+ point: [
970
+ origin[0] + dir[0] / len * hit.timeOfImpact,
971
+ origin[1] + dir[1] / len * hit.timeOfImpact,
972
+ origin[2] + dir[2] / len * hit.timeOfImpact
973
+ ]
895
974
  };
896
975
  }
897
976
  /**
@@ -906,6 +985,8 @@ var Physics3D = class {
906
985
  const R = this.R;
907
986
  const excludeEntry = exclude ? this.entries.get(exclude) : void 0;
908
987
  const flags = opts?.staticOnly ? R.QueryFilterFlags.EXCLUDE_DYNAMIC | R.QueryFilterFlags.EXCLUDE_KINEMATIC : void 0;
988
+ const len = Math.hypot(dir[0], dir[1], dir[2]);
989
+ if (!(len > 0)) return null;
909
990
  const hit = this.world.castShape({
910
991
  x: origin[0],
911
992
  y: origin[1],
@@ -916,9 +997,9 @@ var Physics3D = class {
916
997
  z: 0,
917
998
  w: 1
918
999
  }, {
919
- x: dir[0],
920
- y: dir[1],
921
- z: dir[2]
1000
+ x: dir[0] / len,
1001
+ y: dir[1] / len,
1002
+ z: dir[2] / len
922
1003
  }, new R.Ball(radius), 0, maxLen, true, flags, void 0, excludeEntry?.collider, excludeEntry?.body, (collider) => !collider.isSensor());
923
1004
  if (!hit) return null;
924
1005
  return {
@@ -950,7 +1031,7 @@ function collectBodies(node, out) {
950
1031
  * Convex, always: a hull is a few planes to test against, where a triangle
951
1032
  * mesh is a tree of them, and nothing scattered across a map needs concavity.
952
1033
  */
953
- function meshShapedDesc(R, kind, size, scale, who) {
1034
+ function meshShapedDesc(R, kind, size, scale, who, engine) {
954
1035
  const sx = scale[0] ?? 1;
955
1036
  const sy = scale[1] ?? 1;
956
1037
  const sz = scale[2] ?? 1;
@@ -958,7 +1039,7 @@ function meshShapedDesc(R, kind, size, scale, who) {
958
1039
  const y = (size[1] ?? 1) * sy;
959
1040
  const z = (size[2] ?? 1) * sz;
960
1041
  const radial = Math.max(Math.abs(sx), Math.abs(sz));
961
- if (who && kind !== "box" && kind !== "plane" && Math.abs(sx - sz) > 1e-6) console.warn(`[incanto] Collider on '${who}': shape 'auto' fits a ${kind} to a mesh scaled [${sx}, ${sy}, ${sz}]. A ${kind} has one radius, so the widest of x/z (${radial}) is used. Use a box, or scale x and z the same.`);
1042
+ if (who && kind !== "box" && kind !== "plane" && Math.abs(sx - sz) > 1e-6) diagnose(engine ?? null, "warn", `[incanto] Collider on '${who}': shape 'auto' fits a ${kind} to a mesh scaled [${sx}, ${sy}, ${sz}]. A ${kind} has one radius, so the widest of x/z (${radial}) is used. Use a box, or scale x and z the same.`);
962
1043
  switch (kind) {
963
1044
  case "box": return R.ColliderDesc.cuboid(x / 2, y / 2, z / 2);
964
1045
  case "sphere": return R.ColliderDesc.ball((size[0] ?? 1) * Math.max(radial, Math.abs(sy)));
@@ -1233,9 +1314,9 @@ function buildColliderDesc3D(R, collider, nodeName, node) {
1233
1314
  const model = models[0];
1234
1315
  if (!mesh && !model) throw new IncantoError("BAD_FORMAT", `Collider on '${nodeName}': shape 'auto' needs a MeshInstance3D or ModelInstance3D on the body (or as a child) to take its shape from.`);
1235
1316
  const parts = meshes.length + models.length;
1236
- if (parts > 1) console.warn(`[incanto] Collider on '${nodeName}': shape 'auto' fits ONE mesh and took '${(mesh ?? model)?.name}'; ${parts - 1} other mesh(es) here are not solid. Give each part its own body.`);
1317
+ if (parts > 1) diagnose(node?.tree?.engine ?? null, "warn", `[incanto] Collider on '${nodeName}': shape 'auto' fits ONE mesh and took '${(mesh ?? model)?.name}'; ${parts - 1} other mesh(es) here are not solid. Give each part its own body.`);
1237
1318
  if (mesh) {
1238
- const desc = meshShapedDesc(R, mesh.mesh, mesh.size, mesh.scale, nodeName);
1319
+ const desc = meshShapedDesc(R, mesh.mesh, mesh.size, mesh.scale, nodeName, node?.tree?.engine);
1239
1320
  if (desc) {
1240
1321
  desc.setTranslation(mesh.at[0], mesh.at[1], mesh.at[2]);
1241
1322
  if (mesh.turn[0] !== 0 || mesh.turn[1] !== 0 || mesh.turn[2] !== 0) {
@@ -1253,7 +1334,7 @@ function buildColliderDesc3D(R, collider, nodeName, node) {
1253
1334
  const tri = modelTriangles(node, model);
1254
1335
  if (!tri) return null;
1255
1336
  if (isStaticShape(node)) {
1256
- if (tri.triangles > TRIMESH_BUDGET) console.warn(`[incanto] Collider on '${nodeName}': shape 'auto' built a ${tri.triangles}-triangle mesh from '${model.name}'. Consider a low-poly collision model or an explicit box.`);
1337
+ if (tri.triangles > TRIMESH_BUDGET) diagnose(node?.tree?.engine ?? null, "warn", `[incanto] Collider on '${nodeName}': shape 'auto' built a ${tri.triangles}-triangle mesh from '${model.name}'. Consider a low-poly collision model or an explicit box.`);
1257
1338
  return R.ColliderDesc.trimesh(tri.vertices, tri.indices);
1258
1339
  }
1259
1340
  return R.ColliderDesc.convexHull(tri.vertices);
@@ -1,6 +1,7 @@
1
- import { _ as AudioPlayer } from "./register-mmfugpaQ.js";
2
- import { i as logReport, o as parseDrive } from "./webgl-unavailable-Z5_g8x9H.js";
1
+ import { _ as AudioPlayer } from "./register-6DYnKZcy.js";
2
+ import { i as logReport, o as parseDrive } from "./webgl-unavailable-C8aDbGmR.js";
3
3
  import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-Dlq13Gyj.js";
4
+ import { Z as worldPosition } from "./gameplay-BfHkuzVb.js";
4
5
  //#region src/core/audio-errors.ts
5
6
  /** Every AudioPlayer under `root` whose source could not be played. */
6
7
  function audioErrors(root) {
@@ -100,6 +101,74 @@ function wireDevChannel(opts) {
100
101
  return cleanups;
101
102
  }
102
103
  //#endregion
104
+ //#region src/core/drive-apply.ts
105
+ /**
106
+ * Long enough for the page to draw a frame between the press and the release.
107
+ *
108
+ * Both edges in one tick would still FIRE — the input settles them together and
109
+ * `Clickable` reads press then release in the same update — but no real cursor
110
+ * does that, and everything a game shows for a held button (a pressed state, a
111
+ * hover tint, the sound) would never be on screen. Two frames at 60 Hz.
112
+ */
113
+ const FRAME_MS = 34;
114
+ /** Apply one step of a drive script to a running game. */
115
+ async function applyDriveStep(target, step) {
116
+ const { engine } = target;
117
+ switch (step.kind) {
118
+ case "press":
119
+ engine.input.pressAction(step.action);
120
+ return;
121
+ case "release":
122
+ engine.input.releaseAction(step.action);
123
+ return;
124
+ case "vector":
125
+ engine.input.setActionVector(step.action, step.x, step.y);
126
+ return;
127
+ case "key":
128
+ engine.input.handleKey(step.code, step.down);
129
+ return;
130
+ case "pointer":
131
+ engine.input.handlePointerMove(step.dx, step.dy);
132
+ return;
133
+ case "at":
134
+ engine.input.setPointerPosition(step.x, step.y);
135
+ return;
136
+ case "atNode": {
137
+ const node = engine.scene?.root.getNodeOrNull(step.path);
138
+ const at = node ? worldPosition(node) : [];
139
+ if (at.length === 0) {
140
+ engine.log.error(`[incanto] drive: at '${step.path}' — no such node, so the cursor did not move.`);
141
+ return;
142
+ }
143
+ const screen = target.project(at[0] ?? 0, at[1] ?? 0, at[2] ?? 0);
144
+ if (!screen) {
145
+ engine.log.error(`[incanto] drive: at '${step.path}' — this renderer cannot say where a world point is on screen, so the cursor did not move.`);
146
+ return;
147
+ }
148
+ if (screen.behind === true) {
149
+ engine.log.error(`[incanto] drive: at '${step.path}' — it is BEHIND the camera, so there is no pixel to put the cursor on.`);
150
+ return;
151
+ }
152
+ engine.input.setPointerPosition(screen.x, screen.y);
153
+ return;
154
+ }
155
+ case "click":
156
+ engine.input.handleMouseButton(step.button, true);
157
+ await target.wait(FRAME_MS);
158
+ engine.input.handleMouseButton(step.button, false);
159
+ await target.wait(FRAME_MS);
160
+ return;
161
+ case "mouse":
162
+ engine.input.handleMouseButton(step.button, step.down);
163
+ await target.wait(FRAME_MS);
164
+ return;
165
+ case "step":
166
+ await target.wait(step.ms);
167
+ return;
168
+ default: return;
169
+ }
170
+ }
171
+ //#endregion
103
172
  //#region src/core/editor-switch.ts
104
173
  /**
105
174
  * Cover the page while it changes what it is.
@@ -347,27 +416,20 @@ function pauseWhenHidden(engine, doc = typeof document === "undefined" ? null :
347
416
  if (!doc) return () => {};
348
417
  /** The scale to come back to — null when we did not pause anything. */
349
418
  let heldScale = null;
350
- let mutedByUs = false;
351
419
  const onChange = () => {
352
420
  if (doc.hidden) {
353
421
  if (heldScale === null && engine.timeScale > 0) {
354
422
  heldScale = engine.timeScale;
355
423
  engine.timeScale = 0;
356
424
  }
357
- if (!engine.audio.muted) {
358
- engine.audio.muted = true;
359
- mutedByUs = true;
360
- }
425
+ engine.audio.suspend(true);
361
426
  return;
362
427
  }
363
428
  if (heldScale !== null) {
364
429
  if (engine.timeScale === 0) engine.timeScale = heldScale;
365
430
  heldScale = null;
366
431
  }
367
- if (mutedByUs) {
368
- engine.audio.muted = false;
369
- mutedByUs = false;
370
- }
432
+ engine.audio.suspend(false);
371
433
  };
372
434
  doc.addEventListener("visibilitychange", onChange);
373
435
  return () => doc.removeEventListener("visibilitychange", onChange);
@@ -397,4 +459,33 @@ function defaultReport(what, error) {
397
459
  console.error(`[incanto] teardown: ${what} failed (continuing)`, error);
398
460
  }
399
461
  //#endregion
400
- export { openBundledEditor as a, claimCanvasGestures as c, devServerLibrary as i, audioErrors as l, pauseWhenHidden as n, poseFromRenderer as o, crossFade as r, wireDevChannel as s, teardown as t };
462
+ //#region src/core/picking.ts
463
+ /** Is this object, and every ancestor up to the scene, drawn? */
464
+ function drawn(object) {
465
+ for (let at = object; at; at = at.parent) if (!at.visible) return false;
466
+ return true;
467
+ }
468
+ /**
469
+ * The `incantoNode` under a set of raycast hits, or null.
470
+ *
471
+ * Hits are expected in the order three produces them: nearest first.
472
+ */
473
+ function nodeFromHits(hits, opts = {}) {
474
+ let best = null;
475
+ for (const hit of hits) {
476
+ if (!drawn(hit.object)) continue;
477
+ let obj = hit.object;
478
+ while (obj && !obj.userData?.incantoNode) obj = obj.parent;
479
+ const node = obj?.userData?.incantoNode;
480
+ if (node === void 0) continue;
481
+ if (!opts.byRenderOrder) return node;
482
+ const order = hit.object.renderOrder ?? 0;
483
+ if (!best || order >= best.order) best = {
484
+ node,
485
+ order
486
+ };
487
+ }
488
+ return best?.node ?? null;
489
+ }
490
+ //#endregion
491
+ export { devServerLibrary as a, applyDriveStep as c, audioErrors as d, crossFade as i, wireDevChannel as l, teardown as n, openBundledEditor as o, pauseWhenHidden as r, poseFromRenderer as s, nodeFromHits as t, claimCanvasGestures as u };
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-rm8vVzu_.js";
2
- import { c as JsonValue } from "./schema-B6ugCV1Q.js";
1
+ import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-DZExDn9o.js";
2
+ import { c as JsonValue } from "./rng-Bb-IutXB.js";
3
3
  import { CSSProperties, ReactNode } from "react";
4
4
 
5
5
  //#region src/react/index.d.ts
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
1
+ import { n as jsonEquals, t as jsonClone } from "./json-CfTjpvW8.js";
2
2
  import { createContext, useContext, useEffect, useRef, useState } from "react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  //#region src/react/index.tsx
@@ -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-CvJ1wSRP.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-CwxEDlCm.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-C7ffQWW7.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-Bwvh6q8A.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;