incanto 0.34.0 → 0.35.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 (58) hide show
  1. package/dist/2d.d.ts +5 -5
  2. package/dist/2d.js +3 -3
  3. package/dist/3d.d.ts +122 -14
  4. package/dist/3d.js +5 -5
  5. package/dist/{audit-C4kmDK0o.js → audit-C5oZGAru.js} +37 -0
  6. package/dist/{behavior-CtPScDMo.d.ts → behavior-CGSCWOHB.d.ts} +310 -22
  7. package/dist/{create-game-CFNqoqOX.js → create-game-B0Wfhi2-.js} +6 -5
  8. package/dist/{create-game-QTfghzwM.js → create-game-BUD89Kqh.js} +20 -6
  9. package/dist/debug.d.ts +1 -1
  10. package/dist/{duplicate-DlOvknDO.js → duplicate-C716f-97.js} +1 -1
  11. package/dist/{editor-switch-BJb-CWfA.d.ts → editor-switch-DyXEtH36.d.ts} +1 -1
  12. package/dist/editor.js +1739 -1312
  13. package/dist/env.d.ts +1 -1
  14. package/dist/{environment-presets-B6WiUsaO.js → environment-presets-TAGvmM3z.js} +240 -74
  15. package/dist/{errors-1dXlIwoR.d.ts → errors-BY2kL0hv.d.ts} +1 -1
  16. package/dist/{gameplay-BxFtmfSe.js → gameplay-BvhcQbfJ.js} +75 -11
  17. package/dist/gameplay.d.ts +12 -5
  18. package/dist/gameplay.js +1 -1
  19. package/dist/index.d.ts +22 -7
  20. package/dist/index.js +6 -6
  21. package/dist/{loader-DmXfj6Uv.d.ts → loader-CUcj00M8.d.ts} +19 -2
  22. package/dist/{loader-BwR0DxeM.js → loader-Mig5fY4n.js} +97 -18
  23. package/dist/net.d.ts +2 -2
  24. package/dist/net.js +3 -3
  25. package/dist/{particle-sim-CwJ5rI_P.d.ts → particle-sim-B-vZBF5R.d.ts} +1 -1
  26. package/dist/{pathfinding-DRCe89SI.d.ts → pathfinding-DgOo2KNF.d.ts} +1 -1
  27. package/dist/{physics-2d-BuyAwsW5.js → physics-2d-DqAclql-.js} +22 -9
  28. package/dist/{physics-3d-C8Kn_Nyb.js → physics-3d-DxBH4sIF.js} +27 -11
  29. package/dist/react.d.ts +2 -2
  30. package/dist/react.js +1 -1
  31. package/dist/{register-G3edsjJ2.js → register-BFLg0-_i.js} +490 -12
  32. package/dist/{register-OodmuUMK.js → register-BjbPMA5B.js} +2 -2
  33. package/dist/{register-BLPC10Mj.js → register-DSmIRAf7.js} +11 -5
  34. package/dist/{schema-CcoWb32N.d.ts → schema-3ywbdlrv.d.ts} +10 -0
  35. package/dist/{test-C4B5znap.js → test-9EokzbRd.js} +10 -10
  36. package/dist/test.d.ts +4 -4
  37. package/dist/test.js +2 -2
  38. package/dist/vite.js +1 -1
  39. package/editor/assets/{agent8-BTODHtdM.js → agent8-D3_GWeuh.js} +1 -1
  40. package/editor/assets/{debug-CJEz4EwC.js → debug-CX0LDd1r.js} +1 -1
  41. package/editor/assets/index-CMlKFT0C.js +10773 -0
  42. package/editor/index.html +1 -1
  43. package/package.json +1 -1
  44. package/schemas/scene.schema.json +109 -0
  45. package/skills/incanto-assets.md +21 -0
  46. package/skills/incanto-behaviors-and-scripts.md +16 -0
  47. package/skills/incanto-editor.md +17 -1
  48. package/skills/incanto-gameplay-behaviors.md +6 -0
  49. package/skills/incanto-hud.md +16 -0
  50. package/skills/incanto-localization.md +132 -0
  51. package/skills/incanto-node-reference.md +33 -18
  52. package/skills/incanto-save-slots.md +152 -0
  53. package/templates-app/beacon-isle-3d/package.json +1 -1
  54. package/templates-app/tps-3d/package.json +1 -1
  55. package/templates-app/village-quest-3d/PROJECT/Context.md +16 -0
  56. package/templates-app/village-quest-3d/package.json +1 -1
  57. package/templates-app/village-quest-3d/src/village.scene.json +25 -3
  58. package/editor/assets/index-BYCso8Bf.js +0 -10696
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-BYCso8Bf.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-CMlKFT0C.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="./assets/GameServer-C56iOUgF.js">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incanto",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Vibe-coding-first web game engine SDK — JSON-driven scenes on three.js",
5
5
  "keywords": [
6
6
  "game-engine",
@@ -325,6 +325,9 @@
325
325
  {
326
326
  "$ref": "#/$defs/UiImage"
327
327
  },
328
+ {
329
+ "$ref": "#/$defs/UiLanguageSelect"
330
+ },
328
331
  {
329
332
  "$ref": "#/$defs/UiPanel"
330
333
  },
@@ -1202,6 +1205,7 @@
1202
1205
  },
