incanto 0.68.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 (153) hide show
  1. package/assets/catalog.json +9 -5
  2. package/bin/_behaviors-loader.mjs +22 -0
  3. package/bin/_read-json.mjs +28 -0
  4. package/bin/incanto-assets.mjs +19 -5
  5. package/bin/incanto-check.mjs +92 -15
  6. package/bin/incanto-editor.mjs +128 -5
  7. package/bin/incanto-env.mjs +3 -2
  8. package/bin/incanto-feel.mjs +24 -13
  9. package/bin/incanto-frame.mjs +8 -1
  10. package/bin/incanto-multiplay.mjs +11 -9
  11. package/bin/incanto-new.mjs +128 -5
  12. package/bin/incanto-play.mjs +158 -16
  13. package/bin/incanto-playtest.mjs +66 -23
  14. package/bin/incanto-serve.mjs +160 -0
  15. package/bin/incanto-skills.mjs +14 -2
  16. package/bin/incanto-verify.mjs +165 -44
  17. package/bin/incanto.mjs +4 -2
  18. package/dist/2d.d.ts +285 -36
  19. package/dist/2d.js +4 -4
  20. package/dist/3d.d.ts +158 -10
  21. package/dist/3d.js +8 -8
  22. package/dist/{agent8-CvsfVskX.js → agent8-CmNF01gA.js} +61 -8
  23. package/dist/{audio-player-DOrq7sP-.d.ts → audio-player-DaMxqfNE.d.ts} +33 -14
  24. package/dist/{behavior-DoFPYrgo.d.ts → behavior-DZExDn9o.d.ts} +809 -44
  25. package/dist/{create-game-IZIydDwI.js → create-game-Bwvh6q8A.js} +148 -61
  26. package/dist/{create-game-DbWtVTxD.js → create-game-C7ffQWW7.js} +103 -49
  27. package/dist/debug.d.ts +1 -1
  28. package/dist/debug.js +2 -3
  29. package/dist/diagnostics-Cu85N3tL.d.ts +12 -0
  30. package/dist/{editor-switch-CnIOiyNJ.d.ts → editor-switch-CFU9mCec.d.ts} +22 -13
  31. package/dist/editor.js +1066 -864
  32. package/dist/env.d.ts +1 -1
  33. package/dist/env.js +5 -3
  34. package/dist/{environment-presets-CybQXNqS.js → environment-presets-D6Q5BxeE.js} +299 -46
  35. package/dist/{frame-report-DCnHFmto.d.ts → frame-report-DNxDAb1w.d.ts} +8 -0
  36. package/dist/{frame-report-BSMny7oe.js → frame-report-Dlq13Gyj.js} +1 -0
  37. package/dist/{gameplay-DM1eu_cV.js → gameplay-BfHkuzVb.js} +825 -221
  38. package/dist/gameplay.d.ts +187 -7
  39. package/dist/gameplay.js +1 -1
  40. package/dist/{heightmap-CRK0M4jT.js → heightmap-BYgD5Edk.js} +1 -1
  41. package/dist/index.d.ts +156 -13
  42. package/dist/index.js +10 -12
  43. package/dist/json-CfTjpvW8.js +67 -0
  44. package/dist/{loader-CcB533FR.d.ts → loader-Cff09LMm.d.ts} +2 -2
  45. package/dist/net.d.ts +27 -3
  46. package/dist/net.js +2 -2
  47. package/dist/{noise-CGUMx44x.js → noise-D3nPpmFg.js} +1 -1
  48. package/dist/{physics-2d-B7Y6dPZO.js → physics-2d-CE0Qvy3V.js} +136 -11
  49. package/dist/{physics-3d-bG3n70Ky.js → physics-3d-CpH-2gn5.js} +104 -23
  50. package/dist/{teardown-D2NEmxPB.js → picking-CQJ_PJKh.js} +106 -14
  51. package/dist/react.d.ts +2 -2
  52. package/dist/react.js +2 -2
  53. package/dist/{register-3ta-2Xig.js → register-6DYnKZcy.js} +652 -831
  54. package/dist/{register-ibCjm-wH.js → register-Bkk0wSDB.js} +348 -30
  55. package/dist/{replay-CYvhVHHN.js → replay-DjAkAzMq.js} +224 -14
  56. package/dist/{replay-Dvn8aeBd.d.ts → replay-Dmw-PKQu.d.ts} +20 -3
  57. package/dist/{schema-B6ugCV1Q.d.ts → rng-Bb-IutXB.d.ts} +38 -21
  58. package/dist/{rng-DP-SR7eg.js → rng-CDOMybym.js} +22 -0
  59. package/dist/{loader-BC4PNtJX.js → save-slots-BXVg148r.js} +4558 -2294
  60. package/dist/{sheet-grid-BT6N_Bjs.js → sheet-grid-Cea343VO.js} +6 -2
  61. package/dist/{split-screen-DhrSzZIB.d.ts → split-screen--k-XpBjr.d.ts} +36 -4
  62. package/dist/{split-screen-CYwDkbLF.js → split-screen-PL78oVXP.js} +159 -26
  63. package/dist/{sprite-animation-CY-mrr1L.js → sprite-animation-CqR2o3SA.js} +39 -8
  64. package/dist/{src-D7RIqXYF.js → src-Cxfiv1Hg.js} +2 -17
  65. package/dist/test-iHYVUcDK.js +4036 -0
  66. package/dist/test.d.ts +542 -30
  67. package/dist/test.js +3 -3
  68. package/dist/touch-BnCyPA0G.js +519 -0
  69. package/dist/vite.d.ts +54 -3
  70. package/dist/vite.js +349 -17
  71. package/dist/{webgl-unavailable-N9nQqesw.js → webgl-unavailable-C8aDbGmR.js} +56 -1
  72. package/editor/assets/agent8-D0MS174y.js +1 -0
  73. package/editor/assets/{debug-BBhKuBNV.js → debug-BnXkKuYu.js} +2 -2
  74. package/editor/assets/index-CIu3uc3l.js +11046 -0
  75. package/editor/index.html +1 -1
  76. package/package.json +7 -16
  77. package/schemas/scene.schema.json +30 -3
  78. package/skills/incanto-3d-character.md +14 -1
  79. package/skills/incanto-3d-models.md +12 -0
  80. package/skills/incanto-assets.md +25 -3
  81. package/skills/incanto-audio.md +27 -5
  82. package/skills/incanto-behaviors-and-scripts.md +83 -6
  83. package/skills/incanto-building-2d-games.md +106 -8
  84. package/skills/incanto-building-3d-games.md +118 -6
  85. package/skills/incanto-editor.md +46 -7
  86. package/skills/incanto-environment.md +19 -1
  87. package/skills/incanto-game-feel.md +70 -0
  88. package/skills/incanto-gameplay-behaviors.md +121 -14
  89. package/skills/incanto-hud.md +128 -7
  90. package/skills/incanto-localization.md +13 -5
  91. package/skills/incanto-multiplayer.md +83 -3
  92. package/skills/incanto-node-reference.md +222 -58
  93. package/skills/incanto-performance.md +52 -0
  94. package/skills/incanto-physics-and-input.md +123 -24
  95. package/skills/incanto-playtesting.md +78 -2
  96. package/skills/incanto-save-slots.md +188 -6
  97. package/skills/incanto-scene-json-authoring.md +69 -12
  98. package/skills/incanto-verifying-your-game.md +292 -12
  99. package/skills/incanto-web-integration.md +28 -0
  100. package/skills/incanto-your-first-game.md +5 -2
  101. package/templates-app/beacon-isle-3d/generate-world.ts +77 -9
  102. package/templates-app/beacon-isle-3d/package.json +2 -2
  103. package/templates-app/beacon-isle-3d/src/behaviors.ts +22 -0
  104. package/templates-app/beacon-isle-3d/src/game.scene.json +103 -378
  105. package/templates-app/beacon-isle-3d/src/main.ts +24 -4
  106. package/templates-app/beacon-isle-3d/tsconfig.json +1 -1
  107. package/templates-app/beacon-isle-3d/verify.ts +3 -1
  108. package/templates-app/molehill-2d/.incanto/playtest/lost-seed1.json +4277 -0
  109. package/templates-app/molehill-2d/PROJECT/Context.md +58 -0
  110. package/templates-app/molehill-2d/PROJECT/Requirements.md +39 -0
  111. package/templates-app/molehill-2d/PROJECT/Status.md +27 -0
  112. package/templates-app/molehill-2d/PROJECT/Structure.md +48 -0
  113. package/templates-app/molehill-2d/docs/project-2d-rules.md +44 -0
  114. package/templates-app/molehill-2d/index.html +73 -0
  115. package/templates-app/molehill-2d/package.json +23 -0
  116. package/templates-app/molehill-2d/src/behaviors.ts +198 -0
  117. package/templates-app/molehill-2d/src/game.scene.json +1255 -0
  118. package/templates-app/molehill-2d/src/main.ts +41 -0
  119. package/templates-app/molehill-2d/tsconfig.json +13 -0
  120. package/templates-app/molehill-2d/verify.ts +247 -0
  121. package/templates-app/molehill-2d/vite.config.ts +12 -0
  122. package/templates-app/platformer-2d/index.html +0 -23
  123. package/templates-app/platformer-2d/package.json +2 -2
  124. package/templates-app/platformer-2d/src/behaviors.ts +26 -16
  125. package/templates-app/platformer-2d/src/game.scene.json +143 -625
  126. package/templates-app/platformer-2d/src/main.ts +35 -13
  127. package/templates-app/platformer-2d/tsconfig.json +1 -1
  128. package/templates-app/star-survivor/package.json +2 -2
  129. package/templates-app/star-survivor/src/game.scene.json +41 -195
  130. package/templates-app/star-survivor/src/main.ts +28 -7
  131. package/templates-app/star-survivor/tsconfig.json +1 -1
  132. package/templates-app/tps-3d/PROJECT/Context.md +1 -1
  133. package/templates-app/tps-3d/package.json +2 -2
  134. package/templates-app/tps-3d/src/behaviors.ts +19 -1
  135. package/templates-app/tps-3d/src/game.scene.json +78 -217
  136. package/templates-app/tps-3d/src/main.ts +39 -17
  137. package/templates-app/tps-3d/tsconfig.json +1 -1
  138. package/templates-app/village-quest-3d/.incanto/playtest/swapped-seed1.json +1735 -0
  139. package/templates-app/village-quest-3d/package.json +2 -2
  140. package/templates-app/village-quest-3d/src/behaviors.ts +21 -0
  141. package/templates-app/village-quest-3d/src/grove.scene.json +54 -221
  142. package/templates-app/village-quest-3d/src/main.ts +24 -4
  143. package/templates-app/village-quest-3d/src/village.scene.json +199 -838
  144. package/templates-app/village-quest-3d/tsconfig.json +1 -1
  145. package/templates-app/village-quest-3d/verify.ts +14 -1
  146. package/dist/duplicate-IWIqk0HJ.js +0 -22
  147. package/dist/json-CwwhxQgb.js +0 -36
  148. package/dist/registry-CF70EArN.js +0 -212
  149. package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
  150. package/dist/test-8hoHeRmo.js +0 -2340
  151. package/dist/touch-DEAmqGdf.js +0 -225
  152. package/editor/assets/agent8-BrrHOjMJ.js +0 -1
  153. package/editor/assets/index-eVd0BToA.js +0 -10958
