incanto 0.52.0 → 0.54.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 (67) hide show
  1. package/bin/incanto-check.mjs +56 -1
  2. package/dist/2d.d.ts +95 -4
  3. package/dist/2d.js +3 -3
  4. package/dist/3d.d.ts +111 -6
  5. package/dist/3d.js +10 -6
  6. package/dist/{behavior-uPEuZrUB.d.ts → behavior-CyQoSu4n.d.ts} +212 -10
  7. package/dist/{create-game-B_e9hJf7.js → create-game-BRt6XKmP.js} +31 -12
  8. package/dist/{create-game-CGnoypjL.js → create-game-Czzp6ZuE.js} +26 -14
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/debug.js +1 -1
  11. package/dist/{duplicate-B-OtSRFL.js → duplicate-MNLMAcbz.js} +1 -1
  12. package/dist/editor.js +14 -0
  13. package/dist/{environment-presets-DSZwsKPs.js → environment-presets-BQ_QsIBY.js} +222 -11
  14. package/dist/{gameplay-B6jqvYeM.js → gameplay-CZ2yq37J.js} +32 -23
  15. package/dist/gameplay.d.ts +8 -3
  16. package/dist/gameplay.js +1 -1
  17. package/dist/index.d.ts +178 -6
  18. package/dist/index.js +9 -9
  19. package/dist/{loader-B-Gft32x.js → loader-BTkHYrQn.js} +54 -18
  20. package/dist/{loader-B9iTqs27.d.ts → loader-DhI1jFW_.d.ts} +1 -1
  21. package/dist/{log-report-lxrQY9cH.js → log-report-CPFm4OXf.js} +0 -0
  22. package/dist/net.d.ts +2 -2
  23. package/dist/net.js +1 -1
  24. package/dist/{particle-sim-BzJ1yxoE.d.ts → particle-sim-C5OfBbmU.d.ts} +11 -0
  25. package/dist/{pathfinding-CAR9DjQQ.d.ts → pathfinding-CXGCpRQe.d.ts} +24 -1
  26. package/dist/{physics-2d-Dns-oZlE.js → physics-2d-CfWAggJ1.js} +2 -2
  27. package/dist/{physics-3d-BhI0ehpe.js → physics-3d-Bes-GIuR.js} +3 -3
  28. package/dist/react.d.ts +1 -1
  29. package/dist/react.js +1 -1
  30. package/dist/{register-Trx7WHnD.js → register-en63AEZO.js} +163 -4
  31. package/dist/{register-DVwlnZAZ.js → register-p48lHE2o.js} +433 -36
  32. package/dist/{replay-BU1CCM15.d.ts → replay-ePMz26jw.d.ts} +1 -1
  33. package/dist/{replay-BicPOMX0.js → replay-t1pP0gQg.js} +2 -2
  34. package/dist/{split-screen-paxkQs_q.d.ts → split-screen-BsdOHbzP.d.ts} +1 -1
  35. package/dist/{split-screen-CZ9ccBBQ.js → split-screen-CSb_uZ6W.js} +2 -2
  36. package/dist/{sprite-animation-D_p28jwU.js → sprite-animation-7qvUxF6Z.js} +19 -0
  37. package/dist/{src-5gbZO47I.js → src-vNPQeQ1W.js} +1 -1
  38. package/dist/{teardown-ks3d5W9n.js → teardown-C7uVSJvx.js} +30 -1
  39. package/dist/{test-DAojuFdb.js → test-Tnf1nQl3.js} +25 -13
  40. package/dist/test.d.ts +4 -4
  41. package/dist/test.js +2 -2
  42. package/dist/{touch-BoNg_MnF.js → touch-BnMyy9tr.js} +4 -4
  43. package/dist/vite.js +2 -2
  44. package/editor/assets/{agent8-CCvckvbw.js → agent8-BWaW-D85.js} +1 -1
  45. package/editor/assets/{debug-CLNCOnbc.js → debug-DLF8rUtr.js} +2 -2
  46. package/editor/assets/{index-Cb5Brupb.js → index-BYfiwbQx.js} +91 -91
  47. package/editor/index.html +1 -1
  48. package/package.json +1 -1
  49. package/schemas/scene.schema.json +221 -0
  50. package/skills/incanto-assets.md +45 -2
  51. package/skills/incanto-audio.md +97 -10
  52. package/skills/incanto-building-2d-games.md +55 -3
  53. package/skills/incanto-building-3d-games.md +3 -1
  54. package/skills/incanto-environment.md +3 -1
  55. package/skills/incanto-gameplay-behaviors.md +51 -4
  56. package/skills/incanto-hud.md +7 -0
  57. package/skills/incanto-node-reference.md +36 -0
  58. package/skills/incanto-performance.md +47 -0
  59. package/skills/incanto-physics-and-input.md +38 -0
  60. package/skills/incanto-verifying-your-game.md +50 -1
  61. package/skills/incanto-web-integration.md +1 -0
  62. package/templates-app/beacon-isle-3d/index.html +3 -3
  63. package/templates-app/beacon-isle-3d/package.json +1 -1
  64. package/templates-app/tps-3d/index.html +3 -3
  65. package/templates-app/tps-3d/package.json +1 -1
  66. package/templates-app/village-quest-3d/index.html +3 -3
  67. package/templates-app/village-quest-3d/package.json +1 -1
