incanto 0.60.0 → 0.62.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 (68) hide show
  1. package/bin/incanto-check.mjs +13 -0
  2. package/bin/incanto-verify.mjs +134 -55
  3. package/dist/2d.d.ts +64 -3
  4. package/dist/2d.js +3 -3
  5. package/dist/3d.d.ts +5 -5
  6. package/dist/3d.js +5 -4
  7. package/dist/{pathfinding-BqWBb0kh.d.ts → audio-player-D5GJgb_x.d.ts} +103 -38
  8. package/dist/{behavior-DWKTUzKI.d.ts → behavior-DsgayMsH.d.ts} +79 -1
  9. package/dist/{create-game-ClnIb_M5.js → create-game-BpunnGPX.js} +78 -13
  10. package/dist/{create-game-BCm38FJV.js → create-game-Caut3bqN.js} +21 -294
  11. package/dist/debug.d.ts +1 -1
  12. package/dist/debug.js +1 -1
  13. package/dist/{duplicate-DJQd44CD.js → duplicate-E4FUs5Bn.js} +1 -1
  14. package/dist/editor.js +41 -19
  15. package/dist/{environment-presets-8cjF3t6w.js → environment-presets-BAWeOeqf.js} +50 -13
  16. package/dist/{gameplay-BBEjPFsR.js → gameplay-CaHqDiQD.js} +177 -24
  17. package/dist/gameplay.d.ts +1 -1
  18. package/dist/gameplay.js +1 -1
  19. package/dist/index.d.ts +97 -6
  20. package/dist/index.js +7 -7
  21. package/dist/{loader-D8n7TU8W.js → loader-DEe272nY.js} +900 -2
  22. package/dist/{loader-TvkRFbyL.d.ts → loader-DolLJWJn.d.ts} +13 -1
  23. package/dist/net.d.ts +2 -2
  24. package/dist/net.js +1 -1
  25. package/dist/pathfinding-_fGrCFmH.d.ts +28 -0
  26. package/dist/{physics-2d-DqdVp1bt.js → physics-2d-BXmu2i7W.js} +11 -3
  27. package/dist/{physics-3d-BP0DZb_1.js → physics-3d-ClxP6Uv7.js} +121 -17
  28. package/dist/react.d.ts +1 -1
  29. package/dist/react.js +1 -1
  30. package/dist/{register-BSu2dWGC.js → register-CNh4FlbD.js} +104 -16
  31. package/dist/{register-Da3hXh2H.js → register-D3yx8D4r.js} +228 -16
  32. package/dist/{registry-WWcQcfMr.js → registry-CF70EArN.js} +55 -3
  33. package/dist/{replay-BCMK_VRP.d.ts → replay-C5x2vPF5.d.ts} +2 -2
  34. package/dist/{replay-BlNuIDdg.js → replay-DlgHItNv.js} +57 -197
  35. package/dist/{split-screen-CL5Yvxse.js → split-screen-CBM9wcMX.js} +3 -3
  36. package/dist/{split-screen-BQ3tAsf-.d.ts → split-screen-D7OopelJ.d.ts} +2 -2
  37. package/dist/{src-DFpXBMJN.js → src-CH00_JsR.js} +1 -1
  38. package/dist/{teardown-CTTwhWSe.js → teardown-C7qP-dcC.js} +1 -1
  39. package/dist/{test-BeZ95pqw.js → test-BMkg8zMV.js} +29 -16
  40. package/dist/test.d.ts +45 -4
  41. package/dist/test.js +2 -2
  42. package/dist/vite.js +2 -2
  43. package/editor/assets/{agent8-DSJries_.js → agent8-m5mtAO_A.js} +1 -1
  44. package/editor/assets/{debug-D15Wi5TO.js → debug-CPhzCT8f.js} +1 -1
  45. package/editor/assets/{index-BjC88k97.js → index-D422P4kW.js} +92 -92
  46. package/editor/index.html +1 -1
  47. package/package.json +1 -1
  48. package/schemas/scene.schema.json +91 -0
  49. package/skills/incanto-3d-models.md +1 -1
  50. package/skills/incanto-assets.md +10 -1
  51. package/skills/incanto-audio.md +21 -13
  52. package/skills/incanto-behaviors-and-scripts.md +44 -3
  53. package/skills/incanto-building-2d-games.md +32 -4
  54. package/skills/incanto-building-3d-games.md +1 -1
  55. package/skills/incanto-editor.md +1 -1
  56. package/skills/incanto-gameplay-behaviors.md +39 -2
  57. package/skills/incanto-hud.md +5 -3
  58. package/skills/incanto-localization.md +7 -0
  59. package/skills/incanto-node-reference.md +12 -0
  60. package/skills/incanto-physics-and-input.md +23 -2
  61. package/skills/incanto-scene-json-authoring.md +6 -1
  62. package/skills/incanto-verifying-your-game.md +31 -1
  63. package/templates-app/beacon-isle-3d/package.json +1 -1
  64. package/templates-app/platformer-2d/package.json +1 -1
  65. package/templates-app/star-survivor/package.json +1 -1
  66. package/templates-app/tps-3d/package.json +1 -1
  67. package/templates-app/village-quest-3d/package.json +1 -1
  68. package/dist/particle-sim-C5OfBbmU.d.ts +0 -77
