incanto 0.56.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.
- package/README.md +6 -4
- package/bin/incanto-check.mjs +27 -0
- package/bin/incanto-new.mjs +29 -7
- package/bin/incanto-playtest.mjs +28 -2
- package/bin/incanto-verify.mjs +96 -17
- package/bin/incanto.mjs +106 -0
- package/dist/2d.d.ts +59 -7
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +34 -4
- package/dist/3d.js +5 -5
- package/dist/{behavior-CyQoSu4n.d.ts → behavior-l08AEbq9.d.ts} +5 -0
- package/dist/{create-game-YvaNWhe6.js → create-game-BZwWJIns.js} +146 -7
- package/dist/{create-game-CqJkfMzm.js → create-game-D10bU5_J.js} +83 -78
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-MNLMAcbz.js → duplicate-BPLZDZpd.js} +1 -1
- package/dist/{environment-presets-DLHqgNAF.js → environment-presets-CvvQr_bJ.js} +106 -24
- package/dist/{frame-report-njybhZon.js → frame-report-BSMny7oe.js} +1 -1
- package/dist/{frame-report-DZ70IY26.d.ts → frame-report-DCnHFmto.d.ts} +1 -1
- package/dist/{gameplay-C_SPzmUe.js → gameplay-BVphcxmE.js} +89 -13
- package/dist/gameplay.d.ts +26 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +45 -5
- package/dist/index.js +8 -9
- package/dist/{loader-DhI1jFW_.d.ts → loader-BbEMTuWg.d.ts} +1 -1
- package/dist/{loader-BTkHYrQn.js → loader-BcrRSjxB.js} +682 -682
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{pathfinding-CXGCpRQe.d.ts → pathfinding-mEN4V1CU.d.ts} +1 -1
- package/dist/{physics-2d-CfWAggJ1.js → physics-2d-EqA-jddf.js} +4 -3
- package/dist/{physics-3d-NzDwWPrF.js → physics-3d-Dnz4fsXX.js} +5 -4
- package/dist/quiet-rapier-BAJ4K94N.js +46 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-en63AEZO.js → register-C6ZBFRjd.js} +63 -60
- package/dist/{register-p48lHE2o.js → register-Ch70uByv.js} +1615 -1502
- package/dist/{replay-ePMz26jw.d.ts → replay-Dw6gMlYA.d.ts} +1 -1
- package/dist/{replay-t1pP0gQg.js → replay-s7I2GstT.js} +31 -11
- package/dist/sheet-grid-BT6N_Bjs.js +59 -0
- package/dist/{split-screen-BsdOHbzP.d.ts → split-screen-B0baBwxI.d.ts} +5 -1
- package/dist/{split-screen-CSb_uZ6W.js → split-screen-DLsUrleX.js} +14 -3
- package/dist/{sprite-animation-7qvUxF6Z.js → sprite-animation-C0wXLBZJ.js} +8 -4
- package/dist/{src-51GA-9m-.js → src-C1J09Op6.js} +55 -2
- package/dist/{teardown-C7uVSJvx.js → teardown-Cs113S9F.js} +74 -2
- package/dist/{test-Bu6SPNsV.js → test-ZBga8kQ9.js} +60 -25
- package/dist/test.d.ts +5 -5
- package/dist/test.js +2 -2
- package/dist/{touch-BnMyy9tr.js → touch-DESwnpOc.js} +173 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +3 -3
- package/editor/assets/{agent8-X0Nesj-k.js → agent8-BQQjE9UQ.js} +1 -1
- package/editor/assets/{debug-TXqu-ftX.js → debug-C9UCsXBS.js} +1 -1
- package/editor/assets/{index-DTdwKKIv.js → index-CBgfM3WD.js} +62 -62
- package/editor/index.html +1 -1
- package/package.json +2 -1
- package/skills/incanto-assets.md +5 -1
- package/skills/incanto-audio.md +7 -1
- package/skills/incanto-building-2d-games.md +13 -0
- package/skills/incanto-building-3d-games.md +3 -3
- package/skills/incanto-gameplay-behaviors.md +2 -1
- package/skills/incanto-hud.md +6 -0
- package/skills/incanto-performance.md +5 -0
- package/skills/incanto-physics-and-input.md +14 -0
- package/skills/incanto-playtesting.md +19 -5
- package/skills/incanto-save-slots.md +2 -1
- package/skills/incanto-verifying-your-game.md +15 -4
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +7 -6
- package/templates-app/platformer-2d/PROJECT/Context.md +70 -0
- package/templates-app/platformer-2d/PROJECT/Requirements.md +63 -0
- package/templates-app/platformer-2d/PROJECT/Status.md +60 -0
- package/templates-app/platformer-2d/PROJECT/Structure.md +77 -0
- package/templates-app/platformer-2d/docs/project-2d-rules.md +61 -0
- package/templates-app/platformer-2d/index.html +99 -0
- package/templates-app/platformer-2d/package.json +23 -0
- package/templates-app/platformer-2d/src/behaviors.ts +541 -0
- package/templates-app/platformer-2d/src/game.scene.json +2061 -0
- package/templates-app/platformer-2d/src/main.ts +68 -0
- package/templates-app/platformer-2d/tsconfig.json +13 -0
- package/templates-app/platformer-2d/verify.ts +275 -0
- package/templates-app/platformer-2d/vite.config.ts +12 -0
- package/templates-app/star-survivor/PROJECT/Context.md +55 -0
- package/templates-app/star-survivor/PROJECT/Requirements.md +47 -0
- package/templates-app/star-survivor/PROJECT/Status.md +44 -0
- package/templates-app/star-survivor/PROJECT/Structure.md +63 -0
- package/templates-app/star-survivor/docs/project-2d-rules.md +53 -0
- package/templates-app/star-survivor/index.html +232 -0
- package/templates-app/star-survivor/package.json +23 -0
- package/templates-app/star-survivor/src/behaviors.ts +624 -0
- package/templates-app/star-survivor/src/game.scene.json +464 -0
- package/templates-app/star-survivor/src/main.ts +49 -0
- package/templates-app/star-survivor/tsconfig.json +13 -0
- package/templates-app/star-survivor/verify.ts +193 -0
- package/templates-app/star-survivor/vite.config.ts +12 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +6 -3
- package/templates-app/tps-3d/verify.ts +17 -1
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/grove.scene.json +14 -13
- package/templates-app/village-quest-3d/src/village.scene.json +5 -5
- package/dist/log-report-CPFm4OXf.js +0 -173
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-
|
|
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.
|
|
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",
|
package/skills/incanto-assets.md
CHANGED
|
@@ -182,7 +182,11 @@ frame 40. Those frames draw whatever is at the wrong end of the sheet.
|
|
|
182
182
|
```
|
|
183
183
|
|
|
184
184
|
The suggested sizes are the ones that actually divide your image, so the fix is
|
|
185
|
-
usually in the message.
|
|
185
|
+
usually in the message. **All three nodes that cut a grid out of an image check
|
|
186
|
+
it** — `AnimatedSprite2D`, `AnimatedSprite3D` and `TileMap2D` — because they do
|
|
187
|
+
the same floor-division and it fails the same silent way. An
|
|
188
|
+
`AnimatedSprite3D` with a sheet and no `frameWidth`/`frameHeight` also says so
|
|
189
|
+
rather than drawing a mob-shaped hole. `bunx incanto-assets info <name>` prints the real frame
|
|
186
190
|
size for a built-in, and the editor's 📚 picker fills it in from the catalog's
|
|
187
191
|
metadata — or leaves it BLANK when the catalog does not carry it, because an
|
|
188
192
|
invented frame size is exactly this bug.
|
package/skills/incanto-audio.md
CHANGED
|
@@ -350,6 +350,11 @@ background loop authored in scene JSON. Wire `crossfadeTo` from gameplay:
|
|
|
350
350
|
this.engine.music.crossfadeTo('/audio/boss.mp3', 3);
|
|
351
351
|
```
|
|
352
352
|
|
|
353
|
+
**A track that will never play says so.** A 404 or an undecodable file lands in
|
|
354
|
+
the engine log (`[incanto] music '/audio/boss.mp3' will not play — could not
|
|
355
|
+
load.`) instead of being indistinguishable from a game with no music. The
|
|
356
|
+
autoplay gate is still just the gate — a track waiting for a tap is not an error.
|
|
357
|
+
|
|
353
358
|
> Large music files are NOT bundled — reference them by URL (see §4).
|
|
354
359
|
|
|
355
360
|
**Headless the state machine still runs and `current` still updates** — nothing
|
|
@@ -448,7 +453,8 @@ bricking the game.
|
|
|
448
453
|
### Quality tiers
|
|
449
454
|
|
|
450
455
|
```ts
|
|
451
|
-
import { qualityEnvironment, readDeviceHints, suggestQuality
|
|
456
|
+
import { qualityEnvironment, readDeviceHints, suggestQuality } from 'incanto';
|
|
457
|
+
import { setEnvironment3D } from 'incanto/3d'; // it applies a 3D environment
|
|
452
458
|
|
|
453
459
|
const tier = engine.settings.get('quality') ?? suggestQuality(readDeviceHints());
|
|
454
460
|
setEnvironment3D(engine, qualityEnvironment(tier));
|
|
@@ -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
|
|
24
|
-
bunx incanto
|
|
25
|
-
bunx incanto
|
|
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
|
|
@@ -20,7 +20,8 @@ with `gameplay: false`, or register a subset yourself:
|
|
|
20
20
|
import { registerGameplayBehaviors } from 'incanto/gameplay';
|
|
21
21
|
registerGameplayBehaviors(); // all of them (default in createGame)
|
|
22
22
|
|
|
23
|
-
import {
|
|
23
|
+
import { registerBehavior } from 'incanto';
|
|
24
|
+
import { Health, Pickup } from 'incanto/gameplay';
|
|
24
25
|
registerBehavior('Health', Health); // or pick just what you use
|
|
25
26
|
```
|
|
26
27
|
|
package/skills/incanto-hud.md
CHANGED
|
@@ -167,6 +167,12 @@ widget use `UiButton`'s `pressed`.
|
|
|
167
167
|
Setting `.value` from a behavior updates the control and does **NOT** re-emit
|
|
168
168
|
`changed` — restoring a saved setting must not fire the handler that saved it.
|
|
169
169
|
|
|
170
|
+
**Every prop is live.** `color`, `size`, `width`, `background`, `label` and
|
|
171
|
+
`anchor` are re-read each frame, so flashing the score red, growing a health bar
|
|
172
|
+
or moving a widget to another corner all work from a behavior — and from the
|
|
173
|
+
editor's inspector, which is the same code path. They used to be baked in when
|
|
174
|
+
the widget was built and could never be changed afterwards.
|
|
175
|
+
|
|
170
176
|
## Playable on a controller (focus navigation)
|
|
171
177
|
|
|
172
178
|
A menu you can only click is not playable on a gamepad, and "add controller
|
|
@@ -92,6 +92,11 @@ that built a mesh), dispose it in the behavior's `onExitTree`.
|
|
|
92
92
|
| `renderScale` | `0.25`–`2` | image softness only | **yes** |
|
|
93
93
|
| antialiasing | follows the tier | jagged edges at `low` | **no — boot only** |
|
|
94
94
|
|
|
95
|
+
`maxFps` and `renderScale` apply in **2D as well as 3D** now — they persisted for
|
|
96
|
+
every game and were applied by the 3D boot only, so a 2D options menu saved a
|
|
97
|
+
number and changed nothing. `quality` remains a 3D tier (there are no shadows,
|
|
98
|
+
bloom or clouds to drop in 2D).
|
|
99
|
+
|
|
95
100
|
### quality
|
|
96
101
|
|
|
97
102
|
**Water follows the tier too, and it is the biggest single saving.**
|
|
@@ -192,6 +192,20 @@ engine.input.pressAction('jump'); // one justPressed frame, held until
|
|
|
192
192
|
engine.input.releaseAction('jump'); // …released (one justReleased frame)
|
|
193
193
|
engine.input.setActionVector('move', 1, 0); // analog vector2; (0, 0) clears
|
|
194
194
|
```
|
|
195
|
+
|
|
196
|
+
**An action name you typed that does not exist says so.** A controller tolerates
|
|
197
|
+
a missing action — a game with no sprint key must not crash — but a value you
|
|
198
|
+
NAMED and never declared is a typo, and it used to be indistinguishable from the
|
|
199
|
+
tolerated case:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
[incanto] /Game/Player/Ctl: Unknown input action 'movement'.
|
|
203
|
+
Declared actions: [move, jump]. The character will not move.
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Once per node and action, and only when the prop differs from its default —
|
|
207
|
+
leaving `sprintAction` alone in a game with no sprint key is the ordinary case
|
|
208
|
+
and stays silent.
|
|
195
209
|
Injected state combines with key state (vectors clamped to unit length).
|
|
196
210
|
|
|
197
211
|
## Patterns
|
|
@@ -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` |
|
|
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**
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
|
@@ -146,7 +146,8 @@ The engine does not route scenes — deliberately, because only your game knows
|
|
|
146
146
|
what a key means. What it does is record the key, so the routing is a lookup:
|
|
147
147
|
|
|
148
148
|
```ts
|
|
149
|
-
import {
|
|
149
|
+
import { loadScene } from 'incanto';
|
|
150
|
+
import { createGame2D } from 'incanto/2d';
|
|
150
151
|
import level1 from './level1.scene.json';
|
|
151
152
|
import level2 from './level2.scene.json';
|
|
152
153
|
|
|
@@ -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
|
|
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 —
|
|
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:
|
|
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
|
|
@@ -399,6 +400,10 @@ shouting the same thing every frame has one problem, not six hundred.
|
|
|
399
400
|
|
|
400
401
|
### `bunx incanto-frame` — what is actually on screen
|
|
401
402
|
|
|
403
|
+
Works for **2D and 3D** games alike (as does `incanto-logs`, and `game.frame()`).
|
|
404
|
+
Both used to be answered by the 3D boot only, so a 2D preview that was open and
|
|
405
|
+
drawing reported "no page answered".
|
|
406
|
+
|
|
402
407
|
The rung above framing. `framing` PREDICTS what the camera should see from the
|
|
403
408
|
scene graph; this measures what the GPU drew — the only one that can catch a
|
|
404
409
|
black screen, a material that failed, or a shader drawing where it should not.
|
|
@@ -769,7 +774,13 @@ bunx incanto-playtest src/game.scene.json --runs 20 --seconds 60
|
|
|
769
774
|
A seeded bot plays it headlessly — reading the scene's own `input{}` for its
|
|
770
775
|
controls — and reports the win rate, destinations it could never reach, wires
|
|
771
776
|
that never fired, falls out of the world, and whether anything can hurt the
|
|
772
|
-
player at all. Failing runs come back as replays.
|
|
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.
|
|
773
784
|
|
|
774
785
|
Read `incanto-playtesting.md` before shipping a level.
|
|
775
786
|
|
|
@@ -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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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.
|