incanto 0.57.0 → 0.59.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/README.md +6 -4
- package/bin/incanto-check.mjs +27 -0
- package/bin/incanto-multiplay.mjs +170 -0
- package/bin/incanto-new.mjs +29 -7
- package/bin/incanto-playtest.mjs +28 -2
- package/bin/incanto-verify.mjs +155 -19
- package/bin/incanto.mjs +107 -0
- package/dist/2d.d.ts +8 -2
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +9 -3
- package/dist/3d.js +4 -4
- package/dist/{behavior-l08AEbq9.d.ts → behavior-DWKTUzKI.d.ts} +10 -0
- package/dist/{create-game-C5jQYPah.js → create-game-BiW8Men_.js} +61 -13
- package/dist/{create-game-DpbUrMOQ.js → create-game-CHDLDQsQ.js} +6 -6
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +2 -2
- package/dist/{duplicate-BPLZDZpd.js → duplicate-DJQd44CD.js} +1 -1
- package/dist/{environment-presets-CvvQr_bJ.js → environment-presets-DRAz5EV9.js} +12 -10
- package/dist/{gameplay-BVphcxmE.js → gameplay-BBEjPFsR.js} +62 -34
- package/dist/gameplay.d.ts +1 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +36 -8
- package/dist/index.js +8 -8
- package/dist/{json-BLk7H2Qa.js → json-CwwhxQgb.js} +7 -1
- package/dist/{loader-BcrRSjxB.js → loader-D8n7TU8W.js} +142 -5
- package/dist/{loader-BbEMTuWg.d.ts → loader-TvkRFbyL.d.ts} +1 -1
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{pathfinding-mEN4V1CU.d.ts → pathfinding-BqWBb0kh.d.ts} +1 -1
- package/dist/{physics-2d-BLcvEFDR.js → physics-2d-BaRSRrrZ.js} +14 -3
- package/dist/{physics-3d-QBrfIT2Y.js → physics-3d-CYxjh-HW.js} +15 -4
- package/dist/quiet-rapier-BAJ4K94N.js +46 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +2 -2
- package/dist/{register-C6ZBFRjd.js → register-BpFcgdcL.js} +57 -27
- package/dist/{register-Ch70uByv.js → register-CDrAQqPp.js} +90 -41
- package/dist/{registry-C7u42TID.js → registry-WWcQcfMr.js} +1 -1
- package/dist/{replay-s7I2GstT.js → replay-CEPyQtF_.js} +31 -7
- package/dist/{replay-Dw6gMlYA.d.ts → replay-O-yAGM76.d.ts} +1 -1
- package/dist/{split-screen-B0baBwxI.d.ts → split-screen-BQ3tAsf-.d.ts} +38 -1
- package/dist/{split-screen-DLsUrleX.js → split-screen-DDMZutQ6.js} +56 -13
- package/dist/{sprite-animation-C0wXLBZJ.js → sprite-animation-CY-mrr1L.js} +1 -1
- package/dist/{src-CGjmPw65.js → src-CY21B462.js} +1 -1
- package/dist/{teardown-Cs113S9F.js → teardown-RApWnM1G.js} +1 -1
- package/dist/{test-it1VekWs.js → test-DHYuFyAu.js} +292 -31
- package/dist/test.d.ts +104 -6
- package/dist/test.js +3 -3
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-CGT7r3Mb.js → agent8-BoRGtVxK.js} +1 -1
- package/editor/assets/{debug-BxWSIHG3.js → debug-CzdyCg75.js} +1 -1
- package/editor/assets/{index-CV1m-aX5.js → index-VesuVEhe.js} +91 -91
- package/editor/index.html +1 -1
- package/package.json +3 -1
- package/schemas/scene.schema.json +1174 -70
- package/skills/incanto-building-2d-games.md +13 -0
- package/skills/incanto-building-3d-games.md +3 -3
- package/skills/incanto-localization.md +40 -8
- package/skills/incanto-multiplayer.md +57 -4
- package/skills/incanto-node-reference.md +18 -0
- package/skills/incanto-physics-and-input.md +26 -0
- package/skills/incanto-playtesting.md +19 -5
- package/skills/incanto-verifying-your-game.md +33 -4
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +7 -6
- package/templates-app/platformer-2d/PROJECT/Context.md +70 -0
- package/templates-app/platformer-2d/PROJECT/Requirements.md +63 -0
- package/templates-app/platformer-2d/PROJECT/Status.md +60 -0
- package/templates-app/platformer-2d/PROJECT/Structure.md +77 -0
- package/templates-app/platformer-2d/docs/project-2d-rules.md +61 -0
- package/templates-app/platformer-2d/index.html +99 -0
- package/templates-app/platformer-2d/package.json +23 -0
- package/templates-app/platformer-2d/src/behaviors.ts +541 -0
- package/templates-app/platformer-2d/src/game.scene.json +2061 -0
- package/templates-app/platformer-2d/src/main.ts +68 -0
- package/templates-app/platformer-2d/tsconfig.json +13 -0
- package/templates-app/platformer-2d/verify.ts +275 -0
- package/templates-app/platformer-2d/vite.config.ts +12 -0
- package/templates-app/star-survivor/PROJECT/Context.md +55 -0
- package/templates-app/star-survivor/PROJECT/Requirements.md +47 -0
- package/templates-app/star-survivor/PROJECT/Status.md +44 -0
- package/templates-app/star-survivor/PROJECT/Structure.md +63 -0
- package/templates-app/star-survivor/docs/project-2d-rules.md +53 -0
- package/templates-app/star-survivor/index.html +232 -0
- package/templates-app/star-survivor/package.json +23 -0
- package/templates-app/star-survivor/src/behaviors.ts +624 -0
- package/templates-app/star-survivor/src/game.scene.json +464 -0
- package/templates-app/star-survivor/src/main.ts +49 -0
- package/templates-app/star-survivor/tsconfig.json +13 -0
- package/templates-app/star-survivor/verify.ts +193 -0
- package/templates-app/star-survivor/vite.config.ts +12 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +6 -3
- package/templates-app/tps-3d/verify.ts +17 -1
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/grove.scene.json +14 -13
- package/templates-app/village-quest-3d/src/village.scene.json +5 -5
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import { t as registerCoreNodes } from "./register-
|
|
1
|
+
import { C as Node, b as effectiveOrder, w as diagnose } from "./loader-D8n7TU8W.js";
|
|
2
|
+
import { I as translateOn, t as registerCoreNodes } from "./register-CDrAQqPp.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
|
-
import { i as getNodeSchema, l as registerNode } from "./registry-
|
|
6
|
-
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-
|
|
5
|
+
import { i as getNodeSchema, l as registerNode } from "./registry-WWcQcfMr.js";
|
|
6
|
+
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-CY-mrr1L.js";
|
|
7
7
|
import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
|
|
8
8
|
import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry, SRGBColorSpace } from "three";
|
|
9
9
|
//#region src/2d/physics/collider-validate.ts
|
|
@@ -115,32 +115,51 @@ var Node2D = class extends Node {
|
|
|
115
115
|
* `{shape:'capsule', radius, height}` — never child shape nodes.
|
|
116
116
|
*/
|
|
117
117
|
var PhysicsBody2D = class extends Node2D {
|
|
118
|
-
static props = {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
118
|
+
static props = {
|
|
119
|
+
collider: {
|
|
120
|
+
default: {},
|
|
121
|
+
variants: {
|
|
122
|
+
tag: "shape",
|
|
123
|
+
byTag: {
|
|
124
|
+
rect: {
|
|
125
|
+
shape: "rect",
|
|
126
|
+
size: [32, 32]
|
|
127
|
+
},
|
|
128
|
+
circle: {
|
|
129
|
+
shape: "circle",
|
|
130
|
+
radius: 16
|
|
131
|
+
},
|
|
132
|
+
capsule: {
|
|
133
|
+
shape: "capsule",
|
|
134
|
+
radius: 12,
|
|
135
|
+
height: 32
|
|
136
|
+
}
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
+
},
|
|
140
|
+
/**
|
|
141
|
+
* Whether this body's collider takes part in physics right now.
|
|
142
|
+
*
|
|
143
|
+
* A melee hitbox is off most of the time and live for a few frames of a
|
|
144
|
+
* swing, and there was no way to say so: games expressed it by REPLACING
|
|
145
|
+
* `collider` with `{}`, which tears the rigid body down and builds a new
|
|
146
|
+
* one twice per swing, and makes the engine warn `has no collider —
|
|
147
|
+
* physics skips it` about a scene that is correct. Keep the real collider
|
|
148
|
+
* and turn it off.
|
|
149
|
+
*
|
|
150
|
+
* Off means no contacts and no `triggerEnter`/`triggerExit`. The body
|
|
151
|
+
* itself stays, so nothing is rebuilt and re-arming is one call.
|
|
152
|
+
*/
|
|
153
|
+
enabled: { default: true }
|
|
154
|
+
};
|
|
139
155
|
/** The unified collision model: every collider participant can emit these. */
|
|
140
156
|
static signals = ["triggerEnter", "triggerExit"];
|
|
141
157
|
_collider = {};
|
|
142
158
|
/** @internal Physics resync flag — raised when `collider` is REPLACED. */
|
|
143
159
|
_colliderChanged = false;
|
|
160
|
+
_enabled = true;
|
|
161
|
+
/** @internal Physics resync flag — raised when `enabled` is written. */
|
|
162
|
+
_enabledChanged = false;
|
|
144
163
|
/**
|
|
145
164
|
* Collider shape prop. Change it by REPLACING the object
|
|
146
165
|
* (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
|
|
@@ -153,6 +172,15 @@ var PhysicsBody2D = class extends Node2D {
|
|
|
153
172
|
this._collider = value;
|
|
154
173
|
this._colliderChanged = true;
|
|
155
174
|
}
|
|
175
|
+
/** See the `enabled` prop. Writing it arms or disarms the existing collider. */
|
|
176
|
+
get enabled() {
|
|
177
|
+
return this._enabled;
|
|
178
|
+
}
|
|
179
|
+
set enabled(value) {
|
|
180
|
+
if (value === this._enabled) return;
|
|
181
|
+
this._enabled = value;
|
|
182
|
+
this._enabledChanged = true;
|
|
183
|
+
}
|
|
156
184
|
/** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
|
|
157
185
|
static validateJson(node) {
|
|
158
186
|
validateCollider2D(node.collider, node.name);
|
|
@@ -825,11 +853,12 @@ var Label = class extends Node2D {
|
|
|
825
853
|
_syncObject2D(assets) {
|
|
826
854
|
super._syncObject2D(assets);
|
|
827
855
|
const quad = this._quad();
|
|
828
|
-
|
|
856
|
+
const text = translateOn(this, this.text);
|
|
857
|
+
if (typeof document === "undefined" || text === "") {
|
|
829
858
|
quad.visible = false;
|
|
830
859
|
return;
|
|
831
860
|
}
|
|
832
|
-
const key = `${
|
|
861
|
+
const key = `${text}\0${this.fontSize}\0${this.color}\0${this.font}\0${this.align}`;
|
|
833
862
|
if (key !== this.lastKey) {
|
|
834
863
|
this.lastKey = key;
|
|
835
864
|
this.rasterize();
|
|
@@ -838,13 +867,14 @@ var Label = class extends Node2D {
|
|
|
838
867
|
quad.renderOrder = this.effectiveRenderOrder;
|
|
839
868
|
}
|
|
840
869
|
rasterize() {
|
|
870
|
+
const text = translateOn(this, this.text);
|
|
841
871
|
const dpr = Math.min(globalThis.devicePixelRatio ?? 1, 2);
|
|
842
872
|
this.canvas ??= document.createElement("canvas");
|
|
843
873
|
const ctx = this.canvas.getContext("2d");
|
|
844
874
|
if (!ctx) return;
|
|
845
875
|
const fontSpec = `${this.fontSize * dpr}px ${this.font}`;
|
|
846
876
|
ctx.font = fontSpec;
|
|
847
|
-
const metrics = ctx.measureText(
|
|
877
|
+
const metrics = ctx.measureText(text);
|
|
848
878
|
const w = Math.max(1, Math.ceil(metrics.width));
|
|
849
879
|
const h = Math.ceil(this.fontSize * dpr * 1.4);
|
|
850
880
|
this.canvas.width = w;
|
|
@@ -852,7 +882,7 @@ var Label = class extends Node2D {
|
|
|
852
882
|
ctx.font = fontSpec;
|
|
853
883
|
ctx.fillStyle = this.color;
|
|
854
884
|
ctx.textBaseline = "middle";
|
|
855
|
-
ctx.fillText(
|
|
885
|
+
ctx.fillText(text, 0, h / 2);
|
|
856
886
|
const quad = this._quad();
|
|
857
887
|
const mat = quad.material;
|
|
858
888
|
mat.map?.dispose();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { C as Node, N as Signal, S as InputMap, g as restoreBehaviors, h as captureBehaviors, v as createSaveStore, w as diagnose, x as resolveOrderGroups, y as ORDER_GROUP_BASE } from "./loader-D8n7TU8W.js";
|
|
2
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
4
|
-
import { l as registerNode } from "./registry-
|
|
5
|
+
import { l as registerNode } from "./registry-WWcQcfMr.js";
|
|
5
6
|
//#region src/core/audio/buses.ts
|
|
6
7
|
const AUDIO_LOG_LIMIT = 200;
|
|
7
8
|
var AudioBuses = class {
|
|
@@ -1160,6 +1161,18 @@ var Localization = class {
|
|
|
1160
1161
|
const key = translationKey(value);
|
|
1161
1162
|
return key === null ? value : this.t(key, params);
|
|
1162
1163
|
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Whether the ACTIVE locale or the base declares this key.
|
|
1166
|
+
*
|
|
1167
|
+
* This is the runtime question, and it is not the same as `has`: a key only
|
|
1168
|
+
* `ko` declares is "declared", and an English player still gets the raw key
|
|
1169
|
+
* back from `t()`, because the fallback chain is current → base → the key.
|
|
1170
|
+
* Widgets that ask before translating asked the wrong one and painted
|
|
1171
|
+
* `settings.quality.low` into an English menu.
|
|
1172
|
+
*/
|
|
1173
|
+
declares(key) {
|
|
1174
|
+
return this.tables[this.current]?.[key] !== void 0 || this.tables["en"]?.[key] !== void 0;
|
|
1175
|
+
}
|
|
1163
1176
|
/** Whether any locale declares this key — for the audit, not for the runtime. */
|
|
1164
1177
|
has(key) {
|
|
1165
1178
|
for (const entries of Object.values(this.tables)) if (entries[key] !== void 0) return true;
|
|
@@ -1182,6 +1195,22 @@ function suggestLocale(available, languages) {
|
|
|
1182
1195
|
}
|
|
1183
1196
|
return "en";
|
|
1184
1197
|
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Resolve a text prop on ANY node, not just a DOM HUD widget.
|
|
1200
|
+
*
|
|
1201
|
+
* `@t:` was honoured in exactly one place — `HudWidgetBase._t` — so text that
|
|
1202
|
+
* lives in the WORLD had no localization path at all: a `Label3D` reading
|
|
1203
|
+
* `"@t:sign.welcome"` rasterized those literal characters into the texture
|
|
1204
|
+
* standing over the shop door, in every language, and the audit certified the
|
|
1205
|
+
* scene clean because the key WAS declared.
|
|
1206
|
+
*
|
|
1207
|
+
* Returns the value untouched when there is no tree yet or no marker, so it is
|
|
1208
|
+
* safe to call from a rasterize path that runs before the node is in a scene.
|
|
1209
|
+
*/
|
|
1210
|
+
function translateOn(node, value) {
|
|
1211
|
+
const locale = node.tree?.engine?.locale;
|
|
1212
|
+
return locale ? locale.resolve(value) : value;
|
|
1213
|
+
}
|
|
1185
1214
|
//#endregion
|
|
1186
1215
|
//#region src/core/nodes/hud.ts
|
|
1187
1216
|
const ANCHOR_CSS = {
|
|
@@ -1536,6 +1565,33 @@ var HudWidgetBase = class extends Node {
|
|
|
1536
1565
|
const locale = this.tree?.engine?.locale;
|
|
1537
1566
|
return locale ? locale.resolve(value) : value;
|
|
1538
1567
|
}
|
|
1568
|
+
/**
|
|
1569
|
+
* A translation with an English wording to fall back to.
|
|
1570
|
+
*
|
|
1571
|
+
* For text the ENGINE supplies — "Music", "Mute", "Unlimited" — where there
|
|
1572
|
+
* is no author string to carry a `@t:` marker. Asks `declares()`, not
|
|
1573
|
+
* `has()`: a key only another locale declares would otherwise come back as
|
|
1574
|
+
* the raw key, which is worse than the English.
|
|
1575
|
+
*/
|
|
1576
|
+
/**
|
|
1577
|
+
* What this widget's label READS as — resolved every frame, never stored.
|
|
1578
|
+
*
|
|
1579
|
+
* `UiVolumeSlider` and `UiMuteToggle` used to compute their auto-label in
|
|
1580
|
+
* `_build()` and write the result back into `this.label`, which broke them
|
|
1581
|
+
* twice over: the per-frame `_t(this.label)` then had a plain string with no
|
|
1582
|
+
* marker left to resolve, and `_build` runs from `onReady` — inside
|
|
1583
|
+
* `loadScene`, BEFORE `setScene` merges the scene's `strings` — so the table
|
|
1584
|
+
* was empty at bake time and the English was permanent even for a game that
|
|
1585
|
+
* boots in another language.
|
|
1586
|
+
*/
|
|
1587
|
+
_labelText() {
|
|
1588
|
+
const label = this.label;
|
|
1589
|
+
return typeof label === "string" ? this._t(label) : "";
|
|
1590
|
+
}
|
|
1591
|
+
_tOr(key, fallback) {
|
|
1592
|
+
const locale = this.tree?.engine?.locale;
|
|
1593
|
+
return locale?.declares(key) ? locale.t(key) : fallback;
|
|
1594
|
+
}
|
|
1539
1595
|
};
|
|
1540
1596
|
/** A text line (score, timer, hints). Set `.text` from behaviors. */
|
|
1541
1597
|
var UiText = class extends HudWidgetBase {
|
|
@@ -2126,7 +2182,7 @@ var Settings = class {
|
|
|
2126
2182
|
* without knowing this class exists. Returns the unsubscribe.
|
|
2127
2183
|
*/
|
|
2128
2184
|
bindLocale(locale) {
|
|
2129
|
-
locale.locale = this.
|
|
2185
|
+
locale.locale = this.store.get("language", "") || suggestLocale(locale.available());
|
|
2130
2186
|
return locale.changed.connect((next) => {
|
|
2131
2187
|
this.set("language", next);
|
|
2132
2188
|
});
|
|
@@ -2229,6 +2285,7 @@ function qualityRendering(tier) {
|
|
|
2229
2285
|
}
|
|
2230
2286
|
//#endregion
|
|
2231
2287
|
//#region src/core/engine.ts
|
|
2288
|
+
var engine_exports = /* @__PURE__ */ __exportAll({ Engine: () => Engine });
|
|
2232
2289
|
const MAX_DT_SECONDS = .25;
|
|
2233
2290
|
function rafScheduler(cb) {
|
|
2234
2291
|
let live = true;
|
|
@@ -3193,17 +3250,6 @@ function px(n) {
|
|
|
3193
3250
|
return `${n}px`;
|
|
3194
3251
|
}
|
|
3195
3252
|
/**
|
|
3196
|
-
* The translated string, or the English one.
|
|
3197
|
-
*
|
|
3198
|
-
* `locale.t` returns the KEY when nothing declares it, and a menu reading
|
|
3199
|
-
* `settings.volume.music` is worse than one reading `Music`. English is the
|
|
3200
|
-
* base language and the fallback, always.
|
|
3201
|
-
*/
|
|
3202
|
-
function translated(engine, key, fallback) {
|
|
3203
|
-
const locale = engine?.locale;
|
|
3204
|
-
return locale?.has(key) ? locale.t(key) : fallback;
|
|
3205
|
-
}
|
|
3206
|
-
/**
|
|
3207
3253
|
* A box that holds other widgets — the thing a menu actually is.
|
|
3208
3254
|
*
|
|
3209
3255
|
* Widgets normally mount into their HudLayer's anchor slot, which is why a
|
|
@@ -3377,7 +3423,7 @@ var UiSlider = class extends HudWidgetBase {
|
|
|
3377
3423
|
const wrap = document.createElement("div");
|
|
3378
3424
|
wrap.style.cssText = "display:flex;align-items:center;gap:8px;pointer-events:auto;font:600 13px system-ui,-apple-system,'Segoe UI',sans-serif;color:#fff;";
|
|
3379
3425
|
const text = document.createElement("span");
|
|
3380
|
-
text.textContent = this.
|
|
3426
|
+
text.textContent = this._labelText();
|
|
3381
3427
|
const input = document.createElement("input");
|
|
3382
3428
|
input.type = "range";
|
|
3383
3429
|
input.min = String(this.min);
|
|
@@ -3390,7 +3436,7 @@ var UiSlider = class extends HudWidgetBase {
|
|
|
3390
3436
|
this.last = this.value;
|
|
3391
3437
|
this.emit("changed", this.value);
|
|
3392
3438
|
});
|
|
3393
|
-
if (this.
|
|
3439
|
+
if (this._labelText()) wrap.appendChild(text);
|
|
3394
3440
|
wrap.appendChild(input);
|
|
3395
3441
|
this.input = input;
|
|
3396
3442
|
this.text = text;
|
|
@@ -3402,7 +3448,7 @@ var UiSlider = class extends HudWidgetBase {
|
|
|
3402
3448
|
this.last = this.value;
|
|
3403
3449
|
this.input.value = String(this.value);
|
|
3404
3450
|
}
|
|
3405
|
-
const label = this.
|
|
3451
|
+
const label = this._labelText();
|
|
3406
3452
|
if (this.text && this.text.textContent !== label) this.text.textContent = label;
|
|
3407
3453
|
}
|
|
3408
3454
|
};
|
|
@@ -3442,9 +3488,9 @@ var UiToggle = class extends HudWidgetBase {
|
|
|
3442
3488
|
this.emit("changed", this.value);
|
|
3443
3489
|
});
|
|
3444
3490
|
const text = document.createElement("span");
|
|
3445
|
-
text.textContent = this.
|
|
3491
|
+
text.textContent = this._labelText();
|
|
3446
3492
|
this.tag = text;
|
|
3447
|
-
this.lastLabel = this.
|
|
3493
|
+
this.lastLabel = this._labelText();
|
|
3448
3494
|
wrap.append(input, text);
|
|
3449
3495
|
this.input = input;
|
|
3450
3496
|
this.last = this.value;
|
|
@@ -3457,7 +3503,7 @@ var UiToggle = class extends HudWidgetBase {
|
|
|
3457
3503
|
this.last = this.value;
|
|
3458
3504
|
this.input.checked = this.value;
|
|
3459
3505
|
}
|
|
3460
|
-
const label = this.
|
|
3506
|
+
const label = this._labelText();
|
|
3461
3507
|
if (this.tag && label !== this.lastLabel) {
|
|
3462
3508
|
this.lastLabel = label;
|
|
3463
3509
|
this.tag.textContent = label;
|
|
@@ -3507,24 +3553,33 @@ var UiSelect = class extends HudWidgetBase {
|
|
|
3507
3553
|
this.last = this.value;
|
|
3508
3554
|
this.emit("changed", this.value);
|
|
3509
3555
|
});
|
|
3510
|
-
if (this.
|
|
3556
|
+
if (this._labelText()) wrap.appendChild(text);
|
|
3511
3557
|
wrap.appendChild(select);
|
|
3512
3558
|
this.select = select;
|
|
3513
3559
|
this.fillOptions();
|
|
3514
3560
|
return wrap;
|
|
3515
3561
|
}
|
|
3516
|
-
/**
|
|
3562
|
+
/**
|
|
3563
|
+
* What an option READS as, when it differs from its value.
|
|
3564
|
+
*
|
|
3565
|
+
* The base resolves `@t:` like every other text prop: the VALUE stays exactly
|
|
3566
|
+
* what the author wrote — so a `changed` handler never has to know the active
|
|
3567
|
+
* language — and only the displayed words change. Without this a dropdown was
|
|
3568
|
+
* the one widget with no way to speak anything but English, and
|
|
3569
|
+
* `"options": "@t:diff.easy"` rendered that marker literally on screen.
|
|
3570
|
+
*/
|
|
3517
3571
|
_optionLabel(value) {
|
|
3518
|
-
return value;
|
|
3572
|
+
return this._t(value);
|
|
3519
3573
|
}
|
|
3520
3574
|
fillOptions() {
|
|
3521
3575
|
const select = this.select;
|
|
3522
|
-
if (!select
|
|
3523
|
-
|
|
3576
|
+
if (!select) return;
|
|
3577
|
+
const values = this.options.split(",").map((o) => o.trim()).filter(Boolean);
|
|
3578
|
+
const painted = values.map((v) => `${v}\u0000${this._optionLabel(v)}`).join("");
|
|
3579
|
+
if (painted === this.lastOptions) return;
|
|
3580
|
+
this.lastOptions = painted;
|
|
3524
3581
|
select.textContent = "";
|
|
3525
|
-
for (const
|
|
3526
|
-
const value = raw.trim();
|
|
3527
|
-
if (!value) continue;
|
|
3582
|
+
for (const value of values) {
|
|
3528
3583
|
const option = document.createElement("option");
|
|
3529
3584
|
option.value = value;
|
|
3530
3585
|
option.textContent = this._optionLabel(value);
|
|
@@ -3635,9 +3690,6 @@ var UiSettingSelect = class extends UiSelect {
|
|
|
3635
3690
|
* `settings.quality.high` is worse than one reading `High`. English is the
|
|
3636
3691
|
* base language and the fallback, always.
|
|
3637
3692
|
*/
|
|
3638
|
-
_tOr(key, fallback) {
|
|
3639
|
-
return translated(this._engine(), key, fallback);
|
|
3640
|
-
}
|
|
3641
3693
|
_sync() {
|
|
3642
3694
|
const engine = this._engine();
|
|
3643
3695
|
if (engine) {
|
|
@@ -3807,12 +3859,10 @@ var UiVolumeSlider = class extends UiSlider {
|
|
|
3807
3859
|
bus = "master";
|
|
3808
3860
|
/** Last value we PUSHED in, to tell an engine-side change from a player drag. */
|
|
3809
3861
|
lastPushed = null;
|
|
3810
|
-
|
|
3811
|
-
if (
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
}
|
|
3815
|
-
return super._build();
|
|
3862
|
+
_labelText() {
|
|
3863
|
+
if (this.label) return this._t(this.label);
|
|
3864
|
+
const english = VOLUME_LABELS[this.bus] ?? VOLUME_LABELS.master;
|
|
3865
|
+
return this._tOr(`settings.volume.${this.bus}`, english);
|
|
3816
3866
|
}
|
|
3817
3867
|
_sync() {
|
|
3818
3868
|
const audio = this.tree?.engine?.audio;
|
|
@@ -3846,9 +3896,8 @@ var UiMuteToggle = class extends UiToggle {
|
|
|
3846
3896
|
label: { default: "" }
|
|
3847
3897
|
};
|
|
3848
3898
|
lastPushed = null;
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
return super._build();
|
|
3899
|
+
_labelText() {
|
|
3900
|
+
return this.label ? this._t(this.label) : this._tOr("settings.mute", "Mute");
|
|
3852
3901
|
}
|
|
3853
3902
|
_sync() {
|
|
3854
3903
|
const audio = this.tree?.engine?.audio;
|
|
@@ -3894,4 +3943,4 @@ function registerCoreNodes() {
|
|
|
3894
3943
|
registerNode(UiMuteToggle);
|
|
3895
3944
|
}
|
|
3896
3945
|
//#endregion
|
|
3897
|
-
export {
|
|
3946
|
+
export { UiBar as A, ROLLOFF_MODELS as B, readDeviceHints as C, HudLayer as D, EffectLog as E, suggestLocale as F, synthSfx as G, spatialPan as H, translateOn as I, crossfadeGains as J, MusicManager as K, translationKey as L, BASE_LOCALE as M, Localization as N, HudWidgetBase as O, T_PREFIX as P, SfxEngine as R, qualityRendering as S, LogManager as T, SFX_PRESETS as U, spatialGain as V, SFX_PRESET_NAMES as W, AudioBuses as X, fadeGain as Y, Engine as _, UiMuteToggle as a, qualityCaps as b, UiRenderScaleSelect as c, UiToggle as d, UiVolumeSlider as f, AudioPlayer as g, UiDialogue as h, UiLanguageSelect as i, UiText as j, UiBanner as k, UiSelect as l, UiButton as m, UiFrameCapSelect as n, UiPanel as o, Timer as p, WebAudioMusicBackend as q, UiImage as r, UiQualitySelect as s, registerCoreNodes as t, UiSlider as u, engine_exports as v, suggestQuality as w, qualityEnvironment as x, Settings as y, isAudioContextAvailable as z };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
|
-
import { r as jsonKind, t as jsonClone } from "./json-
|
|
2
|
+
import { r as jsonKind, t as jsonClone } from "./json-CwwhxQgb.js";
|
|
3
3
|
//#region src/core/registry.ts
|
|
4
4
|
/**
|
|
5
5
|
* Validate a prop value against its schema default:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { i as getNodeSchema, o as getNodeType } from "./registry-
|
|
1
|
+
import { D as behaviorSchema, T as parseNodePath } from "./loader-D8n7TU8W.js";
|
|
2
|
+
import { L as translationKey, O as HudWidgetBase } from "./register-CDrAQqPp.js";
|
|
3
|
+
import { i as getNodeSchema, o as getNodeType } from "./registry-WWcQcfMr.js";
|
|
4
4
|
//#region src/core/node-refs.ts
|
|
5
5
|
/**
|
|
6
6
|
* Node-path PROPS, resolved against scene JSON.
|
|
@@ -317,15 +317,28 @@ function auditScene(scene) {
|
|
|
317
317
|
* screen. Reported here — at author time, where it can still be fixed — rather
|
|
318
318
|
* than at runtime in front of someone playing.
|
|
319
319
|
*/
|
|
320
|
+
/**
|
|
321
|
+
* Close to `@t:` without being it — `@T:`, a leading space, `@t :`, `@t.`.
|
|
322
|
+
* Deliberately narrow: an at-sign in ordinary prose ("email me @ home") must
|
|
323
|
+
* not trip it.
|
|
324
|
+
*/
|
|
325
|
+
const NEAR_MISS = /^\s*@\s*t\s*[:.]/i;
|
|
320
326
|
function auditStrings(scene) {
|
|
321
327
|
const tables = scene.strings;
|
|
322
328
|
const declared = /* @__PURE__ */ new Set();
|
|
323
329
|
for (const entries of Object.values(tables ?? {})) for (const key of Object.keys(entries)) declared.add(key);
|
|
330
|
+
const inBase = new Set(Object.keys(tables?.["en"] ?? {}));
|
|
324
331
|
const missing = /* @__PURE__ */ new Set();
|
|
332
|
+
const notInEnglish = /* @__PURE__ */ new Set();
|
|
333
|
+
const nearMiss = /* @__PURE__ */ new Set();
|
|
325
334
|
const walk = (value) => {
|
|
326
335
|
if (typeof value === "string") {
|
|
336
|
+
if (translationKey(value) === null && NEAR_MISS.test(value)) nearMiss.add(value);
|
|
327
337
|
const key = translationKey(value);
|
|
328
|
-
if (key !== null
|
|
338
|
+
if (key !== null) {
|
|
339
|
+
if (!declared.has(key)) missing.add(key);
|
|
340
|
+
else if (tables && !inBase.has(key)) notInEnglish.add(key);
|
|
341
|
+
}
|
|
329
342
|
return;
|
|
330
343
|
}
|
|
331
344
|
if (Array.isArray(value)) {
|
|
@@ -335,9 +348,20 @@ function auditStrings(scene) {
|
|
|
335
348
|
if (value !== null && typeof value === "object") for (const item of Object.values(value)) walk(item);
|
|
336
349
|
};
|
|
337
350
|
walk(scene.root);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
351
|
+
const out = [];
|
|
352
|
+
if (missing.size > 0) {
|
|
353
|
+
const listed = [...missing].sort();
|
|
354
|
+
out.push(`${listed.length} translation key${listed.length === 1 ? "" : "s"} nothing declares: ${listed.join(", ")}. The key itself renders on screen. Add them under "strings": { "en": { ... } } — other locales may omit any of them and fall back to English.`);
|
|
355
|
+
}
|
|
356
|
+
if (nearMiss.size > 0) {
|
|
357
|
+
const listed = [...nearMiss].sort();
|
|
358
|
+
out.push(`${listed.length} value${listed.length === 1 ? "" : "s"} look${listed.length === 1 ? "s" : ""} like a translation marker but ${listed.length === 1 ? "is" : "are"} not: ${listed.map((v) => JSON.stringify(v)).join(", ")}. It must be exactly "@t:key" — lower-case, no space. As written, the text itself renders on screen.`);
|
|
359
|
+
}
|
|
360
|
+
if (notInEnglish.size > 0) {
|
|
361
|
+
const listed = [...notInEnglish].sort();
|
|
362
|
+
out.push(`${listed.length} translation key${listed.length === 1 ? "" : "s"} missing from "en": ${listed.join(", ")}. English is the base every other locale falls back to, so these render as the raw key for anyone not using the locale that declares them.`);
|
|
363
|
+
}
|
|
364
|
+
return out;
|
|
341
365
|
}
|
|
342
366
|
//#endregion
|
|
343
367
|
//#region src/core/replay.ts
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { At as Node, It as Signal, b as Engine } from "./behavior-
|
|
1
|
+
import { At as Node, It as Signal, b as Engine } from "./behavior-DWKTUzKI.js";
|
|
2
2
|
import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
3
|
+
import { t as LoadSceneOptions } from "./loader-TvkRFbyL.js";
|
|
3
4
|
|
|
4
5
|
//#region src/net/types.d.ts
|
|
5
6
|
type Unsubscribe = () => void;
|
|
@@ -277,6 +278,17 @@ interface NetworkManagerOptions {
|
|
|
277
278
|
};
|
|
278
279
|
/** Replication send window in ms (default 50; node `network.throttleMs` can lower it). */
|
|
279
280
|
throttleMs?: number;
|
|
281
|
+
/**
|
|
282
|
+
* How often the FULL sync set is re-sent regardless of what changed, in ms
|
|
283
|
+
* (default 2000; 0 disables).
|
|
284
|
+
*
|
|
285
|
+
* Deltas are right on the wire and wrong in the store: backends shallow-merge
|
|
286
|
+
* one level down, so each send replaces the whole `sync` object and a key
|
|
287
|
+
* that stopped changing is erased from the authoritative state. A keyframe
|
|
288
|
+
* puts it back — for the next joiner, and for a send that never reached the
|
|
289
|
+
* wire, which is never retried.
|
|
290
|
+
*/
|
|
291
|
+
keyframeMs?: number;
|
|
280
292
|
}
|
|
281
293
|
/**
|
|
282
294
|
* Per-engine multiplayer hub: joins a room, exposes the room channels as core
|
|
@@ -308,6 +320,7 @@ declare class NetworkManager {
|
|
|
308
320
|
private readonly server;
|
|
309
321
|
private readonly engine;
|
|
310
322
|
private readonly throttleMs;
|
|
323
|
+
private readonly keyframeMs;
|
|
311
324
|
private readonly subs;
|
|
312
325
|
private readonly messageSignals;
|
|
313
326
|
private readonly collectionSignals;
|
|
@@ -320,8 +333,23 @@ declare class NetworkManager {
|
|
|
320
333
|
/** Sync keys already reported unreadable, so a per-frame send says it once. */
|
|
321
334
|
private readonly reportedDeadKeys;
|
|
322
335
|
/** Name a sync key the owner cannot read — a typo in the prop or the path. */
|
|
336
|
+
/**
|
|
337
|
+
* A sync key that has NEVER been readable, reported once — after a grace
|
|
338
|
+
* period.
|
|
339
|
+
*
|
|
340
|
+
* Behaviours set their props in `onReady`/`update`, and replication runs on
|
|
341
|
+
* the fixed step, so a perfectly correct key reads as `undefined` on the
|
|
342
|
+
* first pass or two. Reporting immediately made the engine's own co-op
|
|
343
|
+
* example print "sync key 'firing' … it is never sent" about a key that is
|
|
344
|
+
* sent from frame 2 onward — a diagnostic that cries wolf teaches its reader
|
|
345
|
+
* to ignore the one that does not.
|
|
346
|
+
*/
|
|
323
347
|
private reportDeadSyncKey;
|
|
324
348
|
private sendAccumulator;
|
|
349
|
+
/** Simulated ms since the last time the FULL sync set went out. */
|
|
350
|
+
private keyframeAccumulator;
|
|
351
|
+
/** Simulated ms this manager has been replicating — see reportDeadSyncKey. */
|
|
352
|
+
private aliveMs;
|
|
325
353
|
private detachReplication;
|
|
326
354
|
private lastOwner;
|
|
327
355
|
private boundScene;
|
|
@@ -399,6 +427,15 @@ interface SplitScreenOptions extends LocalGameServerOptions {
|
|
|
399
427
|
/** Engine seed base (each panel gets seed + index). */
|
|
400
428
|
seed?: number;
|
|
401
429
|
/**
|
|
430
|
+
* Loader options, per panel — `stubMissingBehaviors` for a headless check
|
|
431
|
+
* that has no TypeScript, `resolveScene` for `instance:` sub-scenes.
|
|
432
|
+
*
|
|
433
|
+
* Without these, `createSplitScreen` hard-failed on any real game: it called
|
|
434
|
+
* `loadScene` with nothing registered, so a scene naming one of its own
|
|
435
|
+
* behaviours threw `Unknown behavior` before a single frame ran.
|
|
436
|
+
*/
|
|
437
|
+
load?: LoadSceneOptions;
|
|
438
|
+
/**
|
|
402
439
|
* The room every panel joins. Default: whatever the FIRST panel got.
|
|
403
440
|
*
|
|
404
441
|
* A shared scene usually says `multiplayer: { room: "auto" }`, and "auto"
|