incanto 0.69.0 → 0.70.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 (146) hide show
  1. package/bin/_behaviors-loader.mjs +22 -0
  2. package/bin/_read-json.mjs +28 -0
  3. package/bin/incanto-assets.mjs +5 -4
  4. package/bin/incanto-check.mjs +58 -31
  5. package/bin/incanto-editor.mjs +128 -5
  6. package/bin/incanto-env.mjs +3 -2
  7. package/bin/incanto-feel.mjs +24 -13
  8. package/bin/incanto-frame.mjs +8 -1
  9. package/bin/incanto-multiplay.mjs +11 -9
  10. package/bin/incanto-new.mjs +105 -10
  11. package/bin/incanto-play.mjs +158 -16
  12. package/bin/incanto-playtest.mjs +66 -23
  13. package/bin/incanto-skills.mjs +14 -2
  14. package/bin/incanto-verify.mjs +125 -44
  15. package/bin/incanto.mjs +3 -2
  16. package/dist/2d.d.ts +281 -34
  17. package/dist/2d.js +4 -4
  18. package/dist/3d.d.ts +157 -9
  19. package/dist/3d.js +7 -7
  20. package/dist/{agent8-CvsfVskX.js → agent8-CmNF01gA.js} +61 -8
  21. package/dist/{audio-player-C0j2k-yB.d.ts → audio-player-DaMxqfNE.d.ts} +2 -2
  22. package/dist/{behavior-rm8vVzu_.d.ts → behavior-DZExDn9o.d.ts} +744 -46
  23. package/dist/{create-game-CwxEDlCm.js → create-game-Bwvh6q8A.js} +142 -59
  24. package/dist/{create-game-CvJ1wSRP.js → create-game-C7ffQWW7.js} +97 -47
  25. package/dist/debug.d.ts +1 -1
  26. package/dist/debug.js +2 -3
  27. package/dist/diagnostics-Cu85N3tL.d.ts +12 -0
  28. package/dist/{editor-switch-CnIOiyNJ.d.ts → editor-switch-CFU9mCec.d.ts} +22 -13
  29. package/dist/editor.js +1065 -863
  30. package/dist/env.d.ts +1 -1
  31. package/dist/env.js +5 -3
  32. package/dist/{environment-presets-BO-7XoFV.js → environment-presets-D6Q5BxeE.js} +298 -45
  33. package/dist/{gameplay-D1KixWK8.js → gameplay-BfHkuzVb.js} +809 -211
  34. package/dist/gameplay.d.ts +187 -7
  35. package/dist/gameplay.js +1 -1
  36. package/dist/{heightmap-CRK0M4jT.js → heightmap-BYgD5Edk.js} +1 -1
  37. package/dist/index.d.ts +156 -13
  38. package/dist/index.js +10 -12
  39. package/dist/json-CfTjpvW8.js +67 -0
  40. package/dist/{loader-DadV2QCT.d.ts → loader-Cff09LMm.d.ts} +2 -2
  41. package/dist/net.d.ts +27 -3
  42. package/dist/net.js +2 -2
  43. package/dist/{noise-CGUMx44x.js → noise-D3nPpmFg.js} +1 -1
  44. package/dist/{physics-2d-CF5yyjbj.js → physics-2d-CE0Qvy3V.js} +136 -11
  45. package/dist/{physics-3d-DbYaEcZy.js → physics-3d-CpH-2gn5.js} +104 -23
  46. package/dist/{teardown-CMqFVxO9.js → picking-CQJ_PJKh.js} +103 -12
  47. package/dist/react.d.ts +2 -2
  48. package/dist/react.js +2 -2
  49. package/dist/{register-mmfugpaQ.js → register-6DYnKZcy.js} +546 -817
  50. package/dist/{register-DbVArsns.js → register-Bkk0wSDB.js} +332 -23
  51. package/dist/{replay-BEHLDcQV.js → replay-DjAkAzMq.js} +224 -14
  52. package/dist/{replay-BQfkX4x7.d.ts → replay-Dmw-PKQu.d.ts} +20 -3
  53. package/dist/{schema-B6ugCV1Q.d.ts → rng-Bb-IutXB.d.ts} +38 -21
  54. package/dist/{rng-DP-SR7eg.js → rng-CDOMybym.js} +22 -0
  55. package/dist/{loader-DZE7B07H.js → save-slots-BXVg148r.js} +4570 -2322
  56. package/dist/{split-screen-Cg76MCV5.d.ts → split-screen--k-XpBjr.d.ts} +36 -4
  57. package/dist/{split-screen-CauMwu6y.js → split-screen-PL78oVXP.js} +159 -26
  58. package/dist/{sprite-animation-CY-mrr1L.js → sprite-animation-CqR2o3SA.js} +39 -8
  59. package/dist/{src-CYEu-_S9.js → src-Cxfiv1Hg.js} +2 -17
  60. package/dist/test-iHYVUcDK.js +4036 -0
  61. package/dist/test.d.ts +531 -30
  62. package/dist/test.js +3 -3
  63. package/dist/touch-BnCyPA0G.js +519 -0
  64. package/dist/vite.d.ts +20 -37
  65. package/dist/vite.js +176 -6
  66. package/dist/{webgl-unavailable-Z5_g8x9H.js → webgl-unavailable-C8aDbGmR.js} +44 -0
  67. package/editor/assets/agent8-D0MS174y.js +1 -0
  68. package/editor/assets/{debug-CMULinBW.js → debug-BnXkKuYu.js} +2 -2
  69. package/editor/assets/index-CIu3uc3l.js +11046 -0
  70. package/editor/index.html +1 -1
  71. package/package.json +4 -14
  72. package/schemas/scene.schema.json +30 -3
  73. package/skills/incanto-3d-character.md +14 -1
  74. package/skills/incanto-3d-models.md +12 -0
  75. package/skills/incanto-assets.md +9 -1
  76. package/skills/incanto-audio.md +8 -2
  77. package/skills/incanto-behaviors-and-scripts.md +83 -6
  78. package/skills/incanto-building-2d-games.md +77 -8
  79. package/skills/incanto-building-3d-games.md +58 -4
  80. package/skills/incanto-editor.md +46 -7
  81. package/skills/incanto-environment.md +19 -1
  82. package/skills/incanto-game-feel.md +70 -0
  83. package/skills/incanto-gameplay-behaviors.md +109 -19
  84. package/skills/incanto-hud.md +128 -7
  85. package/skills/incanto-localization.md +13 -5
  86. package/skills/incanto-multiplayer.md +83 -3
  87. package/skills/incanto-node-reference.md +222 -58
  88. package/skills/incanto-performance.md +52 -0
  89. package/skills/incanto-physics-and-input.md +123 -24
  90. package/skills/incanto-playtesting.md +78 -2
  91. package/skills/incanto-save-slots.md +161 -6
  92. package/skills/incanto-scene-json-authoring.md +69 -12
  93. package/skills/incanto-verifying-your-game.md +196 -10
  94. package/skills/incanto-web-integration.md +28 -0
  95. package/skills/incanto-your-first-game.md +3 -1
  96. package/templates-app/beacon-isle-3d/generate-world.ts +77 -9
  97. package/templates-app/beacon-isle-3d/package.json +2 -2
  98. package/templates-app/beacon-isle-3d/src/behaviors.ts +22 -0
  99. package/templates-app/beacon-isle-3d/src/game.scene.json +103 -378
  100. package/templates-app/beacon-isle-3d/src/main.ts +11 -1
  101. package/templates-app/beacon-isle-3d/tsconfig.json +1 -1
  102. package/templates-app/beacon-isle-3d/verify.ts +3 -1
  103. package/templates-app/molehill-2d/.incanto/playtest/lost-seed1.json +4277 -0
  104. package/templates-app/molehill-2d/PROJECT/Context.md +58 -0
  105. package/templates-app/molehill-2d/PROJECT/Requirements.md +39 -0
  106. package/templates-app/molehill-2d/PROJECT/Status.md +27 -0
  107. package/templates-app/molehill-2d/PROJECT/Structure.md +48 -0
  108. package/templates-app/molehill-2d/docs/project-2d-rules.md +44 -0
  109. package/templates-app/molehill-2d/index.html +73 -0
  110. package/templates-app/molehill-2d/package.json +23 -0
  111. package/templates-app/molehill-2d/src/behaviors.ts +198 -0
  112. package/templates-app/molehill-2d/src/game.scene.json +1255 -0
  113. package/templates-app/molehill-2d/src/main.ts +41 -0
  114. package/templates-app/molehill-2d/tsconfig.json +13 -0
  115. package/templates-app/molehill-2d/verify.ts +247 -0
  116. package/templates-app/molehill-2d/vite.config.ts +12 -0
  117. package/templates-app/platformer-2d/package.json +2 -2
  118. package/templates-app/platformer-2d/src/game.scene.json +128 -600
  119. package/templates-app/platformer-2d/src/main.ts +11 -1
  120. package/templates-app/platformer-2d/tsconfig.json +1 -1
  121. package/templates-app/star-survivor/package.json +2 -2
  122. package/templates-app/star-survivor/src/game.scene.json +41 -195
  123. package/templates-app/star-survivor/src/main.ts +11 -1
  124. package/templates-app/star-survivor/tsconfig.json +1 -1
  125. package/templates-app/tps-3d/PROJECT/Context.md +1 -1
  126. package/templates-app/tps-3d/package.json +2 -2
  127. package/templates-app/tps-3d/src/behaviors.ts +19 -1
  128. package/templates-app/tps-3d/src/game.scene.json +78 -217
  129. package/templates-app/tps-3d/src/main.ts +13 -2
  130. package/templates-app/tps-3d/tsconfig.json +1 -1
  131. package/templates-app/village-quest-3d/.incanto/playtest/swapped-seed1.json +1735 -0
  132. package/templates-app/village-quest-3d/package.json +2 -2
  133. package/templates-app/village-quest-3d/src/behaviors.ts +21 -0
  134. package/templates-app/village-quest-3d/src/grove.scene.json +54 -221
  135. package/templates-app/village-quest-3d/src/main.ts +11 -1
  136. package/templates-app/village-quest-3d/src/village.scene.json +199 -838
  137. package/templates-app/village-quest-3d/tsconfig.json +1 -1
  138. package/templates-app/village-quest-3d/verify.ts +14 -1
  139. package/dist/duplicate-Dj4L-5AA.js +0 -22
  140. package/dist/json-CwwhxQgb.js +0 -36
  141. package/dist/registry-CF70EArN.js +0 -212
  142. package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
  143. package/dist/test-D6ISmuFE.js +0 -2383
  144. package/dist/touch-DEAmqGdf.js +0 -225
  145. package/editor/assets/agent8-BDvaZMQJ.js +0 -1
  146. package/editor/assets/index-CZIRr5EL.js +0 -10958
