incanto 0.4.3 → 0.6.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 (44) hide show
  1. package/dist/2d.d.ts +80 -6
  2. package/dist/2d.js +4 -4
  3. package/dist/3d.d.ts +200 -15
  4. package/dist/3d.js +4 -4
  5. package/dist/{audio-player-DqUR3XFs.d.ts → audio-player-DkBqRTs4.d.ts} +1 -1
  6. package/dist/{behavior-BAQq7HGM.d.ts → behavior-CWhW3oa6.d.ts} +54 -0
  7. package/dist/{create-game-CZHROKcT.js → create-game-B_sW_eiE.js} +59 -27
  8. package/dist/{create-game-DiTq3-fQ.js → create-game-DkbZCNaV.js} +292 -14
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/{duplicate-DP2WPYom.js → duplicate-BEvGBtb_.js} +1 -1
  11. package/dist/{gameplay-Ccruc3Wd.js → gameplay-CDFgSG6z.js} +293 -24
  12. package/dist/gameplay.d.ts +113 -2
  13. package/dist/gameplay.js +2 -2
  14. package/dist/index.d.ts +139 -5
  15. package/dist/index.js +59 -6
  16. package/dist/{loader-CGs_G-r0.js → loader-B4OEXDZ8.js} +60 -0
  17. package/dist/{loader-Mo0KghCv.d.ts → loader-D2u0fVW2.d.ts} +1 -1
  18. package/dist/net.d.ts +1 -1
  19. package/dist/net.js +1 -1
  20. package/dist/{particle-sim-DYuSUxvK.js → particle-sim-CFkILGwh.js} +84 -3
  21. package/dist/{particle-sim-CbN4YUuH.d.ts → particle-sim-CwJ5rI_P.d.ts} +3 -0
  22. package/dist/{physics-2d-KuMWPTf6.js → physics-2d-Cgli1aju.js} +95 -10
  23. package/dist/{physics-3d-DmNCeh58.js → physics-3d-CBAQ12LY.js} +121 -43
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +1 -1
  26. package/dist/{register-DPEV9_9t.js → register-BJCfuuZ2.js} +79 -5
  27. package/dist/{register-B0gq63VW.js → register-C35HDZpm.js} +2 -2
  28. package/dist/{register-BRy8FNox.js → register-Dd7Juujf.js} +389 -49
  29. package/dist/{register-BuUV1_KB.js → register-Dl_ixIJe.js} +275 -2
  30. package/dist/test.d.ts +2 -2
  31. package/dist/test.js +10 -10
  32. package/editor/assets/{agent8-DryTXVd4.js → agent8-MciFwn0v.js} +1 -1
  33. package/editor/assets/index-mofVSmuA.js +7417 -0
  34. package/editor/index.html +1 -1
  35. package/package.json +1 -1
  36. package/schemas/scene.schema.json +980 -126
  37. package/skills/incanto-3d-models.md +24 -1
  38. package/skills/incanto-building-2d-games.md +6 -0
  39. package/skills/incanto-building-3d-games.md +46 -1
  40. package/skills/incanto-gameplay-behaviors.md +60 -0
  41. package/skills/incanto-hud.md +58 -0
  42. package/skills/incanto-node-reference.md +148 -0
  43. package/skills/incanto-physics-and-input.md +47 -0
  44. package/editor/assets/index-N8se-PhP.js +0 -7365
@@ -53,7 +53,30 @@ scales so the bounding box stands that many units tall — composes with the nod
53
53
  reactive at runtime), `animation` (see below), `tint` (hex; `""`=off — multiplies into
54
54
  every material to RESKIN one shared GLB into many variants, e.g. base human → green
55
55
  zombie; clones materials per instance so other instances are untouched),
56
- `castShadow`/`receiveShadow`, plus the usual Node3D transform.
56
+ `metalness`/`roughness` (0..1 overrides applied to EVERY material; `-1`=keep authored —
57
+ low-poly kits usually ship fully rough, so `metalness: 0.6, roughness: 0.25` turns flat
58
+ paint into a glinting clear-coat under the scene's sky IBL; per-instance clones like
59
+ tint), `castShadow`/`receiveShadow`, plus the usual Node3D transform.
60
+
61
+ ## Animating named sub-nodes (wheels, turrets, doors) — `poseNode`
62
+
63
+ Many GLBs ship articulated parts as NAMED nodes (`bunx incanto-model <file>` lists them —
64
+ e.g. the Kenney vehicles have `wheel-front-left` … `wheel-back-right`). From a behavior,
65
+ rotate one per frame with `poseNode(name, [rxDeg, ryDeg, rzDeg])` (local Euler XYZ):
66
+
67
+ ```ts
68
+ // spin the wheels by travel + steer the fronts (Kenney car GLB)
69
+ const model = car.getNodesByName('CarModel')[0] as ModelInstance3D;
70
+ this.spin += this.speed * dt * 60; // degrees of roll
71
+ for (const w of ['wheel-back-left', 'wheel-back-right'])
72
+ model.poseNode(w, [this.spin, 0, 0]);
73
+ for (const w of ['wheel-front-left', 'wheel-front-right'])
74
+ model.poseNode(w, [this.spin, steerDeg, 0]);
75
+ ```
76
+
77
+ Poses are queued if the model hasn't finished loading, re-applied every sync, and
78
+ unknown names are ignored (no throw). This is intent-level articulation — for full
79
+ skeletal animation use `animation` clips instead.
57
80
 
58
81
  **`targetHeight` + skinned rigs (the implausible-scale warning).** Mixamo-style humanoid
