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.
- package/bin/incanto-check.mjs +114 -42
- package/bin/incanto-verify.mjs +9 -2
- package/dist/2d.d.ts +29 -4
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +137 -15
- package/dist/3d.js +6 -6
- package/dist/{agent8-BqZ0_O1u.js → agent8-CHTT4unP.js} +1 -1
- package/dist/{audio-player-ioZ7VKC6.d.ts → audio-player-BOrk4eQx.d.ts} +1 -1
- package/dist/{behavior-Bf9P9oB2.d.ts → behavior-J1cmVah0.d.ts} +8 -0
- package/dist/{create-game-CmFAtLZp.js → create-game-BAiA-FjP.js} +7 -7
- package/dist/{create-game-C9rWqqAC.js → create-game-ssvGvP7W.js} +7 -7
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +1 -1
- package/dist/editor.js +965 -893
- package/dist/{environment-presets-BeHoaGhy.js → environment-presets-BkqlWewf.js} +277 -60
- package/dist/{gameplay-BlQe-M07.js → gameplay-Bxe1sMVT.js} +611 -39
- package/dist/gameplay.d.ts +141 -7
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +6 -4
- package/dist/index.js +6 -6
- package/dist/{loader-CBfQzB6S.d.ts → loader-BAeWMYfF.d.ts} +1 -1
- package/dist/net.d.ts +2 -2
- package/dist/net.js +2 -2
- package/dist/{physics-2d-DrIgTlLk.js → physics-2d-C-SfvjzH.js} +94 -11
- package/dist/{physics-3d-BzOCw1VS.js → physics-3d-rOzeg890.js} +202 -15
- package/dist/{picking-C9McVJrO.js → picking-BMV34Pjl.js} +2 -2
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-5IYfooAN.js → register-CjociOtt.js} +19 -4
- package/dist/{register-C8HuRkOf.js → register-DRQyZKGU.js} +45 -9
- package/dist/{replay-datiAE-b.js → replay-B6BbgGM_.js} +42 -6
- package/dist/{replay-CBEChqXq.d.ts → replay-wZK5Ok5c.d.ts} +1 -1
- package/dist/{save-slots-CEuJPUle.js → save-slots-CEUGeLuB.js} +11 -2
- package/dist/{split-screen-Bm5DTElG.js → split-screen-BxZ5rBe7.js} +4 -4
- package/dist/{split-screen-DkqLng6q.d.ts → split-screen-D6sx8z3Z.d.ts} +2 -2
- package/dist/{sprite-animation-CqR2o3SA.js → sprite-animation-Bz3DHD_4.js} +1 -1
- package/dist/{src-DRSsRleD.js → src-CqtvYtSN.js} +1 -1
- package/dist/{test-Djuq3Vhq.js → test-CEl0y2Sw.js} +162 -30
- package/dist/test.d.ts +56 -5
- package/dist/test.js +2 -2
- package/dist/{touch-BnCyPA0G.js → touch-CioTZB-y.js} +1 -1
- package/dist/vite.js +3 -3
- package/editor/assets/{agent8-CMKz2cax.js → agent8-BlzturGi.js} +1 -1
- package/editor/assets/{debug-DLrQm-az.js → debug-BMUi8usj.js} +1 -1
- package/editor/assets/{index-BEKuxYcE.js → index-CrUCQoaB.js} +53 -53
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +634 -10
- package/skills/incanto-3d-character.md +113 -5
- package/skills/incanto-assets.md +9 -3
- package/skills/incanto-behaviors-and-scripts.md +26 -2
- package/skills/incanto-building-2d-games.md +9 -0
- package/skills/incanto-building-3d-games.md +45 -0
- package/skills/incanto-editor.md +6 -1
- package/skills/incanto-environment.md +7 -2
- package/skills/incanto-game-feel.md +16 -11
- package/skills/incanto-gameplay-behaviors.md +179 -16
- package/skills/incanto-node-reference.md +124 -45
- package/skills/incanto-physics-and-input.md +67 -5
- package/skills/incanto-scene-json-authoring.md +9 -3
- package/skills/incanto-verifying-your-game.md +19 -2
- package/templates-app/beacon-isle-3d/PROJECT/Status.md +8 -0
- package/templates-app/beacon-isle-3d/PROJECT/Structure.md +3 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +136 -0
- package/templates-app/beacon-isle-3d/verify.ts +144 -0
- package/templates-app/molehill-2d/package.json +1 -1
- package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
- package/templates-app/platformer-2d/PROJECT/Structure.md +3 -0
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/game.scene.json +126 -0
- package/templates-app/platformer-2d/verify.ts +54 -0
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/tps-3d/PROJECT/Context.md +1 -1
- package/templates-app/tps-3d/PROJECT/Status.md +40 -1
- package/templates-app/tps-3d/PROJECT/Structure.md +19 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/behaviors.ts +9 -0
- package/templates-app/tps-3d/src/cover.scene.json +26 -0
- package/templates-app/tps-3d/src/game.scene.json +331 -75
- package/templates-app/tps-3d/src/main.ts +14 -0
- package/templates-app/tps-3d/src/pillar.scene.json +25 -0
- package/templates-app/tps-3d/verify.ts +582 -3
- package/templates-app/village-quest-3d/package.json +1 -1
|
@@ -27,17 +27,27 @@ character rides the hover spring, not floor contact, so friction would just brak
|
|
|
27
27
|
movement. Declare `move` (vector2) / `jump` /
|
|
28
28
|
`sprint` actions in the scene input map, and call
|
|
29
29
|
`engine.input.attachPointer(canvas, { lockOnClick: true })` for mouse look.
|
|
30
|
+
The move vector is SCREEN space, y-down: `up` is `-y`, so in a harness
|
|
31
|
+
`setActionVector('move', 0, -1)` walks the character FORWARD (away from the
|
|
32
|
+
camera, -z at yaw 0) and `(0, 1)` walks it toward the camera — the guess that
|
|
33
|
+
drops a first probe off the back of the map.
|
|
30
34
|
|
|
31
35
|
## Movement model (vibe-starter-3d parity)
|
|
32
36
|
|
|
33
37
|
Impulse-based on the dynamic body: target speed = `maxSpeed ×
|
|
34
38
|
(1 + (sprintMultiplier−1)·intensity)` where keyboard intensity is 0.6
|
|
35
39
|
(any move key) or 1.0 (+sprint). Defaults: maxSpeed 2.5, sprint ×2 → walk
|
|
36
|
-
≈4 m/s, sprint ≈5 m/s. `jumpVelocity` 4
|
|
40
|
+
≈4 m/s, sprint ≈5 m/s. `jumpVelocity` 4 — and `sprintJumpMultiplier` (1.2) scales with
|
|
41
|
+
the keyboard's INTENSITY, so a jump while walking is ×1.12 in velocity (+26% in height)
|
|
42
|
+
and while sprinting ×1.2 (+44%): standing 0.81 u, walking 1.01, sprinting 1.16 at the
|
|
43
|
+
defaults. Size a ledge against the moving jump, not the standing one; `incanto-feel`
|
|
44
|
+
prints all three. Falls get
|
|
37
45
|
gravityScale 2.5 with a −20 m/s terminal clamp, and a hover spring holds the
|
|
38
|
-
capsule BOTTOM `floatHeight` above the ground (5-ray probe)
|
|
39
|
-
|
|
40
|
-
capsule
|
|
46
|
+
capsule BOTTOM `floatHeight` above the ground (5-ray probe) — in principle; the
|
|
47
|
+
spring sags about 0.13 m under gravity, so at the default `floatHeight` 0.01 the
|
|
48
|
+
capsule rests ON the floor (measured: bottom −0.001 m). Author the Skin y-offset
|
|
49
|
+
as `-(halfHeight + radius)` so the feet sit at the capsule bottom (the example
|
|
50
|
+
templates do this; adding `floatHeight` is off by that much).
|
|
41
51
|
|
|
42
52
|
## Game feel — the props a jump needs to stop feeling broken
|
|
43
53
|
|
|
@@ -54,17 +64,34 @@ did not until now.
|
|
|
54
64
|
| `maxJumps` | `1` | `2` = double jump. Extra jumps work in mid-air. |
|
|
55
65
|
| `airControl` | `0.2` | how much ground control you keep airborne (0 = committed, 1 = full). |
|
|
56
66
|
| `fallGravity` | `2.5` | gravity multiplier while falling. Higher = snappier arc. |
|
|
67
|
+
| `wallJumpImpulse` | `[0, 0]` | `[away, up]` m/s — airborne and pressing INTO a wall, `jump` kicks off it: away along the wall's normal, up. Try `[6.5, 7]`. A fixed arc (the jump cut never halves it) and air control stands down for 0.18 s so the stick still pointing at the wall cannot steer the kick back into it. Emits `wallJumped(normal)`. |
|
|
68
|
+
| `wallSlideSpeed` | `0` | max fall speed while pressing into a wall (`fallGravity` stands down too). Try `2`. |
|
|
57
69
|
| `platformCarry` | `true` | ride whatever you are standing on. Off = the controller steers in WORLD space, so standing still on a moving floor brakes you off it (measured: 95.9% of a moving deck's travel kept, 0.0% with this off). |
|
|
58
70
|
|
|
59
71
|
Every one defaults to OFF (`0` / `1`) or to the previous hard-coded constant, so
|
|
60
72
|
a scene that asks for nothing behaves exactly as before.
|
|
61
73
|
|
|
74
|
+
**The controller owns the wall-jump.** A kick written from a behaviour lasted
|
|
75
|
+
three frames on a platformer built from the tarball: the jump cut halved its
|
|
76
|
+
rise the moment the tap was released (7 → 2.2 m/s) and air control steered
|
|
77
|
+
the horizontal straight back into the wall (−6.5 → 0 in three steps). Anything
|
|
78
|
+
that fights the controller's own feel props has to live INSIDE them. The wall
|
|
79
|
+
is found by a short static-only ray in the input direction, so the character
|
|
80
|
+
must be pressing into it — a `RigidBody3D` has no `isOnWall`.
|
|
81
|
+
|
|
62
82
|
```json
|
|
63
83
|
{ "name": "Ctl", "type": "CharacterController3D",
|
|
64
84
|
"props": { "coyoteSeconds": 0.12, "jumpBufferSeconds": 0.15,
|
|
65
85
|
"jumpCutMultiplier": 0.45, "maxJumps": 2 } }
|
|
66
86
|
```
|
|
67
87
|
|
|
88
|
+
All six are composed in `examples/tps-3d` (the shipped third-person starter), and
|
|
89
|
+
`bunx incanto-feel` measures what they DO rather than echoing them — on that scene:
|
|
90
|
+
tapped apex 0.21 u vs held 0.81 u at the default `jumpVelocity` 4 — tps-3d itself, at 5,
|
|
91
|
+
measures 0.32 / 1.26 (`jumpCutMultiplier`), a double jump line
|
|
92
|
+
(`maxJumps: 2`), and the coyote and buffer windows still measured with a double
|
|
93
|
+
jump declared. It used to give up on both the moment `maxJumps` went above 1.
|
|
94
|
+
|
|
68
95
|
**Jump reads the button EDGE.** Holding the jump key no longer re-jumps every
|
|
69
96
|
frame — that auto-hop was never intended and it made coyote time incoherent (a
|
|
70
97
|
held button would re-fire through the whole window).
|
|
@@ -75,6 +102,30 @@ props and the airborne animation use a stricter test: close to the support AND
|
|
|
75
102
|
not moving upward. Without the velocity term the check is one frame stale, which
|
|
76
103
|
at launch speed is ~6 cm — enough to still read as standing on the floor.
|
|
77
104
|
|
|
105
|
+
### The two numbers a level is built against — composed and measured
|
|
106
|
+
|
|
107
|
+
`stepHeight` (0.35) and `jumpVelocity` (4) decide what a level can ask of the player, and
|
|
108
|
+
no example had set either. `tps-3d` now declares `stepHeight: 0.4` and `jumpVelocity: 5`
|
|
109
|
+
and its harness asserts each by what the player can DO: walked into a 0.3 m curb it rises
|
|
110
|
+
from y 0.84 to 1.14 and keeps going; walked into a 0.9 m ledge it stops at the face;
|
|
111
|
+
and the same scene with `jumpVelocity` back at 4 reaches 0.81 u where 5 reaches 1.26 u.
|
|
112
|
+
Read the number back from nothing — a curb you can climb is the proof.
|
|
113
|
+
|
|
114
|
+
### The rig around the character — composed and measured
|
|
115
|
+
|
|
116
|
+
`cameraCollision` and `platformCarry` are on by default and no example had ever set or
|
|
117
|
+
measured them. `tps-3d` declares both and its harness asserts each by an effect: standing
|
|
118
|
+
1.25 m in front of a wall with the boom pointing at it, the camera closes from 4.2 m to
|
|
119
|
+
0.45 m; standing still on a `Lift` (a `StaticBody3D` walked by `Patrol`) for two seconds,
|
|
120
|
+
the lift travels 4 m and the rider drifts under 0.25 m off it.
|
|
121
|
+
|
|
122
|
+
`floatHeight` was composed too and taken back out. The hover spring holds the capsule
|
|
123
|
+
bottom `floatHeight` above the ground in principle, but it SAGS about 0.13 m under gravity,
|
|
124
|
+
so below that the capsule simply rests on contact and the prop changes nothing — measured
|
|
125
|
+
at rest: 0.01 → y 0.839, 0.05 → 0.840, 0.3 → 1.008, 0.6 → 1.308 for a capsule whose
|
|
126
|
+
contact height is 0.84. Leave it at the default and use `stepHeight` for ledges (the
|
|
127
|
+
docstring's own advice: a raised `floatHeight` makes the character weightless).
|
|
128
|
+
|
|
78
129
|
## Views
|
|
79
130
|
|
|
80
131
|
| view | what it does | key props |
|
|
@@ -157,6 +208,14 @@ skin.animationUpper = '$anims/attack'; // spine-up plays the attack…
|
|
|
157
208
|
- `animationUpperLoop: true` for sustained upper loops (carry, aim, wave).
|
|
158
209
|
- Works with the controller's `animations` map untouched — locomotion logic
|
|
159
210
|
never learns the attack exists.
|
|
211
|
+
- Composed in `examples/tps-3d`: `Shoot.fire()` sets
|
|
212
|
+
`skin.animationUpper = '$anims/shoot'` and nothing else — the run clip keeps
|
|
213
|
+
the legs, the shot rides the spine up. Measured on the CDN's
|
|
214
|
+
`mixamorig/shoot.glb` (4.03 s, 54 tracks retargeted onto `mixamorigSpine…`):
|
|
215
|
+
the layer's action is live 3 frames after the shot and still at weight 1 with
|
|
216
|
+
the base clip on `$anims/run`. While the clip GLB is still LOADING the layer
|
|
217
|
+
waits silently and starts when it lands — a shot fired in the first second of
|
|
218
|
+
a cold page plays late, not never.
|
|
160
219
|
|
|
161
220
|
## Animations without code
|
|
162
221
|
|
|
@@ -210,7 +269,11 @@ A sword in the hand, a hat on the head, sparks on a wingtip — parent them to a
|
|
|
210
269
|
adjustments).
|
|
211
270
|
- Purely VISUAL: it follows the rendered skeleton, so headless the node stays
|
|
212
271
|
at its prop transform — keep hit checks range-based from the body, never
|
|
213
|
-
bone-based.
|
|
272
|
+
bone-based. The sword MESH rides the bone; the sword HITBOX is an `Area3D`
|
|
273
|
+
on the body, aimed from the skin's yaw and armed for the swing's active
|
|
274
|
+
frames — the recipe, with its numbers, is under `DamageOnContact` in
|
|
275
|
+
`incanto-gameplay-behaviors.md` ("A melee swing"). Do not parent the hitbox
|
|
276
|
+
to the skin: physics composes ancestor offsets only, so it never turns.
|
|
214
277
|
- `model.boneNames()` lists every bone at runtime when you need to hunt one
|
|
215
278
|
(the editor's inspector offers the same list as a dropdown on the `bone` prop).
|
|
216
279
|
|
|
@@ -320,6 +383,51 @@ This cannot be an `auditScene` warning: a scene file has no velocity, and the bu
|
|
|
320
383
|
is a behavior writing a heading at runtime. `facingReport()` from `incanto/test`
|
|
321
384
|
is the same check for your own tests.
|
|
322
385
|
|
|
386
|
+
## Facing the mouse in a top-down view (twin-stick)
|
|
387
|
+
|
|
388
|
+
In `quarter` view the mouse does not look, and the controller turns the skin
|
|
389
|
+
toward the MOVE direction — a twin-stick game wants it toward the CURSOR. Set
|
|
390
|
+
`skinPath: ""` so the controller leaves the skin alone (then drive
|
|
391
|
+
`skin.animation` yourself from `movementStateChanged`), and each frame put the
|
|
392
|
+
cursor on the ground and face it — `engine.toWorld(sx, sy)` is the renderer's
|
|
393
|
+
ray to the y=0 plane, `null` headless, so a harness sets the aim directly:
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
const at = this.input.pointerPosition();
|
|
397
|
+
const w = at && this.engine.toWorld?.(at.x, at.y); // [x, y, z] | null
|
|
398
|
+
const [tx, tz] = w ? [w[0], w[2]] : this.aim ?? [NaN, NaN];
|
|
399
|
+
const dx = tx - player.position[0], dz = tz - player.position[2];
|
|
400
|
+
if (Math.hypot(dx, dz) > 0.2) skin.rotation = [0, Math.atan2(dx, dz) * RAD2DEG, 0];
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Measured on `examples/survivor-3d`: a cursor 10 m east is yaw 90°, and a bolt
|
|
404
|
+
given `direction: [dx, 0, dz]` flies where the skin looks. Boot with
|
|
405
|
+
`pointer: { lockOnClick: false }` — a locked cursor is no aim at all.
|
|
406
|
+
|
|
407
|
+
## Lock-on and a dodge, in a few lines each
|
|
408
|
+
|
|
409
|
+
The controller's camera yaw is a public number in radians, and the rig's
|
|
410
|
+
forward is `(-sin yaw, 0, -cos yaw)` — so putting the camera behind the player
|
|
411
|
+
and on the enemy is one assignment per frame, measured on `examples/melee-3d`
|
|
412
|
+
(forward · toEnemy = 1.00):
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
const dx = enemy.position[0] - player.position[0];
|
|
416
|
+
const dz = enemy.position[2] - player.position[2];
|
|
417
|
+
controller.yaw = Math.atan2(-dx, -dz); // every frame while locked
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
A dodge is a dash plus i-frames that start BEFORE the hit — `Health.protect`,
|
|
421
|
+
not `invulnerableFor` (which opens only after one):
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
(player.behavior as Health).protect(0.35); // immune for the roll
|
|
425
|
+
body.linearVelocity = [dir[0] * 9, body.linearVelocity[1], dir[2] * 9];
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Keep writing the velocity for the roll's frames — the controller brakes toward
|
|
429
|
+
its own target speed every step, so a single impulse dies in a few frames.
|
|
430
|
+
|
|
323
431
|
## First-person weapon viewmodel + the tracer-from-the-muzzle rule
|
|
324
432
|
|
|
325
433
|
A weapon VIEWMODEL is just a `MeshInstance3D`/group child of the **root** `Camera`
|
package/skills/incanto-assets.md
CHANGED
|
@@ -9,7 +9,8 @@ description: Where game art comes from in Incanto — the FULL built-in catalog
|
|
|
9
9
|
> installed engine version. Sibling skills live in `node_modules/incanto/skills/`.
|
|
10
10
|
|
|
11
11
|
Resolve art in this order: ① built-ins (zero setup) → ② asset MCP servers /
|
|
12
|
-
known URLs → ③ art-free primitives. Never invent asset URLs
|
|
12
|
+
known URLs → ③ art-free primitives. Never invent asset URLs — `incanto-check`
|
|
13
|
+
asks every remote url's server whether it exists, and a 404 is missing art.
|
|
13
14
|
|
|
14
15
|
**3D characters and props are step ②, always** — the built-in catalog is 2D
|
|
15
16
|
sprites, tiles, items, foliage/terrain textures and audio, with no models in it.
|
|
@@ -182,8 +183,13 @@ does not contain:
|
|
|
182
183
|
|
|
183
184
|
That used to be a browser-only failure — visible in `assetErrors()`, which needs
|
|
184
185
|
a running game and someone to look at it — and it is the single most common way
|
|
185
|
-
a scene draws nothing. Remote (`https:`)
|
|
186
|
-
|
|
186
|
+
a scene draws nothing. Remote (`https:`) urls are ASKED — a HEAD request with a
|
|
187
|
+
four-second deadline, one per url per run — and a 404 is the same warning
|
|
188
|
+
(`$avatar → https://…/base-model.glb is not found on its server (HTTP 404)`);
|
|
189
|
+
a host that cannot be reached is a `note:` and never a failure, so the check
|
|
190
|
+
works offline. Inline (`data:`) urls are left alone. This is the half of "never
|
|
191
|
+
invent asset URLs" a server can answer: a model at a made-up CDN path passed
|
|
192
|
+
every instrument except the browser before it.
|
|
187
193
|
|
|
188
194
|
## A spritesheet grid that does not fit says so
|
|
189
195
|
|
|
@@ -79,7 +79,7 @@ under an existing name (re-registering a different class without it → `DUPLICA
|
|
|
79
79
|
## What a Behavior can reach
|
|
80
80
|
|
|
81
81
|
- `this.node` — the host node (cast to its type when needed)
|
|
82
|
-
- `this.getNode(path)` / `this.emit(...)` / `this.on(...)` — node delegates
|
|
82
|
+
- `this.getNode(path)` / `this.getNodeOrNull(path)` / `this.emit(...)` / `this.on(...)` — node delegates
|
|
83
83
|
- `this.engine` / `this.input` — via the scene tree (TREE_VIOLATION if the scene isn't
|
|
84
84
|
set on an Engine)
|
|
85
85
|
- Engine access in `onReady` (`this.engine` / `this.rng` / `this.log`) works under
|
|
@@ -299,12 +299,36 @@ what that cost: with the `plays` rung finally counting defects,
|
|
|
299
299
|
the player walking off the terrain and falling forever. One `Respawn` node each,
|
|
300
300
|
no TypeScript, and all five are clean.
|
|
301
301
|
|
|
302
|
+
## Checkpoint (built-in behaviour — move where the Respawn puts you)
|
|
303
|
+
|
|
304
|
+
The second most common thing a platformer has. On an `Area3D`/`Area2D`:
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{ "name": "Check1", "type": "Area3D", "groups": ["checkpoint"],
|
|
308
|
+
"props": { "position": [7, 4.5, -37], "collider": { "shape": "box", "size": [3, 3, 3] } },
|
|
309
|
+
"script": { "name": "Checkpoint", "props": { "respawn": "/root/Player/Catch", "dropBelow": 0.6 } } }
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
| Prop | Default | Meaning |
|
|
313
|
+
|---|---|---|
|
|
314
|
+
| `respawn` | `""` (required) | node path of the `Respawn` to redirect |
|
|
315
|
+
| `group` | `"player"` | who can light it (`""` = anything) |
|
|
316
|
+
| `once` | `true` | light it a single time; `false` re-fires on every entry |
|
|
317
|
+
| `dropBelow` | `0` | how far below the area's origin the respawn point sits, so the character lands ON the flag |
|
|
318
|
+
|
|
319
|
+
Touching it sets the `Respawn`'s `to` to the checkpoint's world position and
|
|
320
|
+
emits `activated(other)`; `lit` says whether it has fired. `Respawn.to` is read
|
|
321
|
+
at the moment of the catch, so a checkpoint lit mid-run is where the next fall
|
|
322
|
+
ends — it used to be read once at ready, which is what a platformer built from
|
|
323
|
+
the tarball found. `examples/platformer-3d` has two.
|
|
324
|
+
|
|
302
325
|
## Timer (core node — never setTimeout in game logic)
|
|
303
326
|
|
|
304
327
|
```json
|
|
305
328
|
{ "name": "Spawner", "type": "Timer", "props": { "waitTime": 2, "autostart": true } }
|
|
306
329
|
```
|
|
307
|
-
Emits `timeout` every `waitTime` s (`oneShot` for once). API: `start(time?)`, `stop()`, `running
|
|
330
|
+
Emits `timeout` every `waitTime` s (`oneShot` for once). API: `start(time?)`, `stop()`, `running`,
|
|
331
|
+
`timeLeft` (seconds to the next `timeout`, `0` when stopped — the countdown a HUD draws).
|
|
308
332
|
A `waitTime` of 0 is a load error: the update guard stops a timer with no period
|
|
309
333
|
on its first frame, so it would never fire and never say so.
|
|
310
334
|
|
|
@@ -644,3 +644,12 @@ if (!isWebGLAvailable()) showMyOwnBrowserTooOldScreen(); // ask BEFORE booting
|
|
|
644
644
|
|
|
645
645
|
`isWebGLAvailable()` probes a throwaway canvas and never touches yours; it
|
|
646
646
|
returns `false` outside a browser.
|
|
647
|
+
|
|
648
|
+
## Surfaces and a chaser that walks (2D)
|
|
649
|
+
|
|
650
|
+
A `StaticBody2D` carries the surface it is — `restitution 1` is a trampoline, `friction 0`
|
|
651
|
+
is ice — and its value WINS the pair (see `incanto-physics-and-input.md`). A platformer's
|
|
652
|
+
ground enemy is a `CharacterBody2D` with `Chase` on a child and `ground: true`, so a player
|
|
653
|
+
on a platform is not met by an enemy floating up to it. platformer-2d's course past its last
|
|
654
|
+
ground slab is all three, measured by its harness.
|
|
655
|
+
|
|
@@ -116,6 +116,9 @@ never be one node each. One instanced node draws them all at once:
|
|
|
116
116
|
place.
|
|
117
117
|
`{ "shape": "sphere", "radius": r }` / `{ "shape": "box", "size": [x,y,z] }`
|
|
118
118
|
opt out into a cheaper approximation when a rough blocker is all you want.
|
|
119
|
+
The field follows its stones: replace `transforms`, move the node, or change
|
|
120
|
+
`mesh`, `size` or the `collider` spec at runtime and the hulls are rebuilt on the
|
|
121
|
+
next physics step.
|
|
119
122
|
|
|
120
123
|
### `LoftMesh3D` (smooth swept hulls — vehicle bodies, canopies)
|
|
121
124
|
|
|
@@ -142,6 +145,14 @@ saturated in sunlight: `metalness ~0.45, roughness ~0.35, clearcoat 1,
|
|
|
142
145
|
clearcoatRoughness 0.06, envMapIntensity ~0.9` — high metalness washes the base colour
|
|
143
146
|
out into the sky reflection.
|
|
144
147
|
|
|
148
|
+
**A 3D node with no art draws nothing, and `incanto-check` says so.** A
|
|
149
|
+
`ModelInstance3D` with no `model`, a `Sprite3D` with no `texture`, an
|
|
150
|
+
`AnimatedSprite3D` with no `sheet`, an `InstancedMesh3D` with no `transforms`
|
|
151
|
+
each load clean, validate, take a row in the editor's tree, and put no pixel on
|
|
152
|
+
screen. The audit names them (a template on a `PrefabShelf` and a node authored
|
|
153
|
+
`visible: false` are left alone) — the same rule `TileMap2D` got the day the
|
|
154
|
+
tilemap example shipped a floor nobody could see.
|
|
155
|
+
|
|
145
156
|
### `Sprite3D` / `AnimatedSprite3D` (2D billboard sprites — the 2.5D look)
|
|
146
157
|
A 2D image drawn as a camera-facing quad inside the 3D world (Octopath / Don't
|
|
147
158
|
Starve / classic MapleStory in a 3D scene). Unlit — the art shows as-is. The
|
|
@@ -318,6 +329,40 @@ notice in a screenshot.
|
|
|
318
329
|
(~60–80) so the box tracks the camera and contact shadows stay crisp everywhere.
|
|
319
330
|
- `OmniLight3D` — `color`, `intensity` (physical units — values like 20–40 are normal),
|
|
320
331
|
`range` (0 = unlimited).
|
|
332
|
+
- `SpotLight3D` — a CONE that aims: a flashlight, a searchlight sweeping an arena,
|
|
333
|
+
headlights, a stage spot, a guard's torch. `color`, `intensity` (candela, physical
|
|
334
|
+
decay — see the numbers below), `range` (0 = unlimited), `coneDeg` (the FULL cone
|
|
335
|
+
angle, default 60 — a "30° beam" is 30; 0 and 180 are load errors), `penumbra`
|
|
336
|
+
(0 hard edge … 1 all falloff, default 0.3), `castShadow` + `shadowMapSize` (1024;
|
|
337
|
+
a perspective shadow from the apex — one extra render per frame per light), and
|
|
338
|
+
`lookAt` (a node path: aim at it every frame, exactly as `Camera3D.lookAt` does).
|
|
339
|
+
**It aims down its −Z like a camera**: at rest it points along −Z,
|
|
340
|
+
`rotation: [-90, 0, 0]` points it at the floor, and a headlamp on a +Z-forward
|
|
341
|
+
skin wants `rotation: [0, 180, 0]` (tilt it down with `[20, 180, 0]`).
|
|
342
|
+
|
|
343
|
+
**Intensity is candela with physical falloff**, so the number is about
|
|
344
|
+
DISTANCE: measured on tps-3d-neon's dark deck, a headlamp 2 m off the floor
|
|
345
|
+
reads well at ~500, and a searchlight 14 m up needs ~12 000 to leave a pool you
|
|
346
|
+
can see — 260 was invisible. Start ten times bigger than feels reasonable.
|
|
347
|
+
|
|
348
|
+
**Do not yaw a pitched light on the same node.** Rotation is Euler XYZ, so a
|
|
349
|
+
yaw applied to a node that already pitches −55° swings the cone sideways and,
|
|
350
|
+
at yaw 180, straight UP (measured: dir `(0, 0.82, 0.57)`). To SWEEP, put the
|
|
351
|
+
yaw on a parent pivot and the pitch on the light; to TRACK, use `lookAt`:
|
|
352
|
+
|
|
353
|
+
```json
|
|
354
|
+
{ "name": "SearchlightRig", "type": "Node3D", "props": { "position": [0, 14, 0] },
|
|
355
|
+
"script": { "name": "Oscillate", "props": { "mode": "rotation", "axis": "y", "amplitude": 70, "frequency": 0.08 } },
|
|
356
|
+
"children": [
|
|
357
|
+
{ "name": "Searchlight", "type": "SpotLight3D",
|
|
358
|
+
"props": { "rotation": [-55, 0, 0], "color": "#ffe9b0", "intensity": 12000, "range": 60,
|
|
359
|
+
"coneDeg": 24, "penumbra": 0.4, "castShadow": true } }
|
|
360
|
+
] }
|
|
361
|
+
```
|
|
362
|
+
```json
|
|
363
|
+
{ "name": "Tracker", "type": "SpotLight3D",
|
|
364
|
+
"props": { "position": [0, 14, 0], "intensity": 12000, "coneDeg": 20, "lookAt": "%Player" } }
|
|
365
|
+
```
|
|
321
366
|
|
|
322
367
|
### Vegetation & water
|
|
323
368
|
|
package/skills/incanto-editor.md
CHANGED
|
@@ -169,6 +169,10 @@ Opens a local page (default `http://127.0.0.1:5179/`) with three panes:
|
|
|
169
169
|
with the cursor whatever the world zoom is. (Picking used to look only at the
|
|
170
170
|
world pass, so a HUD could not be selected in the viewport at all, and its
|
|
171
171
|
outline was drawn wherever the game camera happened to be looking.)
|
|
172
|
+
A `HudLayer`'s DOM is drawn over the edit view but never takes the pointer
|
|
173
|
+
there — a centred pause menu used to sit exactly where the selected node's
|
|
174
|
+
gizmo is and eat the drag (a rotate ring under it never saw a pointerdown; a
|
|
175
|
+
move drag toggled Mute). Its widgets are live again in play.
|
|
172
176
|
**`F` frames the SELECTED node** (its whole subtree; a light or empty node has
|
|
173
177
|
no bounds, so the camera goes to it at a readable distance) — and the whole
|
|
174
178
|
scene when nothing is selected, which is the Maya/Unity/Unreal meaning of the
|
|
@@ -247,7 +251,8 @@ files are formatted biome-compatibly (80-col inline-when-fits), so saving from
|
|
|
247
251
|
the editor never breaks `pnpm lint`.
|
|
248
252
|
|
|
249
253
|
Save activates only when the working scene differs from the original
|
|
250
|
-
(`Ctrl/Cmd+S`); undo is `Ctrl/Cmd+Z
|
|
254
|
+
(`Ctrl/Cmd+S`); undo is `Ctrl/Cmd+Z`, and it keeps your selection whenever the
|
|
255
|
+
selected node survives the undo (only undoing the add that created it deselects). Saving writes pretty-printed JSON to
|
|
251
256
|
`--output` (default: the input file).
|
|
252
257
|
|
|
253
258
|
## The agent8 asset library (📚)
|
|
@@ -197,7 +197,12 @@ carries the heightfield as `[x, y, z, tile]` tuples (value noise; grass
|
|
|
197
197
|
top, dirt fill, bedrock base). Reach for it when BLOCKS are the point —
|
|
198
198
|
digging, building, minecraft-style games on `VoxelGrid3D` (colliders are
|
|
199
199
|
the game's job: chunk trimeshes near the player, see the minecraft
|
|
200
|
-
template).
|
|
200
|
+
template). Baked `voxels` land WHEN THE PROP DOES — at load, before any
|
|
201
|
+
`onReady` — so a chunk builder at ready already sees them; a generator that
|
|
202
|
+
also fills the grid at ready must MERGE them, because `setBlocks` replaces
|
|
203
|
+
the map: `grid.setBlocks([...generated, ...grid.blocks()])`, baked blocks
|
|
204
|
+
last so a baked block wins over the terrain the noise put there (the
|
|
205
|
+
minecraft template's beacon tower is exactly this). Options: `seed`, `size` (32 blocks), `height` (8), `water`. The
|
|
201
206
|
voxel `generateIsland` (radial dome falloff, sandy shore) also remains for
|
|
202
207
|
the same use case.
|
|
203
208
|
|
|
@@ -800,7 +805,7 @@ hue reads grey.
|
|
|
800
805
|
| prop | default | meaning |
|
|
801
806
|
|---|---|---|
|
|
802
807
|
| `size` | `[40, 40]` | [width, depth] meters — must be positive (load-time check) |
|
|
803
|
-
| `color` | `"#2a6fbe"` | THE lake hue for `simple` (deep/surface/sky derived from it — use a vivid color); if customized it also tints the fancy ramp |
|
|
808
|
+
| `color` | `"#2a6fbe"` | THE lake hue for `simple` (deep/surface/sky derived from it — use a vivid color); if customized it also tints the fancy ramp — but only the parts `colors` (or a preset) leaves unnamed: with trough, surface and peak all set it only seeds the underwater murk, and the editor's inspector says so under the field |
|
|
804
809
|
| `opacity` | `0.8` | water-body density: lower = clearer (scales how much the refracted scene shows through) |
|
|
805
810
|
| `waveHeight` | `0.04` | wave intensity, meters-ish (0 = mirror-flat; 0.04 = calm sea; 0.08 = the older choppier look) |
|
|
806
811
|
| `waveSpeed` | `1` | animation speed multiplier |
|
|
@@ -22,10 +22,11 @@ feel of /Game/Player
|
|
|
22
22
|
input → motion 1 frame(s)
|
|
23
23
|
|
|
24
24
|
jump apex 0.262 u (tapped)
|
|
25
|
-
held apex 0.981 u
|
|
25
|
+
held apex 0.981 u (standing)
|
|
26
|
+
moving apex 1.24 u walking · 1.41 u sprinting ← size your level against the LARGEST
|
|
26
27
|
time to apex 133 ms
|
|
27
28
|
airtime 267 ms
|
|
28
|
-
coyote window
|
|
29
|
+
coyote window 117 ms
|
|
29
30
|
jump buffer 150 ms
|
|
30
31
|
```
|
|
31
32
|
|
|
@@ -55,19 +56,23 @@ peak 300 u/s ← SOMETHING ELSE moved it
|
|
|
55
56
|
Measure feel somewhere nothing can hit you.
|
|
56
57
|
|
|
57
58
|
**A character that jumps in MID-AIR has no coyote window to measure.** The probe
|
|
58
|
-
lifts the body, waits, presses, and reads "
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
lifts the body, waits, presses, and reads "going UP after the press, and sent
|
|
60
|
+
there by it" as a jump —
|
|
61
|
+
so a double jump (or a jetpack, an updraft) answers yes at any delay. It used to
|
|
62
|
+
read "velocity CHANGED upward" instead, which a glide satisfies too: Beacon
|
|
63
|
+
Isle's glide clamps a −20 m/s fall to −1.6, a +18 change, and the report called
|
|
64
|
+
its coyote window `not measurable — jumps in MID-AIR` about a character that
|
|
65
|
+
does not, with the buffer beside it marked suspect. Now it reads
|
|
64
66
|
|
|
65
67
|
```
|
|
66
|
-
coyote window
|
|
67
|
-
jump buffer 33 ms
|
|
68
|
+
coyote window 117 ms (declared 120)
|
|
69
|
+
jump buffer 33 ms (declared 150)
|
|
68
70
|
```
|
|
69
71
|
|
|
70
|
-
and
|
|
72
|
+
and the 33 is true, not suspect: holding `jump` while falling IS the glide,
|
|
73
|
+
so a press more than two frames before landing becomes a one-frame glide and
|
|
74
|
+
the buffered jump is spent. A starter that glides on the jump button buys a
|
|
75
|
+
short buffer with it. To measure the window of a true mid-air jumper, probe a
|
|
71
76
|
build with the air jump off.
|
|
72
77
|
|
|
73
78
|
**`jumpVelocity: 4` is not a height.** It is an impulse, and what it produces
|