@@ -64,7 +64,7 @@ A text prop becomes translatable by naming a key with `@t:` — the same shape a
64
64
  the `"$assetKey"` references you already write:
65
65
 
66
66
  ```json
67
- { "name": "Start", "type": "UiButton", "props": { "text": "@t:menu.start" } }
67
+ { "name": "Start", "type": "UiButton", "props": { "text": "@t:menu.start" } },
68
68
  { "name": "Wave", "type": "UiText", "props": { "format": "@t:hud.wave" } }
69
69
  ```
70
70
 
@@ -101,10 +101,18 @@ param is left alone rather than blanked.
101
101
 
102
102
  ### Authoring the table
103
103
 
104
- The scene header's **advanced** section has a `strings` field — the same
105
- raw-JSON editor `input` and `multiplayer` use. Until 0.61 it was the one of
106
- those three the panel did not show, so a localized game could be read by the
107
- editor and never authored in it.
104
+ The scene header has a **strings** table: one row per key, one column per
105
+ language, `+ key` and `+ language`. Two things it shows that the JSON cannot:
106
+
107
+ - an **untranslated cell** shows the English it will silently fall back to,
108
+ greyed — that fallback is the design, not an error, and this is where you see
109
+ it happening;
110
+ - a key the **base locale is missing** is marked red, because that is the one
111
+ case that really breaks: nothing to fall back to, so the raw key paints.
112
+
113
+ Deleting a key a prop still says asks first, and tells you how many props will
114
+ start painting the key. The raw-JSON `strings` field is still in **advanced**
115
+ for pasting a whole table in at once.
108
116
 