@@ -1,9 +1,9 @@
1
- import { b as effectiveOrder, f as Node } from "./loader-B-Gft32x.js";
2
- import { t as registerCoreNodes } from "./register-DVwlnZAZ.js";
1
+ import { C as diagnose, b as effectiveOrder, f as Node } from "./loader-BTkHYrQn.js";
2
+ import { t as registerCoreNodes } from "./register-p48lHE2o.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-C7u42TID.js";
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-D_p28jwU.js";
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-7qvUxF6Z.js";
7
7
  import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry } from "three";
8
8
  //#region src/2d/physics/collider-validate.ts
9
9
  /**
@@ -305,6 +305,59 @@ var Joint2D = class extends Node2D {
305
305
  }
306
306
  };
307
307
  //#endregion
308
+ //#region src/2d/sheet-grid.ts
309
+ /**
310
+ * Does this spritesheet's grid actually fit the image?
311
+ *
312
+ * `frameWidth`/`frameHeight` are taken on trust, and the frame math floors the
313
+ * division — so a frame size that does not divide the sheet slices every row a
314
+ * little further off, and an animation naming a frame past the end of the grid
315
+ * reads whatever texels are there. Both draw WRONG ART with no error anywhere,
316
+ * which is the one symptom an author cannot tell apart from having picked the
317
+ * wrong sheet in the first place.
318
+ *
319
+ * Pure arithmetic on numbers that are known the moment the image loads, so it
320
+ * costs nothing and it is exact.
321
+ */
322
+ /**
323
+ * Frame sizes that divide `total` cleanly, biggest first.
324
+ *
325
+ * Bounded to grids of 2–32 cells so the suggestion is a frame size someone
326
+ * might actually have meant, not every factor of the number.
327
+ */
328
+ function divisors(total) {
329
+ const out = [];
330
+ for (let cells = 2; cells <= 32; cells++) if (total % cells === 0) out.push(total / cells);
331
+ return out.sort((a, b) => b - a).slice(0, 6);
332
+ }
333
+ /**
334
+ * The problem with this grid, or null when there is none.
335
+ *
336
+ * `highestFrame` is the largest frame index the scene asks for, when it is
337
+ * known — an animation range that runs off the end is the other half of the
338
+ * same mistake.
339
+ */
340
+ function checkSheetGrid(key, frameWidth, frameHeight, imageWidth, imageHeight, highestFrame) {
341
+ if (!imageWidth || !imageHeight || !frameWidth || !frameHeight) return null;
342
+ if (imageWidth % frameWidth !== 0 || imageHeight % frameHeight !== 0) {
343
+ const parts = [];
344
+ if (imageWidth % frameWidth !== 0) {
345
+ const fits = divisors(imageWidth);
346
+ parts.push(`${imageWidth} px wide is not a whole number of ${frameWidth} px frames` + (fits.length > 0 ? ` (${fits.join(", ")} would divide it)` : ""));
347
+ }
348
+ if (imageHeight % frameHeight !== 0) {
349
+ const fits = divisors(imageHeight);
350
+ parts.push(`${imageHeight} px tall is not a whole number of ${frameHeight} px frames` + (fits.length > 0 ? ` (${fits.join(", ")} would divide it)` : ""));
351
+ }
352
+ return `[incanto] spritesheet '${key}' does not fit its frame size: ${parts.join("; ")}. Every row after the first is cut off centre.`;
353
+ }
354
+ if (highestFrame !== void 0) {
355
+ const frames = imageWidth / frameWidth * (imageHeight / frameHeight);
356
+ if (highestFrame >= frames) return `[incanto] spritesheet '${key}' has ${frames} frames (0–${frames - 1}), and an animation asks for frame ${highestFrame}. Those frames draw whatever is at the wrong end of the sheet.`;
357
+ }
358
+ return null;
359
+ }
360
+ //#endregion
308
361
  //#region src/2d/nodes/sprite-2d.ts
