incanto 0.71.0 → 0.73.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 (92) hide show
  1. package/bin/incanto-check.mjs +114 -42
  2. package/bin/incanto-editor.mjs +15 -9
  3. package/bin/incanto-feel.mjs +11 -1
  4. package/bin/incanto-play.mjs +11 -1
  5. package/bin/incanto-playtest.mjs +11 -1
  6. package/bin/incanto-verify.mjs +9 -2
  7. package/dist/2d.d.ts +29 -4
  8. package/dist/2d.js +3 -3
  9. package/dist/3d.d.ts +137 -15
  10. package/dist/3d.js +6 -6
  11. package/dist/{agent8-Cfmd3ar_.js → agent8-CHTT4unP.js} +1 -1
  12. package/dist/{audio-player-L8yccdMP.d.ts → audio-player-BOrk4eQx.d.ts} +1 -1
  13. package/dist/{behavior-Dcz0fr1S.d.ts → behavior-J1cmVah0.d.ts} +9 -1
  14. package/dist/{create-game-LCHBLfA4.js → create-game-CAu-ucHg.js} +8 -7
  15. package/dist/{create-game-CH4kt78v.js → create-game-CbTLFjis.js} +37 -13
  16. package/dist/debug.d.ts +1 -1
  17. package/dist/debug.js +1 -1
  18. package/dist/editor.js +1036 -930
  19. package/dist/{environment-presets-99BzP_L-.js → environment-presets-BsFJM3v0.js} +277 -60
  20. package/dist/{gameplay-CNULJvwh.js → gameplay-CRte0OVf.js} +589 -35
  21. package/dist/gameplay.d.ts +136 -4
  22. package/dist/gameplay.js +2 -2
  23. package/dist/index.d.ts +6 -4
  24. package/dist/index.js +6 -6
  25. package/dist/{loader-8-IHvbKD.d.ts → loader-BAeWMYfF.d.ts} +10 -3
  26. package/dist/net.d.ts +2 -2
  27. package/dist/net.js +2 -2
  28. package/dist/{physics-2d-FhrXUc6B.js → physics-2d-CovokK7s.js} +94 -11
  29. package/dist/{physics-3d-CFEGxBXZ.js → physics-3d-BiK6Emwn.js} +202 -15
  30. package/dist/{picking-DVo7fI13.js → picking-Dtd8JInh.js} +2 -2
  31. package/dist/react.d.ts +1 -1
  32. package/dist/react.js +1 -1
  33. package/dist/{register-BYQCBySi.js → register-CY9QCCZs.js} +45 -9
  34. package/dist/{register-DuVdzxbq.js → register-CjociOtt.js} +19 -4
  35. package/dist/{replay-DEvp3kyV.js → replay-B6BbgGM_.js} +55 -12
  36. package/dist/{replay-F7IZHdFR.d.ts → replay-wZK5Ok5c.d.ts} +2 -2
  37. package/dist/{save-slots-Bvuh2p_r.js → save-slots-CEUGeLuB.js} +57 -3
  38. package/dist/{split-screen-eULetcg-.js → split-screen-BxZ5rBe7.js} +4 -4
  39. package/dist/{split-screen-CIYf1zSf.d.ts → split-screen-D6sx8z3Z.d.ts} +2 -2
  40. package/dist/{sprite-animation-CqR2o3SA.js → sprite-animation-Bz3DHD_4.js} +1 -1
  41. package/dist/{src-_jk0qLsS.js → src-D-dnVrFY.js} +1 -1
  42. package/dist/{test-CmN54kWv.js → test-DZmCFz0Q.js} +162 -30
  43. package/dist/test.d.ts +56 -5
  44. package/dist/test.js +2 -2
  45. package/dist/{touch-BnCyPA0G.js → touch-CioTZB-y.js} +1 -1
  46. package/dist/vite.d.ts +8 -1
  47. package/dist/vite.js +22 -5
  48. package/editor/assets/{agent8-yLIEYHbd.js → agent8-GuZ5u5bi.js} +1 -1
  49. package/editor/assets/{debug-u31w_yhq.js → debug-BbxionO2.js} +1 -1
  50. package/editor/assets/{index-DK9xMGpW.js → index-CNlTzQbm.js} +92 -92
  51. package/editor/index.html +1 -1
  52. package/package.json +1 -1
  53. package/schemas/scene.schema.json +634 -10
  54. package/skills/incanto-3d-character.md +113 -5
  55. package/skills/incanto-assets.md +9 -3
  56. package/skills/incanto-behaviors-and-scripts.md +26 -2
  57. package/skills/incanto-building-2d-games.md +15 -0
  58. package/skills/incanto-building-3d-games.md +45 -0
  59. package/skills/incanto-editor.md +10 -1
  60. package/skills/incanto-environment.md +7 -2
  61. package/skills/incanto-game-feel.md +16 -11
  62. package/skills/incanto-gameplay-behaviors.md +174 -15
  63. package/skills/incanto-node-reference.md +122 -45
  64. package/skills/incanto-physics-and-input.md +67 -5
  65. package/skills/incanto-scene-json-authoring.md +37 -1
  66. package/skills/incanto-verifying-your-game.md +19 -2
  67. package/templates-app/beacon-isle-3d/PROJECT/Status.md +8 -0
  68. package/templates-app/beacon-isle-3d/PROJECT/Structure.md +3 -0
  69. package/templates-app/beacon-isle-3d/package.json +1 -1
  70. package/templates-app/beacon-isle-3d/src/game.scene.json +512 -184
  71. package/templates-app/beacon-isle-3d/verify.ts +144 -0
  72. package/templates-app/molehill-2d/package.json +1 -1
  73. package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
  74. package/templates-app/platformer-2d/PROJECT/Structure.md +3 -0
  75. package/templates-app/platformer-2d/package.json +1 -1
  76. package/templates-app/platformer-2d/src/game.scene.json +319 -93
  77. package/templates-app/platformer-2d/verify.ts +54 -0
  78. package/templates-app/star-survivor/package.json +1 -1
  79. package/templates-app/star-survivor/src/game.scene.json +18 -9
  80. package/templates-app/tps-3d/PROJECT/Context.md +1 -1
  81. package/templates-app/tps-3d/PROJECT/Status.md +40 -1
  82. package/templates-app/tps-3d/PROJECT/Structure.md +19 -0
  83. package/templates-app/tps-3d/package.json +1 -1
  84. package/templates-app/tps-3d/src/behaviors.ts +9 -0
  85. package/templates-app/tps-3d/src/cover.scene.json +26 -0
  86. package/templates-app/tps-3d/src/game.scene.json +379 -80
  87. package/templates-app/tps-3d/src/main.ts +14 -0
  88. package/templates-app/tps-3d/src/pillar.scene.json +25 -0
  89. package/templates-app/tps-3d/verify.ts +582 -3
  90. package/templates-app/village-quest-3d/package.json +1 -1
  91. package/templates-app/village-quest-3d/src/grove.scene.json +98 -46
  92. package/templates-app/village-quest-3d/src/village.scene.json +449 -223
