incanto 0.72.0 → 0.74.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 (84) hide show
  1. package/bin/incanto-check.mjs +114 -42
  2. package/bin/incanto-verify.mjs +9 -2
  3. package/dist/2d.d.ts +29 -4
  4. package/dist/2d.js +3 -3
  5. package/dist/3d.d.ts +137 -15
  6. package/dist/3d.js +6 -6
  7. package/dist/{agent8-BqZ0_O1u.js → agent8-CHTT4unP.js} +1 -1
  8. package/dist/{audio-player-ioZ7VKC6.d.ts → audio-player-BOrk4eQx.d.ts} +1 -1
  9. package/dist/{behavior-Bf9P9oB2.d.ts → behavior-J1cmVah0.d.ts} +8 -0
  10. package/dist/{create-game-CmFAtLZp.js → create-game-BAiA-FjP.js} +7 -7
  11. package/dist/{create-game-C9rWqqAC.js → create-game-ssvGvP7W.js} +7 -7
  12. package/dist/debug.d.ts +1 -1
  13. package/dist/debug.js +1 -1
  14. package/dist/editor.js +965 -893
  15. package/dist/{environment-presets-BeHoaGhy.js → environment-presets-BkqlWewf.js} +277 -60
  16. package/dist/{gameplay-BlQe-M07.js → gameplay-Bxe1sMVT.js} +611 -39
  17. package/dist/gameplay.d.ts +141 -7
  18. package/dist/gameplay.js +2 -2
  19. package/dist/index.d.ts +6 -4
  20. package/dist/index.js +6 -6
  21. package/dist/{loader-CBfQzB6S.d.ts → loader-BAeWMYfF.d.ts} +1 -1
  22. package/dist/net.d.ts +2 -2
  23. package/dist/net.js +2 -2
  24. package/dist/{physics-2d-DrIgTlLk.js → physics-2d-C-SfvjzH.js} +94 -11
  25. package/dist/{physics-3d-BzOCw1VS.js → physics-3d-rOzeg890.js} +202 -15
  26. package/dist/{picking-C9McVJrO.js → picking-BMV34Pjl.js} +2 -2
  27. package/dist/react.d.ts +1 -1
  28. package/dist/react.js +1 -1
  29. package/dist/{register-5IYfooAN.js → register-CjociOtt.js} +19 -4
  30. package/dist/{register-C8HuRkOf.js → register-DRQyZKGU.js} +45 -9
  31. package/dist/{replay-datiAE-b.js → replay-B6BbgGM_.js} +42 -6
  32. package/dist/{replay-CBEChqXq.d.ts → replay-wZK5Ok5c.d.ts} +1 -1
  33. package/dist/{save-slots-CEuJPUle.js → save-slots-CEUGeLuB.js} +11 -2
  34. package/dist/{split-screen-Bm5DTElG.js → split-screen-BxZ5rBe7.js} +4 -4
  35. package/dist/{split-screen-DkqLng6q.d.ts → split-screen-D6sx8z3Z.d.ts} +2 -2
  36. package/dist/{sprite-animation-CqR2o3SA.js → sprite-animation-Bz3DHD_4.js} +1 -1
  37. package/dist/{src-DRSsRleD.js → src-CqtvYtSN.js} +1 -1
  38. package/dist/{test-Djuq3Vhq.js → test-CEl0y2Sw.js} +162 -30
  39. package/dist/test.d.ts +56 -5
  40. package/dist/test.js +2 -2
  41. package/dist/{touch-BnCyPA0G.js → touch-CioTZB-y.js} +1 -1
  42. package/dist/vite.js +3 -3
  43. package/editor/assets/{agent8-CMKz2cax.js → agent8-BlzturGi.js} +1 -1
  44. package/editor/assets/{debug-DLrQm-az.js → debug-BMUi8usj.js} +1 -1
  45. package/editor/assets/{index-BEKuxYcE.js → index-CrUCQoaB.js} +53 -53
  46. package/editor/index.html +1 -1
  47. package/package.json +1 -1
  48. package/schemas/scene.schema.json +634 -10
  49. package/skills/incanto-3d-character.md +113 -5
  50. package/skills/incanto-assets.md +9 -3
  51. package/skills/incanto-behaviors-and-scripts.md +26 -2
  52. package/skills/incanto-building-2d-games.md +9 -0
  53. package/skills/incanto-building-3d-games.md +45 -0
  54. package/skills/incanto-editor.md +6 -1
  55. package/skills/incanto-environment.md +7 -2
  56. package/skills/incanto-game-feel.md +16 -11
  57. package/skills/incanto-gameplay-behaviors.md +179 -16
  58. package/skills/incanto-node-reference.md +124 -45
  59. package/skills/incanto-physics-and-input.md +67 -5
  60. package/skills/incanto-scene-json-authoring.md +9 -3
  61. package/skills/incanto-verifying-your-game.md +19 -2
  62. package/templates-app/beacon-isle-3d/PROJECT/Status.md +8 -0
  63. package/templates-app/beacon-isle-3d/PROJECT/Structure.md +3 -0
  64. package/templates-app/beacon-isle-3d/package.json +1 -1
  65. package/templates-app/beacon-isle-3d/src/game.scene.json +136 -0
  66. package/templates-app/beacon-isle-3d/verify.ts +144 -0
  67. package/templates-app/molehill-2d/package.json +1 -1
  68. package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
  69. package/templates-app/platformer-2d/PROJECT/Structure.md +3 -0
  70. package/templates-app/platformer-2d/package.json +1 -1
  71. package/templates-app/platformer-2d/src/game.scene.json +126 -0
  72. package/templates-app/platformer-2d/verify.ts +54 -0
  73. package/templates-app/star-survivor/package.json +1 -1
  74. package/templates-app/tps-3d/PROJECT/Context.md +1 -1
  75. package/templates-app/tps-3d/PROJECT/Status.md +40 -1
  76. package/templates-app/tps-3d/PROJECT/Structure.md +19 -0
  77. package/templates-app/tps-3d/package.json +1 -1
  78. package/templates-app/tps-3d/src/behaviors.ts +9 -0
  79. package/templates-app/tps-3d/src/cover.scene.json +26 -0
  80. package/templates-app/tps-3d/src/game.scene.json +331 -75
  81. package/templates-app/tps-3d/src/main.ts +14 -0
  82. package/templates-app/tps-3d/src/pillar.scene.json +25 -0
  83. package/templates-app/tps-3d/verify.ts +582 -3
  84. package/templates-app/village-quest-3d/package.json +1 -1