1203
1206
  "target": {
1204
1207
  "type": "string",
1208
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
1205
1209
  "default": ""
1206
1210
  },
1207
1211
  "bone": {
@@ -1308,6 +1312,7 @@
1308
1312
  },
1309
1313
  "target": {
1310
1314
  "type": "string",
1315
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
1311
1316
  "default": ""
1312
1317
  },
1313
1318
  "bone": {
@@ -1316,6 +1321,7 @@
1316
1321
  },
1317
1322
  "lookAt": {
1318
1323
  "type": "string",
1324
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
1319
1325
  "default": ""
1320
1326
  },
1321
1327
  "maxAngleDeg": {
@@ -1423,6 +1429,7 @@
1423
1429
  },
1424
1430
  "follow": {
1425
1431
  "type": "string",
1432
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
1426
1433
  "default": ""
1427
1434
  },
1428
1435
  "smoothing": {
@@ -2076,6 +2083,7 @@
2076
2083
  },
2077
2084
  "skinPath": {
2078
2085
  "type": "string",
2086
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
2079
2087
  "default": "../Skin"
2080
2088
  },
2081
2089
  "skinYawOffset": {
@@ -2469,6 +2477,7 @@
2469
2477
  },
2470
2478
  "terrain": {
2471
2479
  "type": "string",
2480
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
2472
2481
  "default": ""
2473
2482
  }
2474
2483
  },
@@ -2663,6 +2672,7 @@
2663
2672
  },
2664
2673
  "terrain": {
2665
2674
  "type": "string",
2675
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
2666
2676
  "default": ""
2667
2677
  }
2668
2678
  },
@@ -2956,6 +2966,7 @@
2956
2966
  },
2957
2967
  "target": {
2958
2968
  "type": "string",
2969
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
2959
2970
  "default": ""
2960
2971
  },
2961
2972
  "anchor": {
@@ -3093,6 +3104,7 @@
3093
3104
  },
3094
3105
  "target": {
3095
3106
  "type": "string",
3107
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
3096
3108
  "default": ""
3097
3109
  },
3098
3110
  "anchor": {
@@ -4873,6 +4885,7 @@
4873
4885
  },
4874
4886
  "terrain": {
4875
4887
  "type": "string",
4888
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
4876
4889
  "default": ""
4877
4890
  },
4878
4891
  "carve": {
@@ -5994,6 +6007,7 @@
5994
6007
  },
5995
6008
  "terrain": {
5996
6009
  "type": "string",
6010
+ "description": "A node path: '%UniqueName', '/Absolute/From/Root', or relative to this node ('../Skin').",
5997
6011
  "default": ""
5998
6012
  }
5999
6013
  },
@@ -6609,6 +6623,101 @@
6609
6623
  },
