incanto 0.65.0 → 0.67.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 +87 -0
- package/bin/incanto-editor.mjs +39 -2
- package/bin/incanto-feel.mjs +4 -1
- package/bin/incanto-frame.mjs +52 -3
- package/bin/incanto-logs.mjs +33 -3
- package/bin/incanto-multiplay.mjs +50 -1
- package/bin/incanto-play.mjs +3 -1
- package/bin/incanto-playtest.mjs +3 -25
- package/bin/incanto-verify.mjs +15 -2
- package/dist/2d.d.ts +76 -4
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +213 -14
- package/dist/3d.js +6 -6
- package/dist/{audio-player-BrEHwbK1.d.ts → audio-player-BNUHo0JF.d.ts} +1 -1
- package/dist/{behavior-CmMuQ6CL.d.ts → behavior-ZnfzKDo3.d.ts} +58 -1
- package/dist/{create-game-DIyh5Zy9.js → create-game-DMetd3vR.js} +14 -8
- package/dist/{create-game-W2xzTJhs.js → create-game-FAXdHta3.js} +138 -11
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +3 -2
- package/dist/{duplicate-CqSAtdrh.js → duplicate-B8QnX2-r.js} +1 -1
- package/dist/editor.js +7 -2
- package/dist/{environment-presets-BvHs6HEk.js → environment-presets-C-5g2pMh.js} +113 -9
- package/dist/{gameplay-D-H986A4.js → gameplay-CGSv-9XR.js} +295 -12
- package/dist/gameplay.d.ts +102 -6
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +50 -6
- package/dist/index.js +7 -6
- package/dist/{loader-CTB441z4.d.ts → loader-DyT30ArB.d.ts} +1 -1
- package/dist/{loader-lQDCwNag.js → loader-KZESOZmE.js} +148 -1
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{physics-2d-CmnunaR2.js → physics-2d-_B6zj3zU.js} +57 -4
- package/dist/{physics-3d-DULB1lD1.js → physics-3d-i1IeAY0F.js} +74 -5
- package/dist/{quiet-rapier-BAJ4K94N.js → quiet-rapier-C6fW4zcW.js} +14 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-CgeR4ckF.js → register-Cs53CY7s.js} +110 -16
- package/dist/{register-DO5_Qp0Q.js → register-vIxU3Xbv.js} +38 -4
- package/dist/rendering-options-ByAdi8PF.js +184 -0
- package/dist/{replay-CEw2xAhG.js → replay-BkpDV51O.js} +4 -3
- package/dist/{replay-CThvwkcQ.d.ts → replay-D0GcEHYU.d.ts} +1 -1
- package/dist/{split-screen-DxZzC1nm.d.ts → split-screen-DD5eYIgW.d.ts} +2 -2
- package/dist/{split-screen-gb0c3xdb.js → split-screen-DS8RaUV_.js} +2 -2
- package/dist/{src-DYffzHbd.js → src-BLlsGJW9.js} +1 -1
- package/dist/{teardown-BordHL9o.js → teardown-LB9cTnXJ.js} +2 -2
- package/dist/{test-CXZuXs8k.js → test-81uZ967a.js} +127 -24
- package/dist/test.d.ts +7 -5
- package/dist/test.js +2 -2
- package/dist/{touch-DESwnpOc.js → touch-DEAmqGdf.js} +30 -186
- package/dist/vite.d.ts +0 -7
- package/dist/vite.js +13 -3
- package/editor/assets/{agent8-DO5qzIOD.js → agent8-C2jtNKbx.js} +1 -1
- package/editor/assets/debug-DoU3KeqS.js +3 -0
- package/editor/assets/{index-CqEUVaPN.js → index-CNqHojcC.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +20 -1
- package/skills/incanto-building-3d-games.md +19 -0
- package/skills/incanto-editor.md +5 -1
- package/skills/incanto-environment.md +2 -2
- package/skills/incanto-gameplay-behaviors.md +86 -7
- package/skills/incanto-hud.md +14 -1
- package/skills/incanto-multiplayer.md +14 -0
- package/skills/incanto-node-reference.md +60 -2
- package/skills/incanto-performance.md +35 -0
- package/skills/incanto-physics-and-input.md +69 -0
- package/skills/incanto-playtesting.md +15 -1
- package/skills/incanto-save-slots.md +21 -1
- package/skills/incanto-verifying-your-game.md +19 -0
- package/skills/incanto-web-integration.md +24 -3
- package/skills/incanto-your-first-game.md +4 -4
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +38 -19
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/game.scene.json +99 -53
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/game.scene.json +31 -17
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +14 -8
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/grove.scene.json +20 -11
- package/templates-app/village-quest-3d/src/village.scene.json +10 -7
- package/editor/assets/debug-DntG9CVD.js +0 -3
- package/templates-app/beacon-isle-3d/coverage.json +0 -9
- package/templates-app/platformer-2d/coverage.json +0 -5
- package/templates-app/star-survivor/coverage.json +0 -5
- package/templates-app/tps-3d/coverage.json +0 -5
- package/templates-app/village-quest-3d/coverage.json +0 -9
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-CNqHojcC.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="./assets/GameServer-C56iOUgF.js">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -2350,6 +2350,10 @@
|
|
|
2350
2350
|
"type": "number",
|
|
2351
2351
|
"default": 0.01
|
|
2352
2352
|
},
|
|
2353
|
+
"stepHeight": {
|
|
2354
|
+
"type": "number",
|
|
2355
|
+
"default": 0.35
|
|
2356
|
+
},
|
|
2353
2357
|
"mouseLook": {
|
|
2354
2358
|
"type": "boolean",
|
|
2355
2359
|
"default": true
|
|
@@ -3423,6 +3427,9 @@
|
|
|
3423
3427
|
"damping": {
|
|
3424
3428
|
"type": "number",
|
|
3425
3429
|
"default": 5
|
|
3430
|
+
},
|
|
3431
|
+
"collide": {
|
|
3432
|
+
"default": null
|
|
3426
3433
|
}
|
|
3427
3434
|
},
|
|
3428
3435
|
"additionalProperties": false
|
|
@@ -3577,6 +3584,9 @@
|
|
|
3577
3584
|
"damping": {
|
|
3578
3585
|
"type": "number",
|
|
3579
3586
|
"default": 5
|
|
3587
|
+
},
|
|
3588
|
+
"collide": {
|
|
3589
|
+
"default": null
|
|
3580
3590
|
}
|
|
3581
3591
|
},
|
|
3582
3592
|
"additionalProperties": false
|
|
@@ -5670,6 +5680,15 @@
|
|
|
5670
5680
|
"minItems": 3,
|
|
5671
5681
|
"maxItems": 3,
|
|
5672
5682
|
"default": [0, 0, 0]
|
|
5683
|
+
},
|
|
5684
|
+
"angularVelocity": {
|
|
5685
|
+
"type": "array",
|
|
5686
|
+
"items": {
|
|
5687
|
+
"type": "number"
|
|
5688
|
+
},
|
|
5689
|
+
"minItems": 3,
|
|
5690
|
+
"maxItems": 3,
|
|
5691
|
+
"default": [0, 0, 0]
|
|
5673
5692
|
}
|
|
5674
5693
|
},
|
|
5675
5694
|
"additionalProperties": false
|
|
@@ -7661,7 +7680,7 @@
|
|
|
7661
7680
|
},
|
|
7662
7681
|
"focusable": {
|
|
7663
7682
|
"type": "boolean",
|
|
7664
|
-
"default":
|
|
7683
|
+
"default": true
|
|
7665
7684
|
},
|
|
7666
7685
|
"draggable": {
|
|
7667
7686
|
"type": "boolean",
|
|
@@ -41,8 +41,14 @@ const game = await createGame3D({
|
|
|
41
41
|
scene: sceneJson, // cloned internally — no cast, no structuredClone
|
|
42
42
|
pointer: true, // pointer-look + lock-on-click (FPS pattern); default off
|
|
43
43
|
// behaviors: { ... }, // registered for you (hot-replace tolerant)
|
|
44
|
+
// settings: { namespace: 'my-game' }, // ← if anything else Incanto is
|
|
45
|
+
// served from this domain. Volume, language and the quality tier persist
|
|
46
|
+
// under `incanto:<namespace>:`, and the default is shared by everyone: a
|
|
47
|
+
// neighbouring game's player can boot yours at quarter resolution, muted.
|
|
44
48
|
});
|
|
45
49
|
// game.engine / game.scene / game.renderer / game.physics
|
|
50
|
+
// `game.scene` is the LIVE scene, not the one you booted with — it follows every
|
|
51
|
+
// swap, including `flow.restart()`. `game.sourceJson` is the authored JSON.
|
|
46
52
|
// game.dispose() — ONE call tears everything down (SPA unmount)
|
|
47
53
|
```
|
|
48
54
|
|
|
@@ -629,3 +635,16 @@ or empty, and nothing throws.
|
|
|
629
635
|
headless bot sessions and reports what could not be reached, which wires never
|
|
630
636
|
fired, and whether anything in the level can hurt the player. See
|
|
631
637
|
`incanto-playtesting.md`.
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
### `findFloatingProps` throws rather than reporting a clean scene it never read
|
|
641
|
+
|
|
642
|
+
It answers "which props hover above the terrain", and it needs the scene to LOAD
|
|
643
|
+
and a `Terrain3D` to measure against. It used to return `[]` for both failures —
|
|
644
|
+
so a scene that would not load, a scene with no terrain and a genuinely clean
|
|
645
|
+
scene all printed the same green.
|
|
646
|
+
|
|
647
|
+
It stubs unregistered behaviors (it measures geometry, not logic; a scene with
|
|
648
|
+
any custom behavior used to read as clean — measured 0 floating before
|
|
649
|
+
`registerBehavior`, 26 after) and throws when it cannot measure. A level built
|
|
650
|
+
from mesh floors has no terrain height to read: use a physics raycast instead.
|
package/skills/incanto-editor.md
CHANGED
|
@@ -44,7 +44,11 @@ The `scenes` button opens the project as a tree, not a list of paths:
|
|
|
44
44
|
panel — so `⏎` loads something without arrowing first.
|
|
45
45
|
- Each row carries **when it was last written and how big it is**, which is usually
|
|
46
46
|
how you recognise the file you were just in.
|
|
47
|
-
- **create** makes a new scene at the path in the box —
|
|
47
|
+
- **create** makes a new scene at the path in the box — **matching the project**:
|
|
48
|
+
a 3D game gets `dimension: "3d"` with a `Node3D` root, a 2D game gets 2D, read
|
|
49
|
+
from the scenes already on disk. It used to hardcode 2D for everyone, and node
|
|
50
|
+
types are immutable by design, so a 3D author's new scene could only be deleted
|
|
51
|
+
and re-made by hand — left empty it uses the
|
|
48
52
|
placeholder, which tracks the folder you are standing in, so a scene lands beside
|
|
49
53
|
its siblings rather than at the project root. Parent dirs are created.
|
|
50
54
|
- Loading another scene while you have unsaved EDITS asks first, and the answer
|
|
@@ -799,7 +799,7 @@ hue reads grey.
|
|
|
799
799
|
| `sunDirection` | `[0.5, 0.8, 0.3]` | TOWARD the sun, for the specular glint — match the scene's key light (non-zero, load-time check) |
|
|
800
800
|
| `sunColor` | `"#fff5d6"` | glint/sheen tint |
|
|
801
801
|
| `sunIntensity` | `1` | glint/sheen strength (≥ 0; 0 = off) |
|
|
802
|
-
| `detailStrength` | `0.
|
|
802
|
+
| `detailStrength` | `0.26` | animated detail-normal strength (≥ 0; 0 = vertex normals only) |
|
|
803
803
|
| `absorption` | `0.15` | Beer's-law constant per meter of water depth (≥ 0; lower = clearer/wider turquoise band; red absorbs ~3× faster than blue under the hood) |
|
|
804
804
|
| `refraction` | `true` | screen-space refraction of the submerged scene |
|
|
805
805
|
| `preset` | `"custom"` | **THE one-choice water type** (0.16.0): `ocean` \| `pool` \| `lake` \| `pond` bundles every wave/color/clarity/caustics knob into a hand-tuned sea state. A preset value applies ONLY where a prop still sits at its schema default — pick the type, then override any detail prop and your value wins. ocean = traveling swell + whitecaps + wide turquoise→deep absorption · pool = near-flat crystal water + strong above-water caustics · lake = calm high-reflectivity mirror, green-teal murk · pond = still, mossy, opaque quickly. `custom` (default) changes nothing |
|
|
@@ -1074,7 +1074,7 @@ underneath decides everything else.
|
|
|
1074
1074
|
| `opacity` | `0.85` | upper bound on the body's opacity |
|
|
1075
1075
|
| `foam` | `1` | whitewater dial — 0 = a glassy canal, 2 = raging |
|
|
1076
1076
|
| `ripples` | `1` | surface-detail dial (ripple relief + glitter) |
|
|
1077
|
-
| `terrain` | `
|
|
1077
|
+
| `terrain` | `""` | drape target path; empty = auto-find the first Terrain3D (a wrong path fails at load) |
|
|
1078
1078
|
| `carve` | `true` | cut the bed. The river trenches its own channel into the terrain it drapes on — see below |
|
|
1079
1079
|
| `flowForce` | `1` | how hard the current sweeps bodies downstream (0 = visual only) |
|
|
1080
1080
|
| `spray` | `1` | mist at the foot of every drop the course turns out to contain — the node finds them and hangs a Particles3D plume on each (0 = off) |
|
|
@@ -111,7 +111,7 @@ what breaks one that forgets the other half:
|
|
|
111
111
|
|
|
112
112
|
```json
|
|
113
113
|
{ "signal": "died", "from": "Player", "to": "Score", "handler": "loseLife" },
|
|
114
|
-
{ "signal": "lifeLost", "from": "Score", "to": "Player", "handler": "
|
|
114
|
+
{ "signal": "lifeLost", "from": "Score", "to": "Player", "handler": "reviveFull" }
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
Without the second wire the player is a walking corpse after the first death —
|
|
@@ -516,6 +516,59 @@ is a far worse failure than one running when it was meant to sleep.
|
|
|
516
516
|
|
|
517
517
|
From TypeScript it is the same switch: `this.getNode('../Hunt').behavior.enable()`.
|
|
518
518
|
|
|
519
|
+
## FaceTarget
|
|
520
|
+
|
|
521
|
+
Turn toward the nearest thing in a group and say when you are pointed at it —
|
|
522
|
+
turrets, sentry guns, security cameras, an NPC that watches you walk past.
|
|
523
|
+
|
|
524
|
+
`Chase` finds the nearest target and turns toward it, and then MOVES, which is
|
|
525
|
+
the one thing a turret must not do; `Patrol`/`Chase`'s `facePath` only turns as
|
|
526
|
+
a side effect of locomotion, so a stationary node could not use it. A tower
|
|
527
|
+
defense built before this hand-wrote acquisition and the aim gate.
|
|
528
|
+
|
|
529
|
+
| Prop | Default | Meaning |
|
|
530
|
+
|---|---|---|
|
|
531
|
+
| `targetGroup` | `""` | group to acquire from — nearest live member wins (required) |
|
|
532
|
+
| `range` | `0` | only acquire within this distance (0 = anywhere) |
|
|
533
|
+
| `facePath` | `""` | node to TURN (usually a skin); empty turns its own node |
|
|
534
|
+
| `turnSpeed` | `6` | turn rate rad/s; 100 is an instant snap |
|
|
535
|
+
| `aimTolerance` | `6` | degrees of slop that still counts as pointed at it |
|
|
536
|
+
|
|
537
|
+
Signals: `acquired(node)` · `lostTarget` · **`aimed`** · `lostAim`
|
|
538
|
+
|
|
539
|
+
```jsonc
|
|
540
|
+
{ "name": "Turret", "type": "Node3D",
|
|
541
|
+
"script": { "name": "FaceTarget",
|
|
542
|
+
"props": { "targetGroup": "enemy", "range": 12, "facePath": "../Barrel" } } }
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**`aimed` is the useful one.** "Only fire while actually pointed at it" becomes a
|
|
546
|
+
wire instead of an angle comparison every caller repeats and one caller forgets.
|
|
547
|
+
`onTarget` and `target` are readable from TypeScript for the rest.
|
|
548
|
+
|
|
549
|
+
## Currency
|
|
550
|
+
|
|
551
|
+
Money you can spend. `ScoreKeeper` is a SCORE — it counts up, it has no spend,
|
|
552
|
+
no affordability question, and its counter already drives the win condition, so
|
|
553
|
+
every shop, build mode, upgrade tree and economy game wrote its own wallet.
|
|
554
|
+
|
|
555
|
+
| Prop | Default | Meaning |
|
|
556
|
+
|---|---|---|
|
|
557
|
+
| `amount` | `0` | what you start with |
|
|
558
|
+
| `max` | `0` | ceiling (0 = none) |
|
|
559
|
+
|
|
560
|
+
Signals: `changed(amount)` · `earned(gain)` · `spent(cost)` · **`refused(cost)`**
|
|
561
|
+
Methods: `spend(cost) -> boolean` · `earn(gain)` · `canAfford(cost)` · `setAmount(n)`
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
if (!wallet.spend(TOWER_COST)) return; // `refused` already fired for the HUD
|
|
565
|
+
placeTower(pad);
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
`spend` returns whether it went through and emits `refused` when it did not, so
|
|
569
|
+
"you cannot afford that" is one wire to a banner. It saves through `serialize()`
|
|
570
|
+
like `Health` and `ScoreKeeper`.
|
|
571
|
+
|
|
519
572
|
## Wander
|
|
520
573
|
|
|
521
574
|
Seeded random roaming inside a circle around the spawn point — idle critters,
|
|
@@ -768,8 +821,35 @@ Signals: `waveStarted(index)` · `waveCleared(index)` · `allCleared`
|
|
|
768
821
|
## Projectile
|
|
769
822
|
|
|
770
823
|
Straight-line, constant-velocity motion — bullets, arrows, thrown rocks. Pure
|
|
771
|
-
MOVEMENT by design: **compose** it with `DamageOnContact` (deal damage)
|
|
772
|
-
(auto-despawn)
|
|
824
|
+
MOVEMENT by design: **compose** it with `DamageOnContact` (deal damage) and
|
|
825
|
+
`Lifetime` (auto-despawn) — don't conflate them. A node carries ONE script, so
|
|
826
|
+
those go on CHILD nodes and each one sets **`freeParent: true`** so it frees the
|
|
827
|
+
BULLET rather than itself. Copy this:
|
|
828
|
+
|
|
829
|
+
```jsonc
|
|
830
|
+
{ "name": "Bullet", "type": "Node3D",
|
|
831
|
+
"script": { "name": "Projectile", "props": { "speed": 30 } },
|
|
832
|
+
"children": [
|
|
833
|
+
{ "name": "Hit", "type": "Area3D",
|
|
834
|
+
"props": { "collider": { "shape": "sphere", "radius": 0.2 } },
|
|
835
|
+
"script": { "name": "DamageOnContact",
|
|
836
|
+
"props": { "amount": 10, "targetGroup": "enemy",
|
|
837
|
+
"destroySelf": true, "freeParent": true } } },
|
|
838
|
+
{ "name": "Life", "type": "Node3D",
|
|
839
|
+
"script": { "name": "Lifetime", "props": { "seconds": 2, "freeParent": true } } }
|
|
840
|
+
] }
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
**Without `freeParent` the bullet never dies.** Both behaviors used to free only
|
|
844
|
+
their own node, so the hitbox and the timer removed THEMSELVES and the bullet
|
|
845
|
+
flew on forever — one leaked node per shot, damaging exactly once, with no error.
|
|
846
|
+
Measured on a tower defense built from the published package:
|
|
847
|
+
|
|
848
|
+
```
|
|
849
|
+
t=0.58 bullet z=2.92 | Dmg child: alive | Life child: gone | foe hp: 100
|
|
850
|
+
t=1.18 bullet z=5.92 | Dmg child: gone | Life child: gone | foe hp: 90
|
|
851
|
+
t=2.88 bullet z=14.42 | (both gone) | foe hp: 90 <- still flying
|
|
852
|
+
```
|
|
773
853
|
`direction` is a vector OR `'forward'` (from the node `rotation`); it's a
|
|
774
854
|
union-typed prop so its default is `null` (= `'forward'`).
|
|
775
855
|
|
|
@@ -792,10 +872,9 @@ as a gun that simply never hits anything.) In 2D `rotation` is a scalar and
|
|
|
792
872
|
{ "name": "Bullet", "type": "Area2D", "props": { "rotation": 0 },
|
|
793
873
|
"script": { "name": "Projectile", "props": { "speed": 600, "direction": "forward" } } }
|
|
794
874
|
```
|
|
795
|
-
> Compose: add `DamageOnContact` and `Lifetime`
|
|
796
|
-
>
|
|
797
|
-
>
|
|
798
|
-
> bullet template).
|
|
875
|
+
> Compose: add `DamageOnContact` and `Lifetime` on CHILD nodes, both with
|
|
876
|
+
> `freeParent: true`, as shown above — or use a Spawner that clones a
|
|
877
|
+
> fully-built bullet template.
|
|
799
878
|
|
|
800
879
|
---
|
|
801
880
|
|
package/skills/incanto-hud.md
CHANGED
|
@@ -183,7 +183,20 @@ support" was not something a JSON scene could express at all.
|
|
|
183
183
|
```
|
|
184
184
|
|
|
185
185
|
Arrow keys / d-pad move the focus between the **focusable** widgets under that
|
|
186
|
-
layer
|
|
186
|
+
layer — and only the ones the player can actually SEE: a hidden widget hides
|
|
187
|
+
everything under it, and a hidden layer has no ring at all. (Until 0.67 the walk
|
|
188
|
+
recursed into closed panels, so Enter on a title screen could press a button in
|
|
189
|
+
the shop and spend the gold.) `hud.focusables()` returns the ring, so a game can
|
|
190
|
+
ask what it is stuck with.
|
|
191
|
+
|
|
192
|
+
A **`UiDialogue` takes focus while it is up**: Enter/A picks the highlighted
|
|
193
|
+
choice, left/right move between them, and a line with no choices advances. Its
|
|
194
|
+
choices are DOM buttons inside the widget rather than nodes, so this is the
|
|
195
|
+
widget's own key handling — before 0.67 a choice could only be answered with a
|
|
196
|
+
mouse, and `charsPerSecond: 0` rendered no buttons at all, which was an
|
|
197
|
+
unanswerable soft-lock.
|
|
198
|
+
|
|
199
|
+
`Enter` / `A` activates, and the focused one wears a ring.
|
|
187
200
|
`UiButton`/`UiSlider`/`UiToggle`/`UiSelect` are focusable by default;
|
|
188
201
|
`UiText`/`UiBar`/`UiImage`/`UiPanel` are not, so arrowing never lands on a label.
|
|
189
202
|
|
|
@@ -378,3 +378,17 @@ Kernel constraints (agent8 platform facts — don't fight them):
|
|
|
378
378
|
client transport swap (for a normal single-client game, just omit `{transport}`;
|
|
379
379
|
a split-screen demo like arena-preview also drops its local-only scaffolding) PLUS
|
|
380
380
|
deploying `server/` (the "Going live on agent8" section above) — not literally one line.
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
## The `agrees` rung runs YOUR server
|
|
384
|
+
|
|
385
|
+
`incanto-multiplay` loads `server/src/server.ts` (or `--server FILE`) and runs
|
|
386
|
+
the match against it. It did not until 0.67: every match ran on a bare loopback
|
|
387
|
+
kernel with no remote functions, so `roomState` came back `{}` instead of
|
|
388
|
+
`{"matchMs": 5983.3}`, a `manager.call('claimRelic')` threw
|
|
389
|
+
`Loopback kernel has no remote function 'claimRelic'`, and the report still said
|
|
390
|
+
`ok=true errors=0 problems=[]`. Moving `server/src/server.ts` out of the tree
|
|
391
|
+
changed nothing — a ✓ that could not have been a ✗.
|
|
392
|
+
|
|
393
|
+
If your server cannot be loaded the run REFUSES rather than quietly measuring a
|
|
394
|
+
game without its own rules.
|
|
@@ -290,6 +290,7 @@ Signals: `movementStateChanged(state)`
|
|
|
290
290
|
| `airControl` | `0.2` | number |
|
|
291
291
|
| `fallGravity` | `2.5` | number |
|
|
292
292
|
| `floatHeight` | `0.01` | number |
|
|
293
|
+
| `stepHeight` | `0.35` | number |
|
|
293
294
|
| `mouseLook` | `true` | boolean |
|
|
294
295
|
| `zoomMin` | `0` | number |
|
|
295
296
|
| `zoomMax` | `0` | number |
|
|
@@ -446,6 +447,7 @@ Signals: `movementStateChanged(state)`
|
|
|
446
447
|
| `length` | `0` | number |
|
|
447
448
|
| `stiffness` | `50` | number |
|
|
448
449
|
| `damping` | `5` | number |
|
|
450
|
+
| `collide` | `null` | null |
|
|
449
451
|
|
|
450
452
|
## `Joint3D` — `incanto/3d`
|
|
451
453
|
|
|
@@ -466,6 +468,7 @@ Signals: `movementStateChanged(state)`
|
|
|
466
468
|
| `length` | `0` | number |
|
|
467
469
|
| `stiffness` | `50` | number |
|
|
468
470
|
| `damping` | `5` | number |
|
|
471
|
+
| `collide` | `null` | null |
|
|
469
472
|
|
|
470
473
|
## `Label` — `incanto/2d`
|
|
471
474
|
|
|
@@ -760,6 +763,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
|
|
|
760
763
|
| `friction` | `0.5` | number |
|
|
761
764
|
| `restitution` | `0` | number |
|
|
762
765
|
| `linearVelocity` | `[0,0,0]` | array |
|
|
766
|
+
| `angularVelocity` | `[0,0,0]` | array |
|
|
763
767
|
|
|
764
768
|
Signals: `triggerEnter(other)` · `triggerExit(other)`
|
|
765
769
|
|
|
@@ -1035,7 +1039,7 @@ Signals: `dragStarted` · `dragCancelled` · `droppedOn` · `dropped` · `presse
|
|
|
1035
1039
|
|---|---|---|
|
|
1036
1040
|
| `anchor` | `"bottom"` | one of: `topLeft` `top` `topRight` `left` `center` `right` `bottomLeft` `bottom` `bottomRight` |
|
|
1037
1041
|
| `visible` | `true` | boolean |
|
|
1038
|
-
| `focusable` | `
|
|
1042
|
+
| `focusable` | `true` | boolean |
|
|
1039
1043
|
| `draggable` | `false` | boolean |
|
|
1040
1044
|
| `dropTarget` | `false` | boolean |
|
|
1041
1045
|
| `charsPerSecond` | `40` | number |
|
|
@@ -1327,6 +1331,7 @@ wire their signals through scene `connections`. Full guide: `incanto-gameplay-be
|
|
|
1327
1331
|
|
|
1328
1332
|
| Prop | Default | Kind |
|
|
1329
1333
|
|---|---|---|
|
|
1334
|
+
| `enabled` | `true` | boolean |
|
|
1330
1335
|
| `water` | `""` | node path |
|
|
1331
1336
|
| `draft` | `0.35` | number |
|
|
1332
1337
|
| `stiffness` | `3` | number |
|
|
@@ -1342,17 +1347,21 @@ Signals: `submerged` · `surfaced`
|
|
|
1342
1347
|
|
|
1343
1348
|
| Prop | Default | Kind |
|
|
1344
1349
|
|---|---|---|
|
|
1350
|
+
| `enabled` | `true` | boolean |
|
|
1345
1351
|
| `falloff` | `0.28` | number |
|
|
1346
1352
|
|
|
1347
1353
|
### `Chase`
|
|
1348
1354
|
|
|
1349
1355
|
| Prop | Default | Kind |
|
|
1350
1356
|
|---|---|---|
|
|
1357
|
+
| `enabled` | `true` | boolean |
|
|
1351
1358
|
| `target` | `""` | node path |
|
|
1352
1359
|
| `speed` | `60` | number |
|
|
1353
1360
|
| `stopRange` | `0` | number |
|
|
1354
1361
|
| `loseRange` | `0` | number |
|
|
1355
1362
|
| `moveParent` | `false` | boolean |
|
|
1363
|
+
| `facePath` | `""` | node path |
|
|
1364
|
+
| `turnSpeed` | `100` | number |
|
|
1356
1365
|
|
|
1357
1366
|
Signals: `reachedTarget` · `lostTarget`
|
|
1358
1367
|
|
|
@@ -1360,9 +1369,9 @@ Signals: `reachedTarget` · `lostTarget`
|
|
|
1360
1369
|
|
|
1361
1370
|
| Prop | Default | Kind |
|
|
1362
1371
|
|---|---|---|
|
|
1372
|
+
| `enabled` | `true` | boolean |
|
|
1363
1373
|
| `button` | `0` | number |
|
|
1364
1374
|
| `maxDistance` | `0` | number |
|
|
1365
|
-
| `enabled` | `true` | boolean |
|
|
1366
1375
|
|
|
1367
1376
|
Signals: `clicked` · `hovered` · `unhovered`
|
|
1368
1377
|
|
|
@@ -1370,19 +1379,32 @@ Signals: `clicked` · `hovered` · `unhovered`
|
|
|
1370
1379
|
|
|
1371
1380
|
| Prop | Default | Kind |
|
|
1372
1381
|
|---|---|---|
|
|
1382
|
+
| `enabled` | `true` | boolean |
|
|
1373
1383
|
| `group` | `"player"` | string |
|
|
1374
1384
|
|
|
1375
1385
|
Signals: `totalChanged(total)`
|
|
1376
1386
|
|
|
1387
|
+
### `Currency`
|
|
1388
|
+
|
|
1389
|
+
| Prop | Default | Kind |
|
|
1390
|
+
|---|---|---|
|
|
1391
|
+
| `enabled` | `true` | boolean |
|
|
1392
|
+
| `amount` | `0` | number |
|
|
1393
|
+
| `max` | `0` | number |
|
|
1394
|
+
|
|
1395
|
+
Signals: `changed` · `earned` · `spent` · `refused`
|
|
1396
|
+
|
|
1377
1397
|
### `DamageOnContact`
|
|
1378
1398
|
|
|
1379
1399
|
| Prop | Default | Kind |
|
|
1380
1400
|
|---|---|---|
|
|
1401
|
+
| `enabled` | `true` | boolean |
|
|
1381
1402
|
| `amount` | `10` | number |
|
|
1382
1403
|
| `targetGroup` | `""` | string |
|
|
1383
1404
|
| `oncePerTarget` | `true` | boolean |
|
|
1384
1405
|
| `repeatEvery` | `0` | number |
|
|
1385
1406
|
| `destroySelf` | `false` | boolean |
|
|
1407
|
+
| `freeParent` | `false` | boolean |
|
|
1386
1408
|
|
|
1387
1409
|
Signals: `dealtDamage(amount, target)`
|
|
1388
1410
|
|
|
@@ -1390,6 +1412,7 @@ Signals: `dealtDamage(amount, target)`
|
|
|
1390
1412
|
|
|
1391
1413
|
| Prop | Default | Kind |
|
|
1392
1414
|
|---|---|---|
|
|
1415
|
+
| `enabled` | `true` | boolean |
|
|
1393
1416
|
| `daySeconds` | `240` | number |
|
|
1394
1417
|
| `startHour` | `10` | number |
|
|
1395
1418
|
| `maxSunElevationDeg` | `55` | number |
|
|
@@ -1398,10 +1421,24 @@ Signals: `dealtDamage(amount, target)`
|
|
|
1398
1421
|
|
|
1399
1422
|
Signals: `dayPhaseChanged`
|
|
1400
1423
|
|
|
1424
|
+
### `FaceTarget`
|
|
1425
|
+
|
|
1426
|
+
| Prop | Default | Kind |
|
|
1427
|
+
|---|---|---|
|
|
1428
|
+
| `enabled` | `true` | boolean |
|
|
1429
|
+
| `targetGroup` | `""` | string |
|
|
1430
|
+
| `range` | `0` | number |
|
|
1431
|
+
| `facePath` | `""` | node path |
|
|
1432
|
+
| `turnSpeed` | `6` | number |
|
|
1433
|
+
| `aimTolerance` | `6` | number |
|
|
1434
|
+
|
|
1435
|
+
Signals: `acquired` · `lostTarget` · `aimed` · `lostAim`
|
|
1436
|
+
|
|
1401
1437
|
### `FloatAway`
|
|
1402
1438
|
|
|
1403
1439
|
| Prop | Default | Kind |
|
|
1404
1440
|
|---|---|---|
|
|
1441
|
+
| `enabled` | `true` | boolean |
|
|
1405
1442
|
| `rise` | `1` | number |
|
|
1406
1443
|
| `seconds` | `0.7` | number |
|
|
1407
1444
|
| `hold` | `0.3` | number |
|
|
@@ -1412,6 +1449,7 @@ Signals: `dayPhaseChanged`
|
|
|
1412
1449
|
|
|
1413
1450
|
| Prop | Default | Kind |
|
|
1414
1451
|
|---|---|---|
|
|
1452
|
+
| `enabled` | `true` | boolean |
|
|
1415
1453
|
| `target` | `""` | node path |
|
|
1416
1454
|
| `offset` | `[]` | array |
|
|
1417
1455
|
| `smoothing` | `0` | number |
|
|
@@ -1421,6 +1459,7 @@ Signals: `dayPhaseChanged`
|
|
|
1421
1459
|
|
|
1422
1460
|
| Prop | Default | Kind |
|
|
1423
1461
|
|---|---|---|
|
|
1462
|
+
| `enabled` | `true` | boolean |
|
|
1424
1463
|
| `restartAction` | `"restart"` | string |
|
|
1425
1464
|
| `pauseAction` | `"pause"` | string |
|
|
1426
1465
|
| `freezeOnEnd` | `true` | boolean |
|
|
@@ -1433,6 +1472,7 @@ Signals: `flowChanged`
|
|
|
1433
1472
|
|
|
1434
1473
|
| Prop | Default | Kind |
|
|
1435
1474
|
|---|---|---|
|
|
1475
|
+
| `enabled` | `true` | boolean |
|
|
1436
1476
|
| `max` | `100` | number |
|
|
1437
1477
|
| `regenPerSec` | `0` | number |
|
|
1438
1478
|
| `invulnerableFor` | `0` | number |
|
|
@@ -1444,6 +1484,7 @@ Signals: `damaged(amount, current)` · `healed(amount, current)` · `died` · `r
|
|
|
1444
1484
|
|
|
1445
1485
|
| Prop | Default | Kind |
|
|
1446
1486
|
|---|---|---|
|
|
1487
|
+
| `enabled` | `true` | boolean |
|
|
1447
1488
|
| `action` | `"interact"` | string |
|
|
1448
1489
|
| `range` | `2` | number |
|
|
1449
1490
|
| `actorGroup` | `"player"` | string |
|
|
@@ -1454,8 +1495,10 @@ Signals: `interacted(actor)`
|
|
|
1454
1495
|
|
|
1455
1496
|
| Prop | Default | Kind |
|
|
1456
1497
|
|---|---|---|
|
|
1498
|
+
| `enabled` | `true` | boolean |
|
|
1457
1499
|
| `seconds` | `1` | number |
|
|
1458
1500
|
| `startOnSignal` | `false` | boolean |
|
|
1501
|
+
| `freeParent` | `false` | boolean |
|
|
1459
1502
|
|
|
1460
1503
|
Signals: `expired`
|
|
1461
1504
|
|
|
@@ -1463,6 +1506,7 @@ Signals: `expired`
|
|
|
1463
1506
|
|
|
1464
1507
|
| Prop | Default | Kind |
|
|
1465
1508
|
|---|---|---|
|
|
1509
|
+
| `enabled` | `true` | boolean |
|
|
1466
1510
|
| `to` | `[]` | array |
|
|
1467
1511
|
| `duration` | `1` | number |
|
|
1468
1512
|
| `ease` | `"easeInOut"` | one of: `linear` `easeIn` `easeOut` `easeInOut` |
|
|
@@ -1474,6 +1518,7 @@ Signals: `arrived`
|
|
|
1474
1518
|
|
|
1475
1519
|
| Prop | Default | Kind |
|
|
1476
1520
|
|---|---|---|
|
|
1521
|
+
| `enabled` | `true` | boolean |
|
|
1477
1522
|
| `axis` | `"y"` | one of: `x` `y` `z` |
|
|
1478
1523
|
| `amplitude` | `1` | number |
|
|
1479
1524
|
| `frequency` | `1` | number |
|
|
@@ -1483,6 +1528,7 @@ Signals: `arrived`
|
|
|
1483
1528
|
|
|
1484
1529
|
| Prop | Default | Kind |
|
|
1485
1530
|
|---|---|---|
|
|
1531
|
+
| `enabled` | `true` | boolean |
|
|
1486
1532
|
| `speed` | `120` | number |
|
|
1487
1533
|
| `loop` | `false` | boolean |
|
|
1488
1534
|
|
|
@@ -1492,11 +1538,15 @@ Signals: `waypointReached` · `arrived`
|
|
|
1492
1538
|
|
|
1493
1539
|
| Prop | Default | Kind |
|
|
1494
1540
|
|---|---|---|
|
|
1541
|
+
| `enabled` | `true` | boolean |
|
|
1495
1542
|
| `points` | `[]` | array |
|
|
1496
1543
|
| `speed` | `60` | number |
|
|
1497
1544
|
| `loop` | `true` | boolean |
|
|
1498
1545
|
| `mode` | `"loop"` | one of: `loop` `pingpong` |
|
|
1499
1546
|
| `pauseAt` | `0` | number |
|
|
1547
|
+
| `moveParent` | `false` | boolean |
|
|
1548
|
+
| `facePath` | `""` | node path |
|
|
1549
|
+
| `turnSpeed` | `100` | number |
|
|
1500
1550
|
|
|
1501
1551
|
Signals: `reachedPoint(index)`
|
|
1502
1552
|
|
|
@@ -1504,6 +1554,7 @@ Signals: `reachedPoint(index)`
|
|
|
1504
1554
|
|
|
1505
1555
|
| Prop | Default | Kind |
|
|
1506
1556
|
|---|---|---|
|
|
1557
|
+
| `enabled` | `true` | boolean |
|
|
1507
1558
|
| `value` | `1` | number |
|
|
1508
1559
|
| `kind` | `"coin"` | string |
|
|
1509
1560
|
| `collectorGroup` | `"player"` | string |
|
|
@@ -1514,6 +1565,7 @@ Signals: `collected(value, other)`
|
|
|
1514
1565
|
|
|
1515
1566
|
| Prop | Default | Kind |
|
|
1516
1567
|
|---|---|---|
|
|
1568
|
+
| `enabled` | `true` | boolean |
|
|
1517
1569
|
| `speed` | `300` | number |
|
|
1518
1570
|
| `direction` | `null` | null |
|
|
1519
1571
|
| `gravity` | `0` | number |
|
|
@@ -1522,6 +1574,7 @@ Signals: `collected(value, other)`
|
|
|
1522
1574
|
|
|
1523
1575
|
| Prop | Default | Kind |
|
|
1524
1576
|
|---|---|---|
|
|
1577
|
+
| `enabled` | `true` | boolean |
|
|
1525
1578
|
| `game` | `"game"` | string |
|
|
1526
1579
|
| `slot` | `"1"` | string |
|
|
1527
1580
|
| `label` | `""` | string |
|
|
@@ -1535,6 +1588,7 @@ Signals: `saved` · `restored` · `noSave` · `hasSave`
|
|
|
1535
1588
|
|
|
1536
1589
|
| Prop | Default | Kind |
|
|
1537
1590
|
|---|---|---|
|
|
1591
|
+
| `enabled` | `true` | boolean |
|
|
1538
1592
|
| `score` | `0` | number |
|
|
1539
1593
|
| `lives` | `0` | number |
|
|
1540
1594
|
| `scoreToWin` | `0` | number |
|
|
@@ -1545,6 +1599,7 @@ Signals: `scoreChanged(score)` · `won` · `lost` · `lifeLost(lives)`
|
|
|
1545
1599
|
|
|
1546
1600
|
| Prop | Default | Kind |
|
|
1547
1601
|
|---|---|---|
|
|
1602
|
+
| `enabled` | `true` | boolean |
|
|
1548
1603
|
| `prefab` | `""` | node path |
|
|
1549
1604
|
| `interval` | `1` | number |
|
|
1550
1605
|
| `max` | `0` | number |
|
|
@@ -1558,6 +1613,7 @@ Signals: `spawned(node)` · `finished`
|
|
|
1558
1613
|
|
|
1559
1614
|
| Prop | Default | Kind |
|
|
1560
1615
|
|---|---|---|
|
|
1616
|
+
| `enabled` | `true` | boolean |
|
|
1561
1617
|
| `speed` | `40` | number |
|
|
1562
1618
|
| `radius` | `50` | number |
|
|
1563
1619
|
| `changeEvery` | `2` | number |
|
|
@@ -1566,6 +1622,7 @@ Signals: `spawned(node)` · `finished`
|
|
|
1566
1622
|
|
|
1567
1623
|
| Prop | Default | Kind |
|
|
1568
1624
|
|---|---|---|
|
|
1625
|
+
| `enabled` | `true` | boolean |
|
|
1569
1626
|
| `waves` | `[]` | array |
|
|
1570
1627
|
| `autoStart` | `true` | boolean |
|
|
1571
1628
|
|
|
@@ -1575,6 +1632,7 @@ Signals: `waveStarted(index)` · `waveCleared(index)` · `allCleared`
|
|
|
1575
1632
|
|
|
1576
1633
|
| Prop | Default | Kind |
|
|
1577
1634
|
|---|---|---|
|
|
1635
|
+
| `enabled` | `true` | boolean |
|
|
1578
1636
|
| `aggroTarget` | `""` | node path |
|
|
1579
1637
|
| `aggroRange` | `12` | number |
|
|
1580
1638
|
| `deAggroRange` | `0` | number |
|
|
@@ -289,3 +289,38 @@ Set `maxFps` to 30 and confirm `fps` settles near 30 — if it settles near 20,
|
|
|
289
289
|
is slower than the cap and the cap is not what is limiting it.
|
|
290
290
|
|
|
291
291
|
See `incanto-verifying-your-game.md` for the full loop.
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
## Identical meshes share their GPU objects
|
|
295
|
+
|
|
296
|
+
`MeshInstance3D` used to make a private geometry AND material per node, even
|
|
297
|
+
when byte-identical. Measured at 3,200 units — all runs at the same 3,209 draw
|
|
298
|
+
calls, 1,741,932 triangles and an identical screenshot:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
per-node geometry + material 31.0 ms wall (renderMs 27.3)
|
|
302
|
+
share one geometry 27.0 ms
|
|
303
|
+
+ share two materials 15.4 ms <- 2x the headroom
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
13.1 ms of that cliff was GPU state churn from per-node objects. Nodes now share
|
|
307
|
+
one geometry per (mesh, size) and one material per distinct `material` dict, and
|
|
308
|
+
a node whose look changes gets its own again. **Nothing to do — it is the
|
|
309
|
+
default.** The consequence to know: mutate node PROPS, never the three.js
|
|
310
|
+
material you fished out of a node, because it may not be only yours.
|
|
311
|
+
|
|
312
|
+
This does not replace `InstancedMesh3D`. A field of identical props is still one
|
|
313
|
+
node and one draw call there (12,800 units, 11 draw calls, 2.30 ms); sharing
|
|
314
|
+
helps the one-node-per-thing path, which is what a hand-authored level is.
|
|
315
|
+
|
|
316
|
+
## The quality tier only ever takes away
|
|
317
|
+
|
|
318
|
+
`high` no longer mentions `bloom` or `post` at all, because mentioning them
|
|
319
|
+
CREATED them: on a scene that declared neither, `high` and `medium` both
|
|
320
|
+
rendered a five-pass frame at 3.3 ms where `low` rendered one pass at 1.24 ms —
|
|
321
|
+
62% of the frame on a chain the game never asked for, and
|
|
322
|
+
`engine.scene.environment.bloom` read back `{strength: 0.8, threshold: 1}`.
|
|
323
|
+
|
|
324
|
+
A tier is also REVERSIBLE now: it is applied against the scene FILE rather than
|
|
325
|
+
against whatever the last tier left behind, so `low` → `high` restores exactly
|
|
326
|
+
what you authored instead of leaving it stripped.
|