@@ -285,3 +285,147 @@ const ok = (label: string, cond: boolean): void => {
285
285
  }
286
286
 
287
287
  console.log(process.exitCode ? '\nVERIFY FAILED' : '\nVERIFY OK');
288
+
289
+ // ---- HEIGHTFIELD: this round's surfaces, on terrain instead of a flat arena -----
290
+ // A trampoline pad snapped to the island (`snapToGround: 0.1` on a StaticBody3D
291
+ // with `restitution 1`), a dead ball dropped on it, and a `CharacterBody3D`
292
+ // walker chasing the player up the island's slope (`Chase.ground` auto: the
293
+ // KCC's slopeLimitDeg and stepHeight decide, gravity keeps it on the ground).
294
+ {
295
+ type Vec = { position: number[] };
296
+ type Terrain = { heightAt(x: number, z: number): number };
297
+ const ballY: number[] = [];
298
+ let walkerFrom: number[] = [];
299
+ let walkerTo: number[] = [];
300
+ let groundUnderWalker = 0;
301
+ let targetY = 0;
302
+ let closed = 0;
303
+ const result = await runScript(gameJson, {
304
+ behaviors: BEHAVIORS,
305
+ durationMs: 8000,
306
+ steps: [
307
+ {
308
+ atMs: 100,
309
+ do: (ctx) => {
310
+ const pad = ctx.getNode('/root/Pad') as unknown as Vec;
311
+ const ball = ctx.getNode('/root/Ball') as unknown as Vec;
312
+ ball.position = [pad.position[0] ?? 0, (pad.position[1] ?? 0) + 3, pad.position[2] ?? 0];
313
+ },
314
+ },
315
+ ...Array.from({ length: 30 }, (_, i) => ({
316
+ atMs: 200 + i * 100,
317
+ do: (ctx: { getNode(p: string): unknown }) => {
318
+ ballY.push((ctx.getNode('/root/Ball') as Vec).position[1] ?? 0);
319
+ },
320
+ })),
321
+ {
322
+ atMs: 3300,
323
+ do: (ctx) => {
324
+ const terrain = ctx.getNode('/root/Terrain/Ground/Surface') as unknown as Terrain;
325
+ const walker = ctx.getNode('/root/Walker') as unknown as Vec;
326
+ const player = ctx.getNode('/root/Player') as unknown as Vec;
327
+ const [wx = 0, , wz = 0] = walker.position;
328
+ // the steepest way UP from the walker, 10 m out: that is where the player goes
329
+ let best = { x: wx + 10, z: wz, h: -Infinity };
330
+ for (let a = 0; a < 16; a++) {
331
+ const x = wx + 10 * Math.cos((a / 16) * Math.PI * 2);
332
+ const z = wz + 10 * Math.sin((a / 16) * Math.PI * 2);
333
+ const h = terrain.heightAt(x, z);
334
+ if (h > best.h) best = { x, z, h };
335
+ }
336
+ player.position = [best.x, best.h + 1.4, best.z];
337
+ targetY = best.h;
338
+ walkerFrom = [...walker.position];
339
+ const d0 = Math.hypot(best.x - wx, best.z - wz);
340
+ for (let i = 0; i < 240; i++) ctx.engine.step();
341
+ walkerTo = [...walker.position];
342
+ const [tx = 0, , tz = 0] = walkerTo;
343
+ groundUnderWalker = terrain.heightAt(tx, tz);
344
+ closed = d0 - Math.hypot(best.x - tx, best.z - tz);
345
+ player.position = [-4.5, 18.73, 70.5]; // and back to the spawn for the quest
346
+ },
347
+ },
348
+ ],
349
+ });
350
+ ok('runScript reported no failures (HEIGHTFIELD path)', result.ok);
351
+ let touch = ballY.findIndex((y, i) => i > 0 && y > (ballY[i - 1] ?? 0));
352
+ if (touch < 1) touch = 1;
353
+ const low = ballY[touch - 1] ?? 0;
354
+ const rebound = Math.max(...ballY.slice(touch)) - low;
355
+ ok(
356
+ `a pad snapped to the island with restitution 1 launches a dead ball back up ${rebound.toFixed(2)} m of its 3 m drop`,
357
+ rebound > 1.8,
358
+ );
359
+ const rose = (walkerTo[1] ?? 0) - (walkerFrom[1] ?? 0);
360
+ const gap = targetY - (walkerFrom[1] ?? 0);
361
+ ok(
362
+ `the walker chased the player UP the slope: closed ${closed.toFixed(1)} m of 10, rose ${rose.toFixed(2)} m of a ${gap.toFixed(2)} m climb`,
363
+ closed > 5 && rose > gap * 0.5,
364
+ );
365
+ const hover = (walkerTo[1] ?? 0) - groundUnderWalker;
366
+ ok(
367
+ `…and stayed on the ground while doing it (capsule centre ${hover.toFixed(2)} m over the terrain, 0.95 expected)`,
368
+ hover > 0.5 && hover < 1.6,
369
+ );
370
+ }
371
+
372
+ // ---- FLOATS: what `Buoyancy.water`, `samples` and `drag` do on the open sea --------
373
+ // Set by no example: every raft in the repo took the first Water3D, four samples
374
+ // and the default drag. Two crates float west of the island (the heightfield
375
+ // ends at x −120): the Buoy names its water, samples once and says `drag: 0.1`;
376
+ // the Crate keeps the defaults. Both `drift: 0`, so a 3 m/s shove is the only
377
+ // thing moving them and drag is the only thing that differs. Asserted by how
378
+ // far each travels in 2 s — e^{-drag·t} says 5.4 m against 2.8 m — and by both
379
+ // still sitting at the waterline afterwards.
380
+ {
381
+ type Vec = { position: number[] };
382
+ type Body = { mass: number; applyImpulse(i: [number, number, number]): void };
383
+ type Water = { heightAt(x: number, z: number): number };
384
+ let buoyRest = 0;
385
+ let crateRest = 0;
386
+ let buoyRun = 0;
387
+ let crateRun = 0;
388
+ let buoyOver = 99;
389
+ let crateOver = 99;
390
+ const result = await runScript(gameJson, {
391
+ behaviors: BEHAVIORS,
392
+ durationMs: 8000,
393
+ steps: [
394
+ {
395
+ atMs: 100,
396
+ do: (ctx) => {
397
+ const sea = ctx.getNode('/root/Terrain/Sea') as unknown as Water;
398
+ const buoy = ctx.getNode('/root/Buoy') as unknown as Vec & Body;
399
+ const crate = ctx.getNode('/root/Crate') as unknown as Vec & Body;
400
+ for (let i = 0; i < 240; i++) ctx.engine.step(); // 4 s to settle on the waterline
401
+ buoyRest = buoy.position[1] ?? 0;
402
+ crateRest = crate.position[1] ?? 0;
403
+ const bx = buoy.position[0] ?? 0;
404
+ const cx = crate.position[0] ?? 0;
405
+ buoy.applyImpulse([3 * buoy.mass, 0, 0]);
406
+ crate.applyImpulse([3 * crate.mass, 0, 0]);
407
+ for (let i = 0; i < 120; i++) ctx.engine.step();
408
+ buoyRun = (buoy.position[0] ?? 0) - bx;
409
+ crateRun = (crate.position[0] ?? 0) - cx;
410
+ buoyOver =
411
+ (buoy.position[1] ?? 0) - sea.heightAt(buoy.position[0] ?? 0, buoy.position[2] ?? 0);
412
+ crateOver =
413
+ (crate.position[1] ?? 0) - sea.heightAt(crate.position[0] ?? 0, crate.position[2] ?? 0);
414
+ },
415
+ },
416
+ ],
417
+ });
418
+ ok('runScript reported no failures (FLOATS path)', result.ok);
419
+ ok(
420
+ `water "/root/Terrain/Sea", samples 1: the Buoy settles at y ${buoyRest.toFixed(2)} (sea 9.19 + draft 0.35 ≈ 9.54), the Crate at ${crateRest.toFixed(2)}`,
421
+ Math.abs(buoyRest - 9.54) < 0.5 && Math.abs(crateRest - 9.54) < 0.5,
422
+ );
423
+ ok(
424
+ `drag 0.1 vs 0.9: the same 3 m/s shove carries the Buoy ${buoyRun.toFixed(2)} m and the Crate ${crateRun.toFixed(2)} m in 2 s (5.4 vs 2.8 expected)`,
425
+ buoyRun > crateRun * 1.5 && buoyRun > 4 && crateRun > 1.5 && crateRun < 3.5,
426
+ );
427
+ ok(
428
+ `…and both still ride the waterline (${buoyOver.toFixed(2)} m and ${crateOver.toFixed(2)} m over the sea)`,
429
+ Math.abs(buoyOver - 0.35) < 0.5 && Math.abs(crateOver - 0.35) < 0.5,
430
+ );
431
+ }
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
- "incanto": "^0.71.0",
14
+ "incanto": "^0.73.0",
15
15
  "three": "^0.184.0"
