incanto 0.63.0 → 0.63.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/bin/incanto-playtest.mjs +14 -5
  2. package/bin/incanto-verify.mjs +9 -4
  3. package/dist/2d.js +3 -3
  4. package/dist/3d.js +4 -4
  5. package/dist/{create-game-lLeITaZ5.js → create-game-BsqKOAFN.js} +3 -3
  6. package/dist/{create-game-viBqXUoZ.js → create-game-CSnlQDkz.js} +3 -3
  7. package/dist/{environment-presets-QR7_75KJ.js → environment-presets-BKRo-HXg.js} +1 -1
  8. package/dist/{gameplay-CuqoHHUB.js → gameplay-Dqm4LyKR.js} +1 -1
  9. package/dist/gameplay.js +1 -1
  10. package/dist/index.js +2 -2
  11. package/dist/{physics-2d-CllJXlic.js → physics-2d-BUD-MADM.js} +4 -4
  12. package/dist/{physics-3d-BL_pFJ19.js → physics-3d-BsutZCtX.js} +2 -2
  13. package/dist/react.js +1 -1
  14. package/dist/{register-1cKM8DEj.js → register-BSA93acW.js} +1 -1
  15. package/dist/{replay-Bhdntkvq.js → replay-DKRmiVjk.js} +40 -0
  16. package/dist/{src-BlV9Sv4m.js → src-DQzZD_gx.js} +1 -1
  17. package/dist/{test-Ct1_zf5S.js → test-wPUuuJpP.js} +8 -8
  18. package/dist/test.js +2 -2
  19. package/dist/vite.js +2 -2
  20. package/editor/assets/{agent8-CLZXBRoM.js → agent8-CfacwdPM.js} +1 -1
  21. package/editor/assets/{debug-eaSKFAKW.js → debug-D0kX3cGf.js} +1 -1
  22. package/editor/assets/{index-D66AuRwG.js → index-BrG3srCa.js} +50 -50
  23. package/editor/index.html +1 -1
  24. package/package.json +1 -1
  25. package/skills/incanto-gameplay-behaviors.md +7 -1
  26. package/skills/incanto-physics-and-input.md +40 -8
  27. package/skills/incanto-your-first-game.md +7 -2
  28. package/templates-app/beacon-isle-3d/package.json +1 -1
  29. package/templates-app/platformer-2d/package.json +1 -1
  30. package/templates-app/star-survivor/package.json +1 -1
  31. package/templates-app/tps-3d/PROJECT/Requirements.md +3 -1
  32. package/templates-app/tps-3d/index.html +3 -1
  33. package/templates-app/tps-3d/package.json +1 -1
  34. package/templates-app/tps-3d/src/behaviors.ts +4 -2
  35. package/templates-app/village-quest-3d/package.json +1 -1
  36. package/templates-app/village-quest-3d/src/grove.scene.json +6 -2
  37. package/templates-app/village-quest-3d/src/village.scene.json +8 -4
package/editor/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Incanto Scene Editor</title>
7
7
  <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect width='16' height='16' rx='3' fill='%236ee7dc'/><text x='8' y='12' text-anchor='middle' font-size='11' font-family='monospace' fill='%230e1018'>i</text></svg>" />
8
- <script type="module" crossorigin src="./assets/index-D66AuRwG.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-BrG3srCa.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="./assets/GameServer-C56iOUgF.js">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incanto",
3
- "version": "0.63.0",
3
+ "version": "0.63.1",
4
4
  "description": "Vibe-coding-first web game engine SDK — JSON-driven scenes on three.js",