@@ -1350,6 +1350,132 @@
1350
1350
  }
1351
1351
  ],
1352
1352
  "uid": "n_i3xs3gnb3cyrar1u"
1353
+ },
1354
+ {
1355
+ "name": "CourseFloor",
1356
+ "type": "StaticBody2D",
1357
+ "uid": "n_9p55c0wu33vbe194",
1358
+ "props": { "position": [3300, 520], "collider": { "shape": "rect", "size": [900, 120] } },
1359
+ "children": [
1360
+ {
1361
+ "name": "Skin",
1362
+ "type": "ColorRect2D",
1363
+ "uid": "n_56chxbokw5okl2ar",
1364
+ "props": { "size": [900, 120], "color": "#6b5638", "anchor": [0.5, 0.5] }
1365
+ }
1366
+ ]
1367
+ },
1368
+ {
1369
+ "name": "Trampoline",
1370
+ "type": "StaticBody2D",
1371
+ "uid": "n_vqjbknb54e0zq787",
1372
+ "props": {
1373
+ "position": [3000, 455],
1374
+ "collider": { "shape": "rect", "size": [120, 10] },
1375
+ "restitution": 1
1376
+ },
1377
+ "children": [
1378
+ {
1379
+ "name": "Skin",
1380
+ "type": "ColorRect2D",
1381
+ "uid": "n_bed7i5blyg8p63ij",
1382
+ "props": { "size": [120, 10], "color": "#3b82f6", "anchor": [0.5, 0.5] }
1383
+ }
1384
+ ]
1385
+ },
1386
+ {
1387
+ "name": "Bouncer",
1388
+ "type": "RigidBody2D",
1389
+ "uid": "n_8vd73zp82idtm5gx",
1390
+ "props": { "position": [3000, 150], "collider": { "shape": "circle", "radius": 14 } },
1391
+ "children": [
1392
+ {
1393
+ "name": "Skin",
1394
+ "type": "ColorRect2D",
1395
+ "uid": "n_n18pdy731ltnmb3l",
1396
+ "props": { "size": [28, 28], "color": "#f4f1de", "anchor": [0.5, 0.5] }
1397
+ }
1398
+ ]
1399
+ },
1400
+ {
1401
+ "name": "Ice",
1402
+ "type": "StaticBody2D",
1403
+ "uid": "n_zlp89ikkls0xkbwn",
1404
+ "props": {
1405
+ "position": [3400, 455],
1406
+ "collider": { "shape": "rect", "size": [400, 10] },
1407
+ "friction": 0
1408
+ },
1409
+ "children": [
1410
+ {
1411
+ "name": "Skin",
1412
+ "type": "ColorRect2D",
1413
+ "uid": "n_opic7894wramqynx",
1414
+ "props": { "size": [400, 10], "color": "#bfe9ff", "anchor": [0.5, 0.5] }
1415
+ }
1416
+ ]
1417
+ },
1418
+ {
1419
+ "name": "Sled",
1420
+ "type": "RigidBody2D",
1421
+ "uid": "n_nhcrprd8no2pzb9i",
1422
+ "props": {
1423
+ "position": [3215, 435],
1424
+ "collider": { "shape": "circle", "radius": 14 },
1425
+ "linearVelocity": [260, 0]
1426
+ },
1427
+ "children": [
1428
+ {
1429
+ "name": "Skin",
1430
+ "type": "ColorRect2D",
1431
+ "uid": "n_y2owjzo7khqchken",
1432
+ "props": { "size": [28, 28], "color": "#8c6a3f", "anchor": [0.5, 0.5] }
1433
+ }
1434
+ ]
1435
+ },
1436
+ {
1437
+ "name": "Hound",
1438
+ "type": "CharacterBody2D",
1439
+ "uid": "n_kmnt12lx070t311c",
1440
+ "props": { "position": [3650, 440], "collider": { "shape": "rect", "size": [30, 40] } },
1441
+ "children": [
1442
+ {
1443
+ "name": "AI",
1444
+ "type": "Node2D",
1445
+ "uid": "n_juz8aw6j311cstbs",
1446
+ "script": {
1447
+ "name": "Chase",
1448
+ "props": {
1449
+ "target": "/root/Player",
1450
+ "speed": 160,
1451
+ "stopRange": 24,
1452
+ "loseRange": 500,
1453
+ "moveParent": true,
1454
+ "ground": true
1455
+ }
1456
+ }
1457
+ },
1458
+ {
1459
+ "name": "Skin",
1460
+ "type": "ColorRect2D",
1461
+ "uid": "n_k5h72ttrivo8vy0m",
1462
+ "props": { "size": [30, 40], "color": "#6b4d8a", "anchor": [0.5, 0.5] }
1463
+ }
1464
+ ]
1465
+ },
1466
+ {
1467
+ "name": "Perch",
1468
+ "type": "StaticBody2D",
1469
+ "uid": "n_t6vi97e88isgglef",
1470
+ "props": { "position": [3650, 300], "collider": { "shape": "rect", "size": [160, 20] } },
1471
+ "children": [
1472
+ {
1473
+ "name": "Skin",
1474
+ "type": "ColorRect2D",
1475
+ "uid": "n_m7qcxz6z1sc6rfbv",
1476
+ "props": { "size": [160, 20], "color": "#9aa3bd", "anchor": [0.5, 0.5] }
1477
+ }
1478
+ ]
1353
1479
  }
