incanto 0.60.0 → 0.62.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 +13 -0
- package/bin/incanto-verify.mjs +134 -55
- package/dist/2d.d.ts +64 -3
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +5 -5
- package/dist/3d.js +5 -4
- package/dist/{pathfinding-BqWBb0kh.d.ts → audio-player-D5GJgb_x.d.ts} +103 -38
- package/dist/{behavior-DWKTUzKI.d.ts → behavior-DsgayMsH.d.ts} +79 -1
- package/dist/{create-game-ClnIb_M5.js → create-game-BpunnGPX.js} +78 -13
- package/dist/{create-game-BCm38FJV.js → create-game-Caut3bqN.js} +21 -294
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +1 -1
- package/dist/{duplicate-DJQd44CD.js → duplicate-E4FUs5Bn.js} +1 -1
- package/dist/editor.js +41 -19
- package/dist/{environment-presets-8cjF3t6w.js → environment-presets-BAWeOeqf.js} +50 -13
- package/dist/{gameplay-BBEjPFsR.js → gameplay-CaHqDiQD.js} +177 -24
- package/dist/gameplay.d.ts +1 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +97 -6
- package/dist/index.js +7 -7
- package/dist/{loader-D8n7TU8W.js → loader-DEe272nY.js} +900 -2
- package/dist/{loader-TvkRFbyL.d.ts → loader-DolLJWJn.d.ts} +13 -1
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/pathfinding-_fGrCFmH.d.ts +28 -0
- package/dist/{physics-2d-DqdVp1bt.js → physics-2d-BXmu2i7W.js} +11 -3
- package/dist/{physics-3d-BP0DZb_1.js → physics-3d-ClxP6Uv7.js} +121 -17
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-BSu2dWGC.js → register-CNh4FlbD.js} +104 -16
- package/dist/{register-Da3hXh2H.js → register-D3yx8D4r.js} +228 -16
- package/dist/{registry-WWcQcfMr.js → registry-CF70EArN.js} +55 -3
- package/dist/{replay-BCMK_VRP.d.ts → replay-C5x2vPF5.d.ts} +2 -2
- package/dist/{replay-BlNuIDdg.js → replay-DlgHItNv.js} +57 -197
- package/dist/{split-screen-CL5Yvxse.js → split-screen-CBM9wcMX.js} +3 -3
- package/dist/{split-screen-BQ3tAsf-.d.ts → split-screen-D7OopelJ.d.ts} +2 -2
- package/dist/{src-DFpXBMJN.js → src-CH00_JsR.js} +1 -1
- package/dist/{teardown-CTTwhWSe.js → teardown-C7qP-dcC.js} +1 -1
- package/dist/{test-BeZ95pqw.js → test-BMkg8zMV.js} +29 -16
- package/dist/test.d.ts +45 -4
- package/dist/test.js +2 -2
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-DSJries_.js → agent8-m5mtAO_A.js} +1 -1
- package/editor/assets/{debug-D15Wi5TO.js → debug-CPhzCT8f.js} +1 -1
- package/editor/assets/{index-BjC88k97.js → index-D422P4kW.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +91 -0
- package/skills/incanto-3d-models.md +1 -1
- package/skills/incanto-assets.md +10 -1
- package/skills/incanto-audio.md +21 -13
- package/skills/incanto-behaviors-and-scripts.md +44 -3
- package/skills/incanto-building-2d-games.md +32 -4
- package/skills/incanto-building-3d-games.md +1 -1
- package/skills/incanto-editor.md +1 -1
- package/skills/incanto-gameplay-behaviors.md +39 -2
- package/skills/incanto-hud.md +5 -3
- package/skills/incanto-localization.md +7 -0
- package/skills/incanto-node-reference.md +12 -0
- package/skills/incanto-physics-and-input.md +23 -2
- package/skills/incanto-scene-json-authoring.md +6 -1
- package/skills/incanto-verifying-your-game.md +31 -1
- 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/dist/particle-sim-C5OfBbmU.d.ts +0 -77
package/bin/incanto-check.mjs
CHANGED
|
@@ -167,6 +167,15 @@ const { auditScene, validateScene } = await import(pathToFileURL(join(DIST, 'tes
|
|
|
167
167
|
* knowing what the project declares.
|
|
168
168
|
*/
|
|
169
169
|
const declaredElsewhere = {};
|
|
170
|
+
/**
|
|
171
|
+
* Every asset the project declares.
|
|
172
|
+
*
|
|
173
|
+
* A scene a spawner instantiates resolves its `$ref`s against the HOST's
|
|
174
|
+
* `assets{}` — `remote-player.scene.json` names `$characters/base` and declares
|
|
175
|
+
* nothing itself, which is correct. Checked alone it cannot resolve; checked as
|
|
176
|
+
* part of its project it can.
|
|
177
|
+
*/
|
|
178
|
+
const declaredAssets = {};
|
|
170
179
|
for (const file of files) {
|
|
171
180
|
try {
|
|
172
181
|
const doc = JSON.parse(readFileSync(file, 'utf-8'));
|
|
@@ -174,6 +183,9 @@ for (const file of files) {
|
|
|
174
183
|
if (typeof entries !== 'object' || entries === null) continue;
|
|
175
184
|
declaredElsewhere[locale] = { ...(declaredElsewhere[locale] ?? {}), ...entries };
|
|
176
185
|
}
|
|
186
|
+
for (const [key, entry] of Object.entries(doc?.assets ?? {})) {
|
|
187
|
+
if (typeof entry === 'object' && entry !== null) declaredAssets[key] ??= entry;
|
|
188
|
+
}
|
|
177
189
|
} catch {
|
|
178
190
|
/* a file that will not parse is reported below, on its own line */
|
|
179
191
|
}
|
|
@@ -188,6 +200,7 @@ const results = files.map((file) => {
|
|
|
188
200
|
}
|
|
189
201
|
const res = validateScene(json, {
|
|
190
202
|
strictBehaviors: args.strict,
|
|
203
|
+
declaredAssets,
|
|
191
204
|
resolveScene: (p) => JSON.parse(readFileSync(resolve(dirname(file), p), 'utf-8')),
|
|
192
205
|
});
|
|
193
206
|
if (res.ok) {
|
package/bin/incanto-verify.mjs
CHANGED
|
@@ -147,9 +147,24 @@ const rungs = [];
|
|
|
147
147
|
// the legal values sitting one key away in the same object.
|
|
148
148
|
const broke = out?.results?.find((entry) => entry && entry.ok === false);
|
|
149
149
|
const detail = broke && (broke.message || broke.code) ? `[${broke.code}] ${broke.message}` : null;
|
|
150
|
+
// What `incanto-check` SAID, beyond whether it passed. The rung read `ok` and
|
|
151
|
+
// dropped `warnings` on the floor, so a scene the child describes as "nothing
|
|
152
|
+
// lights this 3D scene … It will render black" was reported by the headline
|
|
153
|
+
// command as "✓ loads — the scene is legal and its assets resolve". The
|
|
154
|
+
// ladder's own header table names this tool as the one for "the scene will
|
|
155
|
+
// not load, OR RENDERS BLACK".
|
|
156
|
+
const notes = (out?.results ?? []).flatMap((entry) => entry?.warnings ?? []);
|
|
157
|
+
// "and its assets resolve" is a claim, and `missing art` is the check saying
|
|
158
|
+
// it does not. Do not make the claim when the child just contradicted it.
|
|
159
|
+
const missingArt = notes.some((n) => /is not in the project|missing|not found/i.test(String(n)));
|
|
150
160
|
rungs.push(
|
|
151
161
|
r.status === 0
|
|
152
|
-
? {
|
|
162
|
+
? {
|
|
163
|
+
name: 'loads',
|
|
164
|
+
status: 'pass',
|
|
165
|
+
summary: missingArt ? 'the scene is legal' : 'the scene is legal and its assets resolve',
|
|
166
|
+
...(notes.length > 0 ? { notes, fix: `incanto-check ${scene}` } : {}),
|
|
167
|
+
}
|
|
153
168
|
: {
|
|
154
169
|
name: 'loads',
|
|
155
170
|
status: 'fail',
|
|
@@ -175,62 +190,126 @@ if (rungs[0].status === 'pass') {
|
|
|
175
190
|
// walkabout template has no end, and calling that a failure sends its author
|
|
176
191
|
// hunting a bug that was never there.
|
|
177
192
|
const noGoal = out && out.declaresWin === false;
|
|
178
|
-
//
|
|
179
|
-
// finished by a random walker, ever. Reporting that as a FAILED rung means
|
|
180
|
-
// the headline command permanently says NOT verified about a correct game,
|
|
181
|
-
// which teaches its author to stop reading it. If every run played to the
|
|
182
|
-
// end of its budget without erroring, falling or wedging, the rung has not
|
|
183
|
-
// failed: it has not measured, and the author's own scripted harness is what
|
|
184
|
-
// can judge this game.
|
|
185
|
-
// Three of the five outcomes are GAMEPLAY, not defects. `won`, `lost` and
|
|
193
|
+
// Three of the six outcomes are GAMEPLAY, not defects. `won`, `lost` and
|
|
186
194
|
// `unfinished` all mean the game ran; a random player dying half the time in
|
|
187
|
-
// a platformer is the hazards working
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
// a platformer is the hazards working, and a quest cannot be finished by a
|
|
196
|
+
// random walker ever — reporting that as a FAILED rung means the headline
|
|
197
|
+
// command permanently says NOT verified about a correct game, which teaches
|
|
198
|
+
// its author to stop reading it.
|
|
199
|
+
//
|
|
200
|
+
// The other three are DEFECTS: `error` (a behaviour threw), `fell` (left the
|
|
201
|
+
// world) and `stuck` (went nowhere). They are counted BEFORE a branch is
|
|
202
|
+
// chosen, and they used to be counted after — only on the path where a win
|
|
203
|
+
// existed to miss. A scene that declares no win took the `noGoal` branch for
|
|
204
|
+
// ANY mix of outcomes, and that branch's summary was the hard-coded phrase
|
|
205
|
+
// "N runs played without error": an assumption wearing the clothes of a
|
|
206
|
+
// measurement. Measured on the shipped `basic-3d-sideview`, whose player
|
|
207
|
+
// walks off the edge on every seeded run:
|
|
208
|
+
//
|
|
209
|
+
// They used to be counted after, and only on the path where a win existed to
|
|
210
|
+
// miss. A scene that declares no win took the `noGoal` branch for ANY mix of
|
|
211
|
+
// outcomes, and that branch's summary was the hard-coded phrase "N runs
|
|
212
|
+
// played without error" — an assumption wearing the clothes of a
|
|
213
|
+
// measurement. Measured on the shipped `basic-3d-sideview`, whose player
|
|
214
|
+
// walks off the edge on every seeded run:
|
|
215
|
+
//
|
|
216
|
+
// incanto-playtest → ✗ fell in 4/4 (last at y=-48.4)
|
|
217
|
+
// incanto-verify → ? plays — nothing declares a win — 8 runs played
|
|
218
|
+
// without error, and there was no end to reach
|
|
219
|
+
// passes what was measured … VERIFY EXIT=0
|
|
220
|
+
//
|
|
221
|
+
// The child had just printed the defect; the parent reported the opposite of
|
|
222
|
+
// it and exited 0. The three outcomes this rung exists to catch were the
|
|
223
|
+
// three it could not report.
|
|
224
|
+
//
|
|
225
|
+
// `stuck` is the one that needs a fair chance before it counts. It means the
|
|
226
|
+
// player went nowhere, and the player goes nowhere for three different
|
|
227
|
+
// reasons: it is wedged (a defect), there IS no player (`2d-phaser-basic` is
|
|
228
|
+
// a ground, a sprite and a camera — the driver had nobody to move), or the
|
|
229
|
+
// game's own behaviours were never loaded, which is most of what makes a
|
|
230
|
+
// character move. Only the first is this rung's business.
|
|
193
231
|
const tally = (name) => out?.runs?.filter((x) => x.outcome === name).length ?? 0;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
232
|
+
const stuckCounts = out?.hasPlayer !== false && Boolean(behaviors);
|
|
233
|
+
const DEFECT = new Set(stuckCounts ? ['error', 'fell', 'stuck'] : ['error', 'fell']);
|
|
234
|
+
const defects = out?.runs?.filter((x) => DEFECT.has(x.outcome)) ?? [];
|
|
235
|
+
const undrivable = total > 0 && defects.length === 0 && tally('stuck') === total;
|
|
236
|
+
const count = (n, word) => n && `${n} ${word}`;
|
|
237
|
+
|
|
238
|
+
if (total === 0) {
|
|
239
|
+
rungs.push({
|
|
240
|
+
name: 'plays',
|
|
241
|
+
status: 'fail',
|
|
242
|
+
// "could not run" with no reason is the tool doing to its reader exactly
|
|
243
|
+
// what this whole ladder exists to prevent: reporting a failure it
|
|
244
|
+
// already knows the cause of. The child printed one; pass it on.
|
|
245
|
+
summary: `the playtest could not run — ${firstLine(r.stderr) || `exit ${r.status}`}`,
|
|
246
|
+
fix: behaviors
|
|
247
|
+
? `see it directly: \`incanto-playtest ${scene} --behaviors ${behaviors}\``
|
|
248
|
+
: `run it with your behaviours: \`incanto-playtest ${scene} --behaviors src/behaviors.ts\``,
|
|
249
|
+
});
|
|
250
|
+
} else if (defects.length > 0) {
|
|
251
|
+
rungs.push({
|
|
252
|
+
name: 'plays',
|
|
253
|
+
status: 'fail',
|
|
254
|
+
summary:
|
|
255
|
+
`${defects.length} of ${total} runs ended in a defect (` +
|
|
256
|
+
[
|
|
257
|
+
count(tally('fell'), 'fell out of the world'),
|
|
258
|
+
count(tally('error'), 'errored'),
|
|
259
|
+
count(tally('stuck'), 'went nowhere'),
|
|
260
|
+
]
|
|
261
|
+
.filter(Boolean)
|
|
262
|
+
.join(', ') +
|
|
263
|
+
')' +
|
|
264
|
+
(won > 0 ? ` — ${won} did finish it` : ''),
|
|
265
|
+
fix:
|
|
266
|
+
tally('error') > 0
|
|
267
|
+
? `read the throw: \`incanto-playtest ${scene}${behaviors ? ` --behaviors ${behaviors}` : ''}\`, and \`engine.stats().errors\` in your own harness`
|
|
268
|
+
: tally('fell') > 0
|
|
269
|
+
? `the player left the world with nothing catching them — give the level a floor, walls, or a respawn (\`incanto-playtest ${scene}${behaviors ? ` --behaviors ${behaviors}` : ''}\` prints where)`
|
|
270
|
+
: `nothing moved — check the input map and the controller: \`incanto-playtest ${scene}${behaviors ? ` --behaviors ${behaviors}` : ''}\``,
|
|
271
|
+
});
|
|
272
|
+
} else if (r.status === 0) {
|
|
273
|
+
rungs.push({
|
|
274
|
+
name: 'plays',
|
|
275
|
+
status: 'pass',
|
|
276
|
+
summary: `${won} of ${total} seeded runs finished it`,
|
|
277
|
+
});
|
|
278
|
+
} else if (undrivable) {
|
|
279
|
+
rungs.push({
|
|
280
|
+
name: 'plays',
|
|
281
|
+
status: 'unmeasured',
|
|
282
|
+
summary:
|
|
283
|
+
out?.hasPlayer === false
|
|
284
|
+
? `nothing here is drivable — ${total} runs had nobody to move`
|
|
285
|
+
: `${total} runs went nowhere, and your behaviours were not loaded — that is most of what moves a character`,
|
|
286
|
+
fix:
|
|
287
|
+
out?.hasPlayer === false
|
|
288
|
+
? 'give the player a character controller, the `player` group, or the name Player — otherwise no seeded run can play this'
|
|
289
|
+
: `name them: \`incanto-verify ${scene} --behaviors src/behaviors.ts\``,
|
|
290
|
+
});
|
|
291
|
+
} else if (noGoal) {
|
|
292
|
+
rungs.push({
|
|
293
|
+
name: 'plays',
|
|
294
|
+
status: 'unmeasured',
|
|
295
|
+
// Now this phrase IS measured: `defects.length === 0` got us here.
|
|
296
|
+
summary: `nothing declares a win — ${total} runs played without a defect, and there was no end to reach`,
|
|
297
|
+
fix: 'if it is meant to be finishable, emit `won` (GameFlow, ScoreKeeper, or your own behaviour)',
|
|
298
|
+
});
|
|
299
|
+
} else {
|
|
300
|
+
// A quest — talk to the NPC, clear the enemies, light the wards — cannot be
|
|
301
|
+
// finished by a random walker, ever. Reporting that as a FAILED rung means
|
|
302
|
+
// the headline command permanently says NOT verified about a correct game,
|
|
303
|
+
// which teaches its author to stop reading it.
|
|
304
|
+
rungs.push({
|
|
305
|
+
name: 'plays',
|
|
306
|
+
status: 'unmeasured',
|
|
307
|
+
summary:
|
|
308
|
+
`${total} runs played without reaching a win` +
|
|
309
|
+
` (${[count(tally('lost'), 'lost'), count(tally('unfinished'), 'ran out the clock')].filter(Boolean).join(', ')})`,
|
|
310
|
+
fix: 'nothing here is broken — a win that takes skill or a sequence is out of reach of random play. Judge it with a scripted run: `bun run verify`, or `runScript` from `incanto/test`',
|
|
311
|
+
});
|
|
312
|
+
}
|
|
234
313
|
} else {
|
|
235
314
|
rungs.push({ name: 'plays', status: 'skipped', summary: 'not run — the scene does not load' });
|
|
236
315
|
}
|
package/dist/2d.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats } from "./behavior-DsgayMsH.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-DolLJWJn.js";
|
|
6
6
|
import { n as AnimationEntry } from "./sprite-animation-CMr6f1K2.js";
|
|
7
|
-
import {
|
|
7
|
+
import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-D5GJgb_x.js";
|
|
8
8
|
import { Group, Mesh, Object3D, Scene, Texture } from "three";
|
|
9
9
|
import * as RapierNs from "@dimforge/rapier2d-compat";
|
|
10
10
|
|
|
@@ -90,6 +90,28 @@ declare class Node2D extends Node {
|
|
|
90
90
|
/** @internal Override point. */
|
|
91
91
|
protected _createObject2D(): Object3D;
|
|
92
92
|
/** @internal Push JSON props onto the backing object. Called every frame. */
|
|
93
|
+
/**
|
|
94
|
+
* Has this node produced the thing `static: true` is about to freeze?
|
|
95
|
+
*
|
|
96
|
+
* `static` latches after the first sync, and a textured node's first sync
|
|
97
|
+
* happens BEFORE its texture has decoded — `TextureLoader.load()` returns a
|
|
98
|
+
* `Texture` with `image === undefined` and fills it asynchronously, while
|
|
99
|
+
* `Renderer2D.render()` calls `assets.load()` and `syncTree2D()` in the same
|
|
100
|
+
* frame. So the state frozen was `visible = false`, forever. Measured on two
|
|
101
|
+
* identical sprites differing only in `static`:
|
|
102
|
+
*
|
|
103
|
+
* ```
|
|
104
|
+
* frame 1 Backdrop(static) visible: false Control visible: false
|
|
105
|
+
* 600 frames later Backdrop(static) visible: false width 1
|
|
106
|
+
* Control visible: true width 64
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* Nothing throws: the texture loaded fine, so `assetErrors()` is empty,
|
|
110
|
+
* `stats().errors` is 0, and `framing` reads props rather than pixels and
|
|
111
|
+
* still calls the node on-screen. Overridden by the nodes that wait on an
|
|
112
|
+
* image; a node with nothing to wait for is ready by definition.
|
|
113
|
+
*/
|
|
114
|
+
_staticReady(_assets: AssetStore2D | null): boolean;
|
|
93
115
|
_syncObject2D(_assets: AssetStore2D | null): void;
|
|
94
116
|
override free(): void;
|
|
95
117
|
}
|
|
@@ -498,6 +520,7 @@ declare class Sprite2D extends Node2D {
|
|
|
498
520
|
protected override _createObject2D(): Object3D;
|
|
499
521
|
/** Override point: AnimatedSprite2D substitutes its frame window here. */
|
|
500
522
|
protected resolveTexture(assets: AssetStore2D | null): ResolvedSpriteTexture | null;
|
|
523
|
+
override _staticReady(assets: AssetStore2D | null): boolean;
|
|
501
524
|
override _syncObject2D(assets: AssetStore2D | null): void;
|
|
502
525
|
}
|
|
503
526
|
//#endregion
|
|
@@ -568,6 +591,24 @@ declare class Camera2D extends Node2D {
|
|
|
568
591
|
get effectiveZoom(): number;
|
|
569
592
|
override update(dt: number): void;
|
|
570
593
|
/** View center after clamping the (vw×vh)/zoom view rect inside `limits`. */
|
|
594
|
+
/**
|
|
595
|
+
* The view centre the renderer draws around, in WORLD space.
|
|
596
|
+
*
|
|
597
|
+
* It used to read `this.position` raw — the LOCAL prop — while every other
|
|
598
|
+
* 2D node gets its ancestors composed for free by the three scene graph. So a
|
|
599
|
+
* camera parented to the player, the Godot/Phaser idiom the authoring skill
|
|
600
|
+
* explicitly permits, framed the world origin:
|
|
601
|
+
*
|
|
602
|
+
* ```
|
|
603
|
+
* player world position : [1400, 900]
|
|
604
|
+
* renderer view centre : {"x":0,"y":0}
|
|
605
|
+
* framing : camera /Level/Player/Cam centred [1400, 900]
|
|
606
|
+
* 1 in view, 0 outside it
|
|
607
|
+
* ```
|
|
608
|
+
*
|
|
609
|
+
* `framing` composes full world matrices, so the instrument the skills tell
|
|
610
|
+
* you to trust certified a view the renderer never drew.
|
|
611
|
+
*/
|
|
571
612
|
clampedCenter(vw: number, vh: number): {
|
|
572
613
|
x: number;
|
|
573
614
|
y: number;
|
|
@@ -701,6 +742,16 @@ declare class Label extends Node2D {
|
|
|
701
742
|
font: string;
|
|
702
743
|
/** 'left' | 'center' | 'right' — anchor of the text block on the node origin. */
|
|
703
744
|
align: string;
|
|
745
|
+
/**
|
|
746
|
+
* 0..1, like `Sprite2D` and `ColorRect2D`.
|
|
747
|
+
*
|
|
748
|
+
* Label was the one 2D drawable without it, and the gap showed up through
|
|
749
|
+
* `FloatAway` — whose own doc names "a 2D `Label`" as a node it works on.
|
|
750
|
+
* `FloatAway` writes `opacity` when the node has one, so on a Label the
|
|
751
|
+
* number rose to full height at FULL opacity and then vanished in a single
|
|
752
|
+
* frame: the pop that behavior exists to avoid.
|
|
753
|
+
*/
|
|
754
|
+
opacity: number;
|
|
704
755
|
private quadMesh;
|
|
705
756
|
private canvas;
|
|
706
757
|
private lastKey;
|
|
@@ -872,6 +923,7 @@ declare class TileMap2D extends Node2D {
|
|
|
872
923
|
private rebuildColliders;
|
|
873
924
|
/** @internal The drawable mesh (lazily created under the backing object). */
|
|
874
925
|
_mesh(): Mesh;
|
|
926
|
+
override _staticReady(assets: AssetStore2D | null): boolean;
|
|
875
927
|
override _syncObject2D(assets: AssetStore2D | null): void;
|
|
876
928
|
/** One quad per visible tile, atlas UVs — a single draw call for the level. */
|
|
877
929
|
private buildGeometry;
|
|
@@ -1081,6 +1133,15 @@ interface UiSize {
|
|
|
1081
1133
|
interface Sync2DScratch {
|
|
1082
1134
|
visited: Set<Object3D>;
|
|
1083
1135
|
cameras: Camera2D[];
|
|
1136
|
+
emitters: Array<{
|
|
1137
|
+
node: Node & SpatialConsumer;
|
|
1138
|
+
parent: Object3D;
|
|
1139
|
+
}>;
|
|
1140
|
+
}
|
|
1141
|
+
/** A node that accepts a per-frame spatial pose (an AudioPlayer with spatial on). */
|
|
1142
|
+
interface SpatialConsumer {
|
|
1143
|
+
spatial: boolean;
|
|
1144
|
+
_setSpatialPose(pose: SpatialPose): void;
|
|
1084
1145
|
}
|
|
1085
1146
|
declare function syncTree2D(root: Node, world: Scene, ui: Scene, assets: AssetStore2D | null, uiSize?: UiSize, scratch?: Sync2DScratch, opts?: {
|
|
1086
1147
|
ignoreStatic?: boolean;
|
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-BpunnGPX.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-CNh4FlbD.js";
|
|
4
|
+
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-BXmu2i7W.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 {
|
|
1
|
+
import { D as QualityCaps, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats } from "./behavior-DsgayMsH.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-
|
|
6
|
-
import {
|
|
7
|
-
import { n as PathGrid
|
|
5
|
+
import { t as LoadSceneOptions } from "./loader-DolLJWJn.js";
|
|
6
|
+
import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-D5GJgb_x.js";
|
|
7
|
+
import { n as PathGrid } from "./pathfinding-_fGrCFmH.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";
|
|
@@ -730,7 +730,7 @@ interface Game3D {
|
|
|
730
730
|
*/
|
|
731
731
|
declare function createGame3D(opts: CreateGame3DOptions): Promise<Game3D>;
|
|
732
732
|
//#endregion
|
|
733
|
-
//#region src/
|
|
733
|
+
//#region src/core/scene/environment.d.ts
|
|
734
734
|
/**
|
|
735
735
|
* The 3D `environment` header, parsed and validated — the renderer's
|
|
736
736
|
* "rendering stage". Pure data in, plain data out (NO three imports), so the
|
package/dist/3d.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { c as parseEnvironment3D, l as sunDirectionFromElevationAzimuth, s as horizonColorFromSky, u as sunDirectionFromSky } from "./loader-DEe272nY.js";
|
|
1
2
|
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
|
|
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,
|
|
3
|
+
import { B as CharacterBody3D, F as Water3D, H as RigidBody3D, I as WATER_CUTOUT_MAX, L as WaterCutout3D, U as StaticBody3D, V as PhysicsBody3D, W as Node3D, q as WATER_MAX_RIPPLES, z as Area3D } from "./gameplay-CaHqDiQD.js";
|
|
4
|
+
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-BAWeOeqf.js";
|
|
5
|
+
import { a as Environment3D, i as syncTree, o as setEnvironment3D, r as Renderer3D, s as AssetStore3D, t as createGame3D } from "./create-game-Caut3bqN.js";
|
|
5
6
|
import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
|
|
6
|
-
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-
|
|
7
|
+
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-ClxP6Uv7.js";
|
|
7
8
|
//#region src/3d/model-verdict.ts
|
|
8
9
|
/** Mixamo exports every bone as `mixamorigX`; the retargeter binds by that name. */
|
|
9
10
|
const MIXAMO = /^mixamorig[:_]?/i;
|
|
@@ -1,5 +1,80 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-DsgayMsH.js";
|
|
2
|
+
import { a as Rng } from "./schema-CFeioQRE.js";
|
|
2
3
|
|
|
4
|
+
//#region src/core/particle-sim.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Deterministic particle pool — pure math, renderer-agnostic, headless-
|
|
7
|
+
* testable. 2D uses the xy plane (y-down, directionDeg 0 = +x, -90 = up);
|
|
8
|
+
* 3D feeds the same sim with a z spread.
|
|
9
|
+
*/
|
|
10
|
+
interface ParticleSimConfig {
|
|
11
|
+
/** Particles per second (0 = burst-only). */
|
|
12
|
+
rate: number;
|
|
13
|
+
/** Seconds, [min, max]. */
|
|
14
|
+
lifetime: [number, number];
|
|
15
|
+
/** Initial speed, [min, max] (units/sec — px in 2D, meters in 3D). */
|
|
16
|
+
speed: [number, number];
|
|
17
|
+
/** Emission direction center, degrees (0 = +x, -90 = up in y-down 2D). */
|
|
18
|
+
directionDeg: number;
|
|
19
|
+
/** Cone width, degrees (360 = all directions). */
|
|
20
|
+
spreadDeg: number;
|
|
21
|
+
/** Constant acceleration (y-down in 2D). */
|
|
22
|
+
gravity: [number, number, number];
|
|
23
|
+
/** Exponential velocity damping per second (0 = none). */
|
|
24
|
+
drag: number;
|
|
25
|
+
maxParticles: number;
|
|
26
|
+
/** Spread emission into the z axis too (3D). */
|
|
27
|
+
spreadZ?: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface ParticleView {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
z: number;
|
|
33
|
+
vx: number;
|
|
34
|
+
vy: number;
|
|
35
|
+
vz: number;
|
|
36
|
+
/** Seconds alive. */
|
|
37
|
+
age: number;
|
|
38
|
+
/** Total lifetime in seconds. */
|
|
39
|
+
life: number;
|
|
40
|
+
/** age/life in [0, 1] — drives size/color/alpha ramps. */
|
|
41
|
+
t: number;
|
|
42
|
+
/** Per-particle random in [0, 1) — stable for the particle's lifetime. */
|
|
43
|
+
seed: number;
|
|
44
|
+
}
|
|
45
|
+
declare class ParticleSim {
|
|
46
|
+
private readonly config;
|
|
47
|
+
private readonly rng;
|
|
48
|
+
private readonly data;
|
|
49
|
+
private alive;
|
|
50
|
+
private spawnAccumulator;
|
|
51
|
+
private everSpawned;
|
|
52
|
+
constructor(config: ParticleSimConfig, rng: Rng);
|
|
53
|
+
/**
|
|
54
|
+
* Shift every live particle — how world-space emission is done.
|
|
55
|
+
*
|
|
56
|
+
* The pool is in the emitter's LOCAL space, so a moving emitter drags its
|
|
57
|
+
* whole plume with it: dust glued to a running player instead of left behind,
|
|
58
|
+
* and the documented "move the emitter and replay" recipe for a one-shot
|
|
59
|
+
* teleporting the previous explosion across the level. Counter-translating by
|
|
60
|
+
* the emitter's own motion each frame leaves the particles where they were
|
|
61
|
+
* born, exactly, and costs one pass over the live ones.
|
|
62
|
+
*/
|
|
63
|
+
translateAll(dx: number, dy: number, dz: number): void;
|
|
64
|
+
/** Change the emission rate LIVE (particles/sec; 0 pauses emission) — lets an
|
|
65
|
+
* emitter toggle on/off at runtime (drift smoke, throttle flames). */
|
|
66
|
+
setRate(rate: number): void;
|
|
67
|
+
get count(): number;
|
|
68
|
+
/** True when nothing is alive and at least one particle has ever spawned. */
|
|
69
|
+
get done(): boolean;
|
|
70
|
+
/** Spawn n particles immediately (fireworks, explosions, flashes). */
|
|
71
|
+
burst(n: number): void;
|
|
72
|
+
update(dt: number): void;
|
|
73
|
+
forEach(fn: (p: ParticleView) => void): void;
|
|
74
|
+
private spawn;
|
|
75
|
+
private kill;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
3
78
|
//#region src/core/nodes/audio-player.d.ts
|
|
4
79
|
/**
|
|
5
80
|
* The per-frame spatial feed the 3D adapter pushes onto a spatial AudioPlayer:
|
|
@@ -23,7 +98,7 @@ interface AudioElementLike {
|
|
|
23
98
|
}
|
|
24
99
|
/**
|
|
25
100
|
* One sound. `play()` (or `autoplay: true`) starts it; `finished` fires when
|
|
26
|
-
* it ends. Browsers block audio before the first user gesture — a blocked
|
|
101
|
+
* it ends — for a procedural preset as well as a `src` clip. Browsers block audio before the first user gesture — a blocked
|
|
27
102
|
* play marks `pendingGesture`; `createGame` retries pending players on the
|
|
28
103
|
* first pointer/key gesture automatically.
|
|
29
104
|
*
|
|
@@ -58,10 +133,12 @@ declare class AudioPlayer extends Node {
|
|
|
58
133
|
/** Start on the first frame in the tree (subject to the gesture policy). */
|
|
59
134
|
autoplay: boolean;
|
|
60
135
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
136
|
+
* Positional audio: the sound pans + attenuates by the emitter's world
|
|
137
|
+
* position relative to the active camera (the listener). Default false →
|
|
138
|
+
* identical non-spatial behavior.
|
|
139
|
+
*
|
|
140
|
+
* Both dimensions. A 2D scene measures in PIXELS, so the distances below —
|
|
141
|
+
* metre-shaped defaults — need setting there; `auditScene` says so.
|
|
65
142
|
*/
|
|
66
143
|
spatial: boolean;
|
|
67
144
|
/** Distance at which spatial gain is full; closer never gets louder. */
|
|
@@ -81,6 +158,21 @@ declare class AudioPlayer extends Node {
|
|
|
81
158
|
loadError: string | null;
|
|
82
159
|
private element;
|
|
83
160
|
private _playing;
|
|
161
|
+
/**
|
|
162
|
+
* `engine.unscaledTime` at which the preset currently sounding ends.
|
|
163
|
+
*
|
|
164
|
+
* A preset used to be fire-and-forget: `playing` stayed false through a sound
|
|
165
|
+
* that was audibly playing, and `finished` — a signal this node DECLARES, so
|
|
166
|
+
* a connection to it loads clean — never fired at all. Wiring "when the coin
|
|
167
|
+
* chime ends, free the pickup" to a preset player produced a wire that is
|
|
168
|
+
* dead for the life of the game and says nothing.
|
|
169
|
+
*
|
|
170
|
+
* The end is known exactly: `attack + sustain + decay` is the length
|
|
171
|
+
* `synthSfx` sizes its buffer to. Measured on the UNSCALED clock because a
|
|
172
|
+
* sound does not slow down when the game does, and a paused game still hears
|
|
173
|
+
* the tail of the hit that paused it.
|
|
174
|
+
*/
|
|
175
|
+
private presetEndsAt;
|
|
84
176
|
private autoplayed;
|
|
85
177
|
/** Last spatial pose pushed by the 3D adapter (null until/unless spatial). */
|
|
86
178
|
private _spatialPose;
|
|
@@ -108,10 +200,10 @@ declare class AudioPlayer extends Node {
|
|
|
108
200
|
play(): void;
|
|
109
201
|
/**
|
|
110
202
|
* Procedural-SFX path: synthesize the preset and fire it through WebAudio
|
|
111
|
-
* (low-latency, overlap-friendly).
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
203
|
+
* (low-latency, overlap-friendly). Headless (no AudioContext) plays nothing —
|
|
204
|
+
* but still reports `playing` and still emits `finished` on schedule, because
|
|
205
|
+
* the length is arithmetic, not a device. WebAudio needs a gesture; a
|
|
206
|
+
* suspended context is resumed by the same unlock listener as `src` players.
|
|
115
207
|
*/
|
|
116
208
|
private playPreset;
|
|
117
209
|
/**
|
|
@@ -130,31 +222,4 @@ declare class AudioPlayer extends Node {
|
|
|
130
222
|
override update(_dt: number): void;
|
|
131
223
|
}
|
|
132
224
|
//#endregion
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Grid A* — the pathfinding every top-down/RPG/tower-defense asks for and
|
|
136
|
-
* agents keep re-inventing badly. Pure and dimension-free: cells in, cells
|
|
137
|
-
* out; the caller maps cells to world units (px, meters, tiles).
|
|
138
|
-
*/
|
|
139
|
-
interface PathGrid {
|
|
140
|
-
width: number;
|
|
141
|
-
height: number;
|
|
142
|
-
/** True = impassable. Out-of-bounds is always solid. */
|
|
143
|
-
solid(x: number, y: number): boolean;
|
|
144
|
-
}
|
|
145
|
-
interface FindPathOptions {
|
|
146
|
-
/** Allow diagonal steps (blocked from cutting corners). Default true. */
|
|
147
|
-
diagonal?: boolean;
|
|
148
|
-
/** Abort guard for huge/impossible searches. Default 20000 nodes. */
|
|
149
|
-
maxExpansions?: number;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* A* from `from` to `to` (inclusive cell coords). Returns the cell path
|
|
153
|
-
* INCLUDING both endpoints, or null when unreachable. Straight steps cost
|
|
154
|
-
* 1, diagonals √2; the heuristic is octile (admissible for both modes).
|
|
155
|
-
*/
|
|
156
|
-
declare function findPath(grid: PathGrid, from: [number, number], to: [number, number], opts?: FindPathOptions): [number, number][] | null;
|
|
157
|
-
/** Build a PathGrid from row-strings ('#' = solid) — tests and tile games. */
|
|
158
|
-
declare function gridFromRows(rows: readonly string[]): PathGrid;
|
|
159
|
-
//#endregion
|
|
160
|
-
export { AudioElementLike as a, gridFromRows as i, PathGrid as n, AudioPlayer as o, findPath as r, SpatialPose as s, FindPathOptions as t };
|
|
225
|
+
export { ParticleSimConfig as a, ParticleSim as i, AudioPlayer as n, ParticleView as o, SpatialPose as r, AudioElementLike as t };
|