16
16
  },
17
17
  "devDependencies": {
@@ -58,3 +58,10 @@
58
58
  - One hand-authored level — extend by copying nodes into the right GROUP
59
59
  (`enemy`/`hazard`/`pit`/`checkpoint`/`platform`); `PlayerController` picks them
60
60
  up with no extra wiring.
61
+
62
+ ## The round's surfaces, in 2D (2026-09-04)
63
+
64
+ A course past GroundD (x 3000+): `Trampoline` (`restitution 1`) with a dead `Bouncer`, `Ice`
65
+ (`friction 0`) with a `Sled` at 260 px/s, and a `Hound` (`CharacterBody2D` + `Chase`
66
+ `ground: true`) under a `Perch`. `verify.ts` measures the rebound (287 of 300 px), the slide
67
+ (400 px of ice) and that the hound stays on the floor under a perched player.
@@ -75,3 +75,6 @@ The vibe-coding rules: read the skills, JSON structure, built-ins first, omit
75
75
  uids, declared assets, y-down space, collider props, GROUP-DRIVEN centralised
76
76
  damage (§8), deferred engine access (§11), debug draw, delta discipline, verify
77
77
  like a user.
78
+
79
+ - `CourseFloor`, `Trampoline`, `Bouncer`, `Ice`, `Sled`, `Hound`, `Perch` — the harness's course for
80
+ the 2D surfaces and the ground chaser.
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
- "incanto": "^0.71.0",
14
+ "incanto": "^0.73.0",
15
15
  "three": "^0.184.0"
16
16
  },
17
17
  "devDependencies": {