5
5
  "keywords": [
6
6
  "game-engine",
@@ -226,7 +226,7 @@ calls `damage(amount)`, then emits `dealtDamage(amount, healthOwnerNode)`.
226
226
  | Prop | Default | Meaning |
227
227
  |---|---|---|
228
228
  | `amount` | `10` | HP removed per contact |
229
- | `targetGroup` | `""` | only damage targets whose Health-owner is in this group (`""` = any) |
229
+ | `targetGroup` | `""` | only damage targets in this group — the tag may sit on the BODY or on the node carrying its `Health` (`""` = any) |
230
230
  | `oncePerTarget` | `true` | damage each target at most **once, ever** |
231
231
  | `repeatEvery` | `0` | seconds between re-hits while still overlapping (`0` = entry only) |
232
232
  | `destroySelf` | `false` | `queueFree()` after the first hit |
@@ -257,6 +257,12 @@ Pair it with `Health.invulnerableFor`, which is the real "don't drain me every
257
257
  frame" guard: with `invulnerableFor: 0.6`, a `repeatEvery` under 0.6 s just
258
258
  lands on i-frames, so 0.5–1 s is the useful range.
259
259
 
260
+ The tag may sit on either end of the entity, because they are commonly
261
+ different nodes: a body holds one behavior, so a player with its own script
262
+ keeps its `Health` on a child, and the group stays on the body. `incanto check`
263
+ reports a `targetGroup` that nothing in the scene can satisfy — a hazard that is
264
+ wired, validated and disarmed is otherwise invisible.
265
+
260
266
  **`targetGroup` — stop enemies killing each other.** With no collision layers in
261
267
  v0 (everything overlaps everything), an enemy's contact hitbox would damage any
262
268
  `Health` it touches, including other enemies clustered at the spawn point. Gate
@@ -428,14 +428,46 @@ From scene JSON, the **`Clickable`** behavior needs no code at all:
428
428
  | `maxDistance` | `0` | ignore clicks further than this (0 = any) |
429
429
  | `enabled` | `true` | stop responding without detaching |
430
430
 
431
- Signals: **`clicked`**, **`hovered`**, **`unhovered`** — wire them in
432
- `connections` like any other. `clicked` fires on RELEASE over the same node the
433
- press started on (a drag that ends elsewhere is not a click, the way every
434
- button on every platform behaves), and a hit on a CHILD counts as a hit on the
435
- node — the raycast lands on the visual mesh, which is usually a child.
436
-
437
- Headless there is no renderer and therefore no raycast: `Clickable` is inert
438
- rather than wrong, and `engine.picker` is null.
431
+ Signals: **`clicked(node)`**, **`hovered(node)`**, **`unhovered(node)`** — wire
432
+ them in `connections` like any other. **Each one carries the node it happened
433
+ on**, so one handler can serve a whole board of tiles:
434
+
435
+ ```jsonc
436
+ { "signal": "clicked", "from": "/Board/Tile3", "to": "/Board", "handler": "onTileClicked" }
437
+ ```
438
+ ```ts
439
+ onTileClicked(tile: Node) { this.flip(tile); } // which tile, without a wire each
440
+ ```
441
+
442
+ `clicked` fires on RELEASE over the same node the press started on (a drag that
443
+ ends elsewhere is not a click, the way every button on every platform behaves),
444
+ and a hit on a CHILD counts as a hit on the node — the raycast lands on the
445
+ visual mesh, which is usually a child.
446
+
447
+ ### Testing a mouse-driven game headlessly
448
+
449
+ There is no renderer and therefore no raycast, so `engine.picker` is null and
450
+ `Clickable` is inert. That is not a dead end: give the engine a picker of your
451
+ own and drive the cursor.
452
+
453
+ ```ts
454
+ // A picker that answers from the tree instead of a GPU raycast.
455
+ engine.picker = (x, y) => hitTestYourBoard(x, y); // return a Node or null
456
+
457
+ engine.input.setPointerPosition(150, 0); // where the cursor IS
458
+ engine.input.handleMouseButton(0, true); // press …
459
+ engine.step();
460
+ engine.input.handleMouseButton(0, false); // … and release
461
+ engine.step(); // `clicked` fires here
462
+ ```
463
+
464
+ Note the BUTTON, not an action: `Clickable` reads `mouseJustPressed` directly,
465
+ so `pressAction('click')` drives nothing. Moving the cursor between the press
466
+ and the release correctly produces no click.
467
+
468
+ Without this a mouse game's entire input surface is load-validated and never
469
+ once executed — the scene is legal, every wire resolves, and nothing has ever
470
+ been clicked.
439
471
 
440
472
  ## Placement rules
441
473
 
@@ -22,12 +22,17 @@ if you skim.
22
22
  ## 1. Three minutes to a running game
23
23
 
24
24
  ```bash
25
- bunx incanto new --list # what the starters are
26
- bunx incanto new my-game --template tps-3d # or platformer-2d, star-survivor, …
25
+ bunx incanto@latest new --list # what the starters are
26
+ bunx incanto@latest new my-game --template tps-3d # or platformer-2d, star-survivor, …
27
27
  cd my-game && bun install
28
28
  bun run check && bun run typecheck && bun run verify
29
29
  ```
30
30
 
31
+ `@latest` matters: `bunx` caches the CLI, and a cached one scaffolds a project
32
+ pinned to ITS version, so you can quietly get an engine a release behind the
33
+ docs you are reading. Check with `grep incanto package.json` if anything below
34
+ does not match what you see.
35
+
31
36
  The starter is a **complete game**, green on arrival, and it is meant to be
32
37
  reshaped rather than read. Four agents building four different games all started
33
38
  here; the two who hand-authored a scene from scratch still scaffolded first, for
@@ -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.63.0",
17
+ "incanto": "^0.63.1",
18
18
  "three": "^0.184.0"
19
19
  },