1354
1480
  ],
1355
1481
  "uid": "n_d9t51gs0vhpnp2jj"
@@ -273,3 +273,57 @@ interface Player {
273
273
  }
274
274
 
275
275
  console.log(process.exitCode ? '\nVERIFY FAILED' : '\nVERIFY OK — Castle Run plays end to end');
276
+
277
+ // ---- TWIN: this round's 3D surfaces, in two dimensions ----------------------------
278
+ // StaticBody2D.restitution/friction (#1295) and Chase.ground (#1297) were built
279
+ // with the 3D case in hand and 2D tests beside it; a course past the last
280
+ // ground slab composes them in the 2D flagship and measures each. y is DOWN.
281
+ {
282
+ type Body = { position: number[] };
283
+ const bouncerY: number[] = [];
284
+ let sledX = 0;
285
+ let houndY = 0;
286
+ let houndX = 0;
287
+ const result = await runScript(sceneJson, {
288
+ behaviors,
289
+ durationMs: 4200,
290
+ steps: [
291
+ ...Array.from({ length: 30 }, (_, i) => ({
292
+ atMs: 100 + i * 100,
293
+ do: (ctx: { getNode(p: string): unknown }) => {
294
+ bouncerY.push((ctx.getNode('Bouncer') as Body).position[1] ?? 0);
295
+ },
296
+ })),
297
+ {
298
+ atMs: 3100,
299
+ do: (ctx) => {
300
+ sledX = (ctx.getNode('Sled') as unknown as Body).position[0] ?? 0;
301
+ // the player on the perch above the hound: a ground chaser closes in below, never up
302
+ const player = ctx.getNode('Player') as unknown as Body;
303
+ player.position = [3650, 260];
304
+ for (let i = 0; i < 120; i++) ctx.engine.step();
305
+ const hound = ctx.getNode('Hound') as unknown as Body;
306
+ houndX = hound.position[0] ?? 0;
307
+ houndY = hound.position[1] ?? 0;
308
+ },
309
+ },
310
+ ],
311
+ });
312
+ ok('runScript reported no failures (TWIN path)', result.ok);
313
+ let touch = bouncerY.findIndex((y, i) => i > 0 && y < (bouncerY[i - 1] ?? 0));
314
+ if (touch < 1) touch = 1;
315
+ const low = bouncerY[touch - 1] ?? 0;
316
+ const rebound = low - Math.min(...bouncerY.slice(touch));
317
+ ok(
318
+ `StaticBody2D restitution 1: a dead ball climbs back ${rebound.toFixed(0)} px of its 300 px drop`,
319
+ rebound > 180,
320
+ );
321
+ ok(
322
+ `StaticBody2D friction 0: the sled slides to x ${sledX.toFixed(0)} across the ice (from 3215)`,
323
+ sledX > 3450,
324
+ );
325
+ ok(
326
+ `Chase.ground: the hound stays on the floor (y ${houndY.toFixed(0)}, floor 440) under a player on the perch, ${Math.abs(houndX - 3650).toFixed(0)} px off its x`,
327
+ houndY > 400 && Math.abs(houndX - 3650) < 60,
328
+ );
329
+ }
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
- "incanto": "^0.72.0",
14
+ "incanto": "^0.74.0",
15
15
  "three": "^0.184.0"