6610
6624
  "required": ["name", "type"]
6611
6625
  },
6626
+ "UiLanguageSelect": {
6627
+ "type": "object",
6628
+ "x-signals": ["dragStarted", "dragCancelled", "droppedOn", "dropped", "changed"],
6629
+ "properties": {
6630
+ "name": {
6631
+ "type": "string"
6632
+ },
6633
+ "uid": {
6634
+ "type": "string"
6635
+ },
6636
+ "type": {
6637
+ "const": "UiLanguageSelect"
6638
+ },
6639
+ "groups": {
6640
+ "type": "array",
6641
+ "items": {
6642
+ "type": "string"
6643
+ }
6644
+ },
6645
+ "tags": {
6646
+ "type": "object"
6647
+ },
6648
+ "props": {
6649
+ "type": "object",
6650
+ "properties": {
6651
+ "anchor": {
6652
+ "type": "string",
6653
+ "enum": [
6654
+ "topLeft",
6655
+ "top",
6656
+ "topRight",
6657
+ "left",
6658
+ "center",
6659
+ "right",
6660
+ "bottomLeft",
6661
+ "bottom",
6662
+ "bottomRight"
6663
+ ],
6664
+ "default": "topLeft"
6665
+ },
6666
+ "visible": {
6667
+ "type": "boolean",
6668
+ "default": true
6669
+ },
6670
+ "focusable": {
6671
+ "type": "boolean",
6672
+ "default": true
6673
+ },
6674
+ "draggable": {
6675
+ "type": "boolean",
6676
+ "default": false
6677
+ },
6678
+ "dropTarget": {
6679
+ "type": "boolean",
6680
+ "default": false
6681
+ },
6682
+ "label": {
6683
+ "type": "string",
6684
+ "default": "Language"
6685
+ },
6686
+ "options": {
6687
+ "type": "string",
6688
+ "default": ""
6689
+ },
6690
+ "value": {
6691
+ "type": "string",
6692
+ "default": ""
6693
+ }
6694
+ },
6695
+ "additionalProperties": false
6696
+ },
6697
+ "script": {
6698
+ "type": "object",
6699
+ "properties": {
6700
+ "name": {
6701
+ "type": "string"
6702
+ },
6703
+ "props": {
6704
+ "type": "object"
6705
+ }
6706
+ },
6707
+ "required": ["name"]
6708
+ },
6709
+ "network": {
6710
+ "type": "object"
6711
+ },
6712
+ "children": {
6713
+ "type": "array",
6714
+ "items": {
6715
+ "$ref": "#/$defs/node"
6716
+ }
6717
+ }
6718
+ },
6719
+ "required": ["name", "type"]
6720
+ },
6612
6721
  "UiPanel": {
6613
6722
  "type": "object",
6614
6723
  "x-signals": ["dragStarted", "dragCancelled", "droppedOn", "dropped"],
@@ -129,3 +129,24 @@ scene built that way stays plain JSON you can also hand-write.
129
129
 
130
130
  Swap in real art later by changing ONLY the `assets{}` entry — node props
131
131
  stay untouched.
132
+
133
+
134
+ ## Textures are shared automatically (3D)
135
+
136
+ A hundred `Sprite3D`s pointing at one atlas cost **one** fetch, one decode and
137
+ one GPU upload. The renderer's asset store keys textures by URL plus their
138
+ sampler settings, so you never need to hoist a texture yourself or worry that
139
+ spawning enemies re-downloads their sheet.
140
+
141
+ Two consequences worth knowing:
142
+
143
+ - **Do not mutate a texture you got from a node.** `Sprite3D` and `Terrain3D`
144
+ hold the shared master. The two nodes with per-node UV state
145
+ (`AnimatedSprite3D`'s frame window, `MeshInstance3D`'s `material.repeat`) get
146
+ a private clone automatically — you do not have to ask for it.
147
+ - **`pixelArt: true` also turns mipmaps off**, which is what makes pixel art stay
148
+ crisp at distance instead of blurring into mud.
149
+
150
+ A texture that 404s now shows up in `game.assetErrors()` alongside models, by the
151
+ URL you wrote — so "why is my sprite invisible" is answerable without opening the
152
+ network tab.
@@ -38,6 +38,22 @@ class CoinCounter extends Behavior {
38
38
  registerBehavior('CoinCounter', CoinCounter); // EXPLICIT — before loadScene
39
39
  ```
40
40
 
41
+ **A behavior that holds progress should be able to save it.** `serialize()` /
42
+ `deserialize(data)` are two more optional hooks in the same list, and they are
43
+ what makes "continue where you left off" possible — see
44
+ `incanto-save-slots.md`. Save only what a fresh `onReady` could not recreate.
45
+
46
+ **A prop that holds a node path must say so.** Mark it `nodePath: true` and the
47
+ editor rewrites it when the target is renamed; leave it off and the rename
48
+ silently breaks the link (`getNodeOrNull` returns null, nothing throws).
49
+
50
+ ```ts
51
+ static readonly props = {
52
+ target: { default: '', nodePath: true }, // fed to getNode/getNodeOrNull
53
+ damage: { default: 10 },
54
+ };
55
+ ```
56
+
41
57
  ## The three-artifact contract (keep them in sync!)
42
58
 
43
59
  1. scene JSON `"script": {"name": "CoinCounter"}`
@@ -81,9 +81,25 @@ Opens a local page (default `http://127.0.0.1:5179/`) with three panes:
81
81
  CharacterBody2D) are ROLLED BACK entirely with the error in the banner; the
82
82
  tree never shows a state the engine would reject. Right-click for
83
83
  duplicate / rename (or double-click the name) / cut / copy / paste-as-child /
84
- delete — all act on the multi-selection. `+` adds a child of any registered
84
+ delete — all act on the multi-selection. **Rename REPAIRS references**: every
85
+ `connections[].from/to` and every node-path prop pointing at the node (or into
86
+ its subtree) is rewritten in the same edit, across all three path forms
87
+ (`%Unique`, `/root/…`, relative `../Skin`), and the editor reports what it
88
+ changed. A prop is rewritten only if its type declares `nodePath: true`, so a
89
+ text prop that merely reads "Player" is never touched. `+` adds a child of any registered
85
90
  type, `✕` deletes the selection. Selecting the ⚙ scene row edits the header
86
91
  (dimension, gravity with real inputs, environment/input/assets/multiplayer).
92
+ - **CONNECTIONS on the selected node** — the inspector's last section shows what
93
+ the node **emits** and what it **receives**, each row being signal → target →
94
+ handler with a ✕ to remove and `+ connect` to add. Signals come from the node
95
+ type merged with its behavior's `static signals`; the target picker lists every
96
+ node in the scene; the handler is a dropdown of real methods when the target
97
+ has no script, and free text when it does (those handlers live in your
98
+ TypeScript, which the editor never loads). An end that no longer resolves is
99
+ marked red with the reason. For what a row cannot express — `filter`, `once`,
100
+ bound args — the scene header still has `connections` as raw JSON.
101
+ **Validation now runs WITH connections**: the editor used to strip them before
102
+ loading, so it reported green for a scene the game hard-fails to open.
87
103
  - **Viewport with DIRECT MANIPULATION** — a mode toolbar (top left) + the
88
104
  Unity/Godot keys: **W move · E rotate · R scale** (active mode highlighted).
89
105
  A collider-visibility toggle sits beside the mode tools, and it shows the
@@ -58,6 +58,12 @@ Two kinds of node path resolve from DIFFERENT origins — mixing them up is the
58
58
  enemy (a child of the Spawner) reaches the player with the ABSOLUTE
59
59
  `/root/Player`, never a bare `Player` (which would look under the enemy).
60
60
 
61
+ Renaming a node in the editor now REWRITES both kinds for you (and reports what
62
+ it changed), so the paths above stay valid across a rename. Hand-edited JSON
63
+ still has to be kept in sync yourself — `bunx incanto-check` catches the
64
+ connections, but a behavior prop pointing at a name that no longer exists
65
+ resolves to null in silence.
66
+
61
67
  **Never write `/root/<RootName>/...`.** If your root node is named `Game`, the
62
68
  path is `/root/Player` — NOT `/root/Game/Player`: `/root/` already *is* the
63
69
  `Game` root, so `/root/Game/...` double-counts it and dangles at load. Likewise
@@ -177,3 +177,19 @@ A drop on a CHILD of a slot counts as a drop on the slot (the cursor lands on th
177
177
  icon inside it, which is the normal case), a drop on nothing cancels, and a
178
178
  widget never drops onto itself.
179
179
 
180
+
181
+
182
+ ## More than one language
183
+
184
+ Any text prop here can name a translation key instead of a literal:
185
+
186
+ ```json
187
+ { "name": "Score", "type": "UiText", "props": { "text": "@t:hud.score" } }
188
+ ```
189
+
190
+ Strings live in the scene header's `strings` block, English is the base, and a
191
+ key a locale omits falls back to English silently — which is the RIGHT answer
192
+ whenever the English term is more precise or the translation would not fit.
193
+ `UiLanguageSelect` is the picker, already wired.
194
+
195
+ Read `incanto-localization.md` before adding a second language.
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: incanto-localization
3
+ description: Ship a game in more than one language: a scene-header `strings` table, `"@t:key"` text props, `engine.t()`, and a ready-made UiLanguageSelect that switches live and persists. English is the base — a key a locale omits falls back to it silently, on purpose. Use whenever a game needs a language other than English, or a language setting.
4
+ ---
5
+
6
+ # Localization — English base, other languages on top
7
+
8
+ Ship a game in more than one language without a second build, a second scene, or
9
+ a page reload.
10
+
11
+ **English is the base and the default.** Every other locale is a partial overlay:
12
+ anything it does not translate falls back to English, silently.
13
+
14
+ ## The rule that matters most
15
+
16
+ > When a translation is ambiguous, when the English term is the more precise one,
17
+ > or when the translation runs so much longer than the English that it breaks the
18
+ > layout — **ship the English.**
19
+
20
+ Leaving a key untranslated is a **decision**, not a gap to fill. `COMBO`,
21
+ `BOSS`, `LV.`, `HP`, `x1.5` are usually clearer in English to players in every
22
+ language, and a "Continue to the next chapter" that becomes three lines in a
23
+ button will break your menu. Omit the key and the English appears. Nothing warns,
24
+ nothing marks it, nothing logs.
25
+
26
+ The only mistake worth reporting is a key **nothing** declares — a typo, which
27
+ renders raw on screen. `bunx incanto-check` tells you at author time.
28
+
29
+ ## Declaring strings
30
+
31
+ One `strings` block in the scene header, locale first:
32
+
33
+ ```json
34
+ {
35
+ "format": 1, "type": "scene", "name": "Menu",
36
+ "strings": {
37
+ "en": {
38
+ "menu.start": "Start Game",
39
+ "menu.options": "Options",
40
+ "settings.language": "Language",
41
+ "hud.wave": "Wave {n}",
42
+ "hud.combo": "COMBO"
43
+ },
44
+ "ko": {
45
+ "menu.start": "게임 시작",
46
+ "menu.options": "설정",
47
+ "settings.language": "언어",
48
+ "hud.wave": "{n} 웨이브"
49
+ }
50
+ },
51
+ "root": { "...": "..." }
52
+ }
53
+ ```
54
+
55
+ `hud.combo` has no Korean entry on purpose — "COMBO" is the term players already
56
+ read. It shows as `COMBO` in both languages, and that is correct.
57
+
58
+ Tables **merge** across scenes: put your shared UI strings in the scene the game
59
+ boots from, and a level that adds three lines of its own keeps them.
60
+
61
+ ## Using them
62
+
63
+ A text prop becomes translatable by naming a key with `@t:` — the same shape as
64
+ the `"$assetKey"` references you already write:
65
+
66
+ ```json
67
+ { "name": "Start", "type": "UiButton", "props": { "text": "@t:menu.start" } }
68
+ { "name": "Wave", "type": "UiText", "props": { "text": "@t:hud.wave" } }
69
+ ```
70
+
71
+ Works on every text-bearing widget: `UiText.text`, `UiButton.text`,
72
+ `UiBar.label`, `UiSelect.label`, `UiBanner.show()`, and `UiDialogue` lines,
73
+ speakers and choices.
74
+
75
+ From a behavior, `engine.t(key, params)`:
76
+
77
+ ```ts
78
+ banner.show(this.engine.t('hud.wave', { n: this.wave }));
79
+ ```
80
+
81
+ `{n}` slots are filled from `params`; a slot with no matching param is left
82
+ alone rather than blanked.
83
+
84
+ ## The language picker
85
+
86
+ One node. Do not build your own.
87
+
88
+ ```json
89
+ { "name": "Language", "type": "UiLanguageSelect",
90
+ "props": { "label": "@t:settings.language" } }
91
+ ```
92
+
93
+ Its options are the locales your scene declares — a game shipping only English
94
+ shows only English — each labeled with its own endonym (`한국어`, not `Korean`),
95
+ because a player who cannot read the language on screen still has to find theirs.
96
+ Picking one switches the game **live** and persists the choice through
97
+ `engine.settings`, so the next visit opens in it.
98
+
99
+ ## What NOT to localize
100
+
101
+ - **Values, not labels.** `UiSelect.options` are values the game compares
102
+ against (`"low,medium,high"`); translating them breaks the comparison.
103
+ - **Node names, group names, asset keys, signal names, action names.** These are
104
+ identifiers.
105
+ - **Anything a behavior parses.** If code does `if (value === 'start')`, that
106
+ string is an identifier wearing a label's clothes.
107
+
108
+ ## Verifying
109
+
110
+ ```bash
111
+ bunx incanto-check src/scenes/menu.scene.json
112
+ ```
113
+
114
+ Reports any `@t:` key that no locale declares. It says nothing about a key one
115
+ locale omits — that is the design, not a warning.
116
+
117
+ To check a language actually renders, set it and read the frame:
118
+
119
+ ```ts
120
+ game.engine.locale.locale = 'ko';
121
+ ```
122
+
123
+ Switching takes effect on the **next frame** — nothing to reload, nothing to
124
+ invalidate. Widgets re-read their text every frame, which is what makes this
125
+ work.
126
+
127
+ ## Fonts
128
+
129
+ The engine's HUD uses the platform font stack. On a machine with no CJK face
130
+ installed, Korean renders as boxes — that is the operating system, not the
131
+ engine. If you ship Korean to an audience you do not control, add a webfont in
132
+ your own page CSS and set it on the HUD layer.
@@ -145,7 +145,7 @@ Signals: `finished`
145
145
  | `renderOrder` | `0` | number |
146
146
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
147
147
  | `snapToGround` | `null` | null |
148
- | `target` | `""` | string |
148
+ | `target` | `""` | node path |
149
149
  | `bone` | `""` | string |
150
150
 
151
151
  ## `BoneLookAt3D` — `incanto/3d`
@@ -160,9 +160,9 @@ Signals: `finished`
160
160
  | `renderOrder` | `0` | number |
161
161
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
162
162
  | `snapToGround` | `null` | null |
163
- | `target` | `""` | string |
163
+ | `target` | `""` | node path |
164
164
  | `bone` | `"Head"` | string |
165
- | `lookAt` | `""` | string |
165
+ | `lookAt` | `""` | node path |
166
166
  | `maxAngleDeg` | `75` | number |
167
167
  | `weight` | `0.85` | number |
168
168
  | `forwardAxis` | `"+z"` | one of: `+x` `-x` `+y` `-y` `+z` `-z` |
@@ -178,7 +178,7 @@ Signals: `finished`
178
178
  | `renderOrder` | `0` | number |
179
179
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
180
180
  | `visible` | `true` | boolean |
181
- | `follow` | `""` | string |
181
+ | `follow` | `""` | node path |
182
182
  | `smoothing` | `0` | number |
183
183
  | `zoom` | `1` | number |
184
184
  | `limits` | `[]` | array |
@@ -289,7 +289,7 @@ Signals: `movementStateChanged(state)`
289
289
  | `moveAction` | `"move"` | string |
290
290
  | `jumpAction` | `"jump"` | string |
291
291
  | `sprintAction` | `"sprint"` | string |
292
- | `skinPath` | `"../Skin"` | string |
292
+ | `skinPath` | `"../Skin"` | node path |
293
293
  | `skinYawOffset` | `0` | number |
294
294
  | `animations` | `{}` | object |
295
295
 
@@ -352,7 +352,7 @@ Signals: `movementStateChanged(state)`
352
352
  | `sway` | `0.5` | number |
353
353
  | `drape` | `null` | null |
354
354
  | `avoidWater` | `true` | boolean |
355
- | `terrain` | `""` | string |
355
+ | `terrain` | `""` | node path |
356
356
 
357
357
  ## `Foliage3D` — `incanto/3d`
358
358
 
@@ -386,7 +386,7 @@ Signals: `movementStateChanged(state)`
386
386
  | `seed` | `1` | number |
387
387
  | `drape` | `null` | null |
388
388
  | `avoidWater` | `true` | boolean |
389
- | `terrain` | `""` | string |
389
+ | `terrain` | `""` | node path |
390
390
 
391
391
  ## `HudLayer` — `incanto`
392
392
 
@@ -428,7 +428,7 @@ Signals: `movementStateChanged(state)`
428
428
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
429
429
  | `visible` | `true` | boolean |
430
430
  | `type` | `"revolute"` | one of: `fixed` `revolute` `rope` `spring` |
431
- | `target` | `""` | string |
431
+ | `target` | `""` | node path |
432
432
  | `anchor` | `[0,0]` | array |
433
433
  | `targetAnchor` | `[0,0]` | array |
434
434
  | `length` | `0` | number |
@@ -448,7 +448,7 @@ Signals: `movementStateChanged(state)`
448
448
  | `orderGroup` | `"default"` | one of: `background` `terrain` `default` `characters` `effects` `overlay` |
449
449
  | `snapToGround` | `null` | null |
450
450
  | `type` | `"spherical"` | one of: `fixed` `spherical` `rope` `spring` |
451
- | `target` | `""` | string |
451
+ | `target` | `""` | node path |
452
452
  | `anchor` | `[0,0,0]` | array |
453
453
  | `targetAnchor` | `[0,0,0]` | array |
454
454
  | `length` | `0` | number |
@@ -731,7 +731,7 @@ Signals: `triggerEnter(other)` · `triggerExit(other)`
731
731
  | `sunDirection` | `[0.5,0.8,0.3]` | array |
732
732
  | `sunColor` | `"#fff6e0"` | string |
733
733
  | `sunIntensity` | `1` | number |
734
- | `terrain` | `""` | string |
734
+ | `terrain` | `""` | node path |
735
735
  | `carve` | `true` | boolean |
736
736
  | `flowForce` | `1` | number |
737
737
  | `spray` | `1` | number |
@@ -910,7 +910,7 @@ Signals: `timeout`
910
910
  | `leafFadeEnd` | `0` | number |
911
911
  | `leafShadows` | `true` | boolean |
912
912
  | `drape` | `null` | null |
913
- | `terrain` | `""` | string |
913
+ | `terrain` | `""` | node path |
914
914
 
915
915
  ## `UILayer` — `incanto/2d`
916
916
 
@@ -1002,6 +1002,21 @@ Signals: `dragStarted` · `dragCancelled` · `droppedOn` · `dropped` · `lineSh
1002
1002
 
1003
1003
  Signals: `dragStarted` · `dragCancelled` · `droppedOn` · `dropped`
1004
1004
 
1005
+ ## `UiLanguageSelect` — `incanto`
1006
+
1007
+ | Prop | Default | Kind |
1008
+ |---|---|---|
1009
+ | `anchor` | `"topLeft"` | one of: `topLeft` `top` `topRight` `left` `center` `right` `bottomLeft` `bottom` `bottomRight` |
1010
+ | `visible` | `true` | boolean |
1011
+ | `focusable` | `true` | boolean |
1012
+ | `draggable` | `false` | boolean |
1013
+ | `dropTarget` | `false` | boolean |
1014
+ | `label` | `"Language"` | string |
1015
+ | `options` | `""` | string |
1016
+ | `value` | `""` | string |
1017
+
1018
+ Signals: `dragStarted` · `dragCancelled` · `droppedOn` · `dropped` · `changed`
1019
+
1005
1020
  ## `UiPanel` — `incanto`
1006
1021
 
1007
1022
  | Prop | Default | Kind |
@@ -1173,7 +1188,7 @@ wire their signals through scene `connections`. Full guide: `incanto-gameplay-be
1173
1188
 
1174
1189
  | Prop | Default | Kind |
1175
1190
  |---|---|---|
1176
- | `water` | `""` | string |
1191
+ | `water` | `""` | node path |
1177
1192
  | `draft` | `0.35` | number |
1178
1193
  | `stiffness` | `3` | number |
1179
1194
  | `damping` | `3.5` | number |
@@ -1194,7 +1209,7 @@ Signals: `submerged` · `surfaced`
1194
1209
 
1195
1210
  | Prop | Default | Kind |
1196
1211
  |---|---|---|
1197
- | `target` | `""` | string |
1212
+ | `target` | `""` | node path |
1198
1213
  | `speed` | `60` | number |
1199
1214
  | `stopRange` | `0` | number |
1200
1215
  | `loseRange` | `0` | number |
@@ -1248,7 +1263,7 @@ Signals: `dayPhaseChanged`
1248
1263
 
1249
1264
  | Prop | Default | Kind |
1250
1265
  |---|---|---|
1251
- | `target` | `""` | string |
1266
+ | `target` | `""` | node path |
1252
1267
  | `offset` | `[]` | array |
1253
1268
  | `smoothing` | `0` | number |
1254
1269
  | `deadzone` | `0` | number |
@@ -1259,7 +1274,7 @@ Signals: `dayPhaseChanged`
1259
1274
  |---|---|---|
1260
1275
  | `restartAction` | `"restart"` | string |
1261
1276
  | `freezeOnEnd` | `true` | boolean |
1262
- | `bannerPath` | `"%Banner"` | string |
1277
+ | `bannerPath` | `"%Banner"` | node path |
1263
1278
 
1264
1279
  Signals: `flowChanged`
1265
1280
 
@@ -1366,7 +1381,7 @@ Signals: `scoreChanged(score)` · `won` · `lost` · `lifeLost(lives)`
1366
1381
 
1367
1382
  | Prop | Default | Kind |
1368
1383
  |---|---|---|
1369
- | `prefab` | `""` | string |
1384
+ | `prefab` | `""` | node path |
1370
1385
  | `interval` | `1` | number |
1371
1386
  | `max` | `0` | number |
1372
1387
  | `at` | `[]` | array |
@@ -1396,14 +1411,14 @@ Signals: `waveStarted(index)` · `waveCleared(index)` · `allCleared`
1396
1411
 
1397
1412
  | Prop | Default | Kind |
1398
1413
  |---|---|---|
1399
- | `aggroTarget` | `""` | string |
1414
+ | `aggroTarget` | `""` | node path |
1400
1415
  | `aggroRange` | `12` | number |
1401
1416
  | `deAggroRange` | `0` | number |
1402
1417
  | `chaseSpeed` | `4` | number |
1403
1418
  | `wanderSpeed` | `1.4` | number |
1404
1419
  | `wanderRadius` | `8` | number |
1405
1420
  | `wanderChangeEvery` | `3` | number |
1406
- | `goalTarget` | `""` | string |
1421
+ | `goalTarget` | `""` | node path |
1407
1422
  | `stopRange` | `1.2` | number |
1408
1423
  | `moveParent` | `false` | boolean |
1409
1424