20
20
  "devDependencies": {
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
- "incanto": "^0.63.0",
14
+ "incanto": "^0.63.1",
15
15
  "three": "^0.184.0"
16
16
  },
17
17
  "devDependencies": {
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
- "incanto": "^0.63.0",
14
+ "incanto": "^0.63.1",
15
15
  "three": "^0.184.0"
16
16
  },
17
17
  "devDependencies": {
@@ -31,7 +31,9 @@
31
31
  strafe-aim rig (always face the camera) would be a custom controller tweak.
32
32
  - Mouse look needs POINTER LOCK (click the canvas). A free cursor never spins the
33
33
  camera (deltas only accumulate while locked or a button is held).
34
- - HUD is DOM, not nodes: 3D scenes have no 2D `UILayer`/`Label`. The crosshair,
34
+ - HUD is DOM here by CHOICE, not by necessity: `HudLayer` + `UiText`/`UiBar`
35
+ work in 3D too and are the ones a headless check can read (incanto-hud.md),
36
+ and this scene keeps a `HudLayer` for its banner. The crosshair,
35
37
  HUD and banner live in `index.html`; `HudUpdater` fills them (guarded for
36
38
  headless). The crosshair marks the camera aim point, not the gun barrel.
37
39
  - Zero-asset: arena, soldier and enemies are primitives; SFX are procedural
@@ -110,7 +110,9 @@
110
110
  width: 56px;
111
111
  height: 56px;
112
112
  }
113
- /* HUD overlay (3D scenes have no 2D UI nodes the HUD is plain DOM) */
113
+ /* This game's HUD is plain DOM. `HudLayer` + `UiText`/`UiBar` work in 3D
114
+ too (see incanto-hud.md) and are readable headlessly — the scene has one
115
+ for the banner. Either is fine; DOM here is a choice, not a limit. */
114
116
  #hud {
115
117
  position: fixed;
116
118
  top: 14px;
@@ -13,7 +13,7 @@
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
14
  "@dimforge/rapier3d-compat": "0.19.3",
15
15
  "@pixiv/three-vrm": "^3.5.3",
16
- "incanto": "^0.63.0",
16
+ "incanto": "^0.63.1",
17
17
  "three": "^0.184.0"
18
18
  },