16
16
  },
17
17
  "devDependencies": {
@@ -5,7 +5,7 @@
5
5
  "Vanguard" — a 3D THIRD-PERSON shooter. The camera rides over your soldier's
6
6
  shoulder (you SEE your character); the crosshair is where the hitscan rifle
7
7
  fires. Move with WASD, look with the mouse (click locks the pointer), Shift to
8
- sprint, Space to jump, click / F to fire, R to reload, Enter to restart. Survive and clear three
8
+ sprint, Space to jump (tap for a short hop, hold for the full jump, press again in the air for a second), click / F to fire, R to reload, Enter to restart. Survive and clear three
9
9
  escalating waves of chasing enemies to **SECURE THE AREA**; let them drain your
10
10
  HP to 0 and the **MISSION FAILS**.
11
11
 
@@ -7,7 +7,13 @@
7
7
  sun + fill light.
8
8
  - **Player** (RigidBody3D, group `player`, `Health` max 100 / 0.6s i-frames):
9
9
  `Controller` (CharacterController3D `view: 'free'`, camDistance 4.2, eyeHeight
10
- 1.1, maxSpeed 4.2, `Shoot` script); a real GLB `Skin` (ModelInstance3D
10
+ 1.1, maxSpeed 4.2, `Shoot` script and the whole game-feel table: coyote
11
+ 0.12 s, jump buffer 0.15 s, `jumpCutMultiplier` 0.45 for a variable-height
12
+ jump, `maxJumps` 2, `airControl` 0.5, `fallGravity` 3; `bun run verify`
13
+ measures each one rather than reading it); a real GLB `Skin` whose UPPER
14
+ BODY plays `$anims/shoot` on every shot (`animationUpper`) while the legs
15
+ keep the controller's run/idle clip — the two-layer animation the 3D
16
+ character skill documents and no example had composed; a real GLB `Skin` (ModelInstance3D
11
17
  `base-model.glb` + mixamo idle/walk/run/jump clips); a `Muzzle` Node3D
12
18
  (shot/kill/empty SFX + flash light).
13
19
  - **Spawner** (WaveSpawner, 3 waves of 4/6/8) cloning a hidden `EnemyTemplate`
@@ -39,3 +45,36 @@
39
45
  (the CDN set is idle/walk/run/jump).
40
46
  - No cover/peek mechanic, no projectile-drop weapons (hitscan only). Co-op
41
47
  multiplayer lives in the sibling `examples/tps-3d-multiplay`.
48
+
49
+ ## stepHeight and jumpVelocity (2026-09-04)
50
+
51
+ The controller declares `stepHeight: 0.4` and `jumpVelocity: 5`; a 0.3 m `Curb` and a
52
+ 0.9 m `Ledge` sit at z −12 (away from the fight) and `verify.ts` walks the player into
53
+ both — over the one (y 0.84 → 1.14), stopped by the other — and compares the jump apex
54
+ against the same scene at `jumpVelocity 4` (1.26 u vs 0.81 u).
55
+
56
+ ## slopeLimitDeg, and the chaser that flew (2026-09-04)
57
+
58
+ The enemy prefab declares `slopeLimitDeg: 50` and `stepHeight: 0.7`; a 40° `Hill` and a
59
+ 70° `Cliff` (each 4 m of slab onto a plateau) sit at the far corner (x 7–22, z −20).
60
+ `verify.ts` puts one enemy at the foot of each with the player on top: it climbs the
61
+ hill and reaches (rose 2.64 m), and is stopped by the cliff (rose 0.32 m, 3.2 m short).
62
+ Composing this found `Chase` flying its body straight at an elevated target; a
63
+ `CharacterBody3D` chaser now walks (`Chase.ground`), and the contact run went from
64
+ 3 hits to 11 once enemies climbed the 0.6 m Ramp by `stepHeight` rather than by the
65
+ pile behind them.
66
+
67
+ ## cameraCollision and platformCarry (2026-09-04)
68
+
69
+ Both declared on the controller; the arena gained a `Lift` (StaticBody3D walked by
70
+ `Patrol`, pingpong x −18 → −10). `verify.ts` asserts: a wall behind the player closes the
71
+ camera to 0.45 m (4.2 in the open); standing still on the lift for two seconds, it travels
72
+ 4 m and the rider drifts under 0.25 m. `floatHeight` was tried and taken back out — the spring sags
73
+ ~0.13 m, so 0.05 rests exactly where 0.01 does (0.84); the number is in the skill.
74
+
75
+ ## Catch (2026-09-04)
76
+
77
+ `Player/Catch` (a `Respawn`, auto line) puts back a player who leaves the arena: the Hill
78
+ and Cliff plateaus, the Lift and a double jump reach over the 3 m walls (24 playtests fell
79
+ 3 times before it, none after). `verify.ts` drops the player over Wall1 and asserts the catch.
80
+
@@ -42,3 +42,22 @@ Headless proof (`bun run verify`) that the loop plays end to end — move, waves
42
42
  Chase closing in, the autopilot aiming + firing the hitscan to clear all waves
43
43
  (WIN), and an enemy hitbox draining the player to 0 (LOSE) — via `incanto/test`'s
44
44
  `runScript` at a seeded fixed timestep.
45
+
46
+ ## Prefabs
47
+
48
+ `src/cover.scene.json` and `src/pillar.scene.json` are the two cover blocks and
49
+ two pillars of the arena, placed from `game.scene.json` with `instance:` and a
50
+ `position` override each — the first 3D prefabs in the repo. `main.ts` hands
51
+ the loader every `*.scene.json` beside it through vite's `import.meta.glob`;
52
+ `verify.ts` imports the two by name (bun has no glob). Every CLI resolves a
53
+ prefab relative to the scene on its own; the harness and the game are the two
54
+ places that say so themselves.
55
+
56
+ - `Arena/Curb`, `Arena/Ledge` — a 0.3 m step the player climbs and a 0.9 m face that stops it,
57
+ the harness's fixtures for `stepHeight`.
58
+ - `Arena/Hill`, `Arena/HillTop`, `Arena/Cliff`, `Arena/CliffTop` — a 40° slope an enemy climbs and a
59
+ 70° face it cannot, the harness's fixtures for `slopeLimitDeg`.
60
+ - `Arena/Lift` — a moving platform (`Patrol` on a `StaticBody3D`), the harness's fixture for
61
+ `platformCarry`.
62
+ - `Player/Catch` — a `Respawn` that returns a player who got over the walls to the spawn.
63
+
@@ -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.72.0",
16
+ "incanto": "^0.74.0",
17
17
  "three": "^0.184.0"
18
18
  },