@@ -6,6 +6,7 @@
6
6
  * (quest, enemies, HUD) — regenerate the world without touching the game.
7
7
  */
8
8
 
9
+ import { newUid } from 'incanto';
9
10
  import { buildTerrainNav, Terrain3D } from 'incanto/3d';
10
11
  import { generateTerrain } from 'incanto/env';
11
12
 
@@ -145,6 +146,8 @@ dressing.push({
145
146
  name: 'ShoreRocks',
146
147
  type: 'InstancedMesh3D',
147
148
  props: {
149
+ // Sunk into the slope on purpose — see the note by the player's Catch.
150
+ snapToGround: false,
148
151
  mesh: 'gem',
149
152
  size: [0.6, 0.45, 0.6],
150
153
  material: { color: '#7e8590', roughness: 0.95, flatShading: true },
@@ -166,7 +169,7 @@ const ward = (i: number, [x, y, z]: [number, number, number]): unknown => ({
166
169
  name: `Ward${i}`,
167
170
  type: 'Node3D',
168
171
  groups: ['ward'],
169
- props: { position: [round2(x), round2(y), round2(z)] },
172
+ props: { position: [round2(x), round2(y), round2(z)], snapToGround: false },
170
173
  children: [
171
174
  {
172
175
  name: 'Plinth',
@@ -228,7 +231,7 @@ const shade = (wardIdx: number, n: number, [x, y, z]: [number, number, number]):
228
231
  name: `Shade${wardIdx}_${n}`,
229
232
  type: 'Node3D',
230
233
  groups: ['shade', `shade-ward${wardIdx}`],
231
- props: { position: [round2(x), round2(y + 0.9), round2(z)] },
234
+ props: { position: [round2(x), round2(y + 0.9), round2(z)], snapToGround: false },
232
235
  script: { name: 'PathFollow', props: { speed: 2.6, loop: true } },
233
236
  children: [
234
237
  {
@@ -411,6 +414,9 @@ const scene = {
411
414
  round2(heightAt(keeperSite[0] - 5, keeperSite[2] - 4)),
412
415
  round2(keeperSite[2] - 4),
413
416
  ],
417
+ // The lamp sits at the top of the tower: the ASSEMBLY owns its
418
+ // children's height, so `findFloatingProps` is not asked about them.
419
+ snapToGround: false,
414
420
  static: true,
415
421
  },
416
422
  children: [
@@ -462,7 +468,7 @@ const scene = {
462
468
  {
463
469
  name: 'Keeper',
464
470
  type: 'Node3D',
465
- props: { position: keeperSite.map(round2) },
471
+ props: { position: keeperSite.map(round2), snapToGround: false },
466
472
  script: { name: 'Interactable', props: { action: 'interact', range: 3 } },
467
473
  children: [
468
474
  {
@@ -511,6 +517,9 @@ const scene = {
511
517
  name: 'Player',
512
518
  type: 'RigidBody3D',
513
519
  groups: ['player'],
520
+ // Its Health is saveable, and a save is keyed by uid — minted, pasted,
521
+ // and stable across regenerations for the same reason the HUD's are.
522
+ uid: 'n_3cqocoi9v0cvoxdv',
514
523
  script: { name: 'Health', props: { max: 100, invulnerableFor: 0.8 } },
515
524
  props: {
516
525
  position: [round2(playerSpawn[0]), round2(playerSpawn[1] + 1.2), round2(playerSpawn[2])],
@@ -519,11 +528,24 @@ const scene = {
519
528
  collider: { shape: 'capsule', radius: 0.34, height: 1 },
520
529
  },
521
530
  children: [
531
+ {
532
+ /*
533
+ * An open terrain has an EDGE. With no catch, walking off it falls
534
+ * forever — `incanto-playtest` calls that `fell` and treats it as a
535
+ * failure whatever the game set out to be.
536
+ */
537
+ name: 'Catch',
538
+ type: 'Respawn',
539
+ },
522
540
  {
523
541
  name: 'Controller',
524
542
  type: 'CharacterController3D',
525
543
  props: {
526
544
  view: 'free',
545
+ // The two `incanto-feel` prints a note about on every run, and the
546
+ // 3D character skill calls coyote time the single biggest one.
547
+ coyoteSeconds: 0.12,
548
+ jumpBufferSeconds: 0.15,
527
549
  camDistance: 5.2,
528
550
  eyeHeight: 1.1,
529
551
  maxSpeed: 4.4,
@@ -623,6 +645,7 @@ const scene = {
623
645
  // ROOT child (a positioned parent would double the offset on screen)
624
646
  props: {
625
647
  visible: false,
648
+ snapToGround: false,
626
649
  position: [
627
650
  round2(keeperSite[0] + 16),
628
651
  round2(heightAt(keeperSite[0] + 16, keeperSite[2] - 14)),
@@ -768,7 +791,10 @@ const scene = {
768
791
  {
769
792
  name: 'Quest',
770
793
  type: 'UiText',
771
- uid: 'n_bi_quest',
794
+ // MINTED by newUid() and pasted, not invented: a save is keyed by
795
+ // uid and this file is regenerated, so they have to be both
796
+ // crypto-shaped and stable across `bun run world`.
797
+ uid: 'n_3txwnq8m201xbwoz',
772
798
  props: {
773
799
  text: 'Find the lighthouse keeper [E]',
774
800
  size: 16,
@@ -779,20 +805,25 @@ const scene = {
779
805
  {
780
806
  name: 'HP',
781
807
  type: 'UiBar',
782
- uid: 'n_bi_hp',
808
+ uid: 'n_vp09cmk3syxo2lcz',
783
809
  props: { anchor: 'topRight', value: 100, max: 100, width: 170 },
784
810
  },
785
- { name: 'Banner', type: 'UiBanner', uid: 'n_bi_banner', props: { anchor: 'center' } },
811
+ {
812
+ name: 'Banner',
813
+ type: 'UiBanner',
814
+ uid: 'n_xwkphpoa7ac9w451',
815
+ props: { anchor: 'center' },
816
+ },
786
817
  {
787
818
  name: 'Dialogue',
788
819
  type: 'UiDialogue',
789
- uid: 'n_bi_dialogue',
820
+ uid: 'n_7wosfkulqmue3jpv',
790
821
  props: { anchor: 'bottom', charsPerSecond: 45 },
791
822
  },
792
823
  {
793
824
  name: 'Hint',
794
825
  type: 'UiText',
795
- uid: 'n_bi_hint',
826
+ uid: 'n_x3funbse7a8nkfnu',
796
827
  props: {
797
828
  text: 'WASD move · Shift sprint · E interact · click/F strike',
798
829
  size: 12,
@@ -811,7 +842,44 @@ const scene = {
811
842
  ],
812
843
  };
813
844
 
814
- await Bun.write(`${import.meta.dir}/src/game.scene.json`, `${JSON.stringify(scene, null, 2)}\n`);
845
+ /*
846
+ * A REGENERATED file keeps the identities the old one had.
847
+ *
848
+ * A uid is the key a save is written against, and this script overwrites the
849
+ * whole scene — so minting fresh ones every run would break every save on the
850
+ * next `bun run world`, and emitting none at all (which is what it used to do)
851
+ * left eighteen scripted nodes unkeyed, so a save could not record what the run
852
+ * consumed. `verify:uids` said so; nothing said it to whoever ran the command.
853
+ *
854
+ * Reused by PATH: the same seed builds the same island, so the same node is at
855
+ * the same place. Anything new gets a real `newUid()`.
856
+ */
857
+ const scenePath = `${import.meta.dir}/src/game.scene.json`;
858
+ const previous = new Map<string, string>();
859
+ try {
860
+ const old = JSON.parse(await Bun.file(scenePath).text()) as { root?: unknown };
861
+ const remember = (node: Record<string, unknown>, path: string): void => {
862
+ const here = `${path}/${String(node.name ?? '?')}`;
863
+ if (typeof node.uid === 'string') previous.set(here, node.uid);
864
+ for (const child of (node.children ?? []) as Record<string, unknown>[]) remember(child, here);
865
+ };
866
+ if (old.root) remember(old.root as Record<string, unknown>, '');
867
+ } catch {
868
+ // No previous file: every uid below is minted fresh, which is the first run.
869
+ }
870
+
871
+ const keyIdentities = (node: Record<string, unknown>, path: string): void => {
872
+ const here = `${path}/${String(node.name ?? '?')}`;
873
+ // Only what a save would write down: a node carrying a behavior.
874
+ if (node.script && typeof node.uid !== 'string') node.uid = previous.get(here) ?? newUid();
875
+ else if (typeof node.uid === 'string' && previous.has(here))
876
+ node.uid = previous.get(here) as string;
877
+ for (const child of (node.children ?? []) as Record<string, unknown>[])
878
+ keyIdentities(child, here);
879
+ };
880
+ keyIdentities(scene.root as unknown as Record<string, unknown>, '');
881
+
882
+ await Bun.write(scenePath, `${JSON.stringify(scene, null, 2)}\n`);
815
883
  console.log(
816
884
  `beacon isle generated: keeper ${keeperSite.map(round2)}, wards ${wardSites.map((w) => w.map(round2)).join(' | ')}, ${placed} groves, ${grassPlaced} grass, ${flowersPlaced} flowers`,
817
885
  );
@@ -6,7 +6,7 @@
6
6
  "dev": "vite",
7
7
  "build": "vite build",
8
8
  "typecheck": "tsc --noEmit",
9
- "check": "incanto-check src",
9
+ "check": "incanto-check src --behaviors src/behaviors.ts",
10
10
  "verify": "bun verify.ts",
11
11
  "world": "bun generate-world.ts"
12
12
  },
@@ -14,7 +14,7 @@
14
14
  "@dimforge/rapier2d-compat": "0.19.3",
15
15
  "@dimforge/rapier3d-compat": "0.19.3",
16
16
  "@pixiv/three-vrm": "^3.5.3",
17
- "incanto": "^0.68.0",
17
+ "incanto": "^0.70.0",
18
18
  "three": "^0.184.0"
19
19
  },
20
20
  "devDependencies": {
@@ -88,6 +88,27 @@ function spawnFootDust(root: Node, at: [number, number, number], scale = 1): voi
88
88
  }
89
89
 
90
90
  export class IsleDirector extends Behavior {
91
+ /**
92
+ * The isle HAS an ending, and never said so.
93
+ *
94
+ * Three wards lit, the Shadeheart down, fireworks over the lighthouse and
95
+ * `BEACON ISLE SHINES` on the banner — and no `won` anywhere, so every
96
+ * instrument that asks the scene whether it is finishable answered no. On
97
+ * the template `bunx incanto-new my-game` scaffolds by DEFAULT:
98
+ *
99
+ * ```
100
+ * ? plays — nothing declares a win — 8 runs played without a defect,
101
+ * and there was no end to reach
102
+ * next: if it is meant to be finishable, emit `won`
103
+ * ```
104
+ *
105
+ * Both sentences were true about the WIRING and wrong about the game. A
106
+ * signal is the cheapest of the three routes the skill offers (`GameFlow`,
107
+ * `ScoreKeeper`, your own behaviour) and the only one that does not also
108
+ * freeze the world — the isle keeps playing after you win it.
109
+ */
110
+ static readonly signals: readonly string[] = ['won'];
111
+
91
112
  private wind: Voice | null = null;
92
113
  private nav: TerrainNav | null = null;
93
114
  private repathClock = 0;
@@ -445,6 +466,7 @@ export class IsleDirector extends Behavior {
445
466
  (this.node.getNode('WinSfx') as AudioPlayer).play();
446
467
  this.fireworksIn = 0.4;
447
468
  this.syncQuestLine();
469
+ this.node.emit('won');
448
470
  });
449
471
  return;
450
472
  }