309
362
  /** Shared unit quad — sprites are sized via the quad's scale, never THREE.Sprite. */
310
363
  const UNIT_PLANE$2 = new PlaneGeometry(1, 1);
@@ -452,6 +505,30 @@ var AnimatedSprite2D = class extends Sprite2D {
452
505
  }
453
506
  this.currentFrame = this.frameList[this.frameIndex] ?? 0;
454
507
  }
508
+ /** Sheets already reported, so a bad grid says so ONCE, not every frame. */
509
+ gridChecked = "";
510
+ /**
511
+ * Tell the author their grid does not fit, the first time it is drawn.
512
+ *
513
+ * Both halves of this were silent: a frame size that does not divide the
514
+ * sheet slices every row further off centre, and an animation naming a frame
515
+ * past the end of the grid draws whatever is at the wrong end of the image.
516
+ */
517
+ checkGrid(ref, frameWidth, frameHeight, width, height) {
518
+ if (this.gridChecked === ref) return;
519
+ this.gridChecked = ref;
520
+ let highest = 0;
521
+ for (const [name, entry] of Object.entries(this.animations)) {
522
+ if (typeof entry === "string") continue;
523
+ try {
524
+ for (const frame of resolveFrames(entry.frames, name)) if (frame > highest) highest = frame;
525
+ } catch {
526
+ return;
527
+ }
528
+ }
529
+ const problem = checkSheetGrid(ref, frameWidth, frameHeight, width, height, highest);
530
+ if (problem) diagnose(this.tree?.engine ?? null, "error", problem);
531
+ }
455
532
  resolveTexture(assets) {
456
533
  if (!this.sheet || !assets) return null;
457
534
  const info = assets.getSheet(this.sheet);
@@ -463,6 +540,7 @@ var AnimatedSprite2D = class extends Sprite2D {
463
540
  const tex = this.ownTexture;
464
541
  const image = info.texture.image;
465
542
  if (!image?.width || !image.height) return null;
543
+ this.checkGrid(this.sheet, info.frameWidth, info.frameHeight, image.width, image.height);
466
544
  const cols = Math.max(1, Math.floor(image.width / info.frameWidth));
467
545
  const col = this.currentFrame % cols;
468
546
  const row = Math.floor(this.currentFrame / cols);
@@ -871,6 +949,8 @@ var Particles2D = class Particles2D extends Node2D {
871
949
  options: ["custom", ...PARTICLE_PRESET_NAMES]
872
950
  },
873
951
  emitting: { default: true },
952
+ /** Particles keep the place they were born instead of following the node. */
953
+ worldSpace: { default: false },
874
954
  rate: { default: 40 },
875
955
  burst: { default: 0 },
876
956
  lifetime: { default: [.6, 1.2] },
@@ -895,6 +975,18 @@ var Particles2D = class Particles2D extends Node2D {
895
975
  };
896
976
  preset = "custom";
897
977
  emitting = true;
978
+ /**
979
+ * Where a particle LIVES once it is born.
980
+ *
981
+ * `false` (default) — the emitter's local space: the plume moves with the
982
+ * node, which is what a torch on a moving platform wants.
983
+ *
984
+ * `true` — the world: a particle keeps the place it was born, so dust is left
985
+ * BEHIND a running player instead of glued to it, and moving a one-shot to
986
+ * the next explosion (the documented `p.position = …; p.replay()` recipe)
987
+ * stops dragging the previous one across the level.
988
+ */
989
+ worldSpace = false;
898
990
  rate = 40;
899
991
  burst = 0;
900
992
  lifetime = [.6, 1.2];
@@ -954,11 +1046,38 @@ var Particles2D = class Particles2D extends Node2D {
954
1046
  }
955
1047
  return this.sim;
956
1048
  }
1049
+ /** Particles alive this frame — an emitter that is running, told from one
1050
+ * that is not. `framing()` says where it IS; this says whether it is going. */
1051
+ get aliveCount() {
1052
+ return this.sim?.count ?? 0;
1053
+ }
1054
+ /** A snapshot of the live particles — positions are in whichever space
1055
+ * `worldSpace` selects, relative to the emitter. */
1056
+ particles() {
1057
+ const out = [];
1058
+ this.sim?.forEach((p) => {
1059
+ out.push({ ...p });
1060
+ });
1061
+ return out;
1062
+ }
1063
+ /** The emitter's world position last frame, for world-space counter-motion. */
1064
+ lastWorld = null;
1065
+ /** Last frame's `emitting`, so turning one ON is a reportable event. */
1066
+ wasEmitting = false;
957
1067
  update(dt) {
958
1068
  const sim = this._ensureSim();
959
- if (this.burst > 0 && !this.bursted) {
1069
+ this.holdWorldSpace(sim);
1070
+ sim.setRate(this.emitting ? this.rate : 0);
1071
+ if (this.burst > 0 && !this.bursted && (this.emitting || this.armedByReplay)) {
960
1072
  this.bursted = true;
1073
+ this.armedByReplay = false;
961
1074
  sim.burst(this.burst);
1075
+ this.report("burst", this.burst);
1076
+ }
1077
+ const on = this.emitting && this.rate > 0;
1078
+ if (on !== this.wasEmitting) {
1079
+ this.wasEmitting = on;
1080
+ if (on) this.report("emit", this.rate);
962
1081
  }
963
1082
  sim.update(dt);
964
1083
  if ((!this.emitting || this.rate === 0) && this.bursted && sim.done && !this.finishedEmitted) {
@@ -966,11 +1085,51 @@ var Particles2D = class Particles2D extends Node2D {
966
1085
  this.emit("finished");
967
1086
  }
968
1087
  }
1088
+ /**
1089
+ * Undo the emitter's own motion on everything already alive.
1090
+ *
1091
+ * Ancestor-summed like the physics helpers, so it follows a moving PARENT
1092
+ * (dust under a player) as well as the node's own position.
1093
+ */
1094
+ holdWorldSpace(sim) {
1095
+ if (!this.worldSpace) {
1096
+ this.lastWorld = null;
1097
+ return;
1098
+ }
1099
+ let x = 0;
1100
+ let y = 0;
1101
+ const z = 0;
1102
+ for (let n = this; n; n = n.parent) if (n instanceof Node2D) {
1103
+ x += n.position[0] ?? 0;
1104
+ y += n.position[1] ?? 0;
1105
+ }
1106
+ const prev = this.lastWorld;
1107
+ if (prev) sim.translateAll(prev[0] - x, prev[1] - y, prev[2] - z);
1108
+ this.lastWorld = [
1109
+ x,
1110
+ y,
1111
+ z
1112
+ ];
1113
+ }
1114
+ /** Tell the engine an effect fired — the visual half of the audio record. */
1115
+ report(kind, amount) {
1116
+ const engine = this.tree?.engine;
1117
+ engine?.effects.record({
1118
+ kind,
1119
+ name: this.preset,
1120
+ from: this.getPath(),
1121
+ amount,
1122
+ at: engine.time
1123
+ });
1124
+ }
969
1125
  /** Restart a one-shot (re-burst + allow finished to fire again). */
970
1126
  replay() {
971
1127
  this.bursted = false;
972
1128
  this.finishedEmitted = false;
1129
+ this.armedByReplay = true;
973
1130
  }
1131
+ /** `replay()` asked for this burst, so `emitting: false` does not hold it. */
1132
+ armedByReplay = false;
974
1133
  mesh = null;
975
1134
  /** Parsed `paletteColors`, reused; rebuilt only when the hex list changes. */
976
1135
  paletteCache = [];