109
117
  ## Checking a translation without a browser
110
118
 
@@ -34,6 +34,16 @@ Authority rules:
34
34
  - movement/cosmetics → **owner-authoritative** (the replication below)
35
35
  - score/economy/inventory → **server-function-authoritative** (extend `server.js`;
36
36
  `$sender.account` is trustworthy, args are NOT; guard with `$lock`)
37
+
38
+ > **`$lock` is NOT reentrant.** Taking one key inside another's callback —
39
+ > `$lock('clock')` inside `$lock('match')`, one string apart in a
40
+ > read-modify-write — DEADLOCKS. The preview server runs every client's calls
41
+ > one at a time, so one stuck call wedges the whole match: every
42
+ > `setMyState` stops, new players can never join, and `local.tick()` never
43
+ > resolves. It says so now (`a call has been running for over 5s and every
44
+ > other client is blocked behind it`) instead of hanging in silence, and
45
+ > `incanto-multiplay` prints its verdict instead of exiting 13 with nothing.
46
+ > Take one lock, do the whole read-modify-write inside it, and release.
37
47
  - physics/AI run client-side; one-simulator needs (NPC waves) use the host-client pattern
38
48
 
39
49
  ## Scene JSON
@@ -61,7 +71,9 @@ working game's. All three are hard `BAD_FORMAT` errors now, and
61
71
  — write ["position"] (on 'Player')