19
19
  "devDependencies": {
@@ -140,12 +140,16 @@ export class Shoot extends Behavior {
140
140
  override onReady(): void {
141
141
  const skin = this.player.getNodeOrNull('Skin') as ModelInstance3D | null;
142
142
  if (!skin) return;
143
+ this.skin = skin;
143
144
  this.controller.on('movementStateChanged', (state) => {
144
145
  const clip = STATE_CLIPS[state as string];
145
146
  if (clip) skin.animation = clip;
146
147
  });
147
148
  }
148
149
 
150
+ /** The visible soldier — the upper-body layer plays the shot on it. */
151
+ private skin: ModelInstance3D | null = null;
152
+
149
153
  override update(dt: number): void {
150
154
  this.clock += dt * 1000;
151
155
 
@@ -192,6 +196,11 @@ export class Shoot extends Behavior {
192
196
  this.ammo--;
193
197
  this.muzzle('ShotSfx')?.play();
194
198
  this.flashUntil = this.clock + FLASH_MS;
199
+ // The shot plays on the UPPER BODY only — `animation` (idle/run, driven by
200
+ // the controller) keeps the legs going, so the soldier fires while running
201
+ // instead of freezing mid-stride. A one-shot: the layer clears itself when
202
+ // the clip ends, and a shot fired before that restarts it.
203
+ if (this.skin) this.skin.animationUpper = '$anims/shoot';
195
204
 
196
205
  const eye = this.eye();
197
206
  const fwd = this.forward();
@@ -0,0 +1,26 @@
1
+ {
2
+ "format": 1,
3
+ "type": "scene",
4
+ "dimension": "3d",
5
+ "name": "Cover",
6
+ "root": {
7
+ "name": "Cover",
8
+ "type": "StaticBody3D",
9
+ "props": { "collider": { "shape": "box", "size": [2, 1.2, 2] } },
10
+ "children": [
11
+ {
12
+ "name": "Skin",
13
+ "type": "MeshInstance3D",
14
+ "props": {
15
+ "mesh": "box",
16
+ "size": [2, 1.2, 2],
17
+ "material": { "color": "#5b6678", "roughness": 0.85, "metalness": 0.1 },
18
+ "castShadow": true,
19
+ "receiveShadow": true
20
+ },
21
+ "uid": "n_wgm7e7zg55s8ugcn"
22
+ }
23
+ ],
24
+ "uid": "n_z4ycq2ses8kh87zd"
25
+ }
26
+ }