incanto 0.57.0 → 0.58.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 (63) hide show
  1. package/README.md +6 -4
  2. package/bin/incanto-check.mjs +27 -0
  3. package/bin/incanto-new.mjs +29 -7
  4. package/bin/incanto-playtest.mjs +28 -2
  5. package/bin/incanto-verify.mjs +87 -15
  6. package/bin/incanto.mjs +106 -0
  7. package/dist/2d.js +2 -2
  8. package/dist/3d.js +2 -2
  9. package/dist/{create-game-DpbUrMOQ.js → create-game-BZwWJIns.js} +1 -1
  10. package/dist/{create-game-C5jQYPah.js → create-game-D10bU5_J.js} +56 -8
  11. package/dist/index.js +1 -1
  12. package/dist/{physics-2d-BLcvEFDR.js → physics-2d-EqA-jddf.js} +2 -1
  13. package/dist/{physics-3d-QBrfIT2Y.js → physics-3d-Dnz4fsXX.js} +2 -1
  14. package/dist/quiet-rapier-BAJ4K94N.js +46 -0
  15. package/dist/react.js +1 -1
  16. package/dist/{src-CGjmPw65.js → src-C1J09Op6.js} +1 -1
  17. package/dist/{test-it1VekWs.js → test-ZBga8kQ9.js} +36 -10
  18. package/dist/test.d.ts +1 -1
  19. package/dist/test.js +1 -1
  20. package/dist/vite.js +2 -2
  21. package/editor/assets/{agent8-CGT7r3Mb.js → agent8-BQQjE9UQ.js} +1 -1
  22. package/editor/assets/{debug-BxWSIHG3.js → debug-C9UCsXBS.js} +1 -1
  23. package/editor/assets/{index-CV1m-aX5.js → index-CBgfM3WD.js} +51 -51
  24. package/editor/index.html +1 -1
  25. package/package.json +2 -1
  26. package/skills/incanto-building-2d-games.md +13 -0
  27. package/skills/incanto-building-3d-games.md +3 -3
  28. package/skills/incanto-playtesting.md +19 -5
  29. package/skills/incanto-verifying-your-game.md +11 -4
  30. package/templates-app/beacon-isle-3d/package.json +1 -1
  31. package/templates-app/beacon-isle-3d/src/game.scene.json +7 -6
  32. package/templates-app/platformer-2d/PROJECT/Context.md +70 -0
  33. package/templates-app/platformer-2d/PROJECT/Requirements.md +63 -0
  34. package/templates-app/platformer-2d/PROJECT/Status.md +60 -0
  35. package/templates-app/platformer-2d/PROJECT/Structure.md +77 -0
  36. package/templates-app/platformer-2d/docs/project-2d-rules.md +61 -0
  37. package/templates-app/platformer-2d/index.html +99 -0
  38. package/templates-app/platformer-2d/package.json +23 -0
  39. package/templates-app/platformer-2d/src/behaviors.ts +541 -0
  40. package/templates-app/platformer-2d/src/game.scene.json +2061 -0
  41. package/templates-app/platformer-2d/src/main.ts +68 -0
  42. package/templates-app/platformer-2d/tsconfig.json +13 -0
  43. package/templates-app/platformer-2d/verify.ts +275 -0
  44. package/templates-app/platformer-2d/vite.config.ts +12 -0
  45. package/templates-app/star-survivor/PROJECT/Context.md +55 -0
  46. package/templates-app/star-survivor/PROJECT/Requirements.md +47 -0
  47. package/templates-app/star-survivor/PROJECT/Status.md +44 -0
  48. package/templates-app/star-survivor/PROJECT/Structure.md +63 -0
  49. package/templates-app/star-survivor/docs/project-2d-rules.md +53 -0
  50. package/templates-app/star-survivor/index.html +232 -0
  51. package/templates-app/star-survivor/package.json +23 -0
  52. package/templates-app/star-survivor/src/behaviors.ts +624 -0
  53. package/templates-app/star-survivor/src/game.scene.json +464 -0
  54. package/templates-app/star-survivor/src/main.ts +49 -0
  55. package/templates-app/star-survivor/tsconfig.json +13 -0
  56. package/templates-app/star-survivor/verify.ts +193 -0
  57. package/templates-app/star-survivor/vite.config.ts +12 -0
  58. package/templates-app/tps-3d/package.json +1 -1
  59. package/templates-app/tps-3d/src/game.scene.json +6 -3
  60. package/templates-app/tps-3d/verify.ts +17 -1
  61. package/templates-app/village-quest-3d/package.json +1 -1
  62. package/templates-app/village-quest-3d/src/grove.scene.json +14 -13
  63. package/templates-app/village-quest-3d/src/village.scene.json +5 -5
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-CV1m-aX5.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-CBgfM3WD.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.57.0",
3
+ "version": "0.58.0",
4
4
  "description": "Vibe-coding-first web game engine SDK — JSON-driven scenes on three.js",
5
5
  "keywords": [
6
6
  "game-engine",
@@ -88,6 +88,7 @@
88
88
  "@pixiv/three-vrm": "^3.5.3"
89
89
  },
