incanto 0.56.0 → 0.58.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/README.md +6 -4
- package/bin/incanto-check.mjs +27 -0
- package/bin/incanto-new.mjs +29 -7
- package/bin/incanto-playtest.mjs +28 -2
- package/bin/incanto-verify.mjs +96 -17
- package/bin/incanto.mjs +106 -0
- package/dist/2d.d.ts +59 -7
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +34 -4
- package/dist/3d.js +5 -5
- package/dist/{behavior-CyQoSu4n.d.ts → behavior-l08AEbq9.d.ts} +5 -0
- package/dist/{create-game-YvaNWhe6.js → create-game-BZwWJIns.js} +146 -7
- package/dist/{create-game-CqJkfMzm.js → create-game-D10bU5_J.js} +83 -78
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-MNLMAcbz.js → duplicate-BPLZDZpd.js} +1 -1
- package/dist/{environment-presets-DLHqgNAF.js → environment-presets-CvvQr_bJ.js} +106 -24
- package/dist/{frame-report-njybhZon.js → frame-report-BSMny7oe.js} +1 -1
- package/dist/{frame-report-DZ70IY26.d.ts → frame-report-DCnHFmto.d.ts} +1 -1
- package/dist/{gameplay-C_SPzmUe.js → gameplay-BVphcxmE.js} +89 -13
- package/dist/gameplay.d.ts +26 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +45 -5
- package/dist/index.js +8 -9
- package/dist/{loader-DhI1jFW_.d.ts → loader-BbEMTuWg.d.ts} +1 -1
- package/dist/{loader-BTkHYrQn.js → loader-BcrRSjxB.js} +682 -682
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{pathfinding-CXGCpRQe.d.ts → pathfinding-mEN4V1CU.d.ts} +1 -1
- package/dist/{physics-2d-CfWAggJ1.js → physics-2d-EqA-jddf.js} +4 -3
- package/dist/{physics-3d-NzDwWPrF.js → physics-3d-Dnz4fsXX.js} +5 -4
- package/dist/quiet-rapier-BAJ4K94N.js +46 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-en63AEZO.js → register-C6ZBFRjd.js} +63 -60
- package/dist/{register-p48lHE2o.js → register-Ch70uByv.js} +1615 -1502
- package/dist/{replay-ePMz26jw.d.ts → replay-Dw6gMlYA.d.ts} +1 -1
- package/dist/{replay-t1pP0gQg.js → replay-s7I2GstT.js} +31 -11
- package/dist/sheet-grid-BT6N_Bjs.js +59 -0
- package/dist/{split-screen-BsdOHbzP.d.ts → split-screen-B0baBwxI.d.ts} +5 -1
- package/dist/{split-screen-CSb_uZ6W.js → split-screen-DLsUrleX.js} +14 -3
- package/dist/{sprite-animation-7qvUxF6Z.js → sprite-animation-C0wXLBZJ.js} +8 -4
- package/dist/{src-51GA-9m-.js → src-C1J09Op6.js} +55 -2
- package/dist/{teardown-C7uVSJvx.js → teardown-Cs113S9F.js} +74 -2
- package/dist/{test-Bu6SPNsV.js → test-ZBga8kQ9.js} +60 -25
- package/dist/test.d.ts +5 -5
- package/dist/test.js +2 -2
- package/dist/{touch-BnMyy9tr.js → touch-DESwnpOc.js} +173 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +3 -3
- package/editor/assets/{agent8-X0Nesj-k.js → agent8-BQQjE9UQ.js} +1 -1
- package/editor/assets/{debug-TXqu-ftX.js → debug-C9UCsXBS.js} +1 -1
- package/editor/assets/{index-DTdwKKIv.js → index-CBgfM3WD.js} +62 -62
- package/editor/index.html +1 -1
- package/package.json +2 -1
- package/skills/incanto-assets.md +5 -1
- package/skills/incanto-audio.md +7 -1
- package/skills/incanto-building-2d-games.md +13 -0
- package/skills/incanto-building-3d-games.md +3 -3
- package/skills/incanto-gameplay-behaviors.md +2 -1
- package/skills/incanto-hud.md +6 -0
- package/skills/incanto-performance.md +5 -0
- package/skills/incanto-physics-and-input.md +14 -0
- package/skills/incanto-playtesting.md +19 -5
- package/skills/incanto-save-slots.md +2 -1
- package/skills/incanto-verifying-your-game.md +15 -4
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +7 -6
- package/templates-app/platformer-2d/PROJECT/Context.md +70 -0
- package/templates-app/platformer-2d/PROJECT/Requirements.md +63 -0
- package/templates-app/platformer-2d/PROJECT/Status.md +60 -0
- package/templates-app/platformer-2d/PROJECT/Structure.md +77 -0
- package/templates-app/platformer-2d/docs/project-2d-rules.md +61 -0
- package/templates-app/platformer-2d/index.html +99 -0
- package/templates-app/platformer-2d/package.json +23 -0
- package/templates-app/platformer-2d/src/behaviors.ts +541 -0
- package/templates-app/platformer-2d/src/game.scene.json +2061 -0
- package/templates-app/platformer-2d/src/main.ts +68 -0
- package/templates-app/platformer-2d/tsconfig.json +13 -0
- package/templates-app/platformer-2d/verify.ts +275 -0
- package/templates-app/platformer-2d/vite.config.ts +12 -0
- package/templates-app/star-survivor/PROJECT/Context.md +55 -0
- package/templates-app/star-survivor/PROJECT/Requirements.md +47 -0
- package/templates-app/star-survivor/PROJECT/Status.md +44 -0
- package/templates-app/star-survivor/PROJECT/Structure.md +63 -0
- package/templates-app/star-survivor/docs/project-2d-rules.md +53 -0
- package/templates-app/star-survivor/index.html +232 -0
- package/templates-app/star-survivor/package.json +23 -0
- package/templates-app/star-survivor/src/behaviors.ts +624 -0
- package/templates-app/star-survivor/src/game.scene.json +464 -0
- package/templates-app/star-survivor/src/main.ts +49 -0
- package/templates-app/star-survivor/tsconfig.json +13 -0
- package/templates-app/star-survivor/verify.ts +193 -0
- package/templates-app/star-survivor/vite.config.ts +12 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +6 -3
- package/templates-app/tps-3d/verify.ts +17 -1
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/grove.scene.json +14 -13
- package/templates-app/village-quest-3d/src/village.scene.json +5 -5
- package/dist/log-report-CPFm4OXf.js +0 -173
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ rule is JSON an AI agent can read, diff, and rewrite — rendered by three.js.
|
|
|
6
6
|
## Start with a whole game
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
bunx incanto
|
|
9
|
+
bunx incanto new my-game # Beacon Isle — the flagship 3D template
|
|
10
|
+
bunx incanto new my-2d --template platformer-2d # the 2D flagship
|
|
10
11
|
cd my-game && bun install && bun run dev
|
|
11
12
|
```
|
|
12
13
|
|
|
@@ -20,8 +21,9 @@ rest declared in JSON. `bun run verify` plays the entire quest **headlessly**
|
|
|
20
21
|
and replays recorded input bit-identically: the agent loop is author → verify →
|
|
21
22
|
fix, no browser needed.
|
|
22
23
|
|
|
23
|
-
`bunx incanto
|
|
24
|
-
quest vignette)
|
|
24
|
+
`bunx incanto new --list` shows all five starters, grouped by dimension: three
|
|
25
|
+
3D (island adventure, third-person shooter, quest vignette) and two 2D
|
|
26
|
+
(platformer, survivors-like).
|
|
25
27
|
|
|
26
28
|
## Or wire the engine yourself
|
|
27
29
|
|
|
@@ -76,7 +78,7 @@ in a world full of them). In code: `physics.debugDraw = true` + `physics.debugSc
|
|
|
76
78
|
|
|
77
79
|
## The agent8 asset library
|
|
78
80
|
|
|
79
|
-
`bunx incanto
|
|
81
|
+
`bunx incanto editor --token <v8 access token>` (or `INCANTO_V8_TOKEN`) adds a
|
|
80
82
|
**📚** button to every field that takes a resource — model, texture, sprite
|
|
81
83
|
sheet, sound. Shelves, search, real previews (a GLB is rendered by the engine
|
|
82
84
|
itself), and one pick writes the URL, or the scene `assets{}` entry plus its
|
package/bin/incanto-check.mjs
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* each scene file.
|
|
15
15
|
*/
|
|
16
16
|
import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
17
18
|
import { dirname, join, resolve } from 'node:path';
|
|
18
19
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
19
20
|
|
|
@@ -87,6 +88,15 @@ function missingArt(file, json) {
|
|
|
87
88
|
join(sceneDir, '..', rel),
|
|
88
89
|
];
|
|
89
90
|
if (candidates.some((p) => existsSync(p))) continue;
|
|
91
|
+
// A BARE PACKAGE SPECIFIER is a bundler import, not a path — and it is the
|
|
92
|
+
// form the assets skill teaches for built-in art
|
|
93
|
+
// (`incanto/assets/items/coin.png`). A scene had no way to say "this URL
|
|
94
|
+
// arrives from the bundler": a made-up placeholder (`$KNIGHT_URL`) got
|
|
95
|
+
// flagged here as art nobody copied, and an empty string — which the loop
|
|
96
|
+
// above still skips — is rejected outright by the loader. So the honest
|
|
97
|
+
// spelling was unwritable, and every 2D example that uses built-in art
|
|
98
|
+
// warned about art that works.
|
|
99
|
+
if (resolvesAsPackageAsset(url, sceneDir)) continue;
|
|
90
100
|
out.push(
|
|
91
101
|
`$${key} → ${url} is not in the project ` +
|
|
92
102
|
`(looked in ${relativeish(root, join(root, 'public'))}, ${relativeish(root, root)}` +
|
|
@@ -96,6 +106,23 @@ function missingArt(file, json) {
|
|
|
96
106
|
return out;
|
|
97
107
|
}
|
|
98
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Can this url be resolved as a file inside an installed package?
|
|
111
|
+
*
|
|
112
|
+
* `incanto/assets/items/coin.png` is what the bundler is handed and what the
|
|
113
|
+
* skills teach; if the package resolves it, the file is really there.
|
|
114
|
+
*/
|
|
115
|
+
function resolvesAsPackageAsset(url, from) {
|
|
116
|
+
if (url.startsWith('.') || url.startsWith('/')) return false;
|
|
117
|
+
// A bare specifier: `pkg/path` or `@scope/pkg/path`.
|
|
118
|
+
if (!/^(@[^/]+\/)?[^@/][^/]*\//.test(url)) return false;
|
|
119
|
+
try {
|
|
120
|
+
return existsSync(createRequire(join(from, 'noop.js')).resolve(url));
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
99
126
|
/** Nearest ancestor with a package.json, else the scene's own directory. */
|
|
100
127
|
function projectRoot(from) {
|
|
101
128
|
let dir = resolve(from);
|
package/bin/incanto-new.mjs
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* incanto-new — scaffold a ready-to-run incanto game from a shipped template:
|
|
4
4
|
*
|
|
5
|
-
* bunx incanto
|
|
6
|
-
* bunx incanto
|
|
7
|
-
* bunx incanto
|
|
5
|
+
* bunx incanto new my-game # Beacon Isle (3D flagship)
|
|
6
|
+
* bunx incanto new my-game --template tps-3d # third-person shooter
|
|
7
|
+
* bunx incanto new --list
|
|
8
8
|
*
|
|
9
9
|
* Copies the template, names the package after the directory, prints the
|
|
10
10
|
* three commands that get you playing. Templates are real npm consumers —
|
|
@@ -19,11 +19,25 @@ const TEMPLATES_DIR = join(PKG, 'templates-app');
|
|
|
19
19
|
|
|
20
20
|
const DESCRIPTIONS = {
|
|
21
21
|
'beacon-isle-3d':
|
|
22
|
-
'
|
|
23
|
-
'village-quest-3d': '
|
|
22
|
+
'island action-adventure (the 3D flagship): generated world, quest NPC, terrain-nav enemies, melee',
|
|
23
|
+
'village-quest-3d': 'quest vignette: dialogue, scene transitions, patrol AI, sword combat',
|
|
24
24
|
'tps-3d': 'third-person arena shooter: GLB soldier, hitscan rifle, enemy waves',
|
|
25
|
+
'platformer-2d':
|
|
26
|
+
'side-scrolling platformer (the 2D flagship): tilemap level, coyote-time jump, follow cam, coins',
|
|
27
|
+
'star-survivor':
|
|
28
|
+
'survivors-like: waves that never stop, auto-attack, upgrades, a clock to outlast',
|
|
25
29
|
};
|
|
26
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Which half of the engine a starter belongs to.
|
|
33
|
+
*
|
|
34
|
+
* The list was three templates, all 3D, printed in one alphabetical column —
|
|
35
|
+
* while the engine shipped a whole 2D half with its own nodes, physics,
|
|
36
|
+
* tilemaps and skill. Someone building a 2D game read that list and concluded
|
|
37
|
+
* there was nothing for them.
|
|
38
|
+
*/
|
|
39
|
+
const DIMENSION = (name) => (name.endsWith('-2d') || name === 'star-survivor' ? '2d' : '3d');
|
|
40
|
+
|
|
27
41
|
function listTemplates() {
|
|
28
42
|
if (!existsSync(TEMPLATES_DIR)) return [];
|
|
29
43
|
return readdirSync(TEMPLATES_DIR, { withFileTypes: true })
|
|
@@ -34,9 +48,17 @@ function listTemplates() {
|
|
|
34
48
|
|
|
35
49
|
const argv = process.argv.slice(2);
|
|
36
50
|
if (argv.includes('--list') || argv.includes('-l')) {
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
const all = listTemplates();
|
|
52
|
+
for (const [dim, heading] of [
|
|
53
|
+
['3d', '3D'],
|
|
54
|
+
['2d', '2D'],
|
|
55
|
+
]) {
|
|
56
|
+
const group = all.filter((t) => DIMENSION(t) === dim);
|
|
57
|
+
if (group.length === 0) continue;
|
|
58
|
+
console.log(`\n ${heading}`);
|
|
59
|
+
for (const t of group) console.log(` ${t.padEnd(20)} ${DESCRIPTIONS[t] ?? ''}`);
|
|
39
60
|
}
|
|
61
|
+
console.log('\n bunx incanto new my-game --template <name> (default: beacon-isle-3d)\n');
|
|
40
62
|
process.exit(0);
|
|
41
63
|
}
|
|
42
64
|
|
package/bin/incanto-playtest.mjs
CHANGED
|
@@ -78,9 +78,35 @@ if (args.behaviors) {
|
|
|
78
78
|
try {
|
|
79
79
|
mod = await import(pathToFileURL(resolve(args.behaviors)).href);
|
|
80
80
|
} catch (e) {
|
|
81
|
+
const why = e?.message ?? String(e);
|
|
82
|
+
// NODE CANNOT IMPORT THE PATTERN WE TEACH. Every template's behaviors.ts
|
|
83
|
+
// does `import gameJson from './game.scene.json'` — the documented way to
|
|
84
|
+
// read your own scene — and node's type stripping refuses a JSON import
|
|
85
|
+
// without `with { type: 'json' }`. Vite is fine with it, bun is fine with
|
|
86
|
+
// it, and this bin's shebang is node, so `--behaviors` failed on the
|
|
87
|
+
// engine's own flagship template. Hand the work to bun rather than telling
|
|
88
|
+
// the author their file is wrong: it is not.
|
|
89
|
+
const nodeCannotReadIt = /import attribute|Unknown file extension/i.test(why);
|
|
90
|
+
if (nodeCannotReadIt && !process.versions.bun && !process.env.INCANTO_BUN_REEXEC) {
|
|
91
|
+
const { spawnSync } = await import('node:child_process');
|
|
92
|
+
const hasBun = spawnSync('bun', ['--version'], { stdio: 'ignore' }).status === 0;
|
|
93
|
+
if (hasBun) {
|
|
94
|
+
const here = fileURLToPath(import.meta.url);
|
|
95
|
+
const again = spawnSync('bun', [here, ...process.argv.slice(2)], {
|
|
96
|
+
stdio: 'inherit',
|
|
97
|
+
env: { ...process.env, INCANTO_BUN_REEXEC: '1' },
|
|
98
|
+
});
|
|
99
|
+
process.exit(again.status ?? 1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
81
102
|
console.error(
|
|
82
|
-
`could not load --behaviors '${args.behaviors}' (${
|
|
83
|
-
|
|
103
|
+
`could not load --behaviors '${args.behaviors}' (${why}).` +
|
|
104
|
+
(nodeCannotReadIt
|
|
105
|
+
? '\n\nThis is node refusing your file, not a problem with it: node cannot' +
|
|
106
|
+
"\nimport JSON from a .ts module without `with { type: 'json' }`, and every" +
|
|
107
|
+
'\nincanto template imports its scene that way. Run it with bun, which' +
|
|
108
|
+
'\nreads the file as written: `bunx incanto playtest <scene> --behaviors <file>`.'
|
|
109
|
+
: '\nPass the file that exports your Behavior subclasses, e.g. src/behaviors.ts.'),
|
|
84
110
|
);
|
|
85
111
|
process.exit(1);
|
|
86
112
|
}
|
package/bin/incanto-verify.mjs
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { spawnSync } from 'node:child_process';
|
|
22
22
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
23
|
-
import { dirname, join } from 'node:path';
|
|
23
|
+
import { dirname, join, relative } from 'node:path';
|
|
24
24
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
25
25
|
|
|
26
26
|
const PKG = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -102,21 +102,55 @@ if (!existsSync(scene) || !statSync(scene).isFile()) {
|
|
|
102
102
|
process.exit(1);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Find the file that holds the game's Behavior subclasses.
|
|
107
|
+
*
|
|
108
|
+
* The scene was auto-discovered from the start; the behaviours were not, and
|
|
109
|
+
* without them a playtest runs the STRUCTURE with every script stubbed. On the
|
|
110
|
+
* engine's own flagship template that means the quest never advances, so bare
|
|
111
|
+
* `incanto-verify` — the exact command the README prints — reported `plays` as
|
|
112
|
+
* a failure and told the reader to re-run a different tool by hand, with the
|
|
113
|
+
* path to a file sitting right next to the scene it had already found.
|
|
114
|
+
*/
|
|
115
|
+
function findBehaviors(sceneFile) {
|
|
116
|
+
const named = flag('--behaviors');
|
|
117
|
+
if (named) return { file: named, guessed: false };
|
|
118
|
+
const near = dirname(sceneFile);
|
|
119
|
+
for (const dir of [near, join(near, '..'), process.cwd(), join(process.cwd(), 'src')]) {
|
|
120
|
+
for (const base of ['behaviors', 'behaviours']) {
|
|
121
|
+
for (const ext of ['.ts', '.js', '.mjs']) {
|
|
122
|
+
const candidate = join(dir, base + ext);
|
|
123
|
+
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
|
124
|
+
return { file: candidate, guessed: true };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { file: null, guessed: false };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const { file: behaviors, guessed: guessedBehaviors } = findBehaviors(scene);
|
|
106
133
|
const rungs = [];
|
|
107
134
|
|
|
108
135
|
// ---- loads ---------------------------------------------------------------
|
|
109
136
|
{
|
|
110
137
|
const r = run('check', [scene, '--json']);
|
|
111
138
|
const out = safeJson(r.stdout);
|
|
112
|
-
|
|
139
|
+
// `incanto-check --json` emits { ok, results: [{ file, ok, code, message }] }.
|
|
140
|
+
// This read `out.files[].problems[].why` — three keys that have never existed —
|
|
141
|
+
// so the summary always fell through to the first line of the pretty-printed
|
|
142
|
+
// JSON, which is `{`. Every scene-validation failure, the top rung of the
|
|
143
|
+
// ladder, rendered as `✗ loads — {` with the sentence naming the node path and
|
|
144
|
+
// the legal values sitting one key away in the same object.
|
|
145
|
+
const broke = out?.results?.find((entry) => entry && entry.ok === false);
|
|
146
|
+
const detail = broke && (broke.message || broke.code) ? `[${broke.code}] ${broke.message}` : null;
|
|
113
147
|
rungs.push(
|
|
114
148
|
r.status === 0
|
|
115
149
|
? { name: 'loads', status: 'pass', summary: 'the scene is legal and its assets resolve' }
|
|
116
150
|
: {
|
|
117
151
|
name: 'loads',
|
|
118
152
|
status: 'fail',
|
|
119
|
-
summary:
|
|
153
|
+
summary: detail ?? firstLine(r.stderr) ?? 'incanto-check failed',
|
|
120
154
|
fix: `fix that, then run again — nothing above this rung means anything until it is green (\`incanto-check ${scene}\` for the detail)`,
|
|
121
155
|
},
|
|
122
156
|
);
|
|
@@ -138,25 +172,61 @@ if (rungs[0].status === 'pass') {
|
|
|
138
172
|
// walkabout template has no end, and calling that a failure sends its author
|
|
139
173
|
// hunting a bug that was never there.
|
|
140
174
|
const noGoal = out && out.declaresWin === false;
|
|
175
|
+
// A quest — talk to the NPC, clear the enemies, light the wards — cannot be
|
|
176
|
+
// finished by a random walker, ever. Reporting that as a FAILED rung means
|
|
177
|
+
// the headline command permanently says NOT verified about a correct game,
|
|
178
|
+
// which teaches its author to stop reading it. If every run played to the
|
|
179
|
+
// end of its budget without erroring, falling or wedging, the rung has not
|
|
180
|
+
// failed: it has not measured, and the author's own scripted harness is what
|
|
181
|
+
// can judge this game.
|
|
182
|
+
// Three of the five outcomes are GAMEPLAY, not defects. `won`, `lost` and
|
|
183
|
+
// `unfinished` all mean the game ran; a random player dying half the time in
|
|
184
|
+
// a platformer is the hazards working. The defects are `error` (a behaviour
|
|
185
|
+
// threw), `fell` (left the world) and `stuck` (went nowhere), and those are
|
|
186
|
+
// what this rung is for.
|
|
187
|
+
const PLAYED = new Set(['won', 'lost', 'unfinished']);
|
|
188
|
+
const playedOut =
|
|
189
|
+
out && (out.runs?.length ?? 0) > 0 && out.runs.every((x) => PLAYED.has(x.outcome));
|
|
190
|
+
const tally = (name) => out?.runs?.filter((x) => x.outcome === name).length ?? 0;
|
|
141
191
|
rungs.push(
|
|
142
192
|
r.status === 0
|
|
143
193
|
? { name: 'plays', status: 'pass', summary: `${won} of ${total} seeded runs finished it` }
|
|
144
|
-
:
|
|
194
|
+
: playedOut
|
|
145
195
|
? {
|
|
146
196
|
name: 'plays',
|
|
147
197
|
status: 'unmeasured',
|
|
148
|
-
summary: `nothing declares a win — ${total} runs played without error, and there was no end to reach`,
|
|
149
|
-
fix: 'if it is meant to be finishable, emit `won` (GameFlow, ScoreKeeper, or your own behaviour)',
|
|
150
|
-
}
|
|
151
|
-
: {
|
|
152
|
-
name: 'plays',
|
|
153
|
-
status: 'fail',
|
|
154
198
|
summary:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
199
|
+
`${total} runs played without reaching a win` +
|
|
200
|
+
` (${[
|
|
201
|
+
tally('lost') && `${tally('lost')} lost`,
|
|
202
|
+
tally('unfinished') && `${tally('unfinished')} ran out the clock`,
|
|
203
|
+
]
|
|
204
|
+
.filter(Boolean)
|
|
205
|
+
.join(', ')})`,
|
|
206
|
+
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`',
|
|
207
|
+
}
|
|
208
|
+
: noGoal
|
|
209
|
+
? {
|
|
210
|
+
name: 'plays',
|
|
211
|
+
status: 'unmeasured',
|
|
212
|
+
summary: `nothing declares a win — ${total} runs played without error, and there was no end to reach`,
|
|
213
|
+
fix: 'if it is meant to be finishable, emit `won` (GameFlow, ScoreKeeper, or your own behaviour)',
|
|
214
|
+
}
|
|
215
|
+
: {
|
|
216
|
+
name: 'plays',
|
|
217
|
+
status: 'fail',
|
|
218
|
+
summary:
|
|
219
|
+
total > 0
|
|
220
|
+
? `no run finished it (${total} tried)`
|
|
221
|
+
: // "could not run" with no reason is the tool doing to its
|
|
222
|
+
// reader exactly what this whole ladder exists to prevent:
|
|
223
|
+
// reporting a failure it already knows the cause of. The
|
|
224
|
+
// child printed one; pass it on.
|
|
225
|
+
`the playtest could not run — ${firstLine(r.stderr) || `exit ${r.status}`}`,
|
|
226
|
+
fix: behaviors
|
|
227
|
+
? `see which runs stalled and where: \`incanto-playtest ${scene} --behaviors ${behaviors}\``
|
|
228
|
+
: `run it with your behaviours — without them the structure plays and your game logic does not: \`incanto-playtest ${scene} --behaviors src/behaviors.ts\``,
|
|
229
|
+
},
|
|
160
230
|
);
|
|
161
231
|
} else {
|
|
162
232
|
rungs.push({ name: 'plays', status: 'skipped', summary: 'not run — the scene does not load' });
|
|
@@ -291,7 +361,16 @@ function firstLine(text) {
|
|
|
291
361
|
}
|
|
292
362
|
|
|
293
363
|
const verdict = ladderVerdict(rungs);
|
|
294
|
-
|
|
364
|
+
if (asJson) {
|
|
365
|
+
console.log(JSON.stringify({ scene, behaviors, ...verdict }, null, 2));
|
|
366
|
+
} else {
|
|
367
|
+
if (guessedBehaviors) {
|
|
368
|
+
console.log(
|
|
369
|
+
`· behaviours: ${relative(process.cwd(), behaviors) || behaviors} (found, not named)`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
console.log(ladderText(verdict));
|
|
373
|
+
}
|
|
295
374
|
// `exitCode`, never `process.exit()`: stdout to a PIPE is written
|
|
296
375
|
// asynchronously, and exiting discards whatever has not flushed. A --json
|
|
297
376
|
// report read by another program came back truncated — silently, and only
|
package/bin/incanto.mjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* incanto — the front door.
|
|
4
|
+
*
|
|
5
|
+
* bunx incanto new my-game scaffold a game (no install needed first)
|
|
6
|
+
* bunx incanto check src validate scene JSON
|
|
7
|
+
* bunx incanto verify the whole verification ladder
|
|
8
|
+
*
|
|
9
|
+
* WHY THIS EXISTS. Every doc used to open with `bunx incanto-new my-game`, and
|
|
10
|
+
* that command 404s for the one person it is written for. `bunx <name>` fetches
|
|
11
|
+
* the PACKAGE called `<name>`; it does not search the registry for a package
|
|
12
|
+
* that happens to ship a bin by that name. There is no `incanto-new` package,
|
|
13
|
+
* so a brand-new reader's very first command failed — while every existing
|
|
14
|
+
* consumer ran it happily, because with `incanto` already in `node_modules` the
|
|
15
|
+
* same words resolve to the local bin. The funnel was the one path nobody
|
|
16
|
+
* walked.
|
|
17
|
+
*
|
|
18
|
+
* `bunx incanto <subcommand>` works from nothing, because `incanto` IS the
|
|
19
|
+
* package name. The `incanto-*` bins all still exist and are unchanged — inside
|
|
20
|
+
* a project they are the shorter thing to type.
|
|
21
|
+
*
|
|
22
|
+
* Dispatch is an in-process import, not a spawn: the child bin then runs under
|
|
23
|
+
* the SAME runtime that started this one, which matters because `--behaviors`
|
|
24
|
+
* loads your TypeScript and bun and node disagree about what they can import.
|
|
25
|
+
*/
|
|
26
|
+
import { readFileSync } from 'node:fs';
|
|
27
|
+
import { dirname, join } from 'node:path';
|
|
28
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
29
|
+
|
|
30
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
|
|
32
|
+
/** Subcommand → bin file, in the order the help should list them. */
|
|
33
|
+
const COMMANDS = {
|
|
34
|
+
new: ['incanto-new.mjs', 'scaffold a ready-to-run game from a template'],
|
|
35
|
+
check: ['incanto-check.mjs', 'validate scene JSON and the assets it names'],
|
|
36
|
+
verify: ['incanto-verify.mjs', 'the whole ladder: loads · plays · feels · draws · says'],
|
|
37
|
+
playtest: ['incanto-playtest.mjs', 'drive the game headlessly and report what happened'],
|
|
38
|
+
play: ['incanto-play.mjs', 'run a scene headlessly for a fixed span'],
|
|
39
|
+
feel: ['incanto-feel.mjs', 'measure jump height, run speed, facing'],
|
|
40
|
+
frame: ['incanto-frame.mjs', 'what the running page is drawing right now'],
|
|
41
|
+
logs: ['incanto-logs.mjs', 'what the running page has been saying'],
|
|
42
|
+
editor: ['incanto-editor.mjs', 'the visual scene composer'],
|
|
43
|
+
model: ['incanto-model.mjs', "a GLB's real bounds, animations and rig"],
|
|
44
|
+
assets: ['incanto-assets.mjs', 'the built-in art and audio catalog'],
|
|
45
|
+
env: ['incanto-env.mjs', 'generate a 3D environment'],
|
|
46
|
+
skills: ['incanto-skills.mjs', 'install the agent skills for this engine version'],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function version() {
|
|
50
|
+
try {
|
|
51
|
+
const pkg = JSON.parse(readFileSync(join(HERE, '..', 'package.json'), 'utf-8'));
|
|
52
|
+
return pkg.version ?? 'unknown';
|
|
53
|
+
} catch {
|
|
54
|
+
return 'unknown';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function help() {
|
|
59
|
+
const width = Math.max(...Object.keys(COMMANDS).map((k) => k.length));
|
|
60
|
+
const lines = Object.entries(COMMANDS).map(
|
|
61
|
+
([name, [, blurb]]) => ` ${name.padEnd(width)} ${blurb}`,
|
|
62
|
+
);
|
|
63
|
+
return [
|
|
64
|
+
`incanto ${version()} — a vibe-coding-first web game engine`,
|
|
65
|
+
'',
|
|
66
|
+
'Usage: bunx incanto <command> [options]',
|
|
67
|
+
'',
|
|
68
|
+
...lines,
|
|
69
|
+
'',
|
|
70
|
+
'Start a game: bunx incanto new my-game',
|
|
71
|
+
'See the templates: bunx incanto new --list',
|
|
72
|
+
'',
|
|
73
|
+
'Each command also ships as its own bin (`incanto-new`, `incanto-check`, …),',
|
|
74
|
+
'which is what to type once the package is installed in your project.',
|
|
75
|
+
].join('\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
79
|
+
|
|
80
|
+
if (!command || command === '--help' || command === '-h' || command === 'help') {
|
|
81
|
+
console.log(help());
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
if (command === '--version' || command === '-v') {
|
|
85
|
+
console.log(version());
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const entry = COMMANDS[command];
|
|
90
|
+
if (!entry) {
|
|
91
|
+
const near = Object.keys(COMMANDS).filter((k) => k.startsWith(command[0] ?? ''));
|
|
92
|
+
console.error(
|
|
93
|
+
`incanto: unknown command '${command}'.` +
|
|
94
|
+
(near.length ? ` Did you mean: ${near.join(', ')}?` : '') +
|
|
95
|
+
'\n\n' +
|
|
96
|
+
help(),
|
|
97
|
+
);
|
|
98
|
+
process.exit(2);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const target = join(HERE, entry[0]);
|
|
102
|
+
// The child reads `process.argv.slice(2)`, so hand it an argv that looks like
|
|
103
|
+
// it was invoked directly. Importing rather than spawning keeps one process
|
|
104
|
+
// and, more importantly, one runtime.
|
|
105
|
+
process.argv = [process.argv[0], target, ...rest];
|
|
106
|
+
await import(pathToFileURL(target).href);
|
package/dist/2d.d.ts
CHANGED
|
@@ -1,7 +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-l08AEbq9.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
|
-
import {
|
|
4
|
+
import { r as FrameStats } from "./frame-report-DCnHFmto.js";
|
|
5
|
+
import { t as LoadSceneOptions } from "./loader-BbEMTuWg.js";
|
|
5
6
|
import { n as AnimationEntry } from "./sprite-animation-CMr6f1K2.js";
|
|
6
7
|
import { r as ParticleView, t as ParticleSim } from "./particle-sim-C5OfBbmU.js";
|
|
7
8
|
import { Group, Mesh, Object3D, Scene, Texture } from "three";
|
|
@@ -388,6 +389,16 @@ interface Game2D {
|
|
|
388
389
|
engine: Engine;
|
|
389
390
|
scene: Scene$1;
|
|
390
391
|
/**
|
|
392
|
+
* What the last drawn frame actually looks like, as numbers.
|
|
393
|
+
*
|
|
394
|
+
* The 3D game has answered this since 0.47 and a 2D game could not, so
|
|
395
|
+
* `incanto-frame` and `incanto-verify`'s `draws` rung were permanently
|
|
396
|
+
* unmeasured for half the engine's games.
|
|
397
|
+
*/
|
|
398
|
+
frame(opts?: {
|
|
399
|
+
grid?: [number, number];
|
|
400
|
+
}): Promise<FrameStats>;
|
|
401
|
+
/**
|
|
391
402
|
* The AUTHORED scene this game booted from — the JSON, not the live tree.
|
|
392
403
|
* A running tree has moved; the editor gets what was written down.
|
|
393
404
|
*/
|
|
@@ -691,6 +702,14 @@ declare class Label extends Node2D {
|
|
|
691
702
|
_quad(): Mesh;
|
|
692
703
|
override _syncObject2D(assets: AssetStore2D | null): void;
|
|
693
704
|
private rasterize;
|
|
705
|
+
/**
|
|
706
|
+
* Hand the canvas and its texture back.
|
|
707
|
+
*
|
|
708
|
+
* LABEL_PLANE is shared by every Label in the process and stays; the material
|
|
709
|
+
* and the CanvasTexture are this node's, and a scene that spawns floating
|
|
710
|
+
* text left one of each behind per freed node.
|
|
711
|
+
*/
|
|
712
|
+
override free(): void;
|
|
694
713
|
}
|
|
695
714
|
//#endregion
|
|
696
715
|
//#region src/2d/nodes/particles-2d.d.ts
|
|
@@ -768,6 +787,15 @@ declare class Particles2D extends Node2D {
|
|
|
768
787
|
* (dust under a player) as well as the node's own position.
|
|
769
788
|
*/
|
|
770
789
|
private holdWorldSpace;
|
|
790
|
+
/** What the current mesh was built FOR — capacity and blend, the two things
|
|
791
|
+
* baked into it. Not the draw count, which changes every frame. */
|
|
792
|
+
private meshKey;
|
|
793
|
+
/**
|
|
794
|
+
* Hand the GPU back. UNIT_PLANE is shared by every 2D particle system in the
|
|
795
|
+
* process, so the geometry stays; the per-node material and the instance
|
|
796
|
+
* buffers inside the InstancedMesh do not.
|
|
797
|
+
*/
|
|
798
|
+
override free(): void;
|
|
771
799
|
/** Tell the engine an effect fired — the visual half of the audio record. */
|
|
772
800
|
private report;
|
|
773
801
|
/** Restart a one-shot (re-burst + allow finished to fire again). */
|
|
@@ -815,6 +843,8 @@ declare class TileMap2D extends Node2D {
|
|
|
815
843
|
private _solid;
|
|
816
844
|
/** Rebuild flags — raised when the map-shaping props are REPLACED. */
|
|
817
845
|
private geometryDirty;
|
|
846
|
+
/** Reported once per atlas, so a bad grid says so on the first build only. */
|
|
847
|
+
private gridChecked;
|
|
818
848
|
private collidersDirty;
|
|
819
849
|
/** Replace the whole array to change the map (mutations are not watched). */
|
|
820
850
|
get cells(): (string | number[])[];
|
|
@@ -909,11 +939,6 @@ interface Renderer2DOptions {
|
|
|
909
939
|
/** MSAA on the canvas. Defaults to TRUE (Phaser parity). */
|
|
910
940
|
antialias?: boolean;
|
|
911
941
|
}
|
|
912
|
-
/**
|
|
913
|
-
* 2D presentation layer: world pass through the active Camera2D
|
|
914
|
-
* (center/zoom/limits), then a screen-space UI pass for UILayer subtrees.
|
|
915
|
-
* 1 unit = 1 px, (0,0) top-left, +y down.
|
|
916
|
-
*/
|
|
917
942
|
declare class Renderer2D {
|
|
918
943
|
/**
|
|
919
944
|
* When set, the world pass frames THIS view instead of the scene's active
|
|
@@ -926,6 +951,33 @@ declare class Renderer2D {
|
|
|
926
951
|
zoom: number;
|
|
927
952
|
} | null;
|
|
928
953
|
private readonly webgl;
|
|
954
|
+
/**
|
|
955
|
+
* The pixels of the next drawn frame — how `incanto-frame` sees a 2D game.
|
|
956
|
+
*
|
|
957
|
+
* Mirrors Renderer3D exactly: read inside the render, so
|
|
958
|
+
* `preserveDrawingBuffer` can stay off, and nudge a frame ourselves after a
|
|
959
|
+
* moment because a browser stops handing rAF to a tab it thinks is hidden —
|
|
960
|
+
* including a window merely covered by another one. Without the nudge the
|
|
961
|
+
* promise never settles and the CLI reports "no page connected" about a page
|
|
962
|
+
* that is right there.
|
|
963
|
+
*/
|
|
964
|
+
captureFrame(): Promise<{
|
|
965
|
+
pixels: Uint8Array;
|
|
966
|
+
width: number;
|
|
967
|
+
height: number;
|
|
968
|
+
}>;
|
|
969
|
+
private pendingCaptures;
|
|
970
|
+
/** Hand the waiting captures this frame's pixels. Called at the end of render. */
|
|
971
|
+
private drainFrameCapture;
|
|
972
|
+
/** The pixel ratio the scene asked for, before any render-scale setting. */
|
|
973
|
+
private basePixelRatio;
|
|
974
|
+
/**
|
|
975
|
+
* Fewer pixels for the same world — the cheapest frames on a weak device.
|
|
976
|
+
*
|
|
977
|
+
* `settings.renderScale` persisted and was applied by the 3D boot only, so a
|
|
978
|
+
* 2D game's resolution slider saved a number and changed nothing, forever.
|
|
979
|
+
*/
|
|
980
|
+
setRenderScale(scale: number): void;
|
|
929
981
|
private readonly worldScene;
|
|
930
982
|
private readonly syncScratch;
|
|
931
983
|
/** Editors set this: keep syncing `static: true` subtrees every frame. */
|
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-BZwWJIns.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-C6ZBFRjd.js";
|
|
4
|
+
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-EqA-jddf.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-l08AEbq9.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
|
-
import { t as
|
|
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-BbEMTuWg.js";
|
|
5
6
|
import { r as ParticleView, t as ParticleSim } from "./particle-sim-C5OfBbmU.js";
|
|
6
|
-
import {
|
|
7
|
-
import { n as PathGrid, s as SpatialPose } from "./pathfinding-CXGCpRQe.js";
|
|
7
|
+
import { n as PathGrid, s as SpatialPose } from "./pathfinding-mEN4V1CU.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";
|
|
@@ -902,6 +902,9 @@ declare class Environment3D {
|
|
|
902
902
|
/** Underwater fog/tint, created lazily the first time the camera submerges. */
|
|
903
903
|
private underwaterFog;
|
|
904
904
|
private readonly underwaterBg;
|
|
905
|
+
/** Told when the scene's HDRI/preset lighting fails to arrive. Set by the
|
|
906
|
+
* renderer, which is the piece that knows the engine and the asset store. */
|
|
907
|
+
onHdriError: ((url: string, error: unknown) => void) | null;
|
|
905
908
|
constructor(scene: Scene);
|
|
906
909
|
/** The sky's unit sun direction, or null when no sky is declared. */
|
|
907
910
|
get sunDirection(): [number, number, number] | null;
|
|
@@ -1141,6 +1144,21 @@ declare class BoneAttachment3D extends Node3D {
|
|
|
1141
1144
|
get attachedBone(): Object3D | null;
|
|
1142
1145
|
_onRender3D(_ctx: RenderContext3D): void;
|
|
1143
1146
|
override free(): void;
|
|
1147
|
+
/** The model this attachment last looked in. */
|
|
1148
|
+
private source;
|
|
1149
|
+
private reportedBone;
|
|
1150
|
+
/**
|
|
1151
|
+
* Say when the bone NAME is wrong, without accusing a model that is still
|
|
1152
|
+
* loading.
|
|
1153
|
+
*
|
|
1154
|
+
* The lookup retries every frame on purpose — a GLB mounts async — so the
|
|
1155
|
+
* node can never conclude on its own that the name is bad. But once the model
|
|
1156
|
+
* HAS bones and this one is not among them, it is a typo, and the list that
|
|
1157
|
+
* would fix it is sitting in `boneNames()` right next to `findBone`. Until
|
|
1158
|
+
* now a misspelled bone left the sword at the mount node's authored position
|
|
1159
|
+
* while the character animated away from it, forever, in silence.
|
|
1160
|
+
*/
|
|
1161
|
+
private reportMissingBone;
|
|
1144
1162
|
}
|
|
1145
1163
|
//#endregion
|
|
1146
1164
|
//#region src/3d/nodes/bone-look-at-3d.d.ts
|
|
@@ -1262,6 +1280,18 @@ declare class CharacterController3D extends Node3D {
|
|
|
1262
1280
|
private get body();
|
|
1263
1281
|
override onEnterTree(): void;
|
|
1264
1282
|
override fixedUpdate(dt: number): void;
|
|
1283
|
+
/** Actions already reported missing, so a per-frame read says it once. */
|
|
1284
|
+
private readonly reportedActions;
|
|
1285
|
+
/**
|
|
1286
|
+
* Read an input action this controller is allowed to be missing.
|
|
1287
|
+
*
|
|
1288
|
+
* Tolerated because a game may declare no sprint key and no jump; reported
|
|
1289
|
+
* because otherwise a TYPO looks exactly like a deliberate omission, and the
|
|
1290
|
+
* engine has already thrown the sentence that fixes it.
|
|
1291
|
+
*/
|
|
1292
|
+
private readAction;
|
|
1293
|
+
/** Report a throw the caller already caught (the move read has its own try). */
|
|
1294
|
+
private reportMissingAction;
|
|
1265
1295
|
override update(dt: number): void;
|
|
1266
1296
|
private pivot;
|
|
1267
1297
|
private cameraCache;
|
package/dist/3d.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { a as
|
|
4
|
-
import { a as
|
|
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-BVphcxmE.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-CvvQr_bJ.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-D10bU5_J.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-Dnz4fsXX.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;
|