59
82
  rigs hide their true size behind a ~0.01 armature scale + bone-driven vertex scaling, so
@@ -240,3 +240,9 @@ solid core carries the colour). `rate: 0` +
240
240
 
241
241
  `node.replay()` re-arms a one-shot. Simulation is deterministic under the
242
242
  engine seed — runScript verifies particle gameplay reproducibly.
243
+
244
+ ## `static: true` for big scenes
245
+
246
+ Mark unchanging subtrees (tile decor, backgrounds) `static: true` — the
247
+ renderer syncs them once and skips them every frame. Flip to `false` to
248
+ resume live syncing.
@@ -62,9 +62,34 @@ All 3D nodes extend `Node3D` and therefore have the transform props:
62
62
  |---|---|---|
63
63
  | `mesh` | `"box"` | `box \| sphere \| capsule \| plane \| cylinder \| gem` — `gem` is a faceted crystal (icosahedron); with `material.flatShading: true` + low `roughness` it reads as a sparkling jewel (spin/tumble it for the sparkle) |
64
64
  | `size` | `[1,1,1]` | box: extents · sphere/gem: radius = x · capsule: radius = x, height = y · plane: x·z ground (laid FLAT on XZ) · cylinder: radius = x, height = y |
65
- | `material` | `{}` | `{color: '#hex', metalness: 0..1, roughness: 0..1, opacity: 0..1, wireframe, flatShading, depthTest, depthWrite, emissive, emissiveIntensity, map, normalMap, repeat: [u,v]}` — the object IS the material state: omitted keys reset to defaults (`#ffffff`, 0, 1, 1, false, false, depthTest/Write true). `opacity < 1` turns on transparency; `flatShading` gives faceted per-face glints (the gem sparkle). `depthTest: false` (+ a high `renderOrder`) makes a flat `plane`/`cylinder` an ALWAYS-ON-TOP ground decal — AoE telegraphs, selection/spawn rings — that never z-fights with or is hidden by bumpy terrain (`depthWrite: false` keeps it from occluding later effects). `map`/`normalMap` are texture URLs (lazy-loaded, headless-safe; map samples sRGB, normalMap linear), tiled `repeat` times across each face — box/plane UVs span 0..1 per face, so for worldspace density use `repeat: [length/tile, height/tile]` (e.g. a 6×2.5 m brick wall at one tile per 2 m → `[3, 1.25]`). `color` tints the map (near-white keeps the texture's own color). NB: under ACES a very bright `emissiveIntensity` blows out to white — keep it modest (~1–2) + a saturated color for a vivid GLOWING look. Unknown keys, malformed `repeat`, or `repeat` without a map hard-fail at load |
65
+ | `material` | `{}` | `{color: '#hex', metalness: 0..1, roughness: 0..1, opacity: 0..1, clearcoat: 0..1, clearcoatRoughness: 0..1, envMapIntensity: >=0, wireframe, flatShading, depthTest, depthWrite, emissive, emissiveIntensity, map, normalMap, repeat: [u,v]}` — the object IS the material state: omitted keys reset to defaults (`#ffffff`, 0, 1, 1, false, false, depthTest/Write true). `opacity < 1` turns on transparency; `flatShading` gives faceted per-face glints (the gem sparkle). `depthTest: false` (+ a high `renderOrder`) makes a flat `plane`/`cylinder` an ALWAYS-ON-TOP ground decal — AoE telegraphs, selection/spawn rings — that never z-fights with or is hidden by bumpy terrain (`depthWrite: false` keeps it from occluding later effects). `map`/`normalMap` are texture URLs (lazy-loaded, headless-safe; map samples sRGB, normalMap linear), tiled `repeat` times across each face — box/plane UVs span 0..1 per face, so for worldspace density use `repeat: [length/tile, height/tile]` (e.g. a 6×2.5 m brick wall at one tile per 2 m → `[3, 1.25]`). `color` tints the map (near-white keeps the texture's own color). CAR PAINT: `clearcoat: 1` adds a glossy lacquer layer with its own reflection over a metallic base (`clearcoatRoughness` ~0.05-0.1 = mirror lacquer) — THE premium vehicle-paint look; `envMapIntensity` scales how strongly the surface mirrors the scene's sky/HDRI (glass windows want ~2, matte surfaces <1; needs a `sky`/`hdri` environment to reflect — at night with no sky there is nothing to mirror). NB: under ACES a very bright `emissiveIntensity` blows out to white — keep it modest (~1–2) + a saturated color for a vivid GLOWING look. Unknown keys, malformed `repeat`, or `repeat` without a map hard-fail at load |
66
66
  | `castShadow` / `receiveShadow` | `false` | |
67
67
 
68
+ ### `LoftMesh3D` (smooth swept hulls — vehicle bodies, canopies)
69
+
70
+ A SMOOTH curved surface swept through cross-sections — what box/capsule assemblies can
71
+ never give you. Superellipse sections are Catmull-Rom-interpolated along Z with per-vertex
72
+ normals, so highlights and sky reflections SWEEP across it like sheet metal. Use it for car
73
+ bodies, glass canopies, boat hulls, fuselages, submarines.
74
+
75
+ | Prop | Default | Notes |
76
+ |---|---|---|
77
+ | `sections` | `[]` | ≥2 stations, ascending `z`: `{z, width, height, y, corner?}` — `width`/`height` are the full cross-section extents, `y` is the section CENTRE height, `corner` 0 = boxy slab … 1 = full ellipse (default 0.7; the lower half always stays flatter — vehicle floors are flat). Malformed sections hard-fail at load |
78
+ | `slices` | `24` | vertices around each ring (8..64) |
79
+ | `smooth` | `4` | interpolated rings per section gap (1..16) |
80
+ | `material` | `{}` | same object as MeshInstance3D **minus textures** (`map`/`normalMap`/`repeat` hard-fail — a loft has no UVs) |
81
+ | `castShadow` / `receiveShadow` | `false` | |
82
+
83
+ Supercar recipe (hand-tuned against McLaren F1 proportions — see `examples/racing-3d`):
84
+ one hull loft (low rounded nose ~`corner 0.9` → wide fender-crown stations at the axles →
85
+ coke-bottle waist pinch → tall rear haunches → squared kamm tail `corner ~0.25`), plus a
86
+ second small loft as the GLASS bubble canopy (`metalness 1, roughness 0.03,
87
+ envMapIntensity ~1.7`), plus a dark interior loft inside it, with rectilinear
88
+ `MeshInstance3D` details (splitter, wing, light bars, mirrors) on top. Paint that stays
89
+ saturated in sunlight: `metalness ~0.45, roughness ~0.35, clearcoat 1,
90
+ clearcoatRoughness 0.06, envMapIntensity ~0.9` — high metalness washes the base colour
91
+ out into the sky reflection.
92
+
68
93
  ### `Sprite3D` / `AnimatedSprite3D` (2D billboard sprites — the 2.5D look)
69
94
  A 2D image drawn as a camera-facing quad inside the 3D world (Octopath / Don't
70
95
  Starve / classic MapleStory in a 3D scene). Unlit — the art shows as-is. The
@@ -251,6 +276,13 @@ demo into a commercial-looking outdoor scene:
251
276
  one extra fullscreen pass per frame — keep `Water3D` at `quality:"simple"`
252
277
  (zero re-renders) when pairing with clouds to hold 60fps. Absent = zero cost.
253
278
  Ideal for flight/sky scenes; see `examples/dogfight-3d`.
279
+ - `bloom`: a glow post pass — bright pixels bleed light, so emissive
280
+ materials (tail-lights, neon, lava, muzzle flashes) and sun glints actually
281
+ GLOW. `{ threshold?, strength? }`: `threshold` 0..1 post-tonemap brightness
282
+ above which pixels bloom (default 0.75 — keeps it to lights/glints; lower =
283
+ dreamier), `strength` how strongly the blurred glow is added back (default
284
+ 0.85; ~1.2 = neon night). One extra half-res blur pass per frame — cheap.
285
+ Caustics/clouds composites take precedence (pipelines don't stack yet).
254
286
  - `shadows`: `true` | `false` | `{ "mapSize": 1024|2048, "radius": n }`.
255
287
  Truthy makes the main DirectionalLight cast (PCF soft, terrain + trees +
256
288
  meshes cast/receive, mesh grass RECEIVES only). One static ortho box (±75 m
@@ -334,3 +366,16 @@ from a `seed`, with `heightAt(x, z)` for spawning/placement and a
334
366
  `{shape: 'heightfield'}` collider recipe (Terrain3D as the CHILD of a
335
367
  `StaticBody3D`). Full themes table, custom layer format and the water/beach
336
368
  pairing live in the **incanto-environment** skill.
369
+
370
+ ## Performance levers for big scenes
371
+
372
+ - **`static: true`** (any Node3D): the renderer syncs that subtree ONCE and
373
+ skips it every frame after — mark terrain, buildings, rocks, fences,
374
+ whole decor groups. Do NOT mark animated nodes (Water3D, Particles3D,
375
+ animated models, cameras): they freeze (the engine warns once). Set
376
+ `static = false` at runtime to resume live syncing after an edit.
377
+ - **`environment.shadows.static: true`**: the shadow pass renders once per
378
+ scene/environment change instead of every frame (measured ~42% of a
379
+ dense-forest frame). Use when shadow CASTERS don't move — pair moving
380
+ characters with sprite/blob shadows. After a one-off world edit call
381
+ `renderer.refreshShadows()`.
@@ -705,3 +705,63 @@ signal into a built-in's method through a `connection`).
705
705
  Generated, always-current prop/signal tables for every behavior live in
706
706
  `incanto-node-reference.md` (the "Gameplay behaviors" section).
707
707
  ```
708
+
709
+ ## Game feel ("juice")
710
+
711
+ ```ts
712
+ import { CameraShake, Cooldown, hitStop, screenFlash } from 'incanto/gameplay';
713
+ ```
714
+
715
+ - **Cooldown** — fire-rate gate, replaces hand-rolled clock math:
716
+ `private gun = new Cooldown(0.2);` then per update `this.gun.tick(dt);
717
+ if (firing && this.gun.tryUse()) shoot();`. `progress` (0..1) drives a
718
+ UiBar reload indicator; `reset()` for reload pickups.
719
+ - **Camera shake** — `FollowCamera` has it built in:
720
+ `(cam.behavior as FollowCamera).shake(8)` (2D pixels; use ~0.2 in 3D
721
+ meters). For cameras WITHOUT a follow script attach the standalone
722
+ `CameraShake` behavior — it composes with any other position writer and
723
+ returns the camera exactly to base.
724
+ - **screenFlash(color?, opacity?, seconds?)** — full-screen damage/pickup
725
+ flash (DOM, headless no-op).
726
+ - **hitStop(engine, seconds?)** — freezes `engine.timeScale` for REAL
727
+ seconds then restores; stacked calls extend. Sells melee impacts.
728
+ - **engine.timeScale** — 1 realtime, 0.5 slow motion, 0 pause: scales
729
+ variable AND fixed updates together (physics, timers, behaviors).
730
+
731
+ ## Game flow (win / lose / restart)
732
+
733
+ Attach `GameFlow` to the scene root (`"script": { "name": "GameFlow" }`),
734
+ declare a `restart` input action, and put a `%Banner` UiBanner in your HUD:
735
+
736
+ ```ts
737
+ const flow = this.node.getRoot().behavior as GameFlow;
738
+ flow.gameOver('YOU DIED'); // freezes engine.timeScale, sticky banner, waits for restart
739
+ flow.win('AREA CLEAR');
740
+ flow.pause(); flow.resume();
741
+ ```
742
+
743
+ Terminal states ignore further transitions; the restart action (or
744
+ `flow.restart()`) reloads the scene from its source JSON — fresh nodes,
745
+ physics, input. Listen to the `flowChanged(state)` signal for custom UI.
746
+ `restartScene(engine)` is exported standalone.
747
+
748
+ ## Boot loading overlay
749
+
750
+ ```ts
751
+ import { preloadSceneAssets } from 'incanto';
752
+ await preloadSceneAssets(sceneJson.assets); // black screen + bar + %, auto-removes
753
+ ```
754
+
755
+ Replaces the ~125-line hand-rolled loading screen; `createLoadingOverlay()`
756
+ gives you the raw overlay when you preload something else.
757
+
758
+ ## Persistence (high scores, unlocks, settings)
759
+
760
+ ```ts
761
+ import { createSaveStore } from 'incanto';
762
+ const save = createSaveStore('my-game'); // namespaced localStorage
763
+ save.set('highScore', Math.max(score, save.get('highScore', 0)));
764
+ ```
765
+
766
+ JSON round-tripped; headless/private-mode falls back to in-memory (never
767
+ throws). `clear()` wipes only your namespace.
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: incanto-hud
3
+ description: Screen-space HUD widgets (HudLayer + UiText / UiBar / UiBanner) — health bars, score text, wave banners declared in scene JSON, no hand-rolled DOM or CSS. Use for any in-game UI overlay in 2D or 3D games.
4
+ ---
5
+
6
+ # HUD widgets
7
+
8
+ Declare your HUD in the scene JSON — no `index.html` markup, no CSS, no
9
+ `document.querySelector` in behaviors:
10
+
11
+ ```json
12
+ {
13
+ "name": "HUD", "type": "HudLayer",
14
+ "children": [
15
+ { "name": "Health", "type": "UiBar",
16
+ "props": { "anchor": "topLeft", "value": 100, "max": 100, "label": "HP" } },
17
+ { "name": "Score", "type": "UiText",
18
+ "props": { "anchor": "topRight", "text": "Score: 0", "size": 20 } },
19
+ { "name": "Banner", "type": "UiBanner" }
20
+ ]
21
+ }
22
+ ```
23
+
24
+ `HudLayer` is a fixed, pointer-transparent DOM overlay above the canvas — it
25
+ works identically over the 2D and 3D renderers and is a silent no-op in
26
+ headless tests (core nodes, no three.js). Widgets pick one of 9 anchors:
27
+ `topLeft top topRight left center right bottomLeft bottom bottomRight`.
28
+ Widgets stacked on the same anchor form a column.
29
+
30
+ ## Driving widgets from behaviors
31
+
32
+ Plain node access — same as everything else:
33
+
34
+ ```ts
35
+ import type { UiBanner, UiBar, UiText } from 'incanto';
36
+
37
+ const hp = this.node.getNode('%Health') as UiBar;
38
+ hp.value = health.current; // fill animates; turns red below 30%
39
+
40
+ (this.node.getNode('%Score') as UiText).text = `Score: ${score}`;
41
+
42
+ const banner = this.node.getNode('%Banner') as UiBanner;
43
+ banner.show('WAVE 2', { color: '#f87171', seconds: 2 }); // queued, fades
44
+ banner.show('YOU DIED', { color: '#ef4444', seconds: 0 }); // sticky until next show()
45
+ ```
46
+
47
+ ## Widget reference
48
+
49
+ - **UiText** — `text`, `size` (px), `color`, `shadow` (readability outline).
50
+ - **UiBar** — `value`/`max` (ratio clamped 0..1), `width`/`height`, `color`,
51
+ `lowColor` + `lowThreshold` (default 0.3), `background`, `label` caption.
52
+ - **UiBanner** — `show(text, {color, seconds})` queues center-screen
53
+ announcements with fade in/out; `seconds: 0` = sticky; `clear()` empties
54
+ the queue; signal `bannerShown(text)`. Props: `size` (font px), `seconds`
55
+ (default duration).
56
+
57
+ Pair with the `Health` / `ScoreKeeper` gameplay behaviors: listen to their
58
+ signals and write the widget props — that is the whole HUD wiring.
@@ -21,6 +21,7 @@ description: GENERATED complete reference of every Incanto node type — props,
21
21
  |---|---|---|
22
22
  | `position` | `[0,0]` | array |
23
23
  | `rotation` | `0` | number |
24
+ | `static` | `false` | boolean |
24
25
  | `scale` | `[1,1]` | array |
25
26
  | `renderOrder` | `0` | number |
26
27
  | `visible` | `true` | boolean |
@@ -42,6 +43,7 @@ Signals: `animationFinished(name)`
42
43
  |---|---|---|
43
44
  | `position` | `[0,0,0]` | array |
44
45
  | `rotation` | `[0,0,0]` | array |
46
+ | `static` | `false` | boolean |
45
47
  | `scale` | `[1,1,1]` | array |
46
48
  | `visible` | `true` | boolean |
47
49
  | `renderOrder` | `0` | number |
@@ -70,6 +72,7 @@ Signals: `animationFinished(name)`
70
72
  |---|---|---|
71
73
  | `position` | `[0,0]` | array |
72
74
  | `rotation` | `0` | number |
75
+ | `static` | `false` | boolean |
73
76
  | `scale` | `[1,1]` | array |
74
77
  | `renderOrder` | `0` | number |
75
78
  | `visible` | `true` | boolean |
@@ -83,6 +86,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
83
86
  |---|---|---|
84
87
  | `position` | `[0,0,0]` | array |
85
88
  | `rotation` | `[0,0,0]` | array |
89
+ | `static` | `false` | boolean |
86
90
  | `scale` | `[1,1,1]` | array |
87
91
  | `visible` | `true` | boolean |
88
92
  | `renderOrder` | `0` | number |
@@ -115,6 +119,7 @@ Signals: `finished`
115
119
  |---|---|---|
116
120
  | `position` | `[0,0,0]` | array |
117
121
  | `rotation` | `[0,0,0]` | array |
122
+ | `static` | `false` | boolean |
118
123
  | `scale` | `[1,1,1]` | array |
119
124
  | `visible` | `true` | boolean |
120
125
  | `renderOrder` | `0` | number |
@@ -126,6 +131,7 @@ Signals: `finished`
126
131
  |---|---|---|
127
132
  | `position` | `[0,0]` | array |
128
133
  | `rotation` | `0` | number |
134
+ | `static` | `false` | boolean |
129
135
  | `scale` | `[1,1]` | array |
130
136
  | `renderOrder` | `0` | number |
131
137
  | `visible` | `true` | boolean |
@@ -141,6 +147,7 @@ Signals: `finished`
141
147
  |---|---|---|
142
148
  | `position` | `[0,0,0]` | array |
143
149
  | `rotation` | `[0,0,0]` | array |
150
+ | `static` | `false` | boolean |
144
151
  | `scale` | `[1,1,1]` | array |
145
152
  | `visible` | `true` | boolean |
146
153
  | `renderOrder` | `0` | number |
@@ -155,6 +162,7 @@ Signals: `finished`
155
162
  |---|---|---|
156
163
  | `position` | `[0,0]` | array |
157
164
  | `rotation` | `0` | number |
165
+ | `static` | `false` | boolean |
158
166
  | `scale` | `[1,1]` | array |
159
167
  | `renderOrder` | `0` | number |
160
168
  | `visible` | `true` | boolean |
@@ -171,6 +179,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
171
179
  |---|---|---|
172
180
  | `position` | `[0,0,0]` | array |
173
181
  | `rotation` | `[0,0,0]` | array |
182
+ | `static` | `false` | boolean |
174
183
  | `scale` | `[1,1,1]` | array |
175
184
  | `visible` | `true` | boolean |
176
185
  | `renderOrder` | `0` | number |
@@ -197,6 +206,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
197
206
  |---|---|---|
198
207
  | `position` | `[0,0,0]` | array |
199
208
  | `rotation` | `[0,0,0]` | array |
209
+ | `static` | `false` | boolean |
200
210
  | `scale` | `[1,1,1]` | array |
201
211
  | `visible` | `true` | boolean |
202
212
  | `renderOrder` | `0` | number |
@@ -230,6 +240,7 @@ Signals: `movementStateChanged(state)`
230
240
  |---|---|---|
231
241
  | `position` | `[0,0]` | array |
232
242
  | `rotation` | `0` | number |
243
+ | `static` | `false` | boolean |
233
244
  | `scale` | `[1,1]` | array |
234
245
  | `renderOrder` | `0` | number |
235
246
  | `visible` | `true` | boolean |
@@ -244,6 +255,7 @@ Signals: `movementStateChanged(state)`
244
255
  |---|---|---|
245
256
  | `position` | `[0,0,0]` | array |
246
257
  | `rotation` | `[0,0,0]` | array |
258
+ | `static` | `false` | boolean |
247
259
  | `scale` | `[1,1,1]` | array |
248
260
  | `visible` | `true` | boolean |
249
261
  | `renderOrder` | `0` | number |
@@ -260,6 +272,7 @@ Signals: `movementStateChanged(state)`
260
272
  |---|---|---|
261
273
  | `position` | `[0,0,0]` | array |
262
274
  | `rotation` | `[0,0,0]` | array |
275
+ | `static` | `false` | boolean |
263
276
  | `scale` | `[1,1,1]` | array |
264
277
  | `visible` | `true` | boolean |
265
278
  | `renderOrder` | `0` | number |
@@ -280,6 +293,7 @@ Signals: `movementStateChanged(state)`
280
293
  |---|---|---|
281
294
  | `position` | `[0,0,0]` | array |
282
295
  | `rotation` | `[0,0,0]` | array |
296
+ | `static` | `false` | boolean |
283
297
  | `scale` | `[1,1,1]` | array |
284
298
  | `visible` | `true` | boolean |
285
299
  | `renderOrder` | `2` | number |
@@ -304,12 +318,56 @@ Signals: `movementStateChanged(state)`
304
318
  | `drape` | `false` | boolean |
305
319
  | `terrain` | `""` | string |
306
320
 
321
+ ## `HudLayer` — `incanto`
322
+
323
+ | Prop | Default | Kind |
324
+ |---|---|---|
325
+ | `zIndex` | `100` | number |
326
+ | `visible` | `true` | boolean |
327
+
328
+ ## `Joint2D` — `incanto/2d`
329
+
330
+ | Prop | Default | Kind |
331
+ |---|---|---|
332
+ | `position` | `[0,0]` | array |
333
+ | `rotation` | `0` | number |
334
+ | `static` | `false` | boolean |
335
+ | `scale` | `[1,1]` | array |
336
+ | `renderOrder` | `0` | number |
337
+ | `visible` | `true` | boolean |
338
+ | `type` | `"revolute"` | one of: `fixed` `revolute` `rope` `spring` |
339
+ | `target` | `""` | string |
340
+ | `anchor` | `[0,0]` | array |
341
+ | `targetAnchor` | `[0,0]` | array |
342
+ | `length` | `0` | number |
343
+ | `stiffness` | `50` | number |
344
+ | `damping` | `5` | number |
345
+
346
+ ## `Joint3D` — `incanto/3d`
347
+
348
+ | Prop | Default | Kind |
349
+ |---|---|---|
350
+ | `position` | `[0,0,0]` | array |
351
+ | `rotation` | `[0,0,0]` | array |
352
+ | `static` | `false` | boolean |
353
+ | `scale` | `[1,1,1]` | array |
354
+ | `visible` | `true` | boolean |
355
+ | `renderOrder` | `0` | number |
356
+ | `type` | `"spherical"` | one of: `fixed` `spherical` `rope` `spring` |
357
+ | `target` | `""` | string |
358
+ | `anchor` | `[0,0,0]` | array |
359
+ | `targetAnchor` | `[0,0,0]` | array |
360
+ | `length` | `0` | number |
361
+ | `stiffness` | `50` | number |
362
+ | `damping` | `5` | number |
363
+
307
364
  ## `Label` — `incanto/2d`
308
365
 
309
366
  | Prop | Default | Kind |
310
367
  |---|---|---|
311
368
  | `position` | `[0,0]` | array |
312
369
  | `rotation` | `0` | number |
370
+ | `static` | `false` | boolean |
313
371
  | `scale` | `[1,1]` | array |
314
372
  | `renderOrder` | `0` | number |
315
373
  | `visible` | `true` | boolean |
@@ -319,12 +377,30 @@ Signals: `movementStateChanged(state)`
319
377
  | `font` | `"monospace"` | string |
320
378
  | `align` | `"left"` | string |
321
379
 
380
+ ## `LoftMesh3D` — `incanto/3d`
381
+
382
+ | Prop | Default | Kind |
383
+ |---|---|---|
384
+ | `position` | `[0,0,0]` | array |
385
+ | `rotation` | `[0,0,0]` | array |
386
+ | `static` | `false` | boolean |
387
+ | `scale` | `[1,1,1]` | array |
388
+ | `visible` | `true` | boolean |
389
+ | `renderOrder` | `0` | number |
390
+ | `sections` | `[]` | array |
391
+ | `slices` | `24` | number |
392
+ | `smooth` | `4` | number |
393
+ | `material` | `{}` | object |
394
+ | `castShadow` | `false` | boolean |
395
+ | `receiveShadow` | `false` | boolean |
396
+
322
397
  ## `MeshInstance3D` — `incanto/3d`
323
398
 
324
399
  | Prop | Default | Kind |
325
400
  |---|---|---|
326
401
  | `position` | `[0,0,0]` | array |
327
402
  | `rotation` | `[0,0,0]` | array |
403
+ | `static` | `false` | boolean |
328
404
  | `scale` | `[1,1,1]` | array |
329
405
  | `visible` | `true` | boolean |
330
406
  | `renderOrder` | `0` | number |
@@ -340,6 +416,7 @@ Signals: `movementStateChanged(state)`
340
416
  |---|---|---|
341
417
  | `position` | `[0,0,0]` | array |
342
418
  | `rotation` | `[0,0,0]` | array |
419
+ | `static` | `false` | boolean |
343
420
  | `scale` | `[1,1,1]` | array |
344
421
  | `visible` | `true` | boolean |
345
422
  | `renderOrder` | `0` | number |
@@ -350,6 +427,8 @@ Signals: `movementStateChanged(state)`
350
427
  | `animationLoop` | `true` | boolean |
351
428
  | `receiveShadow` | `false` | boolean |
352
429
  | `tint` | `""` | string |
430
+ | `metalness` | `-1` | number |
431
+ | `roughness` | `-1` | number |
353
432
 
354
433
  Signals: `animationFinished(name)`
355
434
 
@@ -373,6 +452,7 @@ _No props (structural fields only)._
373
452
  |---|---|---|
374
453
  | `position` | `[0,0]` | array |
375
454
  | `rotation` | `0` | number |
455
+ | `static` | `false` | boolean |
376
456
  | `scale` | `[1,1]` | array |
377
457
  | `renderOrder` | `0` | number |
378
458
  | `visible` | `true` | boolean |
@@ -383,6 +463,7 @@ _No props (structural fields only)._
383
463
  |---|---|---|
384
464
  | `position` | `[0,0,0]` | array |
385
465
  | `rotation` | `[0,0,0]` | array |
466
+ | `static` | `false` | boolean |
386
467
  | `scale` | `[1,1,1]` | array |
387
468
  | `visible` | `true` | boolean |
388
469
  | `renderOrder` | `0` | number |
@@ -393,6 +474,7 @@ _No props (structural fields only)._
393
474
  |---|---|---|
394
475
  | `position` | `[0,0,0]` | array |
395
476
  | `rotation` | `[0,0,0]` | array |
477
+ | `static` | `false` | boolean |
396
478
  | `scale` | `[1,1,1]` | array |
397
479
  | `visible` | `true` | boolean |
398
480
  | `renderOrder` | `0` | number |
@@ -406,6 +488,7 @@ _No props (structural fields only)._
406
488
  |---|---|---|
407
489
  | `position` | `[0,0]` | array |
408
490
  | `rotation` | `0` | number |
491
+ | `static` | `false` | boolean |
409
492
  | `scale` | `[1,1]` | array |
410
493
  | `renderOrder` | `0` | number |
411
494
  | `visible` | `true` | boolean |
@@ -438,6 +521,7 @@ Signals: `finished`
438
521
  |---|---|---|
439
522
  | `position` | `[0,0,0]` | array |
440
523
  | `rotation` | `[0,0,0]` | array |
524
+ | `static` | `false` | boolean |
441
525
  | `scale` | `[1,1,1]` | array |
442
526
  | `visible` | `true` | boolean |
443
527
  | `renderOrder` | `0` | number |
@@ -471,6 +555,7 @@ Signals: `finished`
471
555
  |---|---|---|
472
556
  | `position` | `[0,0]` | array |
473
557
  | `rotation` | `0` | number |
558
+ | `static` | `false` | boolean |
474
559
  | `scale` | `[1,1]` | array |
475
560
  | `renderOrder` | `0` | number |
476
561
  | `visible` | `true` | boolean |
@@ -490,6 +575,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
490
575
  |---|---|---|
491
576
  | `position` | `[0,0,0]` | array |
492
577
  | `rotation` | `[0,0,0]` | array |
578
+ | `static` | `false` | boolean |
493
579
  | `scale` | `[1,1,1]` | array |
494
580
  | `visible` | `true` | boolean |
495
581
  | `renderOrder` | `0` | number |
@@ -509,6 +595,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
509
595
  |---|---|---|
510
596
  | `position` | `[0,0]` | array |
511
597
  | `rotation` | `0` | number |
598
+ | `static` | `false` | boolean |
512
599
  | `scale` | `[1,1]` | array |
513
600
  | `renderOrder` | `0` | number |
514
601
  | `visible` | `true` | boolean |
@@ -525,6 +612,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
525
612
  |---|---|---|
526
613
  | `position` | `[0,0,0]` | array |
527
614
  | `rotation` | `[0,0,0]` | array |
615
+ | `static` | `false` | boolean |
528
616
  | `scale` | `[1,1,1]` | array |
529
617
  | `visible` | `true` | boolean |
530
618
  | `renderOrder` | `0` | number |
@@ -546,6 +634,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
546
634
  |---|---|---|
547
635
  | `position` | `[0,0]` | array |
548
636
  | `rotation` | `0` | number |
637
+ | `static` | `false` | boolean |
549
638
  | `scale` | `[1,1]` | array |
550
639
  | `renderOrder` | `0` | number |
551
640
  | `visible` | `true` | boolean |
@@ -559,6 +648,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
559
648
  |---|---|---|
560
649
  | `position` | `[0,0,0]` | array |
561
650
  | `rotation` | `[0,0,0]` | array |
651
+ | `static` | `false` | boolean |
562
652
  | `scale` | `[1,1,1]` | array |
563
653
  | `visible` | `true` | boolean |
564
654
  | `renderOrder` | `0` | number |
@@ -572,6 +662,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
572
662
  |---|---|---|
573
663
  | `position` | `[0,0,0]` | array |
574
664
  | `rotation` | `[0,0,0]` | array |
665
+ | `static` | `false` | boolean |
575
666
  | `scale` | `[1,1,1]` | array |
576
667
  | `visible` | `true` | boolean |
577
668
  | `renderOrder` | `0` | number |
@@ -604,6 +695,7 @@ Signals: `timeout`
604
695
  |---|---|---|
605
696
  | `position` | `[0,0,0]` | array |
606
697
  | `rotation` | `[0,0,0]` | array |
698
+ | `static` | `false` | boolean |
607
699
  | `scale` | `[1,1,1]` | array |
608
700
  | `visible` | `true` | boolean |
609
701
  | `renderOrder` | `0` | number |
@@ -628,12 +720,51 @@ Signals: `timeout`
628
720
  |---|---|---|
629
721
  | `anchor` | `"top-left"` | one of: `top-left` `top` `top-right` `left` `center` `right` `bottom-left` `bottom` `bottom-right` |
630
722
 
723
+ ## `UiBanner` — `incanto`
724
+
725
+ | Prop | Default | Kind |
726
+ |---|---|---|
727
+ | `anchor` | `"center"` | one of: `topLeft` `top` `topRight` `left` `center` `right` `bottomLeft` `bottom` `bottomRight` |
728
+ | `visible` | `true` | boolean |
729
+ | `size` | `42` | number |
730
+ | `seconds` | `2` | number |
731
+
732
+ Signals: `bannerShown`
733
+
734
+ ## `UiBar` — `incanto`
735
+
736
+ | Prop | Default | Kind |
737
+ |---|---|---|
738
+ | `anchor` | `"topLeft"` | one of: `topLeft` `top` `topRight` `left` `center` `right` `bottomLeft` `bottom` `bottomRight` |
739
+ | `visible` | `true` | boolean |
740
+ | `value` | `100` | number |
741
+ | `max` | `100` | number |
742
+ | `width` | `180` | number |
743
+ | `height` | `14` | number |
744
+ | `color` | `"#4ade80"` | string |
745
+ | `lowColor` | `"#ef4444"` | string |
746
+ | `lowThreshold` | `0.3` | number |
747
+ | `background` | `"rgba(0,0,0,0.5)"` | string |
748
+ | `label` | `""` | string |
749
+
750
+ ## `UiText` — `incanto`
751
+
752
+ | Prop | Default | Kind |
753
+ |---|---|---|
754
+ | `anchor` | `"topLeft"` | one of: `topLeft` `top` `topRight` `left` `center` `right` `bottomLeft` `bottom` `bottomRight` |
755
+ | `visible` | `true` | boolean |
756
+ | `text` | `""` | string |
757
+ | `size` | `16` | number |
758
+ | `color` | `"#ffffff"` | string |
759
+ | `shadow` | `true` | boolean |
760
+
631
761
  ## `VoxelGrid3D` — `incanto/3d`
632
762
 
633
763
  | Prop | Default | Kind |
634
764
  |---|---|---|
635
765
  | `position` | `[0,0,0]` | array |
636
766
  | `rotation` | `[0,0,0]` | array |
767
+ | `static` | `false` | boolean |
637
768
  | `scale` | `[1,1,1]` | array |
638
769
  | `visible` | `true` | boolean |
639
770
  | `renderOrder` | `0` | number |
@@ -647,6 +778,7 @@ Signals: `blocksChanged`
647
778
  |---|---|---|
648
779
  | `position` | `[0,0,0]` | array |
649
780
  | `rotation` | `[0,0,0]` | array |
781
+ | `static` | `false` | boolean |
650
782
  | `scale` | `[1,1,1]` | array |
651
783
  | `visible` | `true` | boolean |
652
784
  | `renderOrder` | `1` | number |
@@ -678,6 +810,12 @@ Ready-made `Behavior`s auto-registered by `createGame2D`/`createGame3D` (opt out
678
810
  with `gameplay: false`). Attach via `"script": {"name": "...", "props": {...}}` and
679
811
  wire their signals through scene `connections`. Full guide: `incanto-gameplay-behaviors.md`.
680
812
 
813
+ ### `CameraShake`
814
+
815
+ | Prop | Default | Kind |
816
+ |---|---|---|
817
+ | `falloff` | `0.28` | number |
818
+
681
819
  ### `Chase`
682
820
 
683
821
  | Prop | Default | Kind |
@@ -718,6 +856,16 @@ Signals: `dealtDamage(amount, target)`
718
856
  | `smoothing` | `0` | number |
719
857
  | `deadzone` | `0` | number |
720
858
 
859
+ ### `GameFlow`
860
+
861
+ | Prop | Default | Kind |
862
+ |---|---|---|
863
+ | `restartAction` | `"restart"` | string |
864
+ | `freezeOnEnd` | `true` | boolean |
865
+ | `bannerPath` | `"%Banner"` | string |
866
+
867
+ Signals: `flowChanged`
868
+
721
869
  ### `Health`
722
870
 
723
871
  | Prop | Default | Kind |
@@ -133,6 +133,53 @@ Injected state combines with key state (vectors clamped to unit length).
133
133
  - Reference: [examples/2d-phaser-sprite-character-gravity](https://github.com/rareboe/Incanto/tree/main/examples/2d-phaser-sprite-character-gravity) — gravity, jump, attack lockout, custom
134
134
  `Player` node type. Verified in Chromium end-to-end.
135
135
 
136
+ ## Joints (Joint2D / Joint3D)
137
+
138
+ Link two bodies: put the joint node as a CHILD of body A, point `target` at
139
+ body B (any node path — `%Name` is typical):
140
+
141
+ ```json
142
+ { "name": "Ball", "type": "RigidBody2D",
143
+ "props": { "collider": { "shape": "circle", "radius": 10 } },
144
+ "children": [
145
+ { "name": "Rope", "type": "Joint2D",
146
+ "props": { "type": "rope", "target": "%Anchor", "length": 100 } }
147
+ ]}
148
+ ```
149
+
150
+ Types — 2D: `fixed` (weld) · `revolute` (pin/hinge) · `rope` (max distance)
151
+ · `spring` (pull toward `length` with `stiffness`/`damping`). 3D swaps
152
+ `revolute` for `spherical` (ball joint). `length: 0` measures the body
153
+ distance at creation. `anchor`/`targetAnchor` are local offsets (px / m).
154
+ Chains work (pendulums, bridges: each link a body + joint to the previous).
155
+
156
+ ## Raycasts
157
+
158
+ Both runtimes expose the same query (2D in PIXELS y-down, 3D in meters):
159
+
160
+ ```ts
161
+ const hit = physics.castRay(origin, dir, maxLen, excludeBody?, { staticOnly?: true });
162
+ // → { distance, normal, node } | null (sensors never block rays)
163
+ ```
164
+
165
+ Exclude the shooter's own body when casting from inside it.
166
+
167
+ ## Gamepad
168
+
169
+ `engine.input.attachGamepad(engine)` polls the first connected pad every
170
+ frame (headless no-op). Buttons are codes `Pad0`..`Pad16` in the SAME space
171
+ as keys — declare them in actions: `"jump": { "keys": ["Space", "Pad0"] }`
172
+ (standard mapping: Pad0=A/×, Pad1=B/○, Pad9=Start). Sticks:
173
+ `input.padAxes(0)` / `padAxes(1)` → deadzoned `{x, y}`. Unplugging releases
174
+ everything (no stuck inputs).
175
+
176
+ ## Changing colliders at runtime
177
+
178
+ REPLACE the object — `body.collider = { shape: 'rect', size: [w, 60] }` —
179
+ and physics rebuilds the body that step. In-place mutation of the existing
180
+ collider object (`body.collider.size[0] = w`) is NOT watched (the per-step
181
+ change scan was removed for performance).
182
+
136
183
  ## Debug drawing
137
184
 
138
185
  `physics.debugDraw = true` (the instance `enablePhysics2D/3D` returns) renders