90
90
  "bin": {
91
+ "incanto": "bin/incanto.mjs",
91
92
  "incanto-skills": "bin/incanto-skills.mjs",
92
93
  "incanto-editor": "bin/incanto-editor.mjs",
93
94
  "incanto-model": "bin/incanto-model.mjs",
@@ -10,6 +10,19 @@ description: Build 2D web games with Incanto — y-down pixel coordinates, strin
10
10
 
11
11
  Prerequisite: `incanto-scene-json-authoring.md` (this directory) for the file format. This skill covers the 2D taxonomy.
12
12
 
13
+ ## Fastest start: scaffold a whole game
14
+
15
+ ```bash
16
+ bunx incanto new my-game --template platformer-2d # tilemap, jump feel, follow cam, coins
17
+ bunx incanto new my-game --template star-survivor # endless waves, auto-attack, upgrades
18
+ bunx incanto new --list # every starter, 3D and 2D
19
+ ```
20
+
21
+ Templates are COMPLETE games — level, HUD, sound, and a `verify.ts` harness
22
+ that plays them end to end — meant to be reshaped rather than read. Prefer
23
+ starting from one over wiring from scratch; `bun run verify` in the new project
24
+ tells you the moment a change breaks the game.
25
+
13
26
  ## Coordinate convention (Phaser/Godot prior)
14
27
 
15
28
  **1 unit = 1 px · (0,0) top-left · +y DOWN · positive rotation = clockwise (degrees).**
@@ -20,9 +20,9 @@ reports a file's real bounding box and animation names.
20
20
  ## Fastest start: scaffold a whole game
21
21
 
22
22
  ```bash
23
- bunx incanto-new my-game # Beacon Isle — 3D flagship template
24
- bunx incanto-new my-game --template tps-3d # third-person shooter starter
25
- bunx incanto-new --list
23
+ bunx incanto new my-game # Beacon Isle — 3D flagship template
24
+ bunx incanto new my-game --template tps-3d # third-person shooter starter
25
+ bunx incanto new --list
26
26
  ```
27
27
 
28
28
  Templates are COMPLETE games (world generation, quest, enemies, verify
@@ -50,7 +50,8 @@ two.
50
50
  | `lost` | `GameFlow` `'gameover'`, a `lost` signal, or the player's `Health.died` |
51
51
  | `fell` | the player left the world past `--fall-below` (default: 50 m under the spawn in 3D, 1000 px under it in 2D) |
52
52
  | `error` | `stats().errors` went above zero — a behavior threw |
53
- | `stuck` | none of the above before the clock ran out |
53
+ | `stuck` | the clock ran out AND the player never got more than 3 m (96 px) from its spawn — it is wedged, or nothing moves it |
54
+ | `unfinished` | the clock ran out on a player that was getting around. Not a defect: a win that needs a SEQUENCE (talk to the NPC, then fetch, then return) is out of reach of a random walker, forever |
54
55
  | `never reached` | destinations the bot never came within `--reach-radius` of (default 2 m in 3D, 32 px in 2D) |
55
56
  | `never fired` | signals a `connections[]` entry listens to that never happened |
56
57
  | `danger` | how many times the player's `Health` emitted `damaged` |
@@ -91,9 +92,20 @@ unreachable or the connection is wrong.
91
92
  is exactly right (a walking simulator). Usually it means the hazards are not
92
93
  wired up.
93
94
 
94
- **`stuck` in every run** with no `won` at all usually means the scene declares
95
- no win condition — add a `GameFlow` or a `ScoreKeeper` and the report becomes
96
- far more useful.
95
+ **`stuck` in every run** means the player is not going anywhere: wedged in
96
+ geometry, spawned inside a collider, or missing the input map that moves it.
97
+ That is a real bug and worth chasing.
98
+
99
+ **`unfinished` in every run** is the normal report for a quest or story game
100
+ and means nothing is wrong. Random play cannot perform a sequence. Judge those
101
+ games with a SCRIPTED harness — `runScript` from `incanto/test`, which every
102
+ template's `verify.ts` is built on — and let the playtest tell you about
103
+ crashes, falls and dead hazards instead. `incanto-verify` marks the rung
104
+ `unmeasured` rather than failed for exactly this reason.
105
+
106
+ (These were one word until 0.58: `stuck` was the default verdict, so a quest
107
+ game that visited every landmark and fired two dozen signals was reported the
108
+ same as a player stuck in a wall.)
97
109
 
98
110
  ## Replays
99
111
 
@@ -116,7 +128,9 @@ Then read where it ended. The player above left a walled 40×40 arena at
116
128
  read straight off the replay.
117
129
 
118
130
  One file per *kind* of failure — twenty identical "stuck" replays teach nothing
119
- the first one does not.
131
+ the first one does not. `unfinished` runs write no replay at all: a replay is
132
+ for reproducing a failure, and eight of them per verify bury the one that is
133
+ real.
120
134
 
121
135
  ## Your behaviors
122
136
 
@@ -29,14 +29,15 @@ failures — but `framing` and `assetErrors()` you have to ASK for.
29
29
  ## 0. The whole ladder, one command: `incanto-verify`
30
30
 
31
31
  ```
32
- $ bunx incanto-verify # finds your scene
32
+ $ bunx incanto verify # finds your scene AND your behaviours
33
+ · behaviours: src/behaviors.ts (found, not named)
33
34
  ✓ loads — the scene is legal and its assets resolve
34
- ? plays — nothing declares a win — 8 runs played without error, and there was no end to reach
35
+ ? plays — 8 runs played without reaching a win (4 lost, 4 ran out the clock)
35
36
  ✓ feels — 5 of 7 fired — silent: /Game/Boss/Roar, /Game/Boss/Boom
36
37
  ? draws — the dev server is running on :5173, but no page answered
37
38
 
38
39
  passes what was measured — plays, draws not measured.
39
- next: if it is meant to be finishable, emit `won` (GameFlow, ScoreKeeper, or your own behaviour)
40
+ next: nothing here is broken a win that takes skill or a sequence is out of reach of random play. Judge it with a scripted run: `bun run verify`
40
41
  ```
41
42
 
42
43
  Runs the rungs below in order and says the ONE thing to do next. Three rules it
@@ -773,7 +774,13 @@ bunx incanto-playtest src/game.scene.json --runs 20 --seconds 60
773
774
  A seeded bot plays it headlessly — reading the scene's own `input{}` for its
774
775
  controls — and reports the win rate, destinations it could never reach, wires
775
776
  that never fired, falls out of the world, and whether anything can hurt the
776
- player at all. Failing runs come back as replays. Exits 1 when nothing ever won.
777
+ player at all. Failing runs come back as replays.
778
+
779
+ **`won`, `lost` and `unfinished` are all gameplay; `error`, `fell` and `stuck`
780
+ are defects.** A random bot cannot perform a sequence, so a quest or story game
781
+ reports `unfinished` in every run and that means nothing is wrong — judge those
782
+ with a scripted `runScript` harness and let this one tell you about crashes,
783
+ falls and dead hazards.
777
784
 
778
785
  Read `incanto-playtesting.md` before shipping a level.
779
786
 
@@ -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.57.0",
17
+ "incanto": "^0.58.0",
18
18
  "three": "^0.184.0"
19
19
  },
20
20
  "devDependencies": {
@@ -2713,7 +2713,8 @@
2713
2713
  "volume": 0.6
2714
2714
  }
2715
2715
  }