@@ -1,4 +1,4 @@
1
- import { P as Scene, b as Engine } from "./behavior-DWKTUzKI.js";
1
+ import { P as Scene, b as Engine } from "./behavior-DsgayMsH.js";
2
2
  import { i as SceneJson } from "./schema-CFeioQRE.js";
3
3
 
4
4
  //#region src/core/scene/loader.d.ts
@@ -6,6 +6,18 @@ interface LoadSceneOptions {
6
6
  /** Resolve a sub-scene path referenced by `{instance: '<path>'}`. */
7
7
  resolveScene?: (path: string) => SceneJson;
8
8
  /**
9
+ * Assets declared ELSEWHERE in the project, for `$ref` validation.
10
+ *
11
+ * A scene a `NetworkSpawner` spawns resolves its refs against the HOST's
12
+ * `assets{}` — `remote-player.scene.json` names `$characters/base` and
13
+ * declares nothing itself, which is correct and cannot be checked from that
14
+ * one file. A tool with the whole project passes the union in; loading the
15
+ * file alone still fails, because alone it really cannot resolve.
16
+ */
17
+ declaredAssets?: Record<string, {
18
+ type?: unknown;
19
+ }>;
20
+ /**
9
21
  * Tool/preview mode (the editor's ▶ play strips behaviors — and their
10
22
  * `static signals` with them): declare each connection's signal on its
11
23
  * from-node instead of hard-failing with UNKNOWN_SIGNAL. Games should
package/dist/net.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { At as Node, d as PropSchema } from "./behavior-DWKTUzKI.js";
2
- import { a as NetworkManagerOptions, c as LocalGameServer, d as ServerClass, f as createLocalGameServer, g as Unsubscribe, h as NetworkTransport, i as NetworkManager, l as LocalGameServerOptions, m as LoopbackTransport, n as SplitScreenPlayer, o as applySyncPatch, p as LoopbackHub, r as createSplitScreen, s as CollectionOptions, t as SplitScreenOptions, u as LocalGameServerTransport } from "./split-screen-BQ3tAsf-.js";
1
+ import { d as PropSchema, jt as Node } from "./behavior-DsgayMsH.js";
2
+ import { a as NetworkManagerOptions, c as LocalGameServer, d as ServerClass, f as createLocalGameServer, g as Unsubscribe, h as NetworkTransport, i as NetworkManager, l as LocalGameServerOptions, m as LoopbackTransport, n as SplitScreenPlayer, o as applySyncPatch, p as LoopbackHub, r as createSplitScreen, s as CollectionOptions, t as SplitScreenOptions, u as LocalGameServerTransport } from "./split-screen-D7OopelJ.js";
3
3
 
4
4
  //#region src/net/agent8.d.ts
5
5
  /**
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-CL5Yvxse.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";
3
3
  export { LocalGameServer, LocalGameServerTransport, LoopbackHub, LoopbackTransport, NetworkManager, NetworkSpawner, applySyncPatch, createAgent8Server, createLocalGameServer, createSplitScreen, registerNodesNet };
@@ -0,0 +1,28 @@
1
+ //#region src/core/pathfinding.d.ts
2
+ /**
3
+ * Grid A* — the pathfinding every top-down/RPG/tower-defense asks for and
4
+ * agents keep re-inventing badly. Pure and dimension-free: cells in, cells
5
+ * out; the caller maps cells to world units (px, meters, tiles).
6
+ */
7
+ interface PathGrid {
8
+ width: number;
9
+ height: number;
10
+ /** True = impassable. Out-of-bounds is always solid. */
11
+ solid(x: number, y: number): boolean;
12
+ }
13
+ interface FindPathOptions {
14
+ /** Allow diagonal steps (blocked from cutting corners). Default true. */
15
+ diagonal?: boolean;
16
+ /** Abort guard for huge/impossible searches. Default 20000 nodes. */
17
+ maxExpansions?: number;
18
+ }
19
+ /**
20
+ * A* from `from` to `to` (inclusive cell coords). Returns the cell path
21
+ * INCLUDING both endpoints, or null when unreachable. Straight steps cost
22
+ * 1, diagonals √2; the heuristic is octile (admissible for both modes).
23
+ */
24
+ declare function findPath(grid: PathGrid, from: [number, number], to: [number, number], opts?: FindPathOptions): [number, number][] | null;
25
+ /** Build a PathGrid from row-strings ('#' = solid) — tests and tile games. */
26
+ declare function gridFromRows(rows: readonly string[]): PathGrid;
27
+ //#endregion
28
+ export { gridFromRows as i, PathGrid as n, findPath as r, FindPathOptions as t };
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { w as diagnose } from "./loader-D8n7TU8W.js";
2
+ import { k as diagnose } from "./loader-DEe272nY.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-BSu2dWGC.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";
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
@@ -100,7 +100,10 @@ var Physics2D = class {
100
100
  node.position = [t.x * SCALE - ox, t.y * SCALE - oy];
101
101
  }
102
102
  if (node instanceof RigidBody2D) {
103
- if (!node.fixedRotation) node.rotation = e.body.rotation() / DEG;
103
+ if (!node.fixedRotation) {
104
+ node.rotation = e.body.rotation() / DEG;
105
+ e.lastRot = node.rotation;
106
+ }
104
107
  const lv = e.body.linvel();
105
108
  node.linearVelocity = [lv.x * SCALE, lv.y * SCALE];
106
109
  e.lastVX = node.linearVelocity[0] ?? 0;
@@ -428,6 +431,7 @@ var Physics2D = class {
428
431
  collider,
429
432
  lastX: wx,
430
433
  lastY: wy,
434
+ lastRot: node.rotation,
431
435
  lastVX: 0,
432
436
  lastVY: 0
433
437
  };
@@ -456,6 +460,10 @@ var Physics2D = class {
456
460
  e.lastX = px;
457
461
  e.lastY = py;
458
462
  }
463
+ if (node.rotation !== e.lastRot) {
464
+ e.body.setRotation(node.rotation * DEG, true);
465
+ e.lastRot = node.rotation;
466
+ }
459
467
  if (node instanceof RigidBody2D) {
460
468
  const vx = node.linearVelocity[0] ?? 0;
461
469
  const vy = node.linearVelocity[1] ?? 0;
@@ -1,10 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { w as diagnose } from "./loader-D8n7TU8W.js";
2
+ import { k as diagnose } from "./loader-DEe272nY.js";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
- import { H as validateCollider3D, I as Area3D, L as CharacterBody3D, R as PhysicsBody3D, V as Node3D, z as RigidBody3D } from "./gameplay-BBEjPFsR.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";
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-8cjF3t6w.js";
7
+ import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-BAWeOeqf.js";
8
8
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
9
9
  //#region src/3d/physics/collider-lines.ts
10
10
  /**
@@ -318,6 +318,9 @@ var Physics3D = class {
318
318
  rot[0] = eulerScratch.x / DEG;
319
319
  rot[1] = eulerScratch.y / DEG;
320
320
  rot[2] = eulerScratch.z / DEG;
321
+ e.lastRot[0] = rot[0];
322
+ e.lastRot[1] = rot[1];
323
+ e.lastRot[2] = rot[2];
321
324
  }
322
325
  const lv = e.body.linvel();
323
326
  const nv = node.linearVelocity;
@@ -644,6 +647,11 @@ var Physics3D = class {
644
647
  wy,
645
648
  wz
646
649
  ],
650
+ lastRot: [
651
+ node.rotation[0] ?? 0,
652
+ node.rotation[1] ?? 0,
653
+ node.rotation[2] ?? 0
654
+ ],
647
655
  lastV: [
648
656
  0,
649
657
  0,
@@ -679,6 +687,21 @@ var Physics3D = class {
679
687
  e.last[2] = p[2];
680
688
  node._interpPrev = node._interpCurr = null;
681
689
  }
690
+ const rx = node.rotation[0] ?? 0;
691
+ const ry = node.rotation[1] ?? 0;
692
+ const rz = node.rotation[2] ?? 0;
693
+ if (rx !== e.lastRot[0] || ry !== e.lastRot[1] || rz !== e.lastRot[2]) {
694
+ quatScratch.setFromEuler(eulerScratch.set(rx * DEG, ry * DEG, rz * DEG, "XYZ"));
695
+ e.body.setRotation({
696
+ x: quatScratch.x,
697
+ y: quatScratch.y,
698
+ z: quatScratch.z,
699
+ w: quatScratch.w
700
+ }, true);
701
+ e.lastRot[0] = rx;
702
+ e.lastRot[1] = ry;
703
+ e.lastRot[2] = rz;
704
+ }
682
705
  if (node instanceof RigidBody3D) {
683
706
  const v = [
684
707
  node.linearVelocity[0] ?? 0,
@@ -854,15 +877,20 @@ function collectBodies(node, out) {
854
877
  * Convex, always: a hull is a few planes to test against, where a triangle
855
878
  * mesh is a tree of them, and nothing scattered across a map needs concavity.
856
879
  */
857
- function meshShapedDesc(R, kind, size, scale) {
858
- const x = (size[0] ?? 1) * scale;
859
- const y = (size[1] ?? 1) * scale;
860
- const z = (size[2] ?? 1) * scale;
880
+ function meshShapedDesc(R, kind, size, scale, who) {
881
+ const sx = scale[0] ?? 1;
882
+ const sy = scale[1] ?? 1;
883
+ const sz = scale[2] ?? 1;
884
+ const x = (size[0] ?? 1) * sx;
885
+ const y = (size[1] ?? 1) * sy;
886
+ const z = (size[2] ?? 1) * sz;
887
+ const radial = Math.max(Math.abs(sx), Math.abs(sz));
888
+ 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.`);
861
889
  switch (kind) {
862
890
  case "box": return R.ColliderDesc.cuboid(x / 2, y / 2, z / 2);
863
- case "sphere": return R.ColliderDesc.ball(x);
864
- case "capsule": return R.ColliderDesc.capsule(y / 2, x);
865
- case "cylinder": return R.ColliderDesc.cylinder(y / 2, x);
891
+ case "sphere": return R.ColliderDesc.ball((size[0] ?? 1) * Math.max(radial, Math.abs(sy)));
892
+ case "capsule": return R.ColliderDesc.capsule(y / 2, (size[0] ?? 1) * radial);
893
+ case "cylinder": return R.ColliderDesc.cylinder(y / 2, (size[0] ?? 1) * radial);
866
894
  case "plane": return R.ColliderDesc.cuboid(x / 2, .01, z / 2);
867
895
  default: return R.ColliderDesc.convexHull(meshPoints(kind, [
868
896
  x,
@@ -937,18 +965,79 @@ const TRIMESH_BUDGET = 2e4;
937
965
  function isStaticShape(node) {
938
966
  return !(node instanceof RigidBody3D || node instanceof CharacterBody3D);
939
967
  }
940
- /** Every mesh a body carries, itself first — `auto` shapes itself on one. */
941
- function meshesUnder(node, out, models) {
968
+ /**
969
+ * Every mesh a body carries, itself first — `auto` shapes itself on one.
970
+ *
971
+ * The transform chain is composed on the way down because `auto` used to drop
972
+ * it: the collector returned `{name, mesh, size}` and the `auto` branch built
973
+ * the shape at the BODY's origin, at the authored size, scale hard-coded to 1.
974
+ * The renderer applies all three every frame (`Node3D._syncObject3D`), so
975
+ * measured on a body whose plank child sits at `[0, 3, 0]`:
976
+ *
977
+ * ```
978
+ * drawn plank top y = 3.2
979
+ * ray straight down from y=10 : HIT Deck — surface at y=0.200
980
+ * ```
981
+ *
982
+ * and on one scaled 4×:
983
+ *
984
+ * ```
985
+ * drawn crate top y = 2.0
986
+ * ray straight down : HIT Crate — surface at y=0.500
987
+ * ray down inside the DRAWN 4 m crate : MISS
988
+ * ```
989
+ *
990
+ * You walk through the deck you can see and land on an invisible slab at ground
991
+ * level — from the shape whose entire promise is that "a blocker can never
992
+ * disagree with what the player sees". The sibling paths in this same file were
993
+ * already right: `syncScatters` honours every scatter row's translation, yaw
994
+ * and scale, and `modelTriangles` bakes a GLB's world matrices. Only the
995
+ * primitive-mesh branch dropped it.
996
+ */
997
+ function meshesUnder(node, out, models, at = [
998
+ 0,
999
+ 0,
1000
+ 0
1001
+ ], turn = [
1002
+ 0,
1003
+ 0,
1004
+ 0
1005
+ ], scale = [
1006
+ 1,
1007
+ 1,
1008
+ 1
1009
+ ]) {
942
1010
  if (isMountedModel(node)) models.push(node);
943
1011
  else {
944
1012
  const self = node;
945
1013
  if (typeof self.mesh === "string" && Array.isArray(self.size)) out.push({
946
1014
  name: node.name,
947
1015
  mesh: self.mesh,
948
- size: self.size
1016
+ size: self.size,
1017
+ at: [...at],
1018
+ turn: [...turn],
1019
+ scale: [...scale]
949
1020
  });
950
1021
  }
951
- for (const child of node.children) meshesUnder(child, out, models);
1022
+ for (const child of node.children) {
1023
+ const t = child;
1024
+ const p = Array.isArray(t.position) ? t.position : [];
1025
+ const r = Array.isArray(t.rotation) ? t.rotation : [];
1026
+ const c = Array.isArray(t.scale) ? t.scale : [];
1027
+ meshesUnder(child, out, models, [
1028
+ at[0] + (p[0] ?? 0) * scale[0],
1029
+ at[1] + (p[1] ?? 0) * scale[1],
1030
+ at[2] + (p[2] ?? 0) * scale[2]
1031
+ ], [
1032
+ turn[0] + (r[0] ?? 0),
1033
+ turn[1] + (r[1] ?? 0),
1034
+ turn[2] + (r[2] ?? 0)
1035
+ ], [
1036
+ scale[0] * (c[0] ?? 1),
1037
+ scale[1] * (c[1] ?? 1),
1038
+ scale[2] * (c[2] ?? 1)
1039
+ ]);
1040
+ }
952
1041
  }
953
1042
  /** Every scatter field asking to be solid (see InstancedMesh3D.collider). */
954
1043
  function collectScatters(node, out) {
@@ -1019,7 +1108,10 @@ function parseCollider3D(R, node) {
1019
1108
  const desc = buildColliderDesc3D(R, collider, node.name, node);
1020
1109
  if (!desc) return null;
1021
1110
  const offset = collider.offset;
1022
- if (Array.isArray(offset)) desc.setTranslation(offset[0] ?? 0, offset[1] ?? 0, offset[2] ?? 0);
1111
+ if (Array.isArray(offset)) {
1112
+ const at = desc.translation;
1113
+ desc.setTranslation(at.x + (offset[0] ?? 0), at.y + (offset[1] ?? 0), at.z + (offset[2] ?? 0));
1114
+ }
1023
1115
  return desc;
1024
1116
  }
1025
1117
  /**
@@ -1070,8 +1162,20 @@ function buildColliderDesc3D(R, collider, nodeName, node) {
1070
1162
  const parts = meshes.length + models.length;
1071
1163
  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.`);
1072
1164
  if (mesh) {
1073
- const desc = meshShapedDesc(R, mesh.mesh, mesh.size, 1);
1074
- if (desc) return desc;
1165
+ const desc = meshShapedDesc(R, mesh.mesh, mesh.size, mesh.scale, nodeName);
1166
+ if (desc) {
1167
+ desc.setTranslation(mesh.at[0], mesh.at[1], mesh.at[2]);
1168
+ if (mesh.turn[0] !== 0 || mesh.turn[1] !== 0 || mesh.turn[2] !== 0) {
1169
+ quatScratch.setFromEuler(eulerScratch.set(mesh.turn[0] * DEG, mesh.turn[1] * DEG, mesh.turn[2] * DEG, "XYZ"));
1170
+ desc.setRotation({
1171
+ x: quatScratch.x,
1172
+ y: quatScratch.y,
1173
+ z: quatScratch.z,
1174
+ w: quatScratch.w
1175
+ });
1176
+ }
1177
+ return desc;
1178
+ }
1075
1179
  } else if (model && node) {
1076
1180
  const tri = modelTriangles(node, model);
1077
1181
  if (!tri) return null;
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-DWKTUzKI.js";
1
+ import { P as Scene, b as Engine, n as BehaviorCtor } from "./behavior-DsgayMsH.js";
2
2
  import { c as JsonValue } from "./schema-CFeioQRE.js";
3
3
  import { CSSProperties, ReactNode } from "react";
4
4
 
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-BCm38FJV.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-ClnIb_M5.js").then((n) => n.n)).createGame2D(o)))(opts);
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);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -1,9 +1,10 @@
1
- import { C as Node, b as effectiveOrder, w as diagnose } from "./loader-D8n7TU8W.js";
2
- import { I as translateOn, t as registerCoreNodes } from "./register-Da3hXh2H.js";
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";
3
3
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
4
4
  import { t as Rng } from "./rng-DP-SR7eg.js";
5
- import { i as getNodeSchema, l as registerNode } from "./registry-WWcQcfMr.js";
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
8
  import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
8
9
  import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry, SRGBColorSpace } from "three";
9
10
  //#region src/2d/physics/collider-validate.ts
@@ -94,6 +95,30 @@ var Node2D = class extends Node {
94
95
  return new Object3D();
95
96
  }
96
97
  /** @internal Push JSON props onto the backing object. Called every frame. */
98
+ /**
99
+ * Has this node produced the thing `static: true` is about to freeze?
100
+ *
101
+ * `static` latches after the first sync, and a textured node's first sync
102
+ * happens BEFORE its texture has decoded — `TextureLoader.load()` returns a
103
+ * `Texture` with `image === undefined` and fills it asynchronously, while
104
+ * `Renderer2D.render()` calls `assets.load()` and `syncTree2D()` in the same
105
+ * frame. So the state frozen was `visible = false`, forever. Measured on two
106
+ * identical sprites differing only in `static`:
107
+ *
108
+ * ```
109
+ * frame 1 Backdrop(static) visible: false Control visible: false
110
+ * 600 frames later Backdrop(static) visible: false width 1
111
+ * Control visible: true width 64
112
+ * ```
113
+ *
114
+ * Nothing throws: the texture loaded fine, so `assetErrors()` is empty,
115
+ * `stats().errors` is 0, and `framing` reads props rather than pixels and
116
+ * still calls the node on-screen. Overridden by the nodes that wait on an
117
+ * image; a node with nothing to wait for is ready by definition.
118
+ */
119
+ _staticReady(_assets) {
120
+ return true;
121
+ }
97
122
  _syncObject2D(_assets) {
98
123
  const o = this._ensureObject2D();
99
124
  o.position.set(this.position[0] ?? 0, -(this.position[1] ?? 0), 0);
@@ -302,7 +327,8 @@ var Joint2D = class extends Node2D {
302
327
  },
303
328
  target: {
304
329
  default: "",
305
- nodePath: true
330
+ nodePath: true,
331
+ required: true
306
332
  },
307
333
  anchor: { default: [0, 0] },
308
334
  targetAnchor: { default: [0, 0] },
@@ -344,7 +370,13 @@ const UNIT_PLANE$2 = new PlaneGeometry(1, 1);
344
370
  var Sprite2D = class extends Node2D {
345
371
  static typeName = "Sprite2D";
346
372
  static props = {
347
- texture: { default: "" },
373
+ texture: {
374
+ default: "",
375
+ asset: {
376
+ kind: "texture",
377
+ refOnly: true
378
+ }
379
+ },
348
380
  anchor: { default: [.5, .5] },
349
381
  flipX: { default: false },
350
382
  flipY: { default: false },
@@ -386,6 +418,9 @@ var Sprite2D = class extends Node2D {
386
418
  height: image.height
387
419
  };
388
420
  }
421
+ _staticReady(assets) {
422
+ return this.texture === "" || this.resolveTexture(assets) !== null;
423
+ }
389
424
  _syncObject2D(assets) {
390
425
  super._syncObject2D(assets);
391
426
  const quad = this._quad();
@@ -422,7 +457,13 @@ var AnimatedSprite2D = class extends Sprite2D {
422
457
  static typeName = "AnimatedSprite2D";
423
458
  static signals = ["animationFinished"];
424
459
  static props = {
425
- sheet: { default: "" },
460
+ sheet: {
461
+ default: "",
462
+ asset: {
463
+ kind: "spritesheet",
464
+ refOnly: true
465
+ }
466
+ },
426
467
  animations: { default: {} },
427
468
  autoplay: { default: "" }
428
469
  };
@@ -564,18 +605,36 @@ var Camera2D = class extends Node2D {
564
605
  if (!this.follow) return;
565
606
  const target = this.getNodeOrNull(this.follow);
566
607
  if (!(target instanceof Node2D)) return;
567
- const tx = target.position[0] ?? 0;
568
- const ty = target.position[1] ?? 0;
608
+ const [tx = 0, ty = 0] = worldPosition(target);
609
+ const [px = 0, py = 0] = worldPosition(this);
569
610
  const s = Math.min(Math.max(this.smoothing, 0), .99);
570
611
  const f = s <= 0 ? 1 : 1 - s ** (dt * 60);
571
- const px = this.position[0] ?? 0;
572
- const py = this.position[1] ?? 0;
573
- this.position = [px + (tx - px) * f, py + (ty - py) * f];
612
+ const [lx = 0, ly = 0] = localFromWorld(this, [px + (tx - px) * f, py + (ty - py) * f]);
613
+ this.position = [lx, ly];
574
614
  }
575
615
  /** View center after clamping the (vw×vh)/zoom view rect inside `limits`. */
616
+ /**
617
+ * The view centre the renderer draws around, in WORLD space.
618
+ *
619
+ * It used to read `this.position` raw — the LOCAL prop — while every other
620
+ * 2D node gets its ancestors composed for free by the three scene graph. So a
621
+ * camera parented to the player, the Godot/Phaser idiom the authoring skill
622
+ * explicitly permits, framed the world origin:
623
+ *
624
+ * ```
625
+ * player world position : [1400, 900]
626
+ * renderer view centre : {"x":0,"y":0}
627
+ * framing : camera /Level/Player/Cam centred [1400, 900]
628
+ * 1 in view, 0 outside it
629
+ * ```
630
+ *
631
+ * `framing` composes full world matrices, so the instrument the skills tell
632
+ * you to trust certified a view the renderer never drew.
633
+ */
576
634
  clampedCenter(vw, vh) {
577
- let cx = this.position[0] ?? 0;
578
- let cy = this.position[1] ?? 0;
635
+ const world = worldPosition(this);
636
+ let cx = world[0] ?? 0;
637
+ let cy = world[1] ?? 0;
579
638
  if (this.limits.length !== 0 && this.limits.length !== 4) throw new IncantoError("BAD_FORMAT", `Camera2D '${this.name}' limits must be [] or [minX,minY,maxX,maxY], got length ${this.limits.length}.`);
580
639
  if (this.limits.length === 4) {
581
640
  const [minX, minY, maxX, maxY] = this.limits;
@@ -828,7 +887,14 @@ var Label = class extends Node2D {
828
887
  fontSize: { default: 16 },
829
888
  color: { default: "#ffffff" },
830
889
  font: { default: "monospace" },
831
- align: { default: "left" }
890
+ align: { default: "left" },
891
+ opacity: {
892
+ default: 1,
893
+ range: {
894
+ min: 0,
895
+ max: 1
896
+ }
897
+ }
832
898
  };
833
899
  text = "";
834
900
  fontSize = 16;
@@ -836,6 +902,16 @@ var Label = class extends Node2D {
836
902
  font = "monospace";
837
903
  /** 'left' | 'center' | 'right' — anchor of the text block on the node origin. */
838
904
  align = "left";
905
+ /**
906
+ * 0..1, like `Sprite2D` and `ColorRect2D`.
907
+ *
908
+ * Label was the one 2D drawable without it, and the gap showed up through
909
+ * `FloatAway` — whose own doc names "a 2D `Label`" as a node it works on.
910
+ * `FloatAway` writes `opacity` when the node has one, so on a Label the
911
+ * number rose to full height at FULL opacity and then vanished in a single
912
+ * frame: the pop that behavior exists to avoid.
913
+ */
914
+ opacity = 1;
839
915
  quadMesh = null;
840
916
  canvas = null;
841
917
  lastKey = "";
@@ -863,7 +939,8 @@ var Label = class extends Node2D {
863
939
  this.lastKey = key;
864
940
  this.rasterize();
865
941
  }
866
- quad.visible = true;
942
+ quad.visible = this.opacity > 0;
943
+ quad.material.opacity = this.opacity;
867
944
  quad.renderOrder = this.effectiveRenderOrder;
868
945
  }
869
946
  rasterize() {
@@ -1306,7 +1383,13 @@ var TileMap2D = class extends Node2D {
1306
1383
  static typeName = "TileMap2D";
1307
1384
  static props = {
1308
1385
  /** Atlas texture asset ref; tiles are tileSize×tileSize squares in it. */
1309
- texture: { default: "" },
1386
+ texture: {
1387
+ default: "",
1388
+ asset: {
1389
+ kind: "texture",
1390
+ refOnly: true
1391
+ }
1392
+ },
1310
1393
  tileSize: { default: 32 },
1311
1394
  /** Atlas columns; 0 = derive from the texture width. */
1312
1395
  columns: { default: 0 },
@@ -1398,6 +1481,11 @@ var TileMap2D = class extends Node2D {
1398
1481
  }
1399
1482
  return this.tileMesh;
1400
1483
  }
1484
+ _staticReady(assets) {
1485
+ if (this.texture === "" || !assets) return this.texture === "";
1486
+ const image = assets.getTexture(this.texture).image;
1487
+ return Boolean(image?.width);
1488
+ }
1401
1489
  _syncObject2D(assets) {
1402
1490
  super._syncObject2D(assets);
1403
1491
  const mesh = this._mesh();