incanto 0.55.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/incanto-verify.mjs +9 -2
- package/dist/2d.d.ts +59 -7
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +34 -4
- package/dist/3d.js +5 -5
- package/dist/{behavior-CyQoSu4n.d.ts → behavior-l08AEbq9.d.ts} +5 -0
- package/dist/{create-game-CzK9_pzg.js → create-game-C5jQYPah.js} +28 -71
- package/dist/{create-game-BRt6XKmP.js → create-game-DpbUrMOQ.js} +146 -7
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-MNLMAcbz.js → duplicate-BPLZDZpd.js} +1 -1
- package/dist/{environment-presets-DAbEdEwh.js → environment-presets-CvvQr_bJ.js} +118 -24
- package/dist/{frame-report-njybhZon.js → frame-report-BSMny7oe.js} +1 -1
- package/dist/{frame-report-DZ70IY26.d.ts → frame-report-DCnHFmto.d.ts} +1 -1
- package/dist/{gameplay-CZ2yq37J.js → gameplay-BVphcxmE.js} +163 -14
- package/dist/gameplay.d.ts +81 -2
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +45 -5
- package/dist/index.js +8 -9
- package/dist/{loader-DhI1jFW_.d.ts → loader-BbEMTuWg.d.ts} +1 -1
- package/dist/{loader-BTkHYrQn.js → loader-BcrRSjxB.js} +682 -682
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{pathfinding-CXGCpRQe.d.ts → pathfinding-mEN4V1CU.d.ts} +1 -1
- package/dist/{physics-2d-CfWAggJ1.js → physics-2d-BLcvEFDR.js} +2 -2
- package/dist/{physics-3d-C_ZJ6f_d.js → physics-3d-QBrfIT2Y.js} +3 -3
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-en63AEZO.js → register-C6ZBFRjd.js} +63 -60
- package/dist/{register-p48lHE2o.js → register-Ch70uByv.js} +1615 -1502
- package/dist/{replay-ePMz26jw.d.ts → replay-Dw6gMlYA.d.ts} +1 -1
- package/dist/{replay-t1pP0gQg.js → replay-s7I2GstT.js} +31 -11
- package/dist/sheet-grid-BT6N_Bjs.js +59 -0
- package/dist/{split-screen-BsdOHbzP.d.ts → split-screen-B0baBwxI.d.ts} +5 -1
- package/dist/{split-screen-CSb_uZ6W.js → split-screen-DLsUrleX.js} +14 -3
- package/dist/{sprite-animation-7qvUxF6Z.js → sprite-animation-C0wXLBZJ.js} +8 -4
- package/dist/{src-BVOVHRL0.js → src-CGjmPw65.js} +55 -2
- package/dist/{teardown-C7uVSJvx.js → teardown-Cs113S9F.js} +74 -2
- package/dist/{test-Ca5eqELC.js → test-it1VekWs.js} +28 -19
- package/dist/test.d.ts +4 -4
- package/dist/test.js +2 -2
- package/dist/{touch-BnMyy9tr.js → touch-DESwnpOc.js} +173 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +3 -3
- package/editor/assets/{agent8-Cz4oHPtm.js → agent8-CGT7r3Mb.js} +1 -1
- package/editor/assets/{debug-r-Fi9nZh.js → debug-BxWSIHG3.js} +1 -1
- package/editor/assets/{index-BfqmvXYD.js → index-CV1m-aX5.js} +62 -62
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/skills/incanto-assets.md +5 -1
- package/skills/incanto-audio.md +7 -1
- package/skills/incanto-building-3d-games.md +18 -5
- package/skills/incanto-gameplay-behaviors.md +21 -1
- package/skills/incanto-hud.md +6 -0
- package/skills/incanto-performance.md +5 -0
- package/skills/incanto-physics-and-input.md +14 -0
- package/skills/incanto-save-slots.md +2 -1
- package/skills/incanto-verifying-your-game.md +4 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/package.json +1 -1
- package/dist/log-report-CPFm4OXf.js +0 -173
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-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-CV1m-aX5.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="./assets/GameServer-C56iOUgF.js">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
package/skills/incanto-assets.md
CHANGED
|
@@ -182,7 +182,11 @@ frame 40. Those frames draw whatever is at the wrong end of the sheet.
|
|
|
182
182
|
```
|
|
183
183
|
|
|
184
184
|
The suggested sizes are the ones that actually divide your image, so the fix is
|
|
185
|
-
usually in the message.
|
|
185
|
+
usually in the message. **All three nodes that cut a grid out of an image check
|
|
186
|
+
it** — `AnimatedSprite2D`, `AnimatedSprite3D` and `TileMap2D` — because they do
|
|
187
|
+
the same floor-division and it fails the same silent way. An
|
|
188
|
+
`AnimatedSprite3D` with a sheet and no `frameWidth`/`frameHeight` also says so
|
|
189
|
+
rather than drawing a mob-shaped hole. `bunx incanto-assets info <name>` prints the real frame
|
|
186
190
|
size for a built-in, and the editor's 📚 picker fills it in from the catalog's
|
|
187
191
|
metadata — or leaves it BLANK when the catalog does not carry it, because an
|
|
188
192
|
invented frame size is exactly this bug.
|
package/skills/incanto-audio.md
CHANGED
|
@@ -350,6 +350,11 @@ background loop authored in scene JSON. Wire `crossfadeTo` from gameplay:
|
|
|
350
350
|
this.engine.music.crossfadeTo('/audio/boss.mp3', 3);
|
|
351
351
|
```
|
|
352
352
|
|
|
353
|
+
**A track that will never play says so.** A 404 or an undecodable file lands in
|
|
354
|
+
the engine log (`[incanto] music '/audio/boss.mp3' will not play — could not
|
|
355
|
+
load.`) instead of being indistinguishable from a game with no music. The
|
|
356
|
+
autoplay gate is still just the gate — a track waiting for a tap is not an error.
|
|
357
|
+
|
|
353
358
|
> Large music files are NOT bundled — reference them by URL (see §4).
|
|
354
359
|
|
|
355
360
|
**Headless the state machine still runs and `current` still updates** — nothing
|
|
@@ -448,7 +453,8 @@ bricking the game.
|
|
|
448
453
|
### Quality tiers
|
|
449
454
|
|
|
450
455
|
```ts
|
|
451
|
-
import { qualityEnvironment, readDeviceHints, suggestQuality
|
|
456
|
+
import { qualityEnvironment, readDeviceHints, suggestQuality } from 'incanto';
|
|
457
|
+
import { setEnvironment3D } from 'incanto/3d'; // it applies a 3D environment
|
|
452
458
|
|
|
453
459
|
const tier = engine.settings.get('quality') ?? suggestQuality(readDeviceHints());
|
|
454
460
|
setEnvironment3D(engine, qualityEnvironment(tier));
|
|
@@ -203,11 +203,24 @@ A nameplate, a sign, a damage number:
|
|
|
203
203
|
- It IS a `Sprite3D`, so `anchor`, `tint`, `opacity`, `alphaTest`, `renderOrder`
|
|
204
204
|
and `orderGroup` all work exactly as they do there.
|
|
205
205
|
|
|
206
|
-
A **damage number** is this node plus
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
A **damage number** is this node plus the `FloatAway` behavior, and no
|
|
207
|
+
TypeScript of your own:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{ "name": "Hit", "type": "Label3D",
|
|
211
|
+
"props": { "text": "12", "height": 0.35, "color": "#ff5a5a" },
|
|
212
|
+
"script": { "name": "FloatAway", "props": { "rise": 1.2, "seconds": 0.7, "drift": 0.3 } } }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
It rises with an ease-out (constant speed reads as a lift, not a hit), holds at
|
|
216
|
+
full opacity for `hold` of its life so the number is readable, fades, and frees
|
|
217
|
+
itself — so a hundred numbers a fight leave nothing in the tree. `drift` keeps
|
|
218
|
+
ten at once from stacking into one. Spawning is `duplicateNode` + set `text` and
|
|
219
|
+
`position`.
|
|
220
|
+
|
|
221
|
+
Re-rasterising is handled: the texture is rebuilt only when the text (or
|
|
222
|
+
size/colour/font) changes, and the old one is disposed, so a number per hit does
|
|
223
|
+
not leak a texture per hit.
|
|
211
224
|
|
|
212
225
|
| Prop | Default | Notes |
|
|
213
226
|
|---|---|---|
|
|
@@ -20,7 +20,8 @@ with `gameplay: false`, or register a subset yourself:
|
|
|
20
20
|
import { registerGameplayBehaviors } from 'incanto/gameplay';
|
|
21
21
|
registerGameplayBehaviors(); // all of them (default in createGame)
|
|
22
22
|
|
|
23
|
-
import {
|
|
23
|
+
import { registerBehavior } from 'incanto';
|
|
24
|
+
import { Health, Pickup } from 'incanto/gameplay';
|
|
24
25
|
registerBehavior('Health', Health); // or pick just what you use
|
|
25
26
|
```
|
|
26
27
|
|
|
@@ -828,6 +829,25 @@ import { CameraShake, Cooldown, hitStop, screenFlash } from 'incanto/gameplay';
|
|
|
828
829
|
tested. (A stepped frame still HAPPENS while paused: `update` runs with
|
|
829
830
|
`dt = 0`, which is how the pause menu's own key polling keeps working.)
|
|
830
831
|
|
|
832
|
+
### `FloatAway` — rise, fade, gone
|
|
833
|
+
|
|
834
|
+
The second half of a damage number, a pickup puff, a `+50`:
|
|
835
|
+
|
|
836
|
+
```json
|
|
837
|
+
{ "name": "Hit", "type": "Label3D",
|
|
838
|
+
"props": { "text": "12", "height": 0.35 },
|
|
839
|
+
"script": { "name": "FloatAway", "props": { "rise": 1.2, "seconds": 0.7, "drift": 0.3 } } }
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
`rise` (world units, negative sinks) · `seconds` · `hold` (fraction of the life
|
|
843
|
+
at full opacity before the fade) · `drift` (sideways, so ten at once do not
|
|
844
|
+
stack) · `freeOnEnd` (default true).
|
|
845
|
+
|
|
846
|
+
Works on anything with `position` and `opacity` — a `Label3D`, a `Sprite3D`
|
|
847
|
+
puff, a 2D `Label`. It reads the start position ONCE, so the ease does not
|
|
848
|
+
compound, and it frees the node a frame after it is invisible rather than on the
|
|
849
|
+
frame it gets there (which reads as a flicker).
|
|
850
|
+
|
|
831
851
|
## Did the effect fire?
|
|
832
852
|
|
|
833
853
|
Sound has `engine.audio`; vision has **`engine.effects`**, and the question is
|
package/skills/incanto-hud.md
CHANGED
|
@@ -167,6 +167,12 @@ widget use `UiButton`'s `pressed`.
|
|
|
167
167
|
Setting `.value` from a behavior updates the control and does **NOT** re-emit
|
|
168
168
|
`changed` — restoring a saved setting must not fire the handler that saved it.
|
|
169
169
|
|
|
170
|
+
**Every prop is live.** `color`, `size`, `width`, `background`, `label` and
|
|
171
|
+
`anchor` are re-read each frame, so flashing the score red, growing a health bar
|
|
172
|
+
or moving a widget to another corner all work from a behavior — and from the
|
|
173
|
+
editor's inspector, which is the same code path. They used to be baked in when
|
|
174
|
+
the widget was built and could never be changed afterwards.
|
|
175
|
+
|
|
170
176
|
## Playable on a controller (focus navigation)
|
|
171
177
|
|
|
172
178
|
A menu you can only click is not playable on a gamepad, and "add controller
|
|
@@ -92,6 +92,11 @@ that built a mesh), dispose it in the behavior's `onExitTree`.
|
|
|
92
92
|
| `renderScale` | `0.25`–`2` | image softness only | **yes** |
|
|
93
93
|
| antialiasing | follows the tier | jagged edges at `low` | **no — boot only** |
|
|
94
94
|
|
|
95
|
+
`maxFps` and `renderScale` apply in **2D as well as 3D** now — they persisted for
|
|
96
|
+
every game and were applied by the 3D boot only, so a 2D options menu saved a
|
|
97
|
+
number and changed nothing. `quality` remains a 3D tier (there are no shadows,
|
|
98
|
+
bloom or clouds to drop in 2D).
|
|
99
|
+
|
|
95
100
|
### quality
|
|
96
101
|
|
|
97
102
|
**Water follows the tier too, and it is the biggest single saving.**
|
|
@@ -192,6 +192,20 @@ engine.input.pressAction('jump'); // one justPressed frame, held until
|
|
|
192
192
|
engine.input.releaseAction('jump'); // …released (one justReleased frame)
|
|
193
193
|
engine.input.setActionVector('move', 1, 0); // analog vector2; (0, 0) clears
|
|
194
194
|
```
|
|
195
|
+
|
|
196
|
+
**An action name you typed that does not exist says so.** A controller tolerates
|
|
197
|
+
a missing action — a game with no sprint key must not crash — but a value you
|
|
198
|
+
NAMED and never declared is a typo, and it used to be indistinguishable from the
|
|
199
|
+
tolerated case:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
[incanto] /Game/Player/Ctl: Unknown input action 'movement'.
|
|
203
|
+
Declared actions: [move, jump]. The character will not move.
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Once per node and action, and only when the prop differs from its default —
|
|
207
|
+
leaving `sprintAction` alone in a game with no sprint key is the ordinary case
|
|
208
|
+
and stays silent.
|
|
195
209
|
Injected state combines with key state (vectors clamped to unit length).
|
|
196
210
|
|
|
197
211
|
## Patterns
|
|
@@ -146,7 +146,8 @@ The engine does not route scenes — deliberately, because only your game knows
|
|
|
146
146
|
what a key means. What it does is record the key, so the routing is a lookup:
|
|
147
147
|
|
|
148
148
|
```ts
|
|
149
|
-
import {
|
|
149
|
+
import { loadScene } from 'incanto';
|
|
150
|
+
import { createGame2D } from 'incanto/2d';
|
|
150
151
|
import level1 from './level1.scene.json';
|
|
151
152
|
import level2 from './level2.scene.json';
|
|
152
153
|
|
|
@@ -399,6 +399,10 @@ shouting the same thing every frame has one problem, not six hundred.
|
|
|
399
399
|
|
|
400
400
|
### `bunx incanto-frame` — what is actually on screen
|
|
401
401
|
|
|
402
|
+
Works for **2D and 3D** games alike (as does `incanto-logs`, and `game.frame()`).
|
|
403
|
+
Both used to be answered by the 3D boot only, so a 2D preview that was open and
|
|
404
|
+
drawing reported "no page answered".
|
|
405
|
+
|
|
402
406
|
The rung above framing. `framing` PREDICTS what the camera should see from the
|
|
403
407
|
scene graph; this measures what the GPU drew — the only one that can catch a
|
|
404
408
|
black screen, a material that failed, or a shader drawing where it should not.
|
|
@@ -1,173 +0,0 @@
|
|
|
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
|
-
export { logText as n, parseDrive as r, logReport as t };
|