2716
- ]
2716
+ ],
2717
+ "uid": "n_klf0y16b0ct0deyk"
2717
2718
  },
2718
2719
  {
2719
2720
  "name": "Sky",
@@ -2917,7 +2918,7 @@
2917
2918
  {
2918
2919
  "name": "Quest",
2919
2920
  "type": "UiText",
2920
- "uid": "n_bi_quest",
2921
+ "uid": "n_ysul3v903v7r9bqw",
2921
2922
  "props": {
2922
2923
  "text": "Find the lighthouse keeper [E]",
2923
2924
  "size": 16,
@@ -2928,7 +2929,7 @@
2928
2929
  {
2929
2930
  "name": "HP",
2930
2931
  "type": "UiBar",
2931
- "uid": "n_bi_hp",
2932
+ "uid": "n_x49lxq49wej0w332",
2932
2933
  "props": {
2933
2934
  "anchor": "topRight",
2934
2935
  "value": 100,
@@ -2939,7 +2940,7 @@
2939
2940
  {
2940
2941
  "name": "Banner",
2941
2942
  "type": "UiBanner",
2942
- "uid": "n_bi_banner",
2943
+ "uid": "n_7kqv8wsefrki826x",
2943
2944
  "props": {
2944
2945
  "anchor": "center"
2945
2946
  }
@@ -2947,7 +2948,7 @@
2947
2948
  {
2948
2949
  "name": "Dialogue",
2949
2950
  "type": "UiDialogue",
2950
- "uid": "n_bi_dialogue",
2951
+ "uid": "n_y0bq7vt5tewf88ny",
2951
2952
  "props": {
2952
2953
  "anchor": "bottom",
2953
2954
  "charsPerSecond": 45
@@ -2956,7 +2957,7 @@
2956
2957
  {
2957
2958
  "name": "Hint",
2958
2959
  "type": "UiText",
2959
- "uid": "n_bi_hint",
2960
+ "uid": "n_osy8mwjnmfp9zicd",
2960
2961
  "props": {
2961
2962
  "text": "WASD move · Shift sprint · E interact · click/F strike",
2962
2963
  "size": 12,
@@ -0,0 +1,70 @@
1
+ # Context — platformer-2d (Incanto)
2
+
3
+ ## Project Overview
4
+
5
+ **Castle Run** — a polished 2D adventure PLATFORMER, the clone-and-modify
6
+ starter for the platformer genre. You run a medieval knight through a castle:
7
+ solid ground with gaps, floating platforms, a horizontal patrolling platform and
8
+ a vertical bobbing lift you RIDE, spinning coins + gems to grab, spikes and a
9
+ bottomless pit to avoid, patrolling goblins you STOMP from above (and chain-
10
+ bounce off), a checkpoint flag, and a gold goal flag. Three hearts per life,
11
+ three lives; touch the goal to clear the castle, run out of lives for GAME OVER.
12
+
13
+ The level is BUILT-IN gameplay wired in scene JSON. What's custom is the
14
+ platformer GAME FEEL the renderer-agnostic library leaves open: a hand-tuned
15
+ `PlayerController` (coyote-time, jump-buffer, double-jump, variable jump height,
16
+ stomp, knockback, hearts+lives, checkpoint respawn, moving-platform carry) plus
17
+ presentation glue (`GoblinSkin`, `FollowCam` with screen-shake, `ParallaxLayer`,
18
+ `HudUpdater`).
19
+
20
+ ## Tech Stack
21
+
22
+ _Exact versions are in `package.json`._
23
+
24
+ - **Game engine**: `incanto` (scene-JSON-first, three.js-rendered) +
25
+ `incanto/2d` (createGame2D, 2D nodes: CharacterBody2D/StaticBody2D/Area2D,
26
+ AnimatedSprite2D, ColorRect2D, Camera2D, UILayer/Label, Particles2D,
27
+ AudioPlayer) + `incanto/gameplay` (auto-registered Pickup, Patrol, Oscillate,
28
+ ScoreKeeper, …).
29
+ - **Art**: built-in animated sheets `medieval-knight` (player) + `goblin`
30
+ (enemy), and `coin` + `gem` item textures — all bundler-imported from the
31
+ package and injected into the scene asset urls in `main.ts`. Ground, platforms,
32
+ spikes, flags and parallax castle are styled `ColorRect2D`.
33
+ - **Audio**: zero-asset procedural SFX presets (jump/coin/hit/hurt/powerup/
34
+ win/lose); an OPTIONAL `engine.music` hook for a looping track.
35
+ - **Build / Lang**: Vite, TypeScript. **Headless verify**: `incanto/test`
36
+ (`runScript`) — see `verify.ts`. No React — a single full-window canvas.
37
+
38
+ ## Critical Memory
39
+
40
+ - READ THE SKILLS FIRST: `node_modules/incanto/skills/` —
41
+ `incanto-gameplay-behaviors.md`, `incanto-physics-and-input.md` (2D bodies +
42
+ input + units), `incanto-building-2d-games.md`, `incanto-audio.md`.
43
+ - MOVEMENT is the custom `PlayerController` (NOT the built-in
44
+ `CharacterController2D`, which is frame-perfect/stiff). It integrates velocity
45
+ on the `CharacterBody2D` and adds the forgiveness a good platformer needs:
46
+ coyote-time, jump-buffer, double-jump, variable jump height (release early =
47
+ short hop), stomp-to-kill + bounce, side-hit knockback + i-frames.
48
+ - DAMAGE IS CENTRALISED + GROUP-DRIVEN: the player has NO built-in `Health`.
49
+ `PlayerController` owns hearts (3) + reads `ScoreKeeper.lives`, and resolves
50
+ ALL contact each frame by AABB against GROUPS — `enemy` (stomp from above /
51
+ hurt on the side), `hazard` (spikes), `pit` (death plane), `checkpoint`,
52
+ `goal`, `platform` (rideable). One authority → no stomp-vs-damage double-hit.
53
+ - RESPAWN is IN-LEVEL: `Health` can't revive (dies once, `heal` is a no-op when
54
+ dead), so the player uses hearts/lives instead and `PlayerController` teleports
55
+ to the last `checkpoint` on death (no scene reload). `ScoreKeeper.loseLife` at
56
+ 0 lives emits `lost`.
57
+ - WIN: touching the `goal` group sets the score to `scoreToWin` (100000) →
58
+ `won`. Coins (10) + gems (50) are score flavour only; they can't reach the
59
+ threshold, so only the flag wins.
60
+ - MOVING-PLATFORM CARRY: the kinematic controller doesn't inherit platform
61
+ velocity, so the player has a `Feet` Area sensor that remembers the `platform`
62
+ it stands on; `PlayerController` adds that platform's per-frame delta so you
63
+ ride it.
64
+ - ENGINE ACCESS IS DEFERRED: `this.engine` throws in `onReady` during
65
+ `loadScene` (scene not attached yet) — read scene config (gravity) lazily on
66
+ the first `fixedUpdate`. `getNode`/signal wiring is fine in `onReady`.
67
+ - `FollowCam` does follow + screen-shake in ONE behavior (one script per node):
68
+ `PlayerController.shake()` calls into it on stomps/landings/hits.
69
+ - Node uids are omitted in the JSON — the loader generates them. `window.game`
70
+ exposes the Game handle in the console.
@@ -0,0 +1,63 @@
1
+ # Requirements — platformer-2d (Incanto)
2
+
3
+ ## Coding Patterns
4
+
5
+ - Game STRUCTURE belongs in `src/game.scene.json` (nodes, props, input map,
6
+ assets, scripts, connections, viewport, physics). Prefer a BUILT-IN gameplay
7
+ behavior over hand-writing logic — check
8
+ `node_modules/incanto/skills/incanto-gameplay-behaviors.md` and
9
+ `incanto-physics-and-input.md` BEFORE writing a Behavior class.
10
+ - MOVEMENT is the custom `PlayerController` on the player `CharacterBody2D` (the
11
+ built-in `CharacterController2D` is intentionally stiff; a great platformer
12
+ needs coyote-time/jump-buffer/double-jump/variable height/stomp). It integrates
13
+ `velocity` + `moveAndSlide()` + `isOnFloor()` itself. Tune feel with the
14
+ constants at the top of `behaviors.ts` (RUN_SPEED, JUMP_V, COYOTE, BUFFER,
15
+ STOMP_BOUNCE, …), not by editing the loop.
16
+ - Custom logic in TypeScript (`src/behaviors.ts`):
17
+ - `PlayerController` — run/gravity/coyote/buffer/double-jump/variable height,
18
+ stomp + knockback, hearts+lives, checkpoint respawn, moving-platform carry,
19
+ and the idle/run/jump sprite + facing + i-frame blink.
20
+ - `GoblinSkin` — face the patrol heading + play the goblin walk clip (it lives
21
+ on the goblin's `AI` child, so it reads the PARENT's position and `../Skin`).
22
+ - `FollowCam` — follow the knight (look-ahead + smoothing + world clamp) AND
23
+ screen-shake on demand (one script per node).
24
+ - `ParallaxLayer` — scroll a castle backdrop slower than the camera.
25
+ - `HudUpdater` — paint score/hearts/lives into the HUD + win/lose banner.
26
+ - DAMAGE + INTERACTIONS are GROUP-DRIVEN, not per-node wiring: tag a node with a
27
+ group and `PlayerController` handles it by AABB each frame — `enemy`, `hazard`,
28
+ `pit`, `checkpoint`, `goal`, `platform`. Adding an enemy/hazard = add a node in
29
+ the right group. (Pickups are the exception: coins/gems use the built-in
30
+ `Pickup` + `connections` `collected → ScoreKeeper.addScore` + `→ AudioPlayer.play`.)
31
+ - Moving platforms: `Patrol` (horizontal ferry) / `Oscillate` (vertical lift) on
32
+ a `StaticBody2D` in the `platform` group — the `Feet` sensor + carry logic ride
33
+ them. Bobbing pickups: `Oscillate` on the sprite child.
34
+ - Keep `main.ts` thin: inject the four built-in asset urls (knight/goblin/coin/
35
+ gem), boot `createGame2D` with the scene + behaviors, optional music, the
36
+ on-screen JUMP button, remove the loader, expose `window.game`. There is NO
37
+ respawn-via-scene-reload (respawn is in-level in `PlayerController`).
38
+ - Omit node `uid`s (the loader generates them).
39
+ - VERIFY headlessly first: `bun run verify` drives the real scene through
40
+ `incanto/test`'s `runScript` and asserts run+jump, coin scoring, STOMP, side-
41
+ hit hearts, checkpoint respawn, the win path, and the lose path.
42
+
43
+ ## Known Issues / Constraints
44
+
45
+ - No collision LAYERS in v0 — gameplay collisions are resolved by `PlayerController`
46
+ AABB against groups, so PLACE content in the right group; physics solids
47
+ (`StaticBody2D`) are only for standing/colliding.
48
+ - `Health` dies once and exposes no revive, so the PLAYER deliberately does NOT
49
+ use it — hearts/lives live in `PlayerController` + `ScoreKeeper`, and respawn is
50
+ an in-level teleport to the last checkpoint (no scene reload, no lost progress
51
+ jump-cut). Goblins are removed with `queueFree()` on stomp (no `Health` needed).
52
+ - Score is flavour: coins 10, gems 50; `scoreToWin` 100000 so ONLY the goal flag
53
+ wins (collecting everything can't reach the threshold).
54
+ - Built on `incanto/2d` physics (auto-enabled — the scene has bodies). Player =
55
+ `CharacterBody2D` (kinematic KCC), platforms/ground = `StaticBody2D`, pickups/
56
+ hazards/checkpoint/goal/feet-sensor = `Area2D`.
57
+ - Sprites: `medieval-knight` (idle 0–5, move 6–11, attack 12–17; jump reuses a
58
+ move frame) and `goblin` (idle 0–6, move 7–12). Swap a sheet by changing only
59
+ the asset entry + the import in `main.ts`.
60
+ - The parallax "castle" is styled `ColorRect2D` bands + towers (no tilemap node
61
+ in v0). Background art is the obvious next polish pass; the hero sprites carry
62
+ the look. Music is OPTIONAL (`MUSIC_URL` empty by default; SFX presets need no
63
+ files).
@@ -0,0 +1,60 @@
1
+ # Status — platformer-2d (Incanto)
2
+
3
+ ## Implemented
4
+
5
+ - Full-window single-canvas page with loading overlay, an on-screen JUMP button,
6
+ and the iframe GAME_SIZE harness.
7
+ - `game.scene.json` — **Castle Run**: viewport (`design [960,540]`, `fit:
8
+ expand`), `physics.gravity [0,1800]`, `move` (A/D + arrows) / `jump`
9
+ (Space/W/Up) input, `knight`/`goblin` sheets + `coin`/`gem` textures, and the
10
+ level —
11
+ - `Game` root: `ScoreKeeper` (`scoreToWin 100000`, `lives 3`).
12
+ - Parallax castle backdrop (sky, sun, clouds, far castle band + towers, near
13
+ castle band + battlements) via `ParallaxLayer`.
14
+ - `GroundA–D` stone with green moss caps and gaps; `Plat1–4` floating
15
+ platforms; `MovePlat` (`Patrol` horizontal) + `Lift` (`Oscillate` vertical),
16
+ both group `platform` (rideable).
17
+ - `Spikes1/2` (`hazard`), `DeathPlane` (`pit`), `Checkpoint`, `Goal`.
18
+ - `Gob1–3` goblins (`Patrol` + `GoblinSkin`, group `enemy`).
19
+ - 12 coins + 2 gems (`Pickup` + bobbing `Oscillate` sprite).
20
+ - `Player` (`CharacterBody2D` + `PlayerController`) with `Skin` (knight) +
21
+ `Feet` sensor; `Camera` (`FollowCam`); SFX players; HUD + banner UILayers.
22
+ - Custom code in `src/behaviors.ts`: `PlayerController` (coyote/buffer/double-
23
+ jump/variable height/stomp/knockback/hearts+lives/checkpoint respawn/platform
24
+ carry/anim), `GoblinSkin`, `FollowCam` (follow + shake), `ParallaxLayer`,
25
+ `HudUpdater`. Particle puffs on double-jump/stomp/land/checkpoint via
26
+ `Particles2D`.
27
+ - `connections`: each coin/gem `collected → addScore` (+ SFX); root `won/lost →
28
+ SfxWin/SfxLose`.
29
+ - `main.ts`: inject the four asset urls, `createGame2D`, optional music, JUMP
30
+ button, remove loader, expose `window.game`. Node uids omitted (loader-
31
+ generated).
32
+
33
+ ## Works (verified)
34
+
35
+ - `bun run check` → 1/1 scene valid. `bun run typecheck` → clean. `bun run lint`
36
+ → clean.
37
+ - `bun run verify` (headless `runScript`): 13/13 assertions pass — run right,
38
+ jump off the ground and settle, a coin raises the score (Pickup → addScore), a
39
+ STOMP removes a goblin and scores, a side hit costs a heart, dying respawns the
40
+ knight at the checkpoint, the goal emits `won` once, and a pit fall with the
41
+ last life emits `lost`.
42
+ - Played in a real browser (`bun run dev`, Playwright-driven): the knight + the
43
+ goblins render as their real animated sheets; coins/gems render as sprites;
44
+ ground has moss caps; the parallax castle bands scroll; HUD shows ◆ coins,
45
+ ♥♥♥ hearts and lives. A tap jump is a short hop (~41px), a held jump is full
46
+ (~156px), and a mid-air press double-jumps — variable height + double-jump
47
+ confirmed. FollowCam tracks + clamps to the level bounds. ZERO console errors.
48
+
49
+ ## Not Included (on purpose — and the next polish passes)
50
+
51
+ - Background castle is styled `ColorRect2D` (no tilemap node in v0): two parallax
52
+ layers (`CastleFarLayer` 0.82 / `CastleNearLayer` 0.6), each a Node2D with ONE
53
+ `ParallaxLayer` whose children (wall band + towers + crenellation merlons + warm
54
+ glowing windows + a gate + a pennant) inherit the scroll. Reads clearly as a
55
+ castle skyline; swap for tile/sprite art later.
56
+ - No wall-jump/dash, no enemy variety beyond the goblin, no melee (the knight
57
+ has an attack sheet 12–17 if you want it), no bundled music (SFX presets only).
58
+ - One hand-authored level — extend by copying nodes into the right GROUP
59
+ (`enemy`/`hazard`/`pit`/`checkpoint`/`platform`); `PlayerController` picks them
60
+ up with no extra wiring.
@@ -0,0 +1,77 @@
1
+ # Structure — platformer-2d (Incanto)
2
+
3
+ ## `index.html`
4
+
5
+ Full-window page: inline favicon, loading overlay (removed once the game boots),
6
+ one `#game` canvas, an on-screen `#btn-jump` button (mobile parity), the
7
+ iframe-embed GAME_SIZE script, and the module entry.
8
+
9
+ ## `src/game.scene.json`
10
+
11
+ THE whole level (open it with `npx incanto-editor`):
12
+
13
+ - scene header — `environment.background`, `viewport` (`design [960,540]`, `fit:
14
+ expand`), `physics.gravity [0,1800]`, `move`/`jump` input, and the
15
+ `knight`/`goblin` spritesheets + `coin`/`gem` texture `assets` (urls injected
16
+ in `main.ts`).
17
+ - `Game` root — `ScoreKeeper` (`scoreToWin 100000`, `lives 3`).
18
+ - Parallax castle backdrop — `Sky`/`Sun`/`Cloud1–3` (far), `CastleFar` band +
19
+ `TowerFarA–C`, `CastleNear` band + `BattlementA/B` — `ColorRect2D` +
20
+ `ParallaxLayer` (factor 0.6–0.96, higher = farther).
21
+ - `GroundA–D` — `StaticBody2D` stone (brown body + green `Moss` cap) with GAPS
22
+ (pits) between them.
23
+ - `Plat1–4` — static floating platforms; `MovePlat` (`Patrol` pingpong, group
24
+ `platform`) ferries the first pit; `Lift` (`Oscillate` vertical, group
25
+ `platform`) crosses the spike pit.
26
+ - `Spikes1/2` (group `hazard`) and `DeathPlane` (group `pit`, far below).
27
+ - `Checkpoint` (group `checkpoint`, blue flag) and `Goal` (group `goal`, gold
28
+ flag + gate).
29
+ - `Gob1–3` — Node2D (group `enemy`) + `Patrol`; children `Skin`
30
+ (`AnimatedSprite2D` goblin) + `AI` (`GoblinSkin`).
31
+ - `Coins/Coin1–12` — Area2D `Pickup` (value 10) with a bobbing `coin` sprite
32
+ child; `Gem1/2` — Area2D `Pickup` (value 50) with a bobbing `gem` sprite.
33
+ - `Player` — `CharacterBody2D` (group `player`, capsule) + `PlayerController`;
34
+ children `Skin` (`AnimatedSprite2D` knight idle/run/jump) + `Feet` (Area2D
35
+ sensor for moving-platform carry).
36
+ - `Camera` — `Camera2D` (`current`) + `FollowCam` (look-ahead, clamped `limits`,
37
+ screen-shake).
38
+ - `SfxJump/Coin/Stomp/Hurt/Checkpoint/Win/Lose` — `AudioPlayer` SFX presets
39
+ (PlayerController plays jump/stomp/hurt/checkpoint directly; coins/win/lose via
40
+ `connections`).
41
+ - `HUD` — UILayer (top-left, `HudUpdater`): Coin / Hearts / Lives / Hint Labels.
42
+ `BannerLayer` — UILayer (center) win/lose `Banner`.
43
+ - `connections` — each coin/gem `collected → ScoreKeeper.addScore` (+ SFX); root
44
+ `won/lost → SfxWin/SfxLose`. (Stomp/hit/checkpoint/respawn/win-trigger are
45
+ resolved by `PlayerController`, not connections.)
46
+
47
+ ## `src/behaviors.ts`
48
+
49
+ - `PlayerController` — the heart: run/gravity/coyote/buffer/double-jump/variable
50
+ height, stomp + bounce, side-hit knockback, hearts+lives, checkpoint respawn,
51
+ moving-platform carry (via the `Feet` sensor), sprite clip + facing + i-frame
52
+ blink. All enemy/hazard/pit/checkpoint/goal contact is AABB against groups in
53
+ `resolveContacts()`.
54
+ - `GoblinSkin` — face the patrol heading + play the goblin walk clip.
55
+ - `FollowCam` — follow (look-ahead + smoothing + world clamp) + `shake()`.
56
+ - `ParallaxLayer` — re-anchor a backdrop to `cameraX · factor` for depth.
57
+ - `HudUpdater` — score/hearts/lives Labels + win/lose banner.
58
+
59
+ ## `src/main.ts`
60
+
61
+ Boot: inject the four built-in asset urls, `createGame2D({ canvas, scene,
62
+ behaviors })`, optional `engine.music`, the JUMP button, remove the loader,
63
+ expose `window.game`.
64
+
65
+ ## `verify.ts`
66
+
67
+ Browserless proof via `incanto/test` `runScript`: run+jump (off the ground and
68
+ back), coin scoring, STOMP (goblin removed + scored), side-hit costs a heart,
69
+ checkpoint respawn (die → back at the flag), WIN (goal → `won`), LOSE (pit with
70
+ the last life → `lost`). Run with `bun run verify`.
71
+
72
+ ## `docs/project-2d-rules.md`
73
+
74
+ The vibe-coding rules: read the skills, JSON structure, built-ins first, omit
75
+ uids, declared assets, y-down space, collider props, GROUP-DRIVEN centralised
76
+ damage (§8), deferred engine access (§11), debug draw, delta discipline, verify
77
+ like a user.
@@ -0,0 +1,61 @@
1
+ <userRequest>
2
+ YOU MUST follow these rules EXACTLY when vibe-coding on this Incanto template
3
+ (Castle Run — a 2D adventure platformer):
4
+
5
+ 1. 📖 READ THE SKILLS FIRST: `node_modules/incanto/skills/` is the engine
6
+ manual, version-pinned to what is installed. Start with
7
+ `incanto-building-2d-games.md` and `incanto-scene-json-authoring.md`, then
8
+ `incanto-gameplay-behaviors.md` (Pickup/Patrol/Oscillate/ScoreKeeper) and
9
+ `incanto-physics-and-input.md` (2D bodies, the input map, units).
10
+ 2. 🧱 STRUCTURE IS JSON: scenes, nodes, props, assets, input maps and
11
+ connections all live in `src/game.scene.json`. Add level geometry there (or
12
+ with `npx incanto-editor`), NOT by constructing nodes ad-hoc in code.
13
+ 3. 🧠 LOGIC IS BEHAVIORS: prefer a BUILT-IN behavior over custom code (Pickup
14
+ coins/gems, Patrol goblins + the moving platform, Oscillate the lift +
15
+ bobbing pickups, ScoreKeeper score/lives/win-lose, AudioPlayer SFX — all
16
+ wired in JSON). Custom code = small TypeScript Behavior classes registered via
17
+ `createGame2D({ behaviors })` and attached with `"script": { "name": "Name" }`.
18
+ This game's custom behaviors are the platformer GAME FEEL the library leaves
19
+ open: `PlayerController` (coyote-time, jump-buffer, double-jump, variable jump
20
+ height, stomp, knockback, hearts+lives, checkpoint respawn, moving-platform
21
+ carry), `GoblinSkin`, `FollowCam` (follow + screen-shake), `ParallaxLayer`,
22
+ `HudUpdater`.
23
+ 4. 🔑 UIDS ARE GENERATED: omit `uid` and the loader assigns one (via the engine's
24
+ `newUid`). NEVER hand-invent readable uid strings.
25
+ 5. 📦 ASSETS ARE DECLARED: `assets` entries need `type` + `url`; reference them as
26
+ `"$key"`. The knight + goblin sheets and the coin + gem textures are BUILT-INS
27
+ (`incanto/assets/...`) imported in `main.ts` and injected into the asset url
28
+ placeholders before boot. Free extra key-values are preserved — use them for
29
+ notes (frame ranges, license, source).
30
+ 6. ⬇️ Y IS DOWN: 2D space is y-down pixels, (0,0) top-left, clockwise degrees.
31
+ Positive gravity falls (`physics.gravity` is `[0, 1800]`). Up is NEGATIVE
32
+ velocity; a jump subtracts y.
33
+ 7. 🧲 COLLIDERS ARE PROPS: `"collider": { "shape": "rect"|"circle"|"capsule", … }`
34
+ on a body node — never child shape nodes. Wrong shapes hard-fail at load.
35
+ 7b. 🦶 STAND THE FEET ON THE GROUND: a sprite-sheet `anchor` is the artist's pivot
36
+ (hip/centre), NOT the feet, and the frame has transparent padding below the art —
37
+ so a character will FLOAT unless you align it. MEASURE the feet (bottom opaque row
38
+ ÷ frame height) and set `anchor.y` so the visible feet sit at the collider bottom
39
+ (here the knight is `0.464`, goblins `0.348`, not the sheet's ~0.61). See
40
+ `incanto-building-2d-games.md` → AnimatedSprite2D "stand the feet on the ground".
41
+ 8. 🎯 DAMAGE IS CENTRALISED + GROUP-DRIVEN: the player takes NO built-in Health —
42
+ `PlayerController` owns hearts/lives and resolves ALL contact by AABB against
43
+ GROUPS each frame, so there's one authority and never a stomp-vs-damage
44
+ double-trigger. Tag content with groups: `enemy` (stompable from above, hurts
45
+ on the side), `hazard` (spikes → lose a heart), `pit` (death plane → instant
46
+ life loss), `checkpoint` (sets respawn), `goal` (win), `platform` (a moving
47
+ body the Feet sensor rides). Add an enemy/hazard = add the node in the right
48
+ group; no wiring needed.
49
+ 9. 🐛 DEBUG WHEN UNSURE: `createGame2D({ debug: true })` (or `VITE_INCANTO_DEBUG=1`)
50
+ shows the Explorer/Inspector/Stats; `physics.debugDraw = true` draws colliders.
51
+ 10. 🎬 DELTA DISCIPLINE: only write props that differ from defaults — the loader
52
+ treats unknown/garbage props as hard errors, which is your friend.
53
+ 11. 🧭 ENGINE ACCESS IS DEFERRED: `this.engine` is NOT available in `onReady`
54
+ during `loadScene` (the scene isn't attached yet) — read scene config
55
+ (gravity, etc.) lazily on the first `fixedUpdate`. `getNode`/signals are fine
56
+ in `onReady`.
57
+ 12. ✅ VERIFY LIKE A USER: `bun run check` (scene valid) → `bun run verify`
58
+ (headless run/jump/coin/stomp/checkpoint/win/lose proof) → `bun run dev`,
59
+ open the browser, and actually PLAY what you changed (run, double-jump, stomp
60
+ a goblin, ride the lift, die and respawn at the flag) before declaring done.
61
+ </userRequest>
@@ -0,0 +1,99 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><rect width=%22100%22 height=%22100%22 rx=%2220%22 fill=%22%236ee7dc%22/><text x=%2250%22 y=%2268%22 font-size=%2255%22 text-anchor=%22middle%22 font-family=%22Arial%22 font-weight=%22bold%22 fill=%22%230b0d14%22>in</text></svg>" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
7
+ <title>Castle Run — Incanto platformer</title>
8
+ <style>
9
+ html,
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ height: 100%;
14
+ overflow: hidden;
15
+ font-family: Arial, sans-serif;
16
+ background: #6aa9cf;
17
+ }
18
+ canvas {
19
+ display: block;
20
+ width: 100%;
21
+ height: 100%;
22
+ }
23
+ .loading-container {
24
+ position: fixed;
25
+ inset: 0;
26
+ z-index: 50;
27
+ display: flex;
28
+ flex-direction: column;
29
+ align-items: center;
30
+ justify-content: center;
31
+ background: #6aa9cf;
32
+ color: white;
33
+ }
34
+ .loading-title {
35
+ font-size: 1.5rem;
36
+ font-weight: bold;
37
+ margin-bottom: 1rem;
38
+ }
39
+ .spinner {
40
+ width: 30px;
41
+ height: 30px;
42
+ border: 3px solid rgba(255, 255, 255, 0.3);
43
+ border-radius: 50%;
44
+ border-top-color: white;
45
+ animation: spin 1s ease-in-out infinite;
46
+ }
47
+ @keyframes spin {
48
+ to {
49
+ transform: rotate(360deg);
50
+ }
51
+ }
52
+ .round-btn {
53
+ position: fixed;
54
+ bottom: 2rem;
55
+ right: 2rem;
56
+ z-index: 1001;
57
+ width: 88px;
58
+ height: 88px;
59
+ border: none;
60
+ border-radius: 9999px;
61
+ background: rgba(255, 209, 102, 0.45);
62
+ color: #1d2a44;
63
+ font-weight: bold;
64
+ font-size: 0.9rem;
65
+ cursor: pointer;
66
+ user-select: none;
67
+ -webkit-user-select: none;
68
+ touch-action: none;
69
+ transition: transform 0.1s;
70
+ }
71
+ .round-btn:active {
72
+ transform: scale(0.9);
73
+ }
74
+ </style>
75
+ </head>
76
+ <body>
77
+ <div class="loading-container" id="loading">
78
+ <h2 class="loading-title">Castle Run</h2>
79
+ <div class="spinner"></div>
80
+ </div>
81
+ <canvas id="game"></canvas>
82
+ <button type="button" id="btn-jump" class="round-btn">JUMP</button>
83
+ <script>
84
+ // agent8 embed harness: report the game size to a hosting iframe parent.
85
+ function postGameSize() {
86
+ window.parent.postMessage(
87
+ { type: 'GAME_SIZE_RESPONSE', width: document.body.scrollWidth, height: document.body.scrollHeight },
88
+ '*',
89
+ );
90
+ }
91
+ window.addEventListener('load', postGameSize);
92
+ window.addEventListener('resize', postGameSize);
93
+ window.addEventListener('message', (e) => {
94
+ if (e.data && e.data.type === 'REQUEST_GAME_SIZE') postGameSize();
95
+ });
96
+ </script>
97
+ <script type="module" src="/src/main.ts"></script>
98
+ </body>
99
+ </html>