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.
- package/bin/_behaviors-loader.mjs +22 -0
- package/bin/_read-json.mjs +28 -0
- package/bin/incanto-assets.mjs +5 -4
- package/bin/incanto-check.mjs +58 -31
- package/bin/incanto-editor.mjs +128 -5
- package/bin/incanto-env.mjs +3 -2
- package/bin/incanto-feel.mjs +24 -13
- package/bin/incanto-frame.mjs +8 -1
- package/bin/incanto-multiplay.mjs +11 -9
- package/bin/incanto-new.mjs +105 -10
- package/bin/incanto-play.mjs +158 -16
- package/bin/incanto-playtest.mjs +66 -23
- package/bin/incanto-skills.mjs +14 -2
- package/bin/incanto-verify.mjs +125 -44
- package/bin/incanto.mjs +3 -2
- package/dist/2d.d.ts +281 -34
- package/dist/2d.js +4 -4
- package/dist/3d.d.ts +157 -9
- package/dist/3d.js +7 -7
- package/dist/{agent8-CvsfVskX.js → agent8-CmNF01gA.js} +61 -8
- package/dist/{audio-player-C0j2k-yB.d.ts → audio-player-DaMxqfNE.d.ts} +2 -2
- package/dist/{behavior-rm8vVzu_.d.ts → behavior-DZExDn9o.d.ts} +744 -46
- package/dist/{create-game-CwxEDlCm.js → create-game-Bwvh6q8A.js} +142 -59
- package/dist/{create-game-CvJ1wSRP.js → create-game-C7ffQWW7.js} +97 -47
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +2 -3
- package/dist/diagnostics-Cu85N3tL.d.ts +12 -0
- package/dist/{editor-switch-CnIOiyNJ.d.ts → editor-switch-CFU9mCec.d.ts} +22 -13
- package/dist/editor.js +1065 -863
- package/dist/env.d.ts +1 -1
- package/dist/env.js +5 -3
- package/dist/{environment-presets-BO-7XoFV.js → environment-presets-D6Q5BxeE.js} +298 -45
- package/dist/{gameplay-D1KixWK8.js → gameplay-BfHkuzVb.js} +809 -211
- package/dist/gameplay.d.ts +187 -7
- package/dist/gameplay.js +1 -1
- package/dist/{heightmap-CRK0M4jT.js → heightmap-BYgD5Edk.js} +1 -1
- package/dist/index.d.ts +156 -13
- package/dist/index.js +10 -12
- package/dist/json-CfTjpvW8.js +67 -0
- package/dist/{loader-DadV2QCT.d.ts → loader-Cff09LMm.d.ts} +2 -2
- package/dist/net.d.ts +27 -3
- package/dist/net.js +2 -2
- package/dist/{noise-CGUMx44x.js → noise-D3nPpmFg.js} +1 -1
- package/dist/{physics-2d-CF5yyjbj.js → physics-2d-CE0Qvy3V.js} +136 -11
- package/dist/{physics-3d-DbYaEcZy.js → physics-3d-CpH-2gn5.js} +104 -23
- package/dist/{teardown-CMqFVxO9.js → picking-CQJ_PJKh.js} +103 -12
- package/dist/react.d.ts +2 -2
- package/dist/react.js +2 -2
- package/dist/{register-mmfugpaQ.js → register-6DYnKZcy.js} +546 -817
- package/dist/{register-DbVArsns.js → register-Bkk0wSDB.js} +332 -23
- package/dist/{replay-BEHLDcQV.js → replay-DjAkAzMq.js} +224 -14
- package/dist/{replay-BQfkX4x7.d.ts → replay-Dmw-PKQu.d.ts} +20 -3
- package/dist/{schema-B6ugCV1Q.d.ts → rng-Bb-IutXB.d.ts} +38 -21
- package/dist/{rng-DP-SR7eg.js → rng-CDOMybym.js} +22 -0
- package/dist/{loader-DZE7B07H.js → save-slots-BXVg148r.js} +4570 -2322
- package/dist/{split-screen-Cg76MCV5.d.ts → split-screen--k-XpBjr.d.ts} +36 -4
- package/dist/{split-screen-CauMwu6y.js → split-screen-PL78oVXP.js} +159 -26
- package/dist/{sprite-animation-CY-mrr1L.js → sprite-animation-CqR2o3SA.js} +39 -8
- package/dist/{src-CYEu-_S9.js → src-Cxfiv1Hg.js} +2 -17
- package/dist/test-iHYVUcDK.js +4036 -0
- package/dist/test.d.ts +531 -30
- package/dist/test.js +3 -3
- package/dist/touch-BnCyPA0G.js +519 -0
- package/dist/vite.d.ts +20 -37
- package/dist/vite.js +176 -6
- package/dist/{webgl-unavailable-Z5_g8x9H.js → webgl-unavailable-C8aDbGmR.js} +44 -0
- package/editor/assets/agent8-D0MS174y.js +1 -0
- package/editor/assets/{debug-CMULinBW.js → debug-BnXkKuYu.js} +2 -2
- package/editor/assets/index-CIu3uc3l.js +11046 -0
- package/editor/index.html +1 -1
- package/package.json +4 -14
- package/schemas/scene.schema.json +30 -3
- package/skills/incanto-3d-character.md +14 -1
- package/skills/incanto-3d-models.md +12 -0
- package/skills/incanto-assets.md +9 -1
- package/skills/incanto-audio.md +8 -2
- package/skills/incanto-behaviors-and-scripts.md +83 -6
- package/skills/incanto-building-2d-games.md +77 -8
- package/skills/incanto-building-3d-games.md +58 -4
- package/skills/incanto-editor.md +46 -7
- package/skills/incanto-environment.md +19 -1
- package/skills/incanto-game-feel.md +70 -0
- package/skills/incanto-gameplay-behaviors.md +109 -19
- package/skills/incanto-hud.md +128 -7
- package/skills/incanto-localization.md +13 -5
- package/skills/incanto-multiplayer.md +83 -3
- package/skills/incanto-node-reference.md +222 -58
- package/skills/incanto-performance.md +52 -0
- package/skills/incanto-physics-and-input.md +123 -24
- package/skills/incanto-playtesting.md +78 -2
- package/skills/incanto-save-slots.md +161 -6
- package/skills/incanto-scene-json-authoring.md +69 -12
- package/skills/incanto-verifying-your-game.md +196 -10
- package/skills/incanto-web-integration.md +28 -0
- package/skills/incanto-your-first-game.md +3 -1
- package/templates-app/beacon-isle-3d/generate-world.ts +77 -9
- package/templates-app/beacon-isle-3d/package.json +2 -2
- package/templates-app/beacon-isle-3d/src/behaviors.ts +22 -0
- package/templates-app/beacon-isle-3d/src/game.scene.json +103 -378
- package/templates-app/beacon-isle-3d/src/main.ts +11 -1
- package/templates-app/beacon-isle-3d/tsconfig.json +1 -1
- package/templates-app/beacon-isle-3d/verify.ts +3 -1
- package/templates-app/molehill-2d/.incanto/playtest/lost-seed1.json +4277 -0
- package/templates-app/molehill-2d/PROJECT/Context.md +58 -0
- package/templates-app/molehill-2d/PROJECT/Requirements.md +39 -0
- package/templates-app/molehill-2d/PROJECT/Status.md +27 -0
- package/templates-app/molehill-2d/PROJECT/Structure.md +48 -0
- package/templates-app/molehill-2d/docs/project-2d-rules.md +44 -0
- package/templates-app/molehill-2d/index.html +73 -0
- package/templates-app/molehill-2d/package.json +23 -0
- package/templates-app/molehill-2d/src/behaviors.ts +198 -0
- package/templates-app/molehill-2d/src/game.scene.json +1255 -0
- package/templates-app/molehill-2d/src/main.ts +41 -0
- package/templates-app/molehill-2d/tsconfig.json +13 -0
- package/templates-app/molehill-2d/verify.ts +247 -0
- package/templates-app/molehill-2d/vite.config.ts +12 -0
- package/templates-app/platformer-2d/package.json +2 -2
- package/templates-app/platformer-2d/src/game.scene.json +128 -600
- package/templates-app/platformer-2d/src/main.ts +11 -1
- package/templates-app/platformer-2d/tsconfig.json +1 -1
- package/templates-app/star-survivor/package.json +2 -2
- package/templates-app/star-survivor/src/game.scene.json +41 -195
- package/templates-app/star-survivor/src/main.ts +11 -1
- package/templates-app/star-survivor/tsconfig.json +1 -1
- package/templates-app/tps-3d/PROJECT/Context.md +1 -1
- package/templates-app/tps-3d/package.json +2 -2
- package/templates-app/tps-3d/src/behaviors.ts +19 -1
- package/templates-app/tps-3d/src/game.scene.json +78 -217
- package/templates-app/tps-3d/src/main.ts +13 -2
- package/templates-app/tps-3d/tsconfig.json +1 -1
- package/templates-app/village-quest-3d/.incanto/playtest/swapped-seed1.json +1735 -0
- package/templates-app/village-quest-3d/package.json +2 -2
- package/templates-app/village-quest-3d/src/behaviors.ts +21 -0
- package/templates-app/village-quest-3d/src/grove.scene.json +54 -221
- package/templates-app/village-quest-3d/src/main.ts +11 -1
- package/templates-app/village-quest-3d/src/village.scene.json +199 -838
- package/templates-app/village-quest-3d/tsconfig.json +1 -1
- package/templates-app/village-quest-3d/verify.ts +14 -1
- package/dist/duplicate-Dj4L-5AA.js +0 -22
- package/dist/json-CwwhxQgb.js +0 -36
- package/dist/registry-CF70EArN.js +0 -212
- package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
- package/dist/test-D6ISmuFE.js +0 -2383
- package/dist/touch-DEAmqGdf.js +0 -225
- package/editor/assets/agent8-BDvaZMQJ.js +0 -1
- 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
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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`
|
|
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()`): `
|
|
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
|
-
|
|
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.
|