62
72
  ```
63
73
 
64
- Valid keys: `mode` (`owner` | `observer`), `sync` (array of prop names),
74
+ Valid keys: `mode` (`owner` — the only one the engine reads; `observer` was a
75
+ sketch that shipped in the validator's list and was never implemented, so it is
76
+ now refused by name), `sync` (array of prop names),
65
77
  `throttleMs` (number). An `owner` with an empty `sync` is an error too — it is
66
78
  a half-finished edit that behaves exactly like a broken one.
67
79
 
@@ -76,7 +88,11 @@ a half-finished edit that behaves exactly like a broken one.
76
88
  keyframe (`keyframeMs`, default 2 s) comes round, which is also what heals a
77
89
  send that never reached the wire. Spawned entities (bullets,
78
90
  pickups) go through **collections**, never extra owner nodes.
79
- - **`NetworkSpawner`** (register with `registerNodesNet()`): `source: "users"` spawns one
91
+ - **`NetworkSpawner`** (register with `registerNodesNet()`): **`scene` is REQUIRED** —
92
+ the key you registered the remote's scene under. Without one the spawner used to
93
+ return from every frame and no remote ever appeared, on a scene that loaded,
94
+ audited and played clean. A spawner in a game with no multiplayer at all says so
95
+ after two seconds, by name. `source: "users"` spawns one
80
96
  instance of the registered scene per OTHER account (self skipped); the flat `sync` patch
81
97
  applies onto the spawned scene's root; `position` lerps when `interpolate: true`
82
98
  (remote entities render slightly in the past — that's correct). Emits
@@ -98,7 +114,10 @@ import { NetworkManager, registerNodesNet, LoopbackHub } from 'incanto/net';
98
114
  // node (`Unknown node type 'Node2D'`).
99
115
  registerNodes2D();
100
116
  registerNodesNet();
101
- const scene = loadScene(json);
117
+ // `{ engine }` on a MANUAL boot, always: `onReady` runs during the load, and a
118
+ // behaviour that reads `this.engine`/`this.rng` there (Wander draws its first
119
+ // heading in it) throws TREE_VIOLATION without it.
120
+ const scene = loadScene(json, { engine });
102
121
  engine.setScene(scene);
103
122
 
104
123
  // DEV / offline / split-screen: zero infrastructure, built-in protocol only
@@ -142,6 +161,31 @@ delta-merged ARRAY `[{...state, account, __updated}]` → an account-keyed Recor
142
161
  `subscribeRoomCollection` arrives as `{items:[{__id,…}], changes}` → a `__id`-keyed
143
162
  Record. It also re-issues every subscription after a reconnect.
144
163
 
164
+ **When the socket drops it keeps trying** — 0.5s, 1, 2, 4, then 8 up to eight
165
+ attempts — and SAYS so on the first failure and again if it gives up:
166
+
167
+ ```
168
+ [incanto] the multiplayer connection dropped and the server would not take it
169
+ back (ECONNREFUSED). Retrying — other players cannot see this one until it
170
+ comes back.
171
+ ```
172
+
173
+ There used to be one attempt and no catch, so a server that was still
174
+ restarting rejected it, the rejection escaped an unawaited `void reconnect()`,
175
+ the re-issue loop never ran and nothing scheduled a second try: **the session
176
+ never came back, and nothing said a word.** A `connect()` that resolved `false`
177
+ was taken for success and re-issued every subscription onto a dead socket.
178
+
179
+ **A replication send that fails now reaches `stats().errors`.** It was
180
+ `void this.setMyState(…)`, so the rejection went to the host's
181
+ unhandled-rejection channel — not `engine.log`, not the error count. A client
182
+ whose socket died kept playing perfectly on its own screen while everyone else
183
+ watched it frozen, and the run reported `errors: 0`.
184
+
185
+ **A transport that REFUSES the connection now throws** `NETWORK_UNAVAILABLE`
186
+ out of `NetworkManager.create`, instead of returning a fully-formed manager
187
+ with `connected === false` that had joined nothing.
188
+
145
189
  ## Writing a custom transport (any backend)
146
190
 
147
191
  Implement `NetworkTransport` (exported from `incanto/net`): `connect`/`disconnect`,
@@ -354,6 +398,22 @@ Kernel constraints (agent8 platform facts — don't fight them):
354
398
  - room data is ephemeral — persist to `$global` before rooms empty
355
399
  - never expose unthrottled per-frame remoteFunction (rapid calls are rejected); guard score/economy with `$lock`
356
400
 
401
+ ## Changing scene in a multiplayer game
402
+
403
+ A manager is bound to the ROOM the scene it was created for declares.
404
+
405
+ - **Same room, new scene** (level 2 of one match, a map vote): nothing to do.
406
+ The manager rebinds to the incoming tree and keeps broadcasting; the new
407
+ owner node re-sends its full state.
408
+ - **Different room** (lobby → match, leaving for another arena): create a NEW
409
+ manager for the new room. Broadcasting stops on purpose — leaking one room's
410
+ state into another is exactly what the binding prevents — and the engine logs
411
+ an error saying which room it is bound to and which the scene asked for.
412
+
413
+ Dispose the old handle whenever you like, before or after creating the new one:
414
+ disposing a handle that a later `create()` already superseded leaves the live
415
+ manager alone.
416
+
357
417
  ## State rules
358
418
 
359
419
  - JSON-safe by construction: vectors as arrays, no `undefined`/`NaN`/typed arrays
@@ -392,3 +452,23 @@ changed nothing — a ✓ that could not have been a ✗.
392
452
 
393
453
  If your server cannot be loaded the run REFUSES rather than quietly measuring a
394
454
  game without its own rules.
455
+
456
+ **Pass `--behaviors src/behaviors.ts` too.** A sync key is very often written by
457
+ game TypeScript (`player.firing = input.isPressed('fire')`), and without the
458
+ behaviors the harness cannot tell a DEAD sync key from one whose writer was
459
+ never loaded — so it says nothing about them at all. With the behaviors there,
460
+ a key the owner cannot read is reported as what it is:
461
+
462
+ ```
463
+ ✗ p1 declares `firing` in its `network.sync` and cannot READ it — it is never
464
+ sent, so no client will ever see it change.
465
+ ```
466
+
467
+ That is the most complete failure of replication there is, and the rung used to
468
+ grade it PASS: an unreadable key was dropped from the comparison, so
469
+ every client agreed perfectly about a value nobody ever sent.
470
+
471
+ **`collection:` spawners are compared too.** The account-keyed half of the check
472
+ cannot see them — a bullet has no account — so a spawner that took a hundred
473
+ entities off the wire and materialised none of them used to be entirely silent.
474
+ Now it says which spawner, and how many entities it dropped.