19
19
  "devDependencies": {
@@ -11,7 +11,7 @@
11
11
  * snaps the visible character to FACE the aim on each shot so the soldier turns
12
12
  * to shoot where you're looking.
13
13
  * - `HudUpdater` — pure presentation glue. The gameplay library owns the numbers,
14
- * not how they're drawn, and 3D scenes have no 2D UI nodes, so this reads
14
+ * not how they're drawn, and this game draws its HUD in plain DOM, so it reads
15
15
  * ScoreKeeper.score / Health.current / the wave index / the ammo count each
16
16
  * frame into the HTML HUD overlay (index.html) and flips the win/lose banner.
17
17
  */
@@ -315,7 +315,9 @@ export class Shoot extends Behavior {
315
315
 
316
316
  // ---------------------------------------------------------------------------
317
317
  // HudUpdater — writes the live numbers into the HTML HUD overlay (index.html).
318
- // 3D scenes have no 2D UILayer/Label nodes, so the HUD is plain DOM. All DOM
318
+ // This HUD is plain DOM by choice. `HudLayer` + `UiText`/`UiBar` also work in
319
+ // 3D and are the readable-headlessly option (incanto-hud.md); the scene keeps a
320
+ // HudLayer for its banner. All DOM
319
321
  // access is guarded: headless runs (verify, runScript) have no `document`, so
320
322
  // every write is a no-op and the gameplay logic is unaffected.
321
323
  // ---------------------------------------------------------------------------
@@ -13,7 +13,7 @@
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
14
  "@dimforge/rapier3d-compat": "0.19.3",
15
15
  "@pixiv/three-vrm": "^3.5.3",
16
- "incanto": "^0.63.0",
16
+ "incanto": "^0.63.1",
17
17
  "three": "^0.184.0"
18
18
  },
19
19
  "devDependencies": {
@@ -735,6 +735,10 @@
735
735
  "preset": "hit",
736
736
  "volume": 0.6
737
737
  }
738
+ },
739
+ {
740
+ "name": "Catch",
741
+ "type": "Respawn"
738
742
  }
739
743
  ],
740
744
  "script": {
@@ -801,7 +805,7 @@
801
805
  "type": "UiText",
802
806
  "uid": "n_dg8p7etf1n6lc495",
803
807
  "props": {
804
- "text": "click/F strike · Shift sprint · portal returns home",
808
+ "text": "click/F strike \u00b7 Shift sprint \u00b7 portal returns home",
805
809
  "size": 12,
806
810
  "color": "#cccccc",
807
811
  "anchor": "bottomRight",
@@ -830,7 +834,7 @@
830
834
  "name": "ResumeHint",
831
835
  "type": "UiText",
832
836
  "props": {
833
- "text": "Esc to resume · R to restart",
837
+ "text": "Esc to resume \u00b7 R to restart",
834
838
  "size": 13,
835
839
  "color": "#c9d2dd"
836
840
  }
@@ -7,13 +7,13 @@
7
7
  "en": {
8
8
  "quest.talkElder": "Talk to the Elder [E]",
9
9
  "ui.playAgain": "Play again",
10
- "ui.hint": "WASD move · Shift sprint · E talk · click/F strike",
10
+ "ui.hint": "WASD move \u00b7 Shift sprint \u00b7 E talk \u00b7 click/F strike",
11
11
  "settings.language": "Language"
12
12
  },
13
13
  "ko": {
14
- "quest.talkElder": "촌장에게 말을 걸어보세요 [E]",
15
- "ui.playAgain": "다시 하기",
16
- "settings.language": "언어"
14
+ "quest.talkElder": "\ucd0c\uc7a5\uc5d0\uac8c \ub9d0\uc744 \uac78\uc5b4\ubcf4\uc138\uc694 [E]",
15
+ "ui.playAgain": "\ub2e4\uc2dc \ud558\uae30",
16
+ "settings.language": "\uc5b8\uc5b4"
17
17
  }
18
18
  },
19
19
  "environment": {
@@ -3430,6 +3430,10 @@
3430
3430
  "preset": "hit",
3431
3431
  "volume": 0.6
3432
3432
  }
3433
+ },
3434
+ {
3435
+ "name": "Catch",
3436
+ "type": "Respawn"
3433
3437
  }
3434
3438
  ]
3435
3439
  },