incanto 0.58.0 → 0.60.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-check.mjs +45 -4
- package/bin/incanto-multiplay.mjs +170 -0
- package/bin/incanto-verify.mjs +68 -4
- package/bin/incanto.mjs +1 -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-D10bU5_J.js → create-game-BCm38FJV.js} +6 -6
- package/dist/{create-game-BZwWJIns.js → create-game-ClnIb_M5.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-8cjF3t6w.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 +53 -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-EqA-jddf.js → physics-2d-DqdVp1bt.js} +12 -2
- package/dist/{physics-3d-Dnz4fsXX.js → physics-3d-BP0DZb_1.js} +13 -3
- package/dist/react.d.ts +1 -1
- package/dist/react.js +2 -2
- package/dist/{register-C6ZBFRjd.js → register-BSu2dWGC.js} +57 -27
- package/dist/{register-Ch70uByv.js → register-Da3hXh2H.js} +137 -46
- package/dist/{registry-C7u42TID.js → registry-WWcQcfMr.js} +1 -1
- package/dist/{replay-Dw6gMlYA.d.ts → replay-BCMK_VRP.d.ts} +15 -2
- package/dist/{replay-s7I2GstT.js → replay-BlNuIDdg.js} +41 -13
- package/dist/{split-screen-B0baBwxI.d.ts → split-screen-BQ3tAsf-.d.ts} +38 -1
- package/dist/{split-screen-DLsUrleX.js → split-screen-CL5Yvxse.js} +56 -13
- package/dist/{sprite-animation-C0wXLBZJ.js → sprite-animation-CY-mrr1L.js} +1 -1
- package/dist/{src-C1J09Op6.js → src-DFpXBMJN.js} +1 -1
- package/dist/{teardown-Cs113S9F.js → teardown-CTTwhWSe.js} +1 -1
- package/dist/{test-ZBga8kQ9.js → test-BeZ95pqw.js} +264 -25
- package/dist/test.d.ts +119 -5
- package/dist/test.js +3 -3
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-BQQjE9UQ.js → agent8-DSJries_.js} +1 -1
- package/editor/assets/{debug-C9UCsXBS.js → debug-D15Wi5TO.js} +1 -1
- package/editor/assets/{index-CBgfM3WD.js → index-BjC88k97.js} +91 -91
- package/editor/index.html +1 -1
- package/package.json +2 -1
- package/schemas/scene.schema.json +1174 -70
- package/skills/incanto-localization.md +64 -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-verifying-your-game.md +22 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/package.json +1 -1
package/bin/incanto-check.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* npx incanto-check src/game.scene.json → specific files (or directories)
|
|
9
9
|
* npx incanto-check --json → machine-readable report
|
|
10
10
|
* npx incanto-check --strict-behaviors → unregistered script names fail too
|
|
11
|
+
* npx incanto-check --strict-warnings → a warning is a failure (CI, agents)
|
|
11
12
|
*
|
|
12
13
|
* Unregistered behaviors are STUBBED by default: structure-only validation
|
|
13
14
|
* that needs no TypeScript. Sub-scene `instance` paths resolve relative to
|
|
@@ -25,6 +26,7 @@ function parseArgs(argv) {
|
|
|
25
26
|
for (const a of argv) {
|
|
26
27
|
if (a === '--json') args.json = true;
|
|
27
28
|
else if (a === '--strict-behaviors') args.strict = true;
|
|
29
|
+
else if (a === '--strict-warnings') args.strictWarnings = true;
|
|
28
30
|
else if (a === '--help' || a === '-h') args.help = true;
|
|
29
31
|
else if (a.startsWith('--')) {
|
|
30
32
|
console.error(`unknown flag: ${a}`);
|
|
@@ -36,7 +38,7 @@ function parseArgs(argv) {
|
|
|
36
38
|
|
|
37
39
|
const args = parseArgs(process.argv.slice(2));
|
|
38
40
|
if (args.help || args.invalid) {
|
|
39
|
-
console.log(`Usage: npx incanto
|
|
41
|
+
console.log(`Usage: npx incanto check [files|dirs...] [--json] [--strict-behaviors] [--strict-warnings]
|
|
40
42
|
|
|
41
43
|
Validates *.scene.json files headlessly with the installed engine.
|
|
42
44
|
With no arguments, scans the current directory recursively
|
|
@@ -155,6 +157,28 @@ if (files.length === 0) {
|
|
|
155
157
|
|
|
156
158
|
const { auditScene, validateScene } = await import(pathToFileURL(join(DIST, 'test.js')).href);
|
|
157
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Every `strings` table in the corpus, unioned.
|
|
162
|
+
*
|
|
163
|
+
* Localization tables MERGE across scenes at runtime — a game's shared UI
|
|
164
|
+
* strings live in the scene it boots from — so auditing one file in isolation
|
|
165
|
+
* reported a key declared in the boot scene as "nothing declares" it, and told
|
|
166
|
+
* its author to add a string they already have. Checking a project means
|
|
167
|
+
* knowing what the project declares.
|
|
168
|
+
*/
|
|
169
|
+
const declaredElsewhere = {};
|
|
170
|
+
for (const file of files) {
|
|
171
|
+
try {
|
|
172
|
+
const doc = JSON.parse(readFileSync(file, 'utf-8'));
|
|
173
|
+
for (const [locale, entries] of Object.entries(doc?.strings ?? {})) {
|
|
174
|
+
if (typeof entries !== 'object' || entries === null) continue;
|
|
175
|
+
declaredElsewhere[locale] = { ...(declaredElsewhere[locale] ?? {}), ...entries };
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
/* a file that will not parse is reported below, on its own line */
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
158
182
|
const results = files.map((file) => {
|
|
159
183
|
let json;
|
|
160
184
|
try {
|
|
@@ -166,7 +190,13 @@ const results = files.map((file) => {
|
|
|
166
190
|
strictBehaviors: args.strict,
|
|
167
191
|
resolveScene: (p) => JSON.parse(readFileSync(resolve(dirname(file), p), 'utf-8')),
|
|
168
192
|
});
|
|
169
|
-
if (res.ok)
|
|
193
|
+
if (res.ok) {
|
|
194
|
+
return {
|
|
195
|
+
file,
|
|
196
|
+
ok: true,
|
|
197
|
+
warnings: [...auditScene(json, { declaredElsewhere }), ...missingArt(file, json)],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
170
200
|
return {
|
|
171
201
|
file,
|
|
172
202
|
ok: false,
|
|
@@ -177,6 +207,7 @@ const results = files.map((file) => {
|
|
|
177
207
|
});
|
|
178
208
|
|
|
179
209
|
const failed = results.filter((r) => !r.ok);
|
|
210
|
+
const warned = results.reduce((n, r) => n + (r.warnings?.length ?? 0), 0);
|
|
180
211
|
if (args.json) {
|
|
181
212
|
console.log(JSON.stringify({ ok: failed.length === 0, results }, null, 2));
|
|
182
213
|
} else {
|
|
@@ -186,10 +217,20 @@ if (args.json) {
|
|
|
186
217
|
for (const w of r.warnings ?? []) console.log(` warn: ${w}`);
|
|
187
218
|
} else console.log(`FAIL ${r.file}\n [${r.code}] ${r.message}`);
|
|
188
219
|
}
|
|
189
|
-
|
|
220
|
+
// Say the warning count on the summary line. A warning printed under an `ok`
|
|
221
|
+
// and a summary that never mentions it is a finding an agent scrolls past.
|
|
222
|
+
console.log(
|
|
223
|
+
`\n${results.length - failed.length}/${results.length} scene(s) valid` +
|
|
224
|
+
(warned > 0
|
|
225
|
+
? `, ${warned} warning(s)${args.strictWarnings ? '' : ' — `--strict-warnings` to fail on them'}`
|
|
226
|
+
: ''),
|
|
227
|
+
);
|
|
190
228
|
}
|
|
191
229
|
// `exitCode`, never `process.exit()`: stdout to a PIPE is written
|
|
192
230
|
// asynchronously, and exiting discards whatever has not flushed. A --json
|
|
193
231
|
// report read by another program came back truncated — silently, and only
|
|
194
232
|
// when piped, which is the only way a program reads it.
|
|
195
|
-
|
|
233
|
+
// A warning is a finding, and a checker read by a CI step or an agent is read
|
|
234
|
+
// through its EXIT CODE. Opt-in, because turning it on by default would fail
|
|
235
|
+
// every project that has been living with one.
|
|
236
|
+
process.exitCode = failed.length === 0 && !(args.strictWarnings && warned > 0) ? 0 : 1;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* incanto-multiplay — run a real match headlessly and check the clients AGREE.
|
|
4
|
+
*
|
|
5
|
+
* bunx incanto multiplay src/game.scene.json
|
|
6
|
+
* bunx incanto multiplay src/game.scene.json --players 3 --seconds 8 --json
|
|
7
|
+
*
|
|
8
|
+
* A desync is the purest silent failure this engine can have: both clients
|
|
9
|
+
* run, neither errors, and they are playing different games. Nothing checked
|
|
10
|
+
* for one — the harness measured room MEMBERSHIP, so "saw p2" meant "p2 is in
|
|
11
|
+
* the room", not "p2's data arrived", and a game whose replication was
|
|
12
|
+
* completely dead reported exactly what a working one did.
|
|
13
|
+
*
|
|
14
|
+
* WHAT THIS PROVES, AND WHAT IT DOES NOT. Every client runs against one
|
|
15
|
+
* in-memory server: no latency, no loss, no reordering. So this measures the
|
|
16
|
+
* APPLY path and the protocol shape, and says nothing about a live wire — a
|
|
17
|
+
* reconnect that never re-joins, a throttle that discards a payload, a batch
|
|
18
|
+
* dropped on a closed socket.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
21
|
+
import { dirname, join, resolve } from 'node:path';
|
|
22
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
|
+
|
|
24
|
+
const PKG = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
|
+
|
|
26
|
+
const argv = process.argv.slice(2);
|
|
27
|
+
const flag = (name, fallback) => {
|
|
28
|
+
const i = argv.indexOf(name);
|
|
29
|
+
return i >= 0 ? argv[i + 1] : fallback;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
33
|
+
console.log(`Usage: incanto multiplay [scene.json] [options]
|
|
34
|
+
|
|
35
|
+
--players N clients in the match (default 2)
|
|
36
|
+
--seconds N simulated seconds (default 5)
|
|
37
|
+
--seed N make the match reproducible (default 1)
|
|
38
|
+
--behaviors FILE your Behavior subclasses (.ts works on bun / node >= 23.6)
|
|
39
|
+
--scenes a=b.json a scene a NetworkSpawner names, repeatable
|
|
40
|
+
--no-late-join skip the late-joining client
|
|
41
|
+
--json machine-readable report
|
|
42
|
+
|
|
43
|
+
Checks that every client holds the SAME value for every replicated key, plus
|
|
44
|
+
one client that joins AFTER the match settles — the only observer that reads
|
|
45
|
+
the authoritative snapshot fresh, and so the only one that can see a value
|
|
46
|
+
erased from it.`);
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const named = argv.find((a) => !a.startsWith('-') && a.endsWith('.json'));
|
|
51
|
+
if (!named) {
|
|
52
|
+
console.error('incanto-multiplay: name the scene, e.g. src/game.scene.json');
|
|
53
|
+
process.exit(2);
|
|
54
|
+
}
|
|
55
|
+
const scenePath = resolve(named);
|
|
56
|
+
if (!existsSync(scenePath)) {
|
|
57
|
+
console.error(`no such scene: ${scenePath}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { playMultiplayer, multiplayText, registerAllNodes } = await import(
|
|
62
|
+
pathToFileURL(join(PKG, 'dist', 'test.js')).href
|
|
63
|
+
);
|
|
64
|
+
registerAllNodes();
|
|
65
|
+
|
|
66
|
+
const sceneJson = JSON.parse(readFileSync(scenePath, 'utf-8'));
|
|
67
|
+
const sceneDir = dirname(scenePath);
|
|
68
|
+
|
|
69
|
+
// Without the game's own behaviours a scene that names one throws
|
|
70
|
+
// `Unknown behavior` before a single frame runs — so load them when we are
|
|
71
|
+
// given them, and stub what is left. A stubbed behaviour still lets the
|
|
72
|
+
// SCENE's replication run, which is what this command measures.
|
|
73
|
+
const behaviorsFile = flag('--behaviors', null);
|
|
74
|
+
if (behaviorsFile) {
|
|
75
|
+
try {
|
|
76
|
+
const mod = await import(pathToFileURL(resolve(behaviorsFile)).href);
|
|
77
|
+
const { registerBehavior, Behavior } = await import(
|
|
78
|
+
pathToFileURL(join(PKG, 'dist', 'index.js')).href
|
|
79
|
+
);
|
|
80
|
+
for (const [name, value] of Object.entries(mod)) {
|
|
81
|
+
if (typeof value === 'function' && value.prototype instanceof Behavior) {
|
|
82
|
+
registerBehavior(name, value, { replace: true });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error(`could not load --behaviors '${behaviorsFile}' (${e?.message ?? e}).`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Scenes a `NetworkSpawner` names.
|
|
93
|
+
*
|
|
94
|
+
* `--scenes remote-player=src/remote.scene.json` is explicit; otherwise every
|
|
95
|
+
* spawner's `scene` prop is looked for as a sibling file, which is where the
|
|
96
|
+
* templates put them. A spawner whose scene cannot be found materialises
|
|
97
|
+
* nothing, and the agreement check reports that as `missing` rather than
|
|
98
|
+
* letting it read as "this game has no remote players".
|
|
99
|
+
*/
|
|
100
|
+
const scenes = {};
|
|
101
|
+
for (let i = 0; i < argv.length; i++) {
|
|
102
|
+
if (argv[i] !== '--scenes') continue;
|
|
103
|
+
const [key, file] = String(argv[i + 1] ?? '').split('=');
|
|
104
|
+
if (key && file) scenes[key] = JSON.parse(readFileSync(resolve(sceneDir, file), 'utf-8'));
|
|
105
|
+
}
|
|
106
|
+
const spawnerScenes = new Set();
|
|
107
|
+
const walk = (node) => {
|
|
108
|
+
if (node?.type === 'NetworkSpawner' && typeof node.props?.scene === 'string') {
|
|
109
|
+
spawnerScenes.add(node.props.scene);
|
|
110
|
+
}
|
|
111
|
+
for (const child of node?.children ?? []) walk(child);
|
|
112
|
+
};
|
|
113
|
+
walk(sceneJson.root);
|
|
114
|
+
for (const name of spawnerScenes) {
|
|
115
|
+
if (scenes[name]) continue;
|
|
116
|
+
for (const guess of [`${name}.scene.json`, `${name}.json`]) {
|
|
117
|
+
const at = resolve(sceneDir, guess);
|
|
118
|
+
if (existsSync(at)) {
|
|
119
|
+
scenes[name] = JSON.parse(readFileSync(at, 'utf-8'));
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let report;
|
|
126
|
+
try {
|
|
127
|
+
report = await playMultiplayer({
|
|
128
|
+
scene: sceneJson,
|
|
129
|
+
scenes,
|
|
130
|
+
players: Number(flag('--players', 2)) || 2,
|
|
131
|
+
seconds: Number(flag('--seconds', 5)) || 5,
|
|
132
|
+
seed: Number(flag('--seed', 1)) || 1,
|
|
133
|
+
lateJoin: !argv.includes('--no-late-join'),
|
|
134
|
+
load: {
|
|
135
|
+
stubMissingBehaviors: true,
|
|
136
|
+
resolveScene: (p) => JSON.parse(readFileSync(resolve(sceneDir, p), 'utf-8')),
|
|
137
|
+
},
|
|
138
|
+
// Move the owner, or every replicated key holds its authored value and two
|
|
139
|
+
// motionless clients agree about nothing in particular.
|
|
140
|
+
drive: (player, frame) => {
|
|
141
|
+
const root = player.engine.scene?.root;
|
|
142
|
+
const owner = root && findOwner(root);
|
|
143
|
+
if (!owner || !Array.isArray(owner.position)) return;
|
|
144
|
+
const spin = (player.account.charCodeAt(1) || 1) * 0.7;
|
|
145
|
+
owner.position = owner.position.map((v, i) =>
|
|
146
|
+
i === 1 && owner.position.length === 3 ? v : Math.sin(frame / 20 + spin + i) * 4,
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.error(`multiplay failed: ${e?.message ?? e}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The single `network.mode === 'owner'` node, the way the manager finds it. */
|
|
156
|
+
function findOwner(node) {
|
|
157
|
+
if (node.network?.mode === 'owner') return node;
|
|
158
|
+
for (const child of node.children ?? []) {
|
|
159
|
+
const found = findOwner(child);
|
|
160
|
+
if (found) return found;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (argv.includes('--json')) {
|
|
166
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
167
|
+
} else {
|
|
168
|
+
process.stdout.write(`${multiplayText(report)}\n`);
|
|
169
|
+
}
|
|
170
|
+
process.exit(report.ok ? 0 : 1);
|
package/bin/incanto-verify.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* what to arrange, and says so in the summary.
|
|
20
20
|
*/
|
|
21
21
|
import { spawnSync } from 'node:child_process';
|
|
22
|
-
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
22
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
23
23
|
import { dirname, join, relative } from 'node:path';
|
|
24
24
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
25
25
|
|
|
@@ -35,18 +35,21 @@ const flag = (name) => {
|
|
|
35
35
|
return i >= 0 ? argv[i + 1] : null;
|
|
36
36
|
};
|
|
37
37
|
if (argv.includes('--help') || argv.includes('-h')) {
|
|
38
|
-
console.log(`Usage: incanto
|
|
38
|
+
console.log(`Usage: incanto verify [scene.json] [--behaviors FILE] [--json]
|
|
39
39
|
|
|
40
40
|
Walks the whole ladder and tells you the ONE thing to do next:
|
|
41
41
|
|
|
42
42
|
loads the scene is legal and every asset resolves (incanto-check)
|
|
43
43
|
plays a seeded run can actually finish it (incanto-playtest)
|
|
44
|
+
feels the sounds and effects it declares actually fired (engine.audio/effects)
|
|
45
|
+
agrees two clients hold the same replicated values (incanto-multiplay)
|
|
44
46
|
draws the GPU drew something and the subject is in shot (incanto-frame)
|
|
45
47
|
says the running game logs no errors and lost no assets (incanto-logs)
|
|
46
48
|
|
|
47
49
|
Without a scene it looks for one *.scene.json under the current directory.
|
|
48
|
-
"draws" needs a dev server with the page open
|
|
49
|
-
|
|
50
|
+
"draws" needs a dev server with the page open, and "agrees" needs a scene with
|
|
51
|
+
a "multiplayer" header; without either they are reported as NOT MEASURED rather
|
|
52
|
+
than failed — a missing measurement is not a broken game.`);
|
|
50
53
|
process.exit(0);
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -274,6 +277,67 @@ if (rungs[0].status === 'pass') {
|
|
|
274
277
|
}
|
|
275
278
|
}
|
|
276
279
|
|
|
280
|
+
// ---- agrees --------------------------------------------------------------
|
|
281
|
+
// A desync is the purest silent failure this engine has: both clients run,
|
|
282
|
+
// neither errors, and they are playing different games. Skipped outright for a
|
|
283
|
+
// single-player game, because a rung that always passes is decoration.
|
|
284
|
+
{
|
|
285
|
+
const isMultiplayer = (() => {
|
|
286
|
+
try {
|
|
287
|
+
const doc = JSON.parse(readFileSync(scene, 'utf-8'));
|
|
288
|
+
return Boolean(doc?.multiplayer);
|
|
289
|
+
} catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
})();
|
|
293
|
+
if (!isMultiplayer) {
|
|
294
|
+
rungs.push({
|
|
295
|
+
name: 'agrees',
|
|
296
|
+
status: 'skipped',
|
|
297
|
+
summary: 'not run — this scene has no `multiplayer` header',
|
|
298
|
+
});
|
|
299
|
+
} else {
|
|
300
|
+
const args = [scene, '--json', '--seconds', '4'];
|
|
301
|
+
if (behaviors) args.push('--behaviors', behaviors);
|
|
302
|
+
const r = run('multiplay', args);
|
|
303
|
+
const out = safeJson(r.stdout);
|
|
304
|
+
if (!out) {
|
|
305
|
+
rungs.push({
|
|
306
|
+
name: 'agrees',
|
|
307
|
+
status: 'unmeasured',
|
|
308
|
+
summary: `the match could not run — ${firstLine(r.stderr) || `exit ${r.status}`}`,
|
|
309
|
+
fix: `see it in full: \`incanto multiplay ${scene}\``,
|
|
310
|
+
});
|
|
311
|
+
} else if (out.keysCompared === 0) {
|
|
312
|
+
// Nothing moved, so nothing was compared. Saying "pass" here is how a
|
|
313
|
+
// rung becomes decorative.
|
|
314
|
+
rungs.push({
|
|
315
|
+
name: 'agrees',
|
|
316
|
+
status: 'unmeasured',
|
|
317
|
+
summary: 'no owner node declared any sync keys, so agreement was not tested',
|
|
318
|
+
fix: 'give the player node `network: { mode: "owner", sync: ["position"] }`',
|
|
319
|
+
});
|
|
320
|
+
} else if (out.ok) {
|
|
321
|
+
rungs.push({
|
|
322
|
+
name: 'agrees',
|
|
323
|
+
status: 'pass',
|
|
324
|
+
summary:
|
|
325
|
+
`${out.keysCompared} replicated value(s) match across clients` +
|
|
326
|
+
`${out.lateJoinChecked ? ' and a late joiner' : ''}` +
|
|
327
|
+
' — over one in-memory server, not a live wire',
|
|
328
|
+
});
|
|
329
|
+
} else {
|
|
330
|
+
const first = out.disagreements?.[0];
|
|
331
|
+
rungs.push({
|
|
332
|
+
name: 'agrees',
|
|
333
|
+
status: 'fail',
|
|
334
|
+
summary: first ? first.summary : (out.problems?.[0] ?? 'the clients disagreed'),
|
|
335
|
+
fix: `every disagreement: \`incanto multiplay ${scene}\``,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
277
341
|
// ---- draws ---------------------------------------------------------------
|
|
278
342
|
{
|
|
279
343
|
const r = run('frame', ['--json']);
|
package/bin/incanto.mjs
CHANGED
|
@@ -35,6 +35,7 @@ const COMMANDS = {
|
|
|
35
35
|
check: ['incanto-check.mjs', 'validate scene JSON and the assets it names'],
|
|
36
36
|
verify: ['incanto-verify.mjs', 'the whole ladder: loads · plays · feels · draws · says'],
|
|
37
37
|
playtest: ['incanto-playtest.mjs', 'drive the game headlessly and report what happened'],
|
|
38
|
+
multiplay: ['incanto-multiplay.mjs', 'run a real match and check the clients agree'],
|
|
38
39
|
play: ['incanto-play.mjs', 'run a scene headlessly for a fixed span'],
|
|
39
40
|
feel: ['incanto-feel.mjs', 'measure jump height, run speed, facing'],
|
|
40
41
|
frame: ['incanto-frame.mjs', 'what the running page is drawing right now'],
|
package/dist/2d.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { At as Node, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-
|
|
1
|
+
import { At as Node, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-DWKTUzKI.js";
|
|
2
2
|
import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DAvWQeld.js";
|
|
3
3
|
import { i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
4
4
|
import { r as FrameStats } from "./frame-report-DCnHFmto.js";
|
|
5
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
5
|
+
import { t as LoadSceneOptions } from "./loader-TvkRFbyL.js";
|
|
6
6
|
import { n as AnimationEntry } from "./sprite-animation-CMr6f1K2.js";
|
|
7
7
|
import { r as ParticleView, t as ParticleSim } from "./particle-sim-C5OfBbmU.js";
|
|
8
8
|
import { Group, Mesh, Object3D, Scene, Texture } from "three";
|
|
@@ -107,6 +107,9 @@ declare class PhysicsBody2D extends Node2D {
|
|
|
107
107
|
private _collider;
|
|
108
108
|
/** @internal Physics resync flag — raised when `collider` is REPLACED. */
|
|
109
109
|
_colliderChanged: boolean;
|
|
110
|
+
private _enabled;
|
|
111
|
+
/** @internal Physics resync flag — raised when `enabled` is written. */
|
|
112
|
+
_enabledChanged: boolean;
|
|
110
113
|
/**
|
|
111
114
|
* Collider shape prop. Change it by REPLACING the object
|
|
112
115
|
* (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
|
|
@@ -114,6 +117,9 @@ declare class PhysicsBody2D extends Node2D {
|
|
|
114
117
|
*/
|
|
115
118
|
get collider(): JsonObject;
|
|
116
119
|
set collider(value: JsonObject);
|
|
120
|
+
/** See the `enabled` prop. Writing it arms or disarms the existing collider. */
|
|
121
|
+
get enabled(): boolean;
|
|
122
|
+
set enabled(value: boolean);
|
|
117
123
|
/** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
|
|
118
124
|
static validateJson(node: Node): void;
|
|
119
125
|
/** @internal Set by Physics2D when the body is created. */
|
package/dist/2d.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
|
-
import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-
|
|
3
|
-
import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-
|
|
4
|
-
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-
|
|
2
|
+
import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-ClnIb_M5.js";
|
|
3
|
+
import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-BSu2dWGC.js";
|
|
4
|
+
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-DqdVp1bt.js";
|
|
5
5
|
//#region src/2d/library-sprite.ts
|
|
6
6
|
/**
|
|
7
7
|
* What a `CharacterController2D`/`3D` will ask a skin to play, and the clip in
|
package/dist/3d.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { At as Node, D as QualityCaps, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-
|
|
1
|
+
import { At as Node, D as QualityCaps, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, n as BehaviorCtor, w as GameStats } from "./behavior-DWKTUzKI.js";
|
|
2
2
|
import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DAvWQeld.js";
|
|
3
3
|
import { i as SceneJson$1, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
4
4
|
import { a as GridCell, c as diffText, d as frameText, i as FrameStatsOptions, l as frameSignature, n as FrameSignature, o as SIGNATURE_GRID, r as FrameStats, s as diffSignatures, t as FrameDiff, u as frameStats } from "./frame-report-DCnHFmto.js";
|
|
5
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
5
|
+
import { t as LoadSceneOptions } from "./loader-TvkRFbyL.js";
|
|
6
6
|
import { r as ParticleView, t as ParticleSim } from "./particle-sim-C5OfBbmU.js";
|
|
7
|
-
import { n as PathGrid, s as SpatialPose } from "./pathfinding-
|
|
7
|
+
import { n as PathGrid, s as SpatialPose } from "./pathfinding-BqWBb0kh.js";
|
|
8
8
|
import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
|
|
9
9
|
import { VRM } from "@pixiv/three-vrm";
|
|
10
10
|
import { Sky } from "three/examples/jsm/objects/Sky.js";
|
|
@@ -333,6 +333,9 @@ declare class PhysicsBody3D extends Node3D {
|
|
|
333
333
|
private _collider;
|
|
334
334
|
/** @internal Physics resync flag — raised when `collider` is REPLACED. */
|
|
335
335
|
_colliderChanged: boolean;
|
|
336
|
+
private _enabled;
|
|
337
|
+
/** @internal Physics resync flag — raised when `enabled` is written. */
|
|
338
|
+
_enabledChanged: boolean;
|
|
336
339
|
/**
|
|
337
340
|
* Collider shape prop. Change it by REPLACING the object
|
|
338
341
|
* (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
|
|
@@ -340,6 +343,9 @@ declare class PhysicsBody3D extends Node3D {
|
|
|
340
343
|
*/
|
|
341
344
|
get collider(): JsonObject;
|
|
342
345
|
set collider(value: JsonObject);
|
|
346
|
+
/** See the `enabled` prop. Writing it arms or disarms the existing collider. */
|
|
347
|
+
get enabled(): boolean;
|
|
348
|
+
set enabled(value: boolean);
|
|
343
349
|
/** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
|
|
344
350
|
static validateJson(node: Node): void;
|
|
345
351
|
/** @internal Set by Physics3D when the body is created. */
|
package/dist/3d.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { a as frameSignature, n as diffSignatures, o as frameStats, r as diffText, s as frameText, t as SIGNATURE_GRID } from "./frame-report-BSMny7oe.js";
|
|
2
|
-
import { B as StaticBody3D, F as WaterCutout3D, I as Area3D, L as CharacterBody3D, N as Water3D, P as WATER_CUTOUT_MAX, R as PhysicsBody3D, V as Node3D, W as WATER_MAX_RIPPLES, z as RigidBody3D } from "./gameplay-
|
|
3
|
-
import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-
|
|
4
|
-
import { a as Environment3D, c as parseEnvironment3D, d as AssetStore3D, i as syncTree, l as sunDirectionFromElevationAzimuth, o as setEnvironment3D, r as Renderer3D, s as horizonColorFromSky, t as createGame3D, u as sunDirectionFromSky } from "./create-game-
|
|
2
|
+
import { B as StaticBody3D, F as WaterCutout3D, I as Area3D, L as CharacterBody3D, N as Water3D, P as WATER_CUTOUT_MAX, R as PhysicsBody3D, V as Node3D, W as WATER_MAX_RIPPLES, z as RigidBody3D } from "./gameplay-BBEjPFsR.js";
|
|
3
|
+
import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-8cjF3t6w.js";
|
|
4
|
+
import { a as Environment3D, c as parseEnvironment3D, d as AssetStore3D, i as syncTree, l as sunDirectionFromElevationAzimuth, o as setEnvironment3D, r as Renderer3D, s as horizonColorFromSky, t as createGame3D, u as sunDirectionFromSky } from "./create-game-BCm38FJV.js";
|
|
5
5
|
import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
|
|
6
|
-
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-
|
|
6
|
+
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-BP0DZb_1.js";
|
|
7
7
|
//#region src/3d/model-verdict.ts
|
|
8
8
|
/** Mixamo exports every bone as `mixamorigX`; the retargeter binds by that name. */
|
|
9
9
|
const MIXAMO = /^mixamorig[:_]?/i;
|
|
@@ -1033,6 +1033,16 @@ declare class Localization {
|
|
|
1033
1033
|
* `startsWith` per widget per frame and nothing else.
|
|
1034
1034
|
*/
|
|
1035
1035
|
resolve(value: string, params?: Record<string, JsonValue>): string;
|
|
1036
|
+
/**
|
|
1037
|
+
* Whether the ACTIVE locale or the base declares this key.
|
|
1038
|
+
*
|
|
1039
|
+
* This is the runtime question, and it is not the same as `has`: a key only
|
|
1040
|
+
* `ko` declares is "declared", and an English player still gets the raw key
|
|
1041
|
+
* back from `t()`, because the fallback chain is current → base → the key.
|
|
1042
|
+
* Widgets that ask before translating asked the wrong one and painted
|
|
1043
|
+
* `settings.quality.low` into an English menu.
|
|
1044
|
+
*/
|
|
1045
|
+
declares(key: string): boolean;
|
|
1036
1046
|
/** Whether any locale declares this key — for the audit, not for the runtime. */
|
|
1037
1047
|
has(key: string): boolean;
|
|
1038
1048
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { j as registerBehavior, n as loadScene, w as diagnose } from "./loader-D8n7TU8W.js";
|
|
3
|
+
import { S as qualityRendering, _ as Engine, b as qualityCaps, g as AudioPlayer } from "./register-Da3hXh2H.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
5
|
import { i as resolveRendering, n as attachTouchControls } from "./touch-DESwnpOc.js";
|
|
6
|
-
import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-
|
|
6
|
+
import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, o as poseFromRenderer, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-CTTwhWSe.js";
|
|
7
7
|
import { o as frameStats } from "./frame-report-BSMny7oe.js";
|
|
8
|
-
import { R as PhysicsBody3D, U as createCausticsQuad, V as Node3D, n as registerGameplayBehaviors } from "./gameplay-
|
|
8
|
+
import { R as PhysicsBody3D, U as createCausticsQuad, V as Node3D, n as registerGameplayBehaviors } from "./gameplay-BBEjPFsR.js";
|
|
9
9
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
10
|
-
import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-
|
|
11
|
-
import { n as enablePhysics3D } from "./physics-3d-
|
|
10
|
+
import { E as Camera3D, U as TextureCache3D, g as ModelInstance3D, n as registerNodes3D, t as resolveEnvironmentHdri, v as DirectionalLight3D } from "./environment-presets-8cjF3t6w.js";
|
|
11
|
+
import { n as enablePhysics3D } from "./physics-3d-BP0DZb_1.js";
|
|
12
12
|
import { ACESFilmicToneMapping, AmbientLight, Box3, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, HalfFloatType, LineBasicMaterial, LineSegments, Matrix4, Mesh, PCFShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, Quaternion, Raycaster, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
|
|
13
13
|
import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
|
|
14
14
|
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import {
|
|
3
|
-
import { _ as Engine, g as AudioPlayer } from "./register-
|
|
2
|
+
import { c as resolveViewport, j as registerBehavior, n as loadScene, s as computeViewport, w as diagnose } from "./loader-D8n7TU8W.js";
|
|
3
|
+
import { _ as Engine, g as AudioPlayer } from "./register-Da3hXh2H.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
5
|
import { i as resolveRendering, n as attachTouchControls } from "./touch-DESwnpOc.js";
|
|
6
|
-
import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-
|
|
6
|
+
import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-CTTwhWSe.js";
|
|
7
7
|
import { o as frameStats } from "./frame-report-BSMny7oe.js";
|
|
8
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
9
|
-
import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-
|
|
8
|
+
import { n as registerGameplayBehaviors } from "./gameplay-BBEjPFsR.js";
|
|
9
|
+
import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-BSu2dWGC.js";
|
|
10
10
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
11
|
-
import { n as enablePhysics2D } from "./physics-2d-
|
|
11
|
+
import { n as enablePhysics2D } from "./physics-2d-DqdVp1bt.js";
|
|
12
12
|
import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
|
|
13
13
|
//#region src/2d/assets.ts
|
|
14
14
|
/**
|
package/dist/debug.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Pt as LogLevel, T as RendererStats, b as Engine } from "./behavior-
|
|
1
|
+
import { Pt as LogLevel, T as RendererStats, b as Engine } from "./behavior-DWKTUzKI.js";
|
|
2
2
|
|
|
3
3
|
//#region src/debug/panel.d.ts
|
|
4
4
|
/** Minimal document surface the overlay needs (injectable for tests). */
|
package/dist/debug.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as jsonClone } from "./json-
|
|
2
|
-
import { s as mergeStaticProps } from "./registry-
|
|
1
|
+
import { t as jsonClone } from "./json-CwwhxQgb.js";
|
|
2
|
+
import { s as mergeStaticProps } from "./registry-WWcQcfMr.js";
|
|
3
3
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
4
4
|
//#region src/debug/capture.ts
|
|
5
5
|
/**
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as registerCoreNodes } from "./register-
|
|
1
|
+
import { w as diagnose } from "./loader-D8n7TU8W.js";
|
|
2
|
+
import { I as translateOn, t as registerCoreNodes } from "./register-Da3hXh2H.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-
|
|
5
|
+
import { i as getNodeSchema, l as registerNode } from "./registry-WWcQcfMr.js";
|
|
6
6
|
import { t as createNoise2D } from "./noise-CGUMx44x.js";
|
|
7
|
-
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-
|
|
8
|
-
import { B as StaticBody3D, F as WaterCutout3D, G as colliderFootDrop, I as Area3D, L as CharacterBody3D, N as Water3D, R as PhysicsBody3D, V as Node3D, w as tolerateUnknownAction, z as RigidBody3D } from "./gameplay-
|
|
7
|
+
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";
|
|
8
|
+
import { B as StaticBody3D, F as WaterCutout3D, G as colliderFootDrop, I as Area3D, L as CharacterBody3D, N as Water3D, R as PhysicsBody3D, V as Node3D, w as tolerateUnknownAction, z as RigidBody3D } from "./gameplay-BBEjPFsR.js";
|
|
9
9
|
import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
|
|
10
10
|
import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
|
|
11
11
|
import { AdditiveBlending, AnimationClip, AnimationMixer, Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, CapsuleGeometry, ClampToEdgeWrapping, Color, ConeGeometry, CylinderGeometry, DataTexture, DirectionalLight, DoubleSide, DynamicDrawUsage, Euler, Group, IcosahedronGeometry, ImageBitmapLoader, InstancedBufferAttribute, InstancedMesh, LinearFilter, LinearMipmapLinearFilter, LoopOnce, LoopRepeat, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshPhysicalMaterial, MeshStandardMaterial, NearestFilter, NoBlending, NoColorSpace, NormalBlending, PerspectiveCamera, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, QuaternionKeyframeTrack, RGBADepthPacking, RGBAFormat, RepeatWrapping, SRGBColorSpace, ShaderChunk, ShaderMaterial, SphereGeometry, Texture, TextureLoader, UniformsLib, UniformsUtils, Vector3, Vector4, VectorKeyframeTrack } from "three";
|
|
@@ -5837,8 +5837,9 @@ var Label3D = class extends Sprite3D {
|
|
|
5837
5837
|
canvasTexture = null;
|
|
5838
5838
|
lastKey = "";
|
|
5839
5839
|
resolveTexture(_assets) {
|
|
5840
|
-
|
|
5841
|
-
|
|
5840
|
+
const text = translateOn(this, this.text);
|
|
5841
|
+
if (text === "" || typeof document === "undefined") return null;
|
|
5842
|
+
const key = `${text}\0${this.height}\0${this.color}\0${this.font}\0${this.outline}`;
|
|
5842
5843
|
if (key !== this.lastKey) {
|
|
5843
5844
|
this.lastKey = key;
|
|
5844
5845
|
this.rasterize();
|
|
@@ -5847,6 +5848,7 @@ var Label3D = class extends Sprite3D {
|
|
|
5847
5848
|
}
|
|
5848
5849
|
/** Draw the text once, and size the quad to what it measured. */
|
|
5849
5850
|
rasterize() {
|
|
5851
|
+
const text = translateOn(this, this.text);
|
|
5850
5852
|
const fontPx = Math.max(8, Math.round(this.height * 256));
|
|
5851
5853
|
this.canvas ??= document.createElement("canvas");
|
|
5852
5854
|
const ctx = this.canvas.getContext("2d");
|
|
@@ -5854,7 +5856,7 @@ var Label3D = class extends Sprite3D {
|
|
|
5854
5856
|
const stroke = Math.max(1, Math.round(fontPx * .14));
|
|
5855
5857
|
const fontSpec = `bold ${fontPx}px ${this.font}`;
|
|
5856
5858
|
ctx.font = fontSpec;
|
|
5857
|
-
const width = Math.max(1, Math.ceil(ctx.measureText(
|
|
5859
|
+
const width = Math.max(1, Math.ceil(ctx.measureText(text).width) + stroke * 2);
|
|
5858
5860
|
const lineHeight = Math.ceil(fontPx * 1.35) + stroke * 2;
|
|
5859
5861
|
this.canvas.width = width;
|
|
5860
5862
|
this.canvas.height = lineHeight;
|
|
@@ -5865,10 +5867,10 @@ var Label3D = class extends Sprite3D {
|
|
|
5865
5867
|
ctx.lineWidth = stroke;
|
|
5866
5868
|
ctx.lineJoin = "round";
|
|
5867
5869
|
ctx.strokeStyle = this.outline;
|
|
5868
|
-
ctx.strokeText(
|
|
5870
|
+
ctx.strokeText(text, width / 2, lineHeight / 2);
|
|
5869
5871
|
}
|
|
5870
5872
|
ctx.fillStyle = this.color;
|
|
5871
|
-
ctx.fillText(
|
|
5873
|
+
ctx.fillText(text, width / 2, lineHeight / 2);
|
|
5872
5874
|
this.canvasTexture?.dispose();
|
|
5873
5875
|
const texture = new CanvasTexture(this.canvas);
|
|
5874
5876
|
texture.colorSpace = SRGBColorSpace;
|