incanto 0.58.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/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-BiW8Men_.js} +6 -6
- package/dist/{create-game-BZwWJIns.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-EqA-jddf.js → physics-2d-BaRSRrrZ.js} +12 -2
- package/dist/{physics-3d-Dnz4fsXX.js → physics-3d-CYxjh-HW.js} +13 -3
- 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-C1J09Op6.js → src-CY21B462.js} +1 -1
- package/dist/{teardown-Cs113S9F.js → teardown-RApWnM1G.js} +1 -1
- package/dist/{test-ZBga8kQ9.js → test-DHYuFyAu.js} +260 -25
- package/dist/test.d.ts +103 -5
- package/dist/test.js +3 -3
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-BQQjE9UQ.js → agent8-BoRGtVxK.js} +1 -1
- package/editor/assets/{debug-C9UCsXBS.js → debug-CzdyCg75.js} +1 -1
- package/editor/assets/{index-CBgfM3WD.js → index-VesuVEhe.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 +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-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
|
@@ -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-CHDLDQsQ.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-BpFcgdcL.js";
|
|
4
|
+
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-BaRSRrrZ.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-DRAz5EV9.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-BiW8Men_.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-CYxjh-HW.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-CDrAQqPp.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-RApWnM1G.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-DRAz5EV9.js";
|
|
11
|
+
import { n as enablePhysics3D } from "./physics-3d-CYxjh-HW.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-CDrAQqPp.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-RApWnM1G.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-BpFcgdcL.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-BaRSRrrZ.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-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-
|
|
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;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as Node, E as Behavior, b as effectiveOrder, j as registerBehavior, n as loadScene, p as SaveSlots, w as diagnose } from "./loader-D8n7TU8W.js";
|
|
2
2
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
|
-
import { t as jsonClone } from "./json-
|
|
4
|
-
import { t as duplicateNode } from "./duplicate-
|
|
3
|
+
import { t as jsonClone } from "./json-CwwhxQgb.js";
|
|
4
|
+
import { t as duplicateNode } from "./duplicate-DJQd44CD.js";
|
|
5
5
|
import { Color, CubeCamera, DepthTexture, DoubleSide, Euler, FloatType, Frustum, HalfFloatType, LinearMipmapLinearFilter, MathUtils, Matrix4, Mesh, MeshDepthMaterial, Object3D, PerspectiveCamera, Plane, PlaneGeometry, Quaternion, ShaderMaterial, Sphere, Vector2, Vector3, Vector4, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
|
|
6
6
|
//#region src/3d/frustum.ts
|
|
7
7
|
const scratchFrustum = new Frustum();
|
|
@@ -2441,43 +2441,62 @@ var Node3D = class Node3D extends Node {
|
|
|
2441
2441
|
* `{shape:'capsule', radius, height}` (meters, y-up).
|
|
2442
2442
|
*/
|
|
2443
2443
|
var PhysicsBody3D = class extends Node3D {
|
|
2444
|
-
static props = {
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2444
|
+
static props = {
|
|
2445
|
+
collider: {
|
|
2446
|
+
default: {},
|
|
2447
|
+
variants: {
|
|
2448
|
+
tag: "shape",
|
|
2449
|
+
byTag: {
|
|
2450
|
+
auto: { shape: "auto" },
|
|
2451
|
+
box: {
|
|
2452
|
+
shape: "box",
|
|
2453
|
+
size: [
|
|
2454
|
+
1,
|
|
2455
|
+
1,
|
|
2456
|
+
1
|
|
2457
|
+
]
|
|
2458
|
+
},
|
|
2459
|
+
sphere: {
|
|
2460
|
+
shape: "sphere",
|
|
2461
|
+
radius: .5
|
|
2462
|
+
},
|
|
2463
|
+
capsule: {
|
|
2464
|
+
shape: "capsule",
|
|
2465
|
+
radius: .35,
|
|
2466
|
+
height: 1
|
|
2467
|
+
},
|
|
2468
|
+
trimesh: {
|
|
2469
|
+
shape: "trimesh",
|
|
2470
|
+
vertices: [],
|
|
2471
|
+
indices: []
|
|
2472
|
+
},
|
|
2473
|
+
heightfield: { shape: "heightfield" }
|
|
2474
|
+
}
|
|
2473
2475
|
}
|
|
2474
|
-
}
|
|
2475
|
-
|
|
2476
|
+
},
|
|
2477
|
+
/**
|
|
2478
|
+
* Whether this body's collider takes part in physics right now.
|
|
2479
|
+
*
|
|
2480
|
+
* A melee hitbox is off most of the time and live for a few frames of a
|
|
2481
|
+
* swing, and there was no way to say so: games expressed it by REPLACING
|
|
2482
|
+
* `collider` with `{}`, which tears the rigid body down and builds a new
|
|
2483
|
+
* one twice per swing, and makes the engine warn `has no collider —
|
|
2484
|
+
* physics skips it` about a scene that is correct. Keep the real collider
|
|
2485
|
+
* and turn it off.
|
|
2486
|
+
*
|
|
2487
|
+
* Off means no contacts and no `triggerEnter`/`triggerExit`. The body
|
|
2488
|
+
* itself stays, so nothing is rebuilt and re-arming is one call.
|
|
2489
|
+
*/
|
|
2490
|
+
enabled: { default: true }
|
|
2491
|
+
};
|
|
2476
2492
|
/** The unified collision model: every collider participant can emit these. */
|
|
2477
2493
|
static signals = ["triggerEnter", "triggerExit"];
|
|
2478
2494
|
_collider = {};
|
|
2479
2495
|
/** @internal Physics resync flag — raised when `collider` is REPLACED. */
|
|
2480
2496
|
_colliderChanged = false;
|
|
2497
|
+
_enabled = true;
|
|
2498
|
+
/** @internal Physics resync flag — raised when `enabled` is written. */
|
|
2499
|
+
_enabledChanged = false;
|
|
2481
2500
|
/**
|
|
2482
2501
|
* Collider shape prop. Change it by REPLACING the object
|
|
2483
2502
|
* (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
|
|
@@ -2490,6 +2509,15 @@ var PhysicsBody3D = class extends Node3D {
|
|
|
2490
2509
|
this._collider = value;
|
|
2491
2510
|
this._colliderChanged = true;
|
|
2492
2511
|
}
|
|
2512
|
+
/** See the `enabled` prop. Writing it arms or disarms the existing collider. */
|
|
2513
|
+
get enabled() {
|
|
2514
|
+
return this._enabled;
|
|
2515
|
+
}
|
|
2516
|
+
set enabled(value) {
|
|
2517
|
+
if (value === this._enabled) return;
|
|
2518
|
+
this._enabled = value;
|
|
2519
|
+
this._enabledChanged = true;
|
|
2520
|
+
}
|
|
2493
2521
|
/** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
|
|
2494
2522
|
static validateJson(node) {
|
|
2495
2523
|
const body = node;
|