incanto 0.66.0 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/_behaviors-loader.mjs +87 -0
- package/bin/incanto-editor.mjs +39 -2
- package/bin/incanto-feel.mjs +4 -1
- package/bin/incanto-frame.mjs +52 -3
- package/bin/incanto-logs.mjs +33 -3
- package/bin/incanto-multiplay.mjs +50 -1
- package/bin/incanto-play.mjs +3 -1
- package/bin/incanto-playtest.mjs +3 -25
- package/bin/incanto-verify.mjs +15 -2
- package/dist/2d.d.ts +41 -4
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +86 -14
- package/dist/3d.js +6 -6
- package/dist/{audio-player-_UAcHxnC.d.ts → audio-player-BNUHo0JF.d.ts} +1 -1
- package/dist/{behavior-BXNLfIJk.d.ts → behavior-ZnfzKDo3.d.ts} +39 -1
- package/dist/{create-game-Dd7H4bJV.js → create-game-DMetd3vR.js} +14 -8
- package/dist/{create-game-DYJCIzO0.js → create-game-FAXdHta3.js} +77 -11
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +3 -2
- package/dist/{duplicate-CqSAtdrh.js → duplicate-B8QnX2-r.js} +1 -1
- package/dist/editor.js +7 -2
- package/dist/{environment-presets-BlPsEmq6.js → environment-presets-C-5g2pMh.js} +46 -6
- package/dist/{gameplay-D1RADWu3.js → gameplay-CGSv-9XR.js} +28 -5
- package/dist/gameplay.d.ts +25 -4
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +50 -6
- package/dist/index.js +7 -6
- package/dist/{loader-Cga7FVP4.d.ts → loader-DyT30ArB.d.ts} +1 -1
- package/dist/{loader-lQDCwNag.js → loader-KZESOZmE.js} +148 -1
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{physics-2d-Cji5A6sX.js → physics-2d-_B6zj3zU.js} +29 -2
- package/dist/{physics-3d-BkHjwJgI.js → physics-3d-i1IeAY0F.js} +30 -3
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-BmuqYTiY.js → register-Cs53CY7s.js} +81 -16
- package/dist/{register-D0CxCveZ.js → register-vIxU3Xbv.js} +3 -3
- package/dist/rendering-options-ByAdi8PF.js +184 -0
- package/dist/{replay-IsZbNu6d.js → replay-BkpDV51O.js} +4 -3
- package/dist/{replay-BHoB6fCU.d.ts → replay-D0GcEHYU.d.ts} +1 -1
- package/dist/{split-screen-eJUFjhi1.d.ts → split-screen-DD5eYIgW.d.ts} +2 -2
- package/dist/{split-screen-D_i7GRcY.js → split-screen-DS8RaUV_.js} +2 -2
- package/dist/{src-BTLbXFPZ.js → src-BLlsGJW9.js} +1 -1
- package/dist/{teardown-byR9USax.js → teardown-LB9cTnXJ.js} +2 -2
- package/dist/{test-DgrD0jHD.js → test-81uZ967a.js} +127 -24
- package/dist/test.d.ts +7 -5
- package/dist/test.js +2 -2
- package/dist/{touch-DESwnpOc.js → touch-DEAmqGdf.js} +30 -186
- package/dist/vite.d.ts +0 -7
- package/dist/vite.js +13 -3
- package/editor/assets/{agent8-9N-Pd_YS.js → agent8-C2jtNKbx.js} +1 -1
- package/editor/assets/debug-DoU3KeqS.js +3 -0
- package/editor/assets/{index-CeDhIPTC.js → index-CNqHojcC.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +1 -1
- package/skills/incanto-building-3d-games.md +19 -0
- package/skills/incanto-editor.md +5 -1
- package/skills/incanto-environment.md +2 -2
- package/skills/incanto-gameplay-behaviors.md +1 -1
- package/skills/incanto-hud.md +14 -1
- package/skills/incanto-multiplayer.md +14 -0
- package/skills/incanto-node-reference.md +1 -1
- package/skills/incanto-performance.md +35 -0
- package/skills/incanto-playtesting.md +15 -1
- package/skills/incanto-save-slots.md +21 -1
- package/skills/incanto-verifying-your-game.md +19 -0
- package/skills/incanto-your-first-game.md +4 -4
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +38 -19
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/game.scene.json +99 -53
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/game.scene.json +31 -17
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +14 -8
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/grove.scene.json +20 -11
- package/templates-app/village-quest-3d/src/village.scene.json +10 -7
- package/editor/assets/debug-CkbJICYp.js +0 -3
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-
|
|
2
|
+
import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-KZESOZmE.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
5
|
import { l as registerNode } from "./registry-CF70EArN.js";
|
|
6
|
+
import { r as capturePointer } from "./touch-DEAmqGdf.js";
|
|
6
7
|
//#region src/core/audio/buses.ts
|
|
7
8
|
const AUDIO_LOG_LIMIT = 200;
|
|
8
9
|
var AudioBuses = class {
|
|
@@ -1339,11 +1340,29 @@ var HudLayer = class extends Node {
|
|
|
1339
1340
|
}
|
|
1340
1341
|
}
|
|
1341
1342
|
}
|
|
1342
|
-
/**
|
|
1343
|
+
/**
|
|
1344
|
+
* Focusable widgets under this layer, in tree order — **on screen only**.
|
|
1345
|
+
*
|
|
1346
|
+
* This filtered on each widget's OWN `visible` and recursed into hidden
|
|
1347
|
+
* containers anyway, so arrow-keys walked into closed panels and Enter really
|
|
1348
|
+
* pressed what it found. Measured on a title screen: 23 focus stops, 21 of
|
|
1349
|
+
* them under an ancestor with `visible = false`, and
|
|
1350
|
+
*
|
|
1351
|
+
* gold before = 150 -> Enter on the hidden Shop/Row0/Buy -> gold 120,
|
|
1352
|
+
* screen still 'title'
|
|
1353
|
+
*
|
|
1354
|
+
* The layer's own visibility was not a gate either. It is now: a HUD nobody
|
|
1355
|
+
* can see has no ring, and a subtree nobody can see is not walked.
|
|
1356
|
+
*
|
|
1357
|
+
* PUBLIC, because a game that wants to know what its ring contains could not
|
|
1358
|
+
* ask — and "why is focus on that" is exactly the question this answers.
|
|
1359
|
+
*/
|
|
1343
1360
|
focusables() {
|
|
1344
1361
|
const out = [];
|
|
1362
|
+
if (!this.visible) return out;
|
|
1345
1363
|
const walk = (node) => {
|
|
1346
|
-
if (node instanceof HudWidgetBase && node.
|
|
1364
|
+
if (node instanceof HudWidgetBase && !node.visible) return;
|
|
1365
|
+
if (node instanceof HudWidgetBase && node.focusable) out.push(node);
|
|
1347
1366
|
for (const child of node.children) walk(child);
|
|
1348
1367
|
};
|
|
1349
1368
|
walk(this);
|
|
@@ -1513,7 +1532,7 @@ var HudWidgetBase = class extends Node {
|
|
|
1513
1532
|
el.addEventListener("pointerdown", (event) => {
|
|
1514
1533
|
if (!this.draggable) return;
|
|
1515
1534
|
event.preventDefault();
|
|
1516
|
-
el
|
|
1535
|
+
capturePointer(el, event.pointerId);
|
|
1517
1536
|
dragging = this;
|
|
1518
1537
|
draggingFrom = dropTargetFromElement(el.parentElement);
|
|
1519
1538
|
el.style.opacity = "0.6";
|
|
@@ -1553,10 +1572,16 @@ var HudWidgetBase = class extends Node {
|
|
|
1553
1572
|
this._mountWidget();
|
|
1554
1573
|
if (!this._element) return;
|
|
1555
1574
|
}
|
|
1556
|
-
this._element.style.display = this.visible ? "" : "none";
|
|
1557
1575
|
this._followAnchor();
|
|
1558
1576
|
this._sync();
|
|
1577
|
+
if (this._element.style.display !== "none") this._shownDisplay = this._element.style.display;
|
|
1578
|
+
this._element.style.display = this.visible ? this._shownDisplay : "none";
|
|
1559
1579
|
}
|
|
1580
|
+
/**
|
|
1581
|
+
* The display this widget's own css asks for while shown — `grid` for a grid
|
|
1582
|
+
* panel, `flex` for a row, `''` for a widget that never says.
|
|
1583
|
+
*/
|
|
1584
|
+
_shownDisplay = "";
|
|
1560
1585
|
/** Which anchor slot this widget was mounted into. */
|
|
1561
1586
|
mountedAnchor = null;
|
|
1562
1587
|
/**
|
|
@@ -2280,14 +2305,9 @@ function qualityEnvironment(tier) {
|
|
|
2280
2305
|
};
|
|
2281
2306
|
if (tier === "medium") return {
|
|
2282
2307
|
shadows: { static: true },
|
|
2283
|
-
bloom: {},
|
|
2284
2308
|
post: null
|
|
2285
2309
|
};
|
|
2286
|
-
return {
|
|
2287
|
-
shadows: true,
|
|
2288
|
-
bloom: {},
|
|
2289
|
-
post: {}
|
|
2290
|
-
};
|
|
2310
|
+
return { shadows: true };
|
|
2291
2311
|
}
|
|
2292
2312
|
const QUALITY_CAPS = Object.freeze({
|
|
2293
2313
|
low: Object.freeze({
|
|
@@ -2436,7 +2456,7 @@ var Engine = class {
|
|
|
2436
2456
|
* reset on reload. Reading it here costs nothing (localStorage, once) and
|
|
2437
2457
|
* `bindAudio` makes the volumes write themselves.
|
|
2438
2458
|
*/
|
|
2439
|
-
settings
|
|
2459
|
+
settings;
|
|
2440
2460
|
/**
|
|
2441
2461
|
* Translations, and the locale in force. English is the base and the default;
|
|
2442
2462
|
* anything a locale does not declare falls back to it silently.
|
|
@@ -2510,6 +2530,7 @@ var Engine = class {
|
|
|
2510
2530
|
this.scheduler = opts.scheduler ?? rafScheduler;
|
|
2511
2531
|
this.rng = new Rng(opts.seed ?? Math.random() * 4294967295 >>> 0);
|
|
2512
2532
|
this.maxFps = opts.maxFps ?? 0;
|
|
2533
|
+
this.settings = opts.settings instanceof Settings ? opts.settings : new Settings(opts.settings?.namespace ?? "settings");
|
|
2513
2534
|
this.sfx._setBuses(this.audio);
|
|
2514
2535
|
this.music.onTrackError = (src, reason) => {
|
|
2515
2536
|
diagnose(this, "error", `[incanto] music '${src}' will not play — ${reason}.`);
|
|
@@ -2636,8 +2657,9 @@ var Engine = class {
|
|
|
2636
2657
|
captureState() {
|
|
2637
2658
|
const root = this.scene?.root;
|
|
2638
2659
|
if (!root) return {};
|
|
2639
|
-
const { state, unaddressable } = captureBehaviors(root, this.scene?.source);
|
|
2660
|
+
const { state, unaddressable, unrecordable } = captureBehaviors(root, this.scene?.source);
|
|
2640
2661
|
for (const path of unaddressable) diagnose(this, "warn", `save: ${path} has state to save and no uid to key it under.`);
|
|
2662
|
+
if (unrecordable.length > 0) diagnose(this, "error", `[incanto] save: ${unrecordable.length} authored node(s) this run consumed have no uid, so nothing records that they are gone and LOADING THIS SAVE BRINGS THEM BACK: ${unrecordable.slice(0, 8).join(", ")}${unrecordable.length > 8 ? `, +${unrecordable.length - 8} more` : ""}. Give each one a "uid" from newUid().`);
|
|
2641
2663
|
return state;
|
|
2642
2664
|
}
|
|
2643
2665
|
/** Give it back, after the scene is loaded and onReady has run. */
|
|
@@ -3187,6 +3209,7 @@ var UiDialogue = class extends HudWidgetBase {
|
|
|
3187
3209
|
];
|
|
3188
3210
|
static props = {
|
|
3189
3211
|
...HudWidgetBase.props,
|
|
3212
|
+
focusable: { default: true },
|
|
3190
3213
|
anchor: {
|
|
3191
3214
|
default: "bottom",
|
|
3192
3215
|
options: [
|
|
@@ -3236,6 +3259,49 @@ var UiDialogue = class extends HudWidgetBase {
|
|
|
3236
3259
|
});
|
|
3237
3260
|
}
|
|
3238
3261
|
/**
|
|
3262
|
+
* A dialogue takes focus while it is up.
|
|
3263
|
+
*
|
|
3264
|
+
* It paints a focus ring and the generated reference advertises the prop, and
|
|
3265
|
+
* it was inert: choices are raw DOM buttons inside the widget, so
|
|
3266
|
+
* `HudLayer.focusables()` can never see them, and this class — unlike
|
|
3267
|
+
* `UiButton` a hundred lines above it — never overrode `_activate`. Eleven
|
|
3268
|
+
* keys and eight pad codes did nothing to a waiting choice while the same
|
|
3269
|
+
* Enter pressed a sibling button. Native Tab worked, and is closed by any
|
|
3270
|
+
* game that binds Tab; a pad has no Tab at all.
|
|
3271
|
+
*/
|
|
3272
|
+
focusable = true;
|
|
3273
|
+
/** Which choice the keyboard/pad is on. Reset by every new line. */
|
|
3274
|
+
cursor = 0;
|
|
3275
|
+
/**
|
|
3276
|
+
* Enter / A, and left / right between the options.
|
|
3277
|
+
*
|
|
3278
|
+
* `dir` is 0 for "activate" and ±1 for a nudge, the same shape `UiSelect`
|
|
3279
|
+
* uses — so one key layout drives a menu, a slider and a conversation.
|
|
3280
|
+
*/
|
|
3281
|
+
_activate(dir) {
|
|
3282
|
+
if (!this.active || !this.current) return;
|
|
3283
|
+
const choices = this.current.choices;
|
|
3284
|
+
if (!choices || choices.length === 0) {
|
|
3285
|
+
this.advance();
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
if (dir !== 0) {
|
|
3289
|
+
this.cursor = (this.cursor + dir + choices.length) % choices.length;
|
|
3290
|
+
this.paintCursor();
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
this.choose(this.cursor);
|
|
3294
|
+
}
|
|
3295
|
+
/** Show which option the keyboard is on — a ring nobody can see is not one. */
|
|
3296
|
+
paintCursor() {
|
|
3297
|
+
const buttons = this.choicesEl?.children;
|
|
3298
|
+
if (!buttons) return;
|
|
3299
|
+
for (let i = 0; i < buttons.length; i++) {
|
|
3300
|
+
const el = buttons[i];
|
|
3301
|
+
if (el) el.style.outline = i === this.cursor ? "2px solid #fff" : "none";
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3239
3305
|
* Player pressed "next": mid-typing reveals the whole line; a fully-typed
|
|
3240
3306
|
* line without choices advances (choice lines wait for `choose`).
|
|
3241
3307
|
*/
|
|
@@ -3318,12 +3384,11 @@ var UiDialogue = class extends HudWidgetBase {
|
|
|
3318
3384
|
this.revealed = Math.round(progress * this.current.text.length);
|
|
3319
3385
|
if (this.speakerEl) this.speakerEl.textContent = this.current.speaker;
|
|
3320
3386
|
}
|
|
3321
|
-
|
|
3322
|
-
if (wasTyping) this.revealed = Math.min(this.current.text.length, this.revealed + this.charsPerSecond * dt);
|
|
3387
|
+
if (this.revealed < this.current.text.length) this.revealed = Math.min(this.current.text.length, this.revealed + this.charsPerSecond * dt);
|
|
3323
3388
|
const doneTyping = this.revealed >= this.current.text.length;
|
|
3324
3389
|
if (this.textEl) this.textEl.textContent = this.showing;
|
|
3325
3390
|
if (this.hintEl) this.hintEl.style.visibility = doneTyping && !this.current.choices ? "visible" : "hidden";
|
|
3326
|
-
if (doneTyping &&
|
|
3391
|
+
if (doneTyping && this.current.choices && this.choicesEl && this.choicesEl.children.length === 0) this.current.choices.forEach((label, i) => {
|
|
3327
3392
|
const b = document.createElement("button");
|
|
3328
3393
|
b.type = "button";
|
|
3329
3394
|
b.textContent = label;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { O as Node, k as diagnose, w as effectiveOrder } from "./loader-
|
|
2
|
-
import { L as translateOn, t as registerCoreNodes } from "./register-
|
|
1
|
+
import { O as Node, k as diagnose, w as effectiveOrder } from "./loader-KZESOZmE.js";
|
|
2
|
+
import { L as translateOn, t as registerCoreNodes } from "./register-Cs53CY7s.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
5
|
import { i as getNodeSchema, l as registerNode } from "./registry-CF70EArN.js";
|
|
6
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
|
-
import { F as localFromWorld, I as worldPosition } from "./gameplay-
|
|
7
|
+
import { F as localFromWorld, I as worldPosition } from "./gameplay-CGSv-9XR.js";
|
|
8
8
|
import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
|
|
9
9
|
import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry, SRGBColorSpace } from "three";
|
|
10
10
|
//#region src/2d/physics/collider-validate.ts
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
//#region src/core/drive-script.ts
|
|
2
|
+
const num = (raw) => {
|
|
3
|
+
if (raw === void 0) return null;
|
|
4
|
+
const n = Number(raw);
|
|
5
|
+
return Number.isFinite(n) ? n : null;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* `vector move 0 1; step 2000` → steps, or an error naming the bad command.
|
|
9
|
+
*
|
|
10
|
+
* Semicolons and newlines both separate, so a whole plan fits in one shell
|
|
11
|
+
* argument and a longer one fits in a file. `#` starts a comment.
|
|
12
|
+
*
|
|
13
|
+
* A command it does not know REFUSES THE WHOLE SCRIPT. Skipping it would leave
|
|
14
|
+
* an agent looking at a frame that never moved, believing it had walked
|
|
15
|
+
* somewhere — the failure mode this exists to prevent.
|
|
16
|
+
*/
|
|
17
|
+
function parseDrive(text) {
|
|
18
|
+
const steps = [];
|
|
19
|
+
let totalMs = 0;
|
|
20
|
+
const lines = text.split(/[;\n]/).map((l) => l.replace(/#.*$/, "").trim()).filter(Boolean);
|
|
21
|
+
for (const line of lines) {
|
|
22
|
+
const [word, ...rest] = line.split(/\s+/);
|
|
23
|
+
const bad = (why) => ({
|
|
24
|
+
steps: [],
|
|
25
|
+
error: `${why}: "${line}"`,
|
|
26
|
+
totalMs: 0
|
|
27
|
+
});
|
|
28
|
+
switch (word) {
|
|
29
|
+
case "press":
|
|
30
|
+
case "release": {
|
|
31
|
+
const action = rest[0];
|
|
32
|
+
if (!action) return bad(`${word} needs an action name`);
|
|
33
|
+
steps.push(word === "press" ? {
|
|
34
|
+
kind: "press",
|
|
35
|
+
action
|
|
36
|
+
} : {
|
|
37
|
+
kind: "release",
|
|
38
|
+
action
|
|
39
|
+
});
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case "vector": {
|
|
43
|
+
const action = rest[0];
|
|
44
|
+
const x = num(rest[1]);
|
|
45
|
+
const y = num(rest[2]);
|
|
46
|
+
if (!action || x === null || y === null) return bad("vector needs an action and two numbers");
|
|
47
|
+
steps.push({
|
|
48
|
+
kind: "vector",
|
|
49
|
+
action,
|
|
50
|
+
x,
|
|
51
|
+
y
|
|
52
|
+
});
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case "key": {
|
|
56
|
+
const code = rest[0];
|
|
57
|
+
const dir = rest[1];
|
|
58
|
+
if (!code || dir !== "down" && dir !== "up") return bad("key needs a code and down|up");
|
|
59
|
+
steps.push({
|
|
60
|
+
kind: "key",
|
|
61
|
+
code,
|
|
62
|
+
down: dir === "down"
|
|
63
|
+
});
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "pointer": {
|
|
67
|
+
const dx = num(rest[0]);
|
|
68
|
+
const dy = num(rest[1]);
|
|
69
|
+
if (dx === null || dy === null) return bad("pointer needs two numbers");
|
|
70
|
+
steps.push({
|
|
71
|
+
kind: "pointer",
|
|
72
|
+
dx,
|
|
73
|
+
dy
|
|
74
|
+
});
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "step": {
|
|
78
|
+
const ms = num(rest[0]);
|
|
79
|
+
if (ms === null || ms < 0) return bad("step needs a duration in ms");
|
|
80
|
+
steps.push({
|
|
81
|
+
kind: "step",
|
|
82
|
+
ms
|
|
83
|
+
});
|
|
84
|
+
totalMs += ms;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
default: return bad("not a command");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
steps,
|
|
92
|
+
error: null,
|
|
93
|
+
totalMs
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/core/log-report.ts
|
|
98
|
+
const RANK = {
|
|
99
|
+
debug: 0,
|
|
100
|
+
info: 1,
|
|
101
|
+
warn: 2,
|
|
102
|
+
error: 3
|
|
103
|
+
};
|
|
104
|
+
/** Under this, with the loop running, is a frame rate somebody can feel. */
|
|
105
|
+
const SLOW_FPS = 50;
|
|
106
|
+
/** How many distinct messages to carry. Beyond this it is a log file, not a report. */
|
|
107
|
+
const MAX_GROUPS = 20;
|
|
108
|
+
/** The engine logs values, not strings — format them the way a console would. */
|
|
109
|
+
function format(parts) {
|
|
110
|
+
return parts.map((p) => {
|
|
111
|
+
if (typeof p === "string") return p;
|
|
112
|
+
if (p instanceof Error) return p.message;
|
|
113
|
+
try {
|
|
114
|
+
return JSON.stringify(p);
|
|
115
|
+
} catch {
|
|
116
|
+
return String(p);
|
|
117
|
+
}
|
|
118
|
+
}).join(" ");
|
|
119
|
+
}
|
|
120
|
+
function logReport(input) {
|
|
121
|
+
const counts = {
|
|
122
|
+
debug: 0,
|
|
123
|
+
info: 0,
|
|
124
|
+
warn: 0,
|
|
125
|
+
error: 0
|
|
126
|
+
};
|
|
127
|
+
const byMessage = /* @__PURE__ */ new Map();
|
|
128
|
+
for (const e of input.entries) {
|
|
129
|
+
counts[e.level] += 1;
|
|
130
|
+
const message = format(e.parts);
|
|
131
|
+
const key = `${e.level}\0${message}`;
|
|
132
|
+
const seen = byMessage.get(key);
|
|
133
|
+
if (seen) {
|
|
134
|
+
seen.count += 1;
|
|
135
|
+
seen.lastMs = Math.max(seen.lastMs, e.timeMs);
|
|
136
|
+
} else byMessage.set(key, {
|
|
137
|
+
level: e.level,
|
|
138
|
+
message,
|
|
139
|
+
count: 1,
|
|
140
|
+
lastMs: e.timeMs
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const grouped = [...byMessage.values()].sort((a, b) => RANK[b.level] - RANK[a.level] || b.lastMs - a.lastMs).slice(0, MAX_GROUPS);
|
|
144
|
+
const hidden = input.hidden === true;
|
|
145
|
+
const slow = !hidden && input.stats.running && input.stats.fps > 0 && input.stats.fps < SLOW_FPS;
|
|
146
|
+
return {
|
|
147
|
+
hidden,
|
|
148
|
+
ok: counts.error === 0 && input.stats.errors === 0 && input.assetErrors.length === 0,
|
|
149
|
+
slow,
|
|
150
|
+
counts,
|
|
151
|
+
grouped,
|
|
152
|
+
worst: grouped.find((g) => g.level === "error") ?? grouped[0] ?? null,
|
|
153
|
+
stats: input.stats,
|
|
154
|
+
assetErrors: input.assetErrors
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** The report as a person or an agent reads it: what is wrong, then the numbers. */
|
|
158
|
+
function logText(r) {
|
|
159
|
+
const lines = [];
|
|
160
|
+
if (r.stats.errors > 0) lines.push(`${r.stats.errors} error${r.stats.errors === 1 ? "" : "s"} were swallowed to keep the game alive — it is running, and something in it is broken`);
|
|
161
|
+
for (const a of r.assetErrors) lines.push(`asset ${a.ref} never loaded: ${a.error} (${a.url})`);
|
|
162
|
+
for (const g of r.grouped) {
|
|
163
|
+
if (g.level === "debug") continue;
|
|
164
|
+
lines.push(`[${g.level}] ${g.message}${g.count > 1 ? ` ×${g.count}` : ""}`);
|
|
165
|
+
}
|
|
166
|
+
if (lines.length === 0) lines.push("nothing wrong — no warnings, no errors, no failed assets");
|
|
167
|
+
if (!r.stats.running) lines.push(`not running — the loop is stopped (${r.stats.nodes} nodes in the scene)`);
|
|
168
|
+
else if (r.hidden) lines.push(`${r.stats.nodes} nodes · frame rate not measurable — the page is hidden (a window covered by another counts), and the browser throttles it. Bring it to the front`);
|
|
169
|
+
else lines.push(`${Math.round(r.stats.fps)} fps · ${r.stats.frameMs.toFixed(1)}ms per frame · ${r.stats.nodes} nodes${r.slow ? " — slow enough to feel" : ""}`);
|
|
170
|
+
return lines.join("\n");
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/core/rendering-options.ts
|
|
174
|
+
function resolveRendering(environment, fallback, devicePixelRatio, explicit) {
|
|
175
|
+
const rendering = environment?.rendering ?? {};
|
|
176
|
+
const scenePixelRatio = rendering.pixelRatio === "device" ? devicePixelRatio : rendering.pixelRatio;
|
|
177
|
+
return {
|
|
178
|
+
antialias: explicit?.antialias ?? rendering.antialias ?? fallback.antialias,
|
|
179
|
+
pixelRatio: explicit?.pixelRatio ?? scenePixelRatio ?? fallback.pixelRatio,
|
|
180
|
+
preserveDrawingBuffer: explicit?.preserveDrawingBuffer ?? rendering.preserveDrawingBuffer ?? fallback.preserveDrawingBuffer
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
export { parseDrive as i, logReport as n, logText as r, resolveRendering as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as handlerShadowWarnings, j as nodeRefWarnings } from "./loader-
|
|
2
|
-
import { R as translationKey, k as HudWidgetBase } from "./register-
|
|
1
|
+
import { D as handlerShadowWarnings, j as nodeRefWarnings } from "./loader-KZESOZmE.js";
|
|
2
|
+
import { R as translationKey, k as HudWidgetBase } from "./register-Cs53CY7s.js";
|
|
3
3
|
import { o as getNodeType } from "./registry-CF70EArN.js";
|
|
4
4
|
//#region src/core/audit.ts
|
|
5
5
|
const ANIMATED_TYPES = new Set([
|
|
@@ -166,7 +166,8 @@ function auditScene(scene, opts = {}) {
|
|
|
166
166
|
if (cameraDrivers.length > 0 && followedCameras.length > 0) warnings.push(`${followedCameras.join(", ")}: a FollowCamera writes this camera's position every frame, and so does the CharacterController3D at ${cameraDrivers.join(", ")} — the controller owns the camera in every view it has. Drop one of them: use the controller's own \`view\`/\`camDistance\`, or take the controller off the camera duty.`);
|
|
167
167
|
if (cameras > 0 && currentCameras === 0) warnings.push("no camera has \"current\": true — the screen renders from a default view, probably not what you framed.");
|
|
168
168
|
if (currentCameras > 1) warnings.push(`${currentCameras} cameras claim "current": true — only the first found wins.`);
|
|
169
|
-
|
|
169
|
+
const isFragment = cameras === 0;
|
|
170
|
+
if (scene.dimension === "3d" && !isFragment && lights === 0 && !isLit(scene.environment)) warnings.push("nothing lights this 3D scene — no light node, no environment sky/hdri, and no ambient intensity. It will render black. Add a DirectionalLight3D, or an \"environment\": { \"sky\": { \"type\": \"atmosphere\" } } / { \"preset\": \"...\" }.");
|
|
170
171
|
if (damageGroups.length > 0) {
|
|
171
172
|
const hurtable = hurtableGroups(root);
|
|
172
173
|
for (const { path, group } of damageGroups) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Lt as Signal, b as Engine, jt as Node } from "./behavior-
|
|
1
|
+
import { Lt as Signal, b as Engine, jt as Node } from "./behavior-ZnfzKDo3.js";
|
|
2
2
|
import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
3
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
3
|
+
import { t as LoadSceneOptions } from "./loader-DyT30ArB.js";
|
|
4
4
|
|
|
5
5
|
//#region src/net/types.d.ts
|
|
6
6
|
type Unsubscribe = () => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { O as Node, V as Signal, k as diagnose, n as loadScene, t as buildNodeJson } from "./loader-
|
|
2
|
-
import { t as registerCoreNodes, v as Engine } from "./register-
|
|
1
|
+
import { O as Node, V as Signal, k as diagnose, n as loadScene, t as buildNodeJson } from "./loader-KZESOZmE.js";
|
|
2
|
+
import { t as registerCoreNodes, v as Engine } from "./register-Cs53CY7s.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
|
|
5
5
|
import { l as registerNode } from "./registry-CF70EArN.js";
|
|
@@ -214,6 +214,6 @@ function newUid() {
|
|
|
214
214
|
//#endregion
|
|
215
215
|
//#region src/index.ts
|
|
216
216
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
217
|
-
const VERSION = "0.
|
|
217
|
+
const VERSION = "0.67.0";
|
|
218
218
|
//#endregion
|
|
219
219
|
export { preloadUrls as a, preloadSceneAssets as i, newUid as n, findPath as o, assetUrls as r, gridFromRows as s, VERSION as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as AudioPlayer } from "./register-
|
|
2
|
-
import {
|
|
1
|
+
import { _ as AudioPlayer } from "./register-Cs53CY7s.js";
|
|
2
|
+
import { i as parseDrive, n as logReport } from "./rendering-options-ByAdi8PF.js";
|
|
3
3
|
import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-BSMny7oe.js";
|
|
4
4
|
//#region src/core/audio-errors.ts
|
|
5
5
|
/** Every AudioPlayer under `root` whose source could not be played. */
|