@bitmagic/cli 0.1.32 → 0.1.34
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 +1 -1
- package/dist/browser-gl.d.ts +12 -0
- package/dist/browser-gl.js +15 -0
- package/dist/browser-gl.js.map +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +57 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/scaffold/project.d.ts +33 -0
- package/dist/scaffold/project.js +41 -6
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scaffold/suggest-template.d.ts +31 -0
- package/dist/scaffold/suggest-template.js +41 -0
- package/dist/scaffold/suggest-template.js.map +1 -0
- package/dist/verify/browser.js +115 -1
- package/dist/verify/browser.js.map +1 -1
- package/dist/verify/classify.d.ts +28 -0
- package/dist/verify/classify.js +48 -0
- package/dist/verify/classify.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ browser reloads itself on every change and the assets panel shows each generatio
|
|
|
37
37
|
| `bitmagic login [--env <env>]` | Device-code auth against Auth0; stores credentials per environment. |
|
|
38
38
|
| `bitmagic logout [--env <env>]` | Remove stored credentials for an environment. |
|
|
39
39
|
| `bitmagic whoami [--env <env>]` | Show the identity the CLI is authenticated as. |
|
|
40
|
-
| `bitmagic init [dir] [--template <id>] [--name <name>] [--idea "<pitch>"] [--env <env>] [--force]` | Scaffold a new project: mints a game, downloads the vendored engine, writes `AGENTS.md`, `GAME-DESIGN.md` and templates. `--idea` seeds the design doc's pitch. |
|
|
40
|
+
| `bitmagic init [dir] [--template <id>] [--name <name>] [--idea "<pitch>"] [--env <env>] [--force]` | Scaffold a new project: mints a game, downloads the vendored engine, writes `AGENTS.md`, `GAME-DESIGN.md` and templates. Starts from `empty-3d` unless told otherwise. `--idea` seeds the design doc's pitch **and** picks the template whose camera and controls fit it — "a doom-like corridor shooter" scaffolds `first-person`, "jump between floating islands" scaffolds `sidescroller`. `--template` overrides both; a suggestion that fails for any reason falls back to `empty-3d` rather than failing the scaffold. |
|
|
41
41
|
| `bitmagic check` | Typecheck (`tsc --noEmit`) against the vendored engine. Fast; does not prove the game runs. |
|
|
42
42
|
| `bitmagic dev [--port <port>] [--editor-port <port>] [--no-open]` | Build, then serve everything and keep it running: `tsc --watch`, `vite` on 3010, and a **Game / Editor** view on 3011 that reloads itself when the project changes. Opens the view in your browser; `--no-open` (or `BITMAGIC_NO_OPEN=1`) skips that. The one command to leave open. See **The dev view** below. |
|
|
43
43
|
| `bitmagic reload [--port <port>]` | Tell a running `bitmagic dev` to reload the browser now. For agents: run it when you finish a round of edits. Exits 0 and does nothing when no dev server is running. |
|
package/dist/browser-gl.d.ts
CHANGED
|
@@ -16,6 +16,18 @@
|
|
|
16
16
|
* detached agent run, an ssh session). The Vulkan backend needs no display at all and reaches the
|
|
17
17
|
* discrete GPU either way, so the result does not depend on how the CLI was invoked.
|
|
18
18
|
*
|
|
19
|
+
* `--disable-gpu-compositing` is load-bearing and must not be dropped as "an optimisation we do
|
|
20
|
+
* not need". Headless Chrome cannot composite a GPU-backed WebGL canvas: with GPU compositing on,
|
|
21
|
+
* ANGLE's Vulkan backend LOSES the context about 400ms into the page (measured: `webglcontextlost`
|
|
22
|
+
* at 373-420ms, in both `launch` and `launchPersistentContext`), and everything after that draws
|
|
23
|
+
* into nothing. It is nearly invisible — `requestAnimationFrame` keeps firing, the engine logs its
|
|
24
|
+
* usual start lines, the DOM HUD still paints, and the only symptom is that every screenshot comes
|
|
25
|
+
* back black. `bitmagic verify` reported PASSED like that, and since a project with no cover art
|
|
26
|
+
* publishes its verify screenshot as the game's thumbnail, a black frame would have shipped with
|
|
27
|
+
* nothing to flag it. Turning compositing off makes the compositor read the canvas back on the CPU
|
|
28
|
+
* — the GPU still does all the rendering, and the fill-rate win is intact (measured 0.29ms/frame
|
|
29
|
+
* against 97.8ms in software).
|
|
30
|
+
*
|
|
19
31
|
* `--enable-unsafe-swiftshader` stays on in BOTH modes. It does not force software rendering
|
|
20
32
|
* (`--use-angle=swiftshader` does that); it only permits the fallback, which Chrome otherwise
|
|
21
33
|
* deprecation-warns about and is on a path to refusing outright. Keeping it means a machine with
|
package/dist/browser-gl.js
CHANGED
|
@@ -16,6 +16,18 @@
|
|
|
16
16
|
* detached agent run, an ssh session). The Vulkan backend needs no display at all and reaches the
|
|
17
17
|
* discrete GPU either way, so the result does not depend on how the CLI was invoked.
|
|
18
18
|
*
|
|
19
|
+
* `--disable-gpu-compositing` is load-bearing and must not be dropped as "an optimisation we do
|
|
20
|
+
* not need". Headless Chrome cannot composite a GPU-backed WebGL canvas: with GPU compositing on,
|
|
21
|
+
* ANGLE's Vulkan backend LOSES the context about 400ms into the page (measured: `webglcontextlost`
|
|
22
|
+
* at 373-420ms, in both `launch` and `launchPersistentContext`), and everything after that draws
|
|
23
|
+
* into nothing. It is nearly invisible — `requestAnimationFrame` keeps firing, the engine logs its
|
|
24
|
+
* usual start lines, the DOM HUD still paints, and the only symptom is that every screenshot comes
|
|
25
|
+
* back black. `bitmagic verify` reported PASSED like that, and since a project with no cover art
|
|
26
|
+
* publishes its verify screenshot as the game's thumbnail, a black frame would have shipped with
|
|
27
|
+
* nothing to flag it. Turning compositing off makes the compositor read the canvas back on the CPU
|
|
28
|
+
* — the GPU still does all the rendering, and the fill-rate win is intact (measured 0.29ms/frame
|
|
29
|
+
* against 97.8ms in software).
|
|
30
|
+
*
|
|
19
31
|
* `--enable-unsafe-swiftshader` stays on in BOTH modes. It does not force software rendering
|
|
20
32
|
* (`--use-angle=swiftshader` does that); it only permits the fallback, which Chrome otherwise
|
|
21
33
|
* deprecation-warns about and is on a path to refusing outright. Keeping it means a machine with
|
|
@@ -47,6 +59,9 @@ export function browserGlArgs(env, platform = process.platform) {
|
|
|
47
59
|
// driver versions over bugs that do not touch a headless WebGL canvas.
|
|
48
60
|
'--ignore-gpu-blocklist',
|
|
49
61
|
'--enable-gpu',
|
|
62
|
+
// Not optional — without it the WebGL context dies ~400ms in and every frame is black. See
|
|
63
|
+
// the file header.
|
|
64
|
+
'--disable-gpu-compositing',
|
|
50
65
|
'--enable-unsafe-swiftshader',
|
|
51
66
|
];
|
|
52
67
|
}
|
package/dist/browser-gl.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-gl.js","sourceRoot":"","sources":["../src/browser-gl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"browser-gl.js","sourceRoot":"","sources":["../src/browser-gl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAKH,oGAAoG;AACpG,MAAM,gBAAgB,GAAG,CAAC,6BAA6B,EAAE,gBAAgB,EAAE,yBAAyB,CAAC,CAAC;AAEtG;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAC3B,GAAuC,EACvC,WAAqB,OAAO,CAAC,QAAQ;IAErC,IAAI,GAAG,CAAC,EAAE,KAAK,MAAM,IAAI,GAAG,CAAC,4BAA4B,KAAK,MAAM;QAAE,OAAO,CAAC,GAAG,gBAAgB,CAAC,CAAC;IACnG,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,CAAC,6BAA6B,CAAC,CAAC;IACjE,OAAO;QACL,oBAAoB;QACpB,0BAA0B;QAC1B,6FAA6F;QAC7F,uEAAuE;QACvE,wBAAwB;QACxB,cAAc;QACd,2FAA2F;QAC3F,mBAAmB;QACnB,2BAA2B;QAC3B,6BAA6B;KAC9B,CAAC;AACJ,CAAC"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ declare const rawCommands: {
|
|
|
46
46
|
};
|
|
47
47
|
readonly template: {
|
|
48
48
|
readonly type: "string";
|
|
49
|
-
readonly description: "Template id (see templates in the engine bundle).";
|
|
49
|
+
readonly description: "Template id (see templates in the engine bundle). Defaults to empty-3d, or to whatever --idea suggests.";
|
|
50
50
|
};
|
|
51
51
|
readonly name: {
|
|
52
52
|
readonly type: "string";
|
|
@@ -54,7 +54,7 @@ declare const rawCommands: {
|
|
|
54
54
|
};
|
|
55
55
|
readonly idea: {
|
|
56
56
|
readonly type: "string";
|
|
57
|
-
readonly description: "One-line pitch
|
|
57
|
+
readonly description: "One-line pitch. Seeds GAME-DESIGN.md and picks the template whose camera and controls fit it. Optional — you can write it yourself.";
|
|
58
58
|
};
|
|
59
59
|
readonly env: {
|
|
60
60
|
readonly type: "string";
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const initCommand: import("citty").CommandDef<{
|
|
|
6
6
|
};
|
|
7
7
|
readonly template: {
|
|
8
8
|
readonly type: "string";
|
|
9
|
-
readonly description: "Template id (see templates in the engine bundle).";
|
|
9
|
+
readonly description: "Template id (see templates in the engine bundle). Defaults to empty-3d, or to whatever --idea suggests.";
|
|
10
10
|
};
|
|
11
11
|
readonly name: {
|
|
12
12
|
readonly type: "string";
|
|
@@ -14,7 +14,7 @@ export declare const initCommand: import("citty").CommandDef<{
|
|
|
14
14
|
};
|
|
15
15
|
readonly idea: {
|
|
16
16
|
readonly type: "string";
|
|
17
|
-
readonly description: "One-line pitch
|
|
17
|
+
readonly description: "One-line pitch. Seeds GAME-DESIGN.md and picks the template whose camera and controls fit it. Optional — you can write it yourself.";
|
|
18
18
|
};
|
|
19
19
|
readonly env: {
|
|
20
20
|
readonly type: "string";
|
package/dist/commands/init.js
CHANGED
|
@@ -10,7 +10,8 @@ import { readDefaultEnvironment } from '../config/credentials.js';
|
|
|
10
10
|
import { getAccessToken } from '../auth/session.js';
|
|
11
11
|
import { apiPost } from '../http/client.js';
|
|
12
12
|
import { fetchEngineManifest, downloadAndVerify, extractEngine } from '../scaffold/engine-download.js';
|
|
13
|
-
import { resolveTemplate, scaffoldProject } from '../scaffold/project.js';
|
|
13
|
+
import { DEFAULT_TEMPLATE_ID, SUGGESTION_EXCLUDED_TEMPLATE_IDS, readTemplateIndex, resolveTemplate, scaffoldProject, } from '../scaffold/project.js';
|
|
14
|
+
import { suggestTemplate } from '../scaffold/suggest-template.js';
|
|
14
15
|
const deps = {
|
|
15
16
|
fetch: globalThis.fetch,
|
|
16
17
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
@@ -85,15 +86,64 @@ function printNextSteps(output, dir, gameId, installSucceeded, hasIdea) {
|
|
|
85
86
|
output.info(' bitmagic cover');
|
|
86
87
|
output.info(' bitmagic check');
|
|
87
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Decide which template to scaffold from.
|
|
91
|
+
*
|
|
92
|
+
* `--template` wins outright and skips the model call entirely: someone who named a template has
|
|
93
|
+
* already made the decision this function otherwise makes for them.
|
|
94
|
+
*
|
|
95
|
+
* With `--idea` and no `--template`, the pitch names a camera and control scheme far more often
|
|
96
|
+
* than not ("a doom-like corridor shooter" is first-person; "jump between floating islands" is a
|
|
97
|
+
* sidescroller), so it is worth one small model call to start from the right controls rather than
|
|
98
|
+
* making the creator's agent rewrite them. A suggestion that fails for ANY reason falls through to
|
|
99
|
+
* the default — the creator gets a working scaffold and a one-line note, never a failed `init`.
|
|
100
|
+
*/
|
|
101
|
+
async function chooseTemplate(templatesDir, args, environment, token, output) {
|
|
102
|
+
if (args.template !== undefined) {
|
|
103
|
+
return { template: resolveTemplate(templatesDir, args.template), source: 'explicit' };
|
|
104
|
+
}
|
|
105
|
+
const fallback = resolveTemplate(templatesDir);
|
|
106
|
+
if (args.idea === undefined) {
|
|
107
|
+
return { template: fallback, source: 'default' };
|
|
108
|
+
}
|
|
109
|
+
const candidates = readTemplateIndex(templatesDir).filter((t) => !SUGGESTION_EXCLUDED_TEMPLATE_IDS.includes(t.id));
|
|
110
|
+
// Nothing left to choose between — don't spend a round trip to be told so.
|
|
111
|
+
if (candidates.length === 0) {
|
|
112
|
+
return { template: fallback, source: 'default' };
|
|
113
|
+
}
|
|
114
|
+
output.info('Matching your idea to a template…');
|
|
115
|
+
const suggestion = await suggestTemplate({
|
|
116
|
+
environment,
|
|
117
|
+
token,
|
|
118
|
+
idea: args.idea,
|
|
119
|
+
templates: candidates,
|
|
120
|
+
defaultTemplateId: DEFAULT_TEMPLATE_ID,
|
|
121
|
+
deps: { fetch: globalThis.fetch },
|
|
122
|
+
});
|
|
123
|
+
if (!suggestion) {
|
|
124
|
+
output.info(`Could not match your idea to a template — starting from "${fallback.id}".`);
|
|
125
|
+
return { template: fallback, source: 'default' };
|
|
126
|
+
}
|
|
127
|
+
// Safe by construction: suggestTemplate only returns an id it found in the list it was given,
|
|
128
|
+
// which is the same index resolveTemplate reads.
|
|
129
|
+
const template = resolveTemplate(templatesDir, suggestion.templateId);
|
|
130
|
+
output.info(suggestion.reason
|
|
131
|
+
? `Using template "${template.id}" — ${suggestion.reason}.`
|
|
132
|
+
: `Using template "${template.id}".`);
|
|
133
|
+
return { template, source: 'idea' };
|
|
134
|
+
}
|
|
88
135
|
export const initCommand = defineCommand({
|
|
89
136
|
meta: { name: 'init', description: 'Scaffold a new Bitmagic game project.' },
|
|
90
137
|
args: {
|
|
91
138
|
dir: { type: 'positional', required: false, description: 'Directory to create the project in.' },
|
|
92
|
-
template: {
|
|
139
|
+
template: {
|
|
140
|
+
type: 'string',
|
|
141
|
+
description: `Template id (see templates in the engine bundle). Defaults to ${DEFAULT_TEMPLATE_ID}, or to whatever --idea suggests.`,
|
|
142
|
+
},
|
|
93
143
|
name: { type: 'string', description: 'Game name.' },
|
|
94
144
|
idea: {
|
|
95
145
|
type: 'string',
|
|
96
|
-
description: 'One-line pitch
|
|
146
|
+
description: 'One-line pitch. Seeds GAME-DESIGN.md and picks the template whose camera and controls fit it. Optional — you can write it yourself.',
|
|
97
147
|
},
|
|
98
148
|
env: { type: 'string', description: 'Environment (dev, prod, local).' },
|
|
99
149
|
force: { type: 'boolean', description: 'Scaffold into a non-empty directory.' },
|
|
@@ -124,7 +174,7 @@ export const initCommand = defineCommand({
|
|
|
124
174
|
const extracted = path.join(workDir, 'extracted');
|
|
125
175
|
const templatesDir = path.join(workDir, 'templates');
|
|
126
176
|
await extractEngine(tarballPath, extracted, templatesDir);
|
|
127
|
-
const template =
|
|
177
|
+
const { template, source: templateSource } = await chooseTemplate(templatesDir, args, environment, token, output);
|
|
128
178
|
// Minted only after the engine is in hand: a failed download must not leave an orphaned,
|
|
129
179
|
// unreachable game row behind.
|
|
130
180
|
const projectName = args.name ?? path.basename(targetDir);
|
|
@@ -150,6 +200,9 @@ export const initCommand = defineCommand({
|
|
|
150
200
|
gameId: created.gameId,
|
|
151
201
|
root: targetDir,
|
|
152
202
|
template: template.id,
|
|
203
|
+
// 'explicit' (--template), 'idea' (matched from --idea) or 'default'. An agent driving
|
|
204
|
+
// --json needs to know whether the template was chosen or merely fallen back to.
|
|
205
|
+
templateSource,
|
|
153
206
|
genre: template.genre,
|
|
154
207
|
engineVersion: manifest.version,
|
|
155
208
|
// An agent needs to know whether to run the install itself before anything else works.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,YAAY,EAAe,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACvG,OAAO,
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,YAAY,EAAe,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACvG,OAAO,EACL,mBAAmB,EACnB,gCAAgC,EAChC,iBAAiB,EACjB,eAAe,EACf,eAAe,GAEhB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAGlE,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,UAAU,CAAC,KAAK;IACvB,KAAK,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;CAC/E,CAAC;AAMF,oGAAoG;AACpG,SAAS,kBAAkB,CAAC,GAAW,EAAE,KAAc;IACrD,IAAI,KAAK;QAAE,OAAO;IAClB,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAAyB,CAAC,IAAI,CAAC;QAC7C,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,2DAA2D;YAC3D,OAAO;QACT,CAAC;QACD,MAAM,IAAI,QAAQ,CAAC,yBAAyB,GAAG,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,QAAQ,CAChB,GAAG,GAAG,4EAA4E,CACnF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,mBAAmB,CAAC,GAAW;IACtC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC7E,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,IAAI,CACV,oFAAoF;YAClF,qCAAqC,GAAG,oCAAoC,CAC/E,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,cAAc,CACrB,MAAc,EACd,GAAW,EACX,MAAc,EACd,gBAAyB,EACzB,OAAgB;IAEhB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChB,MAAM,CAAC,IAAI,CAAC,WAAW,GAAG,UAAU,MAAM,IAAI,CAAC,CAAC;IAChD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC3B,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC;IAC3B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,mFAAmF,CAAC,CAAC;IACjG,MAAM,CAAC,IAAI,CAAC,mEAAmE,CAAC,CAAC;IACjF,MAAM,CAAC,IAAI,CAAC,mFAAmF,CAAC,CAAC;IACjG,MAAM,CAAC,IAAI,CAAC,8EAA8E,CAAC,CAAC;IAC5F,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;IAC/E,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAChC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;AAClC,CAAC;AAUD;;;;;;;;;;;GAWG;AACH,KAAK,UAAU,cAAc,CAC3B,YAAoB,EACpB,IAA0C,EAC1C,WAAwB,EACxB,KAAa,EACb,MAAc;IAEd,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACxF,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;IAC/C,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACnD,CAAC;IAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC,MAAM,CACvD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,gCAAgC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CACxD,CAAC;IACF,2EAA2E;IAC3E,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACnD,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC;QACvC,WAAW;QACX,KAAK;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,UAAU;QACrB,iBAAiB,EAAE,mBAAmB;QACtC,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE;KAClC,CAAC,CAAC;IAEH,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,4DAA4D,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;QACzF,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACnD,CAAC;IAED,8FAA8F;IAC9F,iDAAiD;IACjD,MAAM,QAAQ,GAAG,eAAe,CAAC,YAAY,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IACtE,MAAM,CAAC,IAAI,CACT,UAAU,CAAC,MAAM;QACf,CAAC,CAAC,mBAAmB,QAAQ,CAAC,EAAE,OAAO,UAAU,CAAC,MAAM,GAAG;QAC3D,CAAC,CAAC,mBAAmB,QAAQ,CAAC,EAAE,IAAI,CACvC,CAAC;IACF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;IACvC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,uCAAuC,EAAE;IAC5E,IAAI,EAAE;QACJ,GAAG,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,qCAAqC,EAAE;QAChG,QAAQ,EAAE;YACR,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,iEAAiE,mBAAmB,mCAAmC;SACrI;QACD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE;QACnD,IAAI,EAAE;YACJ,IAAI,EAAE,QAAQ;YACd,WAAW,EACT,qIAAqI;SACxI;QACD,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,iCAAiC,EAAE;QACvE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,sCAAsC,EAAE;QAC/E,IAAI,EAAE;YACJ,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,kEAAkE;SAChF;KAEF;IACD,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE;QAChB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;QAChD,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;QAEnD,MAAM,WAAW,GAAG,kBAAkB,CAAC;YACrC,QAAQ,EAAE,IAAI,CAAC,GAAG;YAClB,aAAa,EAAE,sBAAsB,EAAE;SACxC,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,WAAW,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QAEhE,MAAM,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;QAE5F,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;YACxD,MAAM,CAAC,IAAI,CAAC,sBAAsB,QAAQ,CAAC,OAAO,GAAG,CAAC,CAAC;YACvD,MAAM,iBAAiB,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE;gBACzE,KAAK,EAAE,UAAU,CAAC,KAAK;aACxB,CAAC,CAAC;YAEH,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YAClD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YACrD,MAAM,aAAa,CAAC,WAAW,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;YAC1D,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,cAAc,CAC/D,YAAY,EACZ,IAAI,EACJ,WAAW,EACX,KAAK,EACL,MAAM,CACP,CAAC;YAEF,yFAAyF;YACzF,+BAA+B;YAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YAC1D,MAAM,OAAO,GAAG,MAAM,OAAO,CAC3B,WAAW,EACX,mBAAmB,EACnB,KAAK,EACL,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,EAC5C,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,CAC5B,CAAC;YAEF,eAAe,CAAC;gBACd,SAAS;gBACT,kBAAkB,EAAE,SAAS;gBAC7B,YAAY;gBACZ,QAAQ;gBACR,QAAQ,EAAE;oBACR,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,aAAa,EAAE,QAAQ,CAAC,OAAO;oBAC/B,KAAK,EAAE,QAAQ,CAAC,KAAK;oBACrB,QAAQ,EAAE,QAAQ,CAAC,EAAE;iBACtB;gBACD,WAAW;gBACX,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxD,CAAC,CAAC;YAEH,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;YACxD,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;YAC7F,MAAM,CAAC,MAAM,CAAC;gBACZ,EAAE,EAAE,IAAI;gBACR,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,QAAQ,CAAC,EAAE;gBACrB,uFAAuF;gBACvF,iFAAiF;gBACjF,cAAc;gBACd,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,aAAa,EAAE,QAAQ,CAAC,OAAO;gBAC/B,uFAAuF;gBACvF,qBAAqB,EAAE,gBAAgB;gBACvC,2FAA2F;gBAC3F,eAAe,EAAE,IAAI,CAAC,IAAI,KAAK,SAAS;aACzC,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -2,8 +2,34 @@ import { type ProjectMetadata } from './project-files.js';
|
|
|
2
2
|
export interface TemplateEntry {
|
|
3
3
|
id: string;
|
|
4
4
|
genre: string;
|
|
5
|
+
/** Both optional: only the suggester reads them, and an older bundle's index may omit either. */
|
|
6
|
+
name?: string;
|
|
7
|
+
description?: string;
|
|
5
8
|
recommended?: boolean;
|
|
6
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The CLI's default, deliberately NOT the index's `recommended` entry.
|
|
12
|
+
*
|
|
13
|
+
* `recommended` is standard-3d, a scene pre-populated with demo objects — the right first impression
|
|
14
|
+
* in the web Creator, where someone wants something to look at immediately. In the CLI lane a coding
|
|
15
|
+
* agent builds the scene from GAME-DESIGN.md, so those objects are furniture it has to delete first.
|
|
16
|
+
* Same third-person controller, empty world.
|
|
17
|
+
*
|
|
18
|
+
* `recommended` stays where it is: flipping it in templates/index.json would also move the Creator UI
|
|
19
|
+
* and the lab's `loadRecommendedTemplateId`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_TEMPLATE_ID = "empty-3d";
|
|
22
|
+
/**
|
|
23
|
+
* Templates `--idea` may not be matched to, though `--template` still scaffolds them.
|
|
24
|
+
*
|
|
25
|
+
* standard-3d IS the default template plus demo objects — same genre, same third-person controller.
|
|
26
|
+
* Offering both to the suggester asks it to choose on a difference that does not matter here (the
|
|
27
|
+
* creator's agent builds the scene either way) and, observed live, it picks standard-3d for ordinary
|
|
28
|
+
* third-person ideas on the strength of its name and "the default Bitmagic scene template"
|
|
29
|
+
* description. Withholding it makes the choice purely about camera and controls, which is the only
|
|
30
|
+
* thing the templates really differ on.
|
|
31
|
+
*/
|
|
32
|
+
export declare const SUGGESTION_EXCLUDED_TEMPLATE_IDS: string[];
|
|
7
33
|
/**
|
|
8
34
|
* Directories moved out of the extracted tree into the project's vendored engine/.
|
|
9
35
|
*
|
|
@@ -61,6 +87,13 @@ export declare const OPTIONAL_VENDORED_DIRS: string[];
|
|
|
61
87
|
* `upgrade` didn't exist yet.
|
|
62
88
|
*/
|
|
63
89
|
export declare const PLATFORM_GENERATED_FILES: Array<[string, () => string]>;
|
|
90
|
+
/**
|
|
91
|
+
* Every template the downloaded bundle carries, in index order.
|
|
92
|
+
*
|
|
93
|
+
* Separate from `resolveTemplate` so `init` can hand the list to the template suggester without
|
|
94
|
+
* parsing the file a second time — and so the two "this bundle is unusable" errors stay in one place.
|
|
95
|
+
*/
|
|
96
|
+
export declare function readTemplateIndex(templatesDir: string): TemplateEntry[];
|
|
64
97
|
export declare function resolveTemplate(templatesDir: string, requestedId?: string): TemplateEntry;
|
|
65
98
|
export interface ScaffoldOptions {
|
|
66
99
|
targetDir: string;
|
package/dist/scaffold/project.js
CHANGED
|
@@ -5,6 +5,29 @@ import { aliasSourceDir } from './aliases.js';
|
|
|
5
5
|
import { renderPackageJson, renderTsconfig, renderIndexHtml, renderViteConfig, renderVitePublishConfig, renderGitignore, renderBitmagicJson, renderAgentsMd, renderGameDesignMd, renderClaudeMd, renderSkillsReadme, renderGenerateSkill, renderPlanningSkill, renderEngineApiSkill, } from './project-files.js';
|
|
6
6
|
import { hashAgentsMd } from './agents-md.js';
|
|
7
7
|
import { CLAUDE_SETTINGS_FILE, renderClaudeSettings } from './claude-settings.js';
|
|
8
|
+
/**
|
|
9
|
+
* The CLI's default, deliberately NOT the index's `recommended` entry.
|
|
10
|
+
*
|
|
11
|
+
* `recommended` is standard-3d, a scene pre-populated with demo objects — the right first impression
|
|
12
|
+
* in the web Creator, where someone wants something to look at immediately. In the CLI lane a coding
|
|
13
|
+
* agent builds the scene from GAME-DESIGN.md, so those objects are furniture it has to delete first.
|
|
14
|
+
* Same third-person controller, empty world.
|
|
15
|
+
*
|
|
16
|
+
* `recommended` stays where it is: flipping it in templates/index.json would also move the Creator UI
|
|
17
|
+
* and the lab's `loadRecommendedTemplateId`.
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_TEMPLATE_ID = 'empty-3d';
|
|
20
|
+
/**
|
|
21
|
+
* Templates `--idea` may not be matched to, though `--template` still scaffolds them.
|
|
22
|
+
*
|
|
23
|
+
* standard-3d IS the default template plus demo objects — same genre, same third-person controller.
|
|
24
|
+
* Offering both to the suggester asks it to choose on a difference that does not matter here (the
|
|
25
|
+
* creator's agent builds the scene either way) and, observed live, it picks standard-3d for ordinary
|
|
26
|
+
* third-person ideas on the strength of its name and "the default Bitmagic scene template"
|
|
27
|
+
* description. Withholding it makes the choice purely about camera and controls, which is the only
|
|
28
|
+
* thing the templates really differ on.
|
|
29
|
+
*/
|
|
30
|
+
export const SUGGESTION_EXCLUDED_TEMPLATE_IDS = ['standard-3d'];
|
|
8
31
|
/**
|
|
9
32
|
* Directories moved out of the extracted tree into the project's vendored engine/.
|
|
10
33
|
*
|
|
@@ -80,7 +103,13 @@ export const PLATFORM_GENERATED_FILES = [
|
|
|
80
103
|
['.claude/skills/planning-a-game/SKILL.md', renderPlanningSkill],
|
|
81
104
|
['.claude/skills/finding-engine-apis/SKILL.md', renderEngineApiSkill],
|
|
82
105
|
];
|
|
83
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Every template the downloaded bundle carries, in index order.
|
|
108
|
+
*
|
|
109
|
+
* Separate from `resolveTemplate` so `init` can hand the list to the template suggester without
|
|
110
|
+
* parsing the file a second time — and so the two "this bundle is unusable" errors stay in one place.
|
|
111
|
+
*/
|
|
112
|
+
export function readTemplateIndex(templatesDir) {
|
|
84
113
|
const indexPath = path.join(templatesDir, 'index.json');
|
|
85
114
|
let raw;
|
|
86
115
|
try {
|
|
@@ -91,14 +120,20 @@ export function resolveTemplate(templatesDir, requestedId) {
|
|
|
91
120
|
}
|
|
92
121
|
const parsed = JSON.parse(raw);
|
|
93
122
|
const templates = parsed?.templates ?? [];
|
|
94
|
-
|
|
95
|
-
// doubles as the fallback for the no-id case below.
|
|
96
|
-
const [first] = templates;
|
|
97
|
-
if (!first) {
|
|
123
|
+
if (templates.length === 0) {
|
|
98
124
|
throw new CliError('The engine bundle lists no templates.');
|
|
99
125
|
}
|
|
126
|
+
return templates;
|
|
127
|
+
}
|
|
128
|
+
export function resolveTemplate(templatesDir, requestedId) {
|
|
129
|
+
const templates = readTemplateIndex(templatesDir);
|
|
100
130
|
if (requestedId === undefined) {
|
|
101
|
-
|
|
131
|
+
// Two fallbacks deep on purpose: an engine bundle predating empty-3d still resolves through
|
|
132
|
+
// `recommended`, and an index where nothing is marked recommended still resolves through the
|
|
133
|
+
// first entry. Neither is a reason to refuse to scaffold.
|
|
134
|
+
return (templates.find((t) => t.id === DEFAULT_TEMPLATE_ID) ??
|
|
135
|
+
templates.find((t) => t.recommended) ??
|
|
136
|
+
templates[0]);
|
|
102
137
|
}
|
|
103
138
|
const match = templates.find((t) => t.id === requestedId);
|
|
104
139
|
if (!match) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/scaffold/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,GAErB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/scaffold/project.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Environment } from '../config/environments.js';
|
|
2
|
+
import { type ApiDeps } from '../http/client.js';
|
|
3
|
+
import type { TemplateEntry } from './project.js';
|
|
4
|
+
export interface SuggestTemplateOptions {
|
|
5
|
+
environment: Environment;
|
|
6
|
+
token: string;
|
|
7
|
+
/** The creator's `--idea` pitch. */
|
|
8
|
+
idea: string;
|
|
9
|
+
/** Every template this engine bundle carries — the model may only choose from these. */
|
|
10
|
+
templates: TemplateEntry[];
|
|
11
|
+
defaultTemplateId: string;
|
|
12
|
+
deps: ApiDeps;
|
|
13
|
+
}
|
|
14
|
+
export interface TemplateSuggestion {
|
|
15
|
+
templateId: string;
|
|
16
|
+
/** One short clause for the creator; may be empty if the model omitted it. */
|
|
17
|
+
reason: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ask api-server which template fits the creator's idea, or null if it cannot say.
|
|
21
|
+
*
|
|
22
|
+
* **This function never throws.** Template selection is a convenience on top of a default that is
|
|
23
|
+
* already a working answer, so nothing here — being offline, an expired feature flag, a model that
|
|
24
|
+
* invents an id — is allowed to fail `bitmagic init`. Every failure path returns null and the
|
|
25
|
+
* caller scaffolds the default template.
|
|
26
|
+
*
|
|
27
|
+
* The template list travels in the request because api-server has no copy of the bundle the CLI
|
|
28
|
+
* just downloaded, and because validating the reply against the ids we SENT is what makes a
|
|
29
|
+
* hallucinated id harmless rather than a scaffold from a directory that does not exist.
|
|
30
|
+
*/
|
|
31
|
+
export declare function suggestTemplate(options: SuggestTemplateOptions): Promise<TemplateSuggestion | null>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { apiPost } from '../http/client.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ask api-server which template fits the creator's idea, or null if it cannot say.
|
|
4
|
+
*
|
|
5
|
+
* **This function never throws.** Template selection is a convenience on top of a default that is
|
|
6
|
+
* already a working answer, so nothing here — being offline, an expired feature flag, a model that
|
|
7
|
+
* invents an id — is allowed to fail `bitmagic init`. Every failure path returns null and the
|
|
8
|
+
* caller scaffolds the default template.
|
|
9
|
+
*
|
|
10
|
+
* The template list travels in the request because api-server has no copy of the bundle the CLI
|
|
11
|
+
* just downloaded, and because validating the reply against the ids we SENT is what makes a
|
|
12
|
+
* hallucinated id harmless rather than a scaffold from a directory that does not exist.
|
|
13
|
+
*/
|
|
14
|
+
export async function suggestTemplate(options) {
|
|
15
|
+
const { environment, token, idea, templates, defaultTemplateId, deps } = options;
|
|
16
|
+
let response;
|
|
17
|
+
try {
|
|
18
|
+
response = await apiPost(environment, '/api/cli/v1/templates/suggest', token, {
|
|
19
|
+
idea,
|
|
20
|
+
defaultTemplateId,
|
|
21
|
+
templates: templates.map((t) => ({
|
|
22
|
+
id: t.id,
|
|
23
|
+
name: t.name ?? t.id,
|
|
24
|
+
description: t.description ?? '',
|
|
25
|
+
genre: t.genre,
|
|
26
|
+
})),
|
|
27
|
+
}, deps);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const { templateId } = response;
|
|
33
|
+
if (typeof templateId !== 'string' || !templates.some((t) => t.id === templateId)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
templateId,
|
|
38
|
+
reason: typeof response.reason === 'string' ? response.reason.trim() : '',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=suggest-template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggest-template.js","sourceRoot":"","sources":["../../src/scaffold/suggest-template.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAyB1D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA+B;IAE/B,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,iBAAiB,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAEjF,IAAI,QAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,OAAO,CACtB,WAAW,EACX,+BAA+B,EAC/B,KAAK,EACL;YACE,IAAI;YACJ,iBAAiB;YACjB,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC/B,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE;gBACpB,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE;gBAChC,KAAK,EAAE,CAAC,CAAC,KAAK;aACf,CAAC,CAAC;SACJ,EACD,IAAI,CACL,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,GAAG,QAAQ,CAAC;IAChC,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,UAAU,CAAC,EAAE,CAAC;QAClF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,UAAU;QACV,MAAM,EAAE,OAAO,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE;KAC1E,CAAC;AACJ,CAAC"}
|
package/dist/verify/browser.js
CHANGED
|
@@ -38,6 +38,7 @@ export async function runHeadlessCheck(options) {
|
|
|
38
38
|
try {
|
|
39
39
|
const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
|
|
40
40
|
const page = await context.newPage();
|
|
41
|
+
await armContextLossProbe(page);
|
|
41
42
|
page.on('console', (message) => consoleLines.push(`${message.type()}: ${message.text()}`));
|
|
42
43
|
page.on('pageerror', (error) => pageErrors.push(error.message));
|
|
43
44
|
page.on('requestfailed', (request) => consoleLines.push(`requestfailed: ${request.url()} ${request.failure()?.errorText ?? ''}`));
|
|
@@ -82,10 +83,20 @@ export async function runHeadlessCheck(options) {
|
|
|
82
83
|
// why this replaced a getContext() probe.
|
|
83
84
|
bufferWidth: element.width,
|
|
84
85
|
bufferHeight: element.height,
|
|
86
|
+
// Page coordinates of the rect, for sampling the captured frame below. Kept out of
|
|
87
|
+
// CanvasObservation, which is about whether a renderer claimed the canvas, not where.
|
|
88
|
+
left: Math.round(rect.left),
|
|
89
|
+
top: Math.round(rect.top),
|
|
85
90
|
};
|
|
86
91
|
});
|
|
87
92
|
const screenshotBuffer = await page.screenshot();
|
|
88
93
|
fs.writeFileSync(options.screenshotPath, screenshotBuffer);
|
|
94
|
+
const canvasFrame = canvas === null
|
|
95
|
+
? null
|
|
96
|
+
: await measureCanvasFrame(page, screenshotBuffer, canvas);
|
|
97
|
+
const webglContextLost = await page.evaluate(
|
|
98
|
+
// eslint-disable-next-line no-undef -- browser-side callback, see the canvas probe above.
|
|
99
|
+
(flag) => window[flag] === true, CONTEXT_LOST_FLAG);
|
|
89
100
|
let start;
|
|
90
101
|
if (options.startInteraction !== undefined && interaction !== null) {
|
|
91
102
|
start = {
|
|
@@ -96,7 +107,15 @@ export async function runHeadlessCheck(options) {
|
|
|
96
107
|
menuVisibleAfterSettle: await isMenuVisible(page),
|
|
97
108
|
};
|
|
98
109
|
}
|
|
99
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
consoleLines,
|
|
112
|
+
pageErrors,
|
|
113
|
+
canvas,
|
|
114
|
+
screenshotBytes: screenshotBuffer.length,
|
|
115
|
+
webglContextLost,
|
|
116
|
+
canvasFrame,
|
|
117
|
+
start,
|
|
118
|
+
};
|
|
100
119
|
}
|
|
101
120
|
finally {
|
|
102
121
|
// Written on success and failure alike, including when the try block above threw before
|
|
@@ -106,6 +125,101 @@ export async function runHeadlessCheck(options) {
|
|
|
106
125
|
await browser.close();
|
|
107
126
|
}
|
|
108
127
|
}
|
|
128
|
+
/** Window flag the init script below sets when the page loses its WebGL context. */
|
|
129
|
+
const CONTEXT_LOST_FLAG = '__bitmagicWebglContextLost';
|
|
130
|
+
/**
|
|
131
|
+
* Arm a `webglcontextlost` listener before any page script runs.
|
|
132
|
+
*
|
|
133
|
+
* Registered in the CAPTURE phase on `window`, because the event is dispatched at the canvas and
|
|
134
|
+
* does NOT bubble — a listener on the canvas itself would also have to wait for the canvas to
|
|
135
|
+
* exist, which is after the engine has already created it.
|
|
136
|
+
*
|
|
137
|
+
* Worth the plumbing because a lost context is otherwise undetectable from out here: the page
|
|
138
|
+
* keeps running its animation loop, the engine keeps logging, the HUD keeps painting, and the only
|
|
139
|
+
* difference is that nothing the renderer draws survives. That is exactly how a headless GPU run
|
|
140
|
+
* produced a passing verify whose screenshot was solid black (see `browser-gl.ts`).
|
|
141
|
+
*/
|
|
142
|
+
async function armContextLossProbe(page) {
|
|
143
|
+
await page.addInitScript((flag) => {
|
|
144
|
+
/* eslint-disable no-undef -- browser-side callback, see the canvas probe above. */
|
|
145
|
+
window.addEventListener('webglcontextlost', () => {
|
|
146
|
+
window[flag] = true;
|
|
147
|
+
}, true);
|
|
148
|
+
/* eslint-enable no-undef */
|
|
149
|
+
}, CONTEXT_LOST_FLAG);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* What the canvas rect of `screenshot` actually contains: how much of it is lit, and how many
|
|
153
|
+
* distinct colours it holds. The second is what catches a canvas that drew nothing on a page whose
|
|
154
|
+
* backdrop is not black — see `CanvasFrameObservation`.
|
|
155
|
+
*
|
|
156
|
+
* Decoded in the BROWSER rather than in Node: it needs a PNG decoder, the page already has one,
|
|
157
|
+
* and adding an image library to the CLI's dependencies for a single check is a poor trade for
|
|
158
|
+
* something every creator installs. The image goes back in as a data URL and is drawn to a 2D
|
|
159
|
+
* canvas, whose `getImageData` is a plain CPU readback of its own backing store — unaffected by
|
|
160
|
+
* whatever went wrong on the WebGL/compositing path that produced the frame.
|
|
161
|
+
*
|
|
162
|
+
* Returns null rather than throwing on any failure. A measurement that could not be taken must
|
|
163
|
+
* classify as "not observed"; failing a creator's game because a probe broke would be worse than
|
|
164
|
+
* the gap it closes.
|
|
165
|
+
*/
|
|
166
|
+
async function measureCanvasFrame(page, screenshot, rect) {
|
|
167
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
168
|
+
return null;
|
|
169
|
+
try {
|
|
170
|
+
return await page.evaluate(async (input) => {
|
|
171
|
+
/* eslint-disable no-undef -- browser-side callback, see the canvas probe above. */
|
|
172
|
+
const image = new Image();
|
|
173
|
+
image.src = input.dataUrl;
|
|
174
|
+
await image.decode();
|
|
175
|
+
// The screenshot is a viewport capture at deviceScaleFactor 1, so its pixels are the
|
|
176
|
+
// page's CSS pixels; clamp to the image in case the canvas overhangs the viewport.
|
|
177
|
+
const left = Math.max(0, input.left);
|
|
178
|
+
const top = Math.max(0, input.top);
|
|
179
|
+
const width = Math.min(input.width, image.naturalWidth - left);
|
|
180
|
+
const height = Math.min(input.height, image.naturalHeight - top);
|
|
181
|
+
if (width <= 0 || height <= 0)
|
|
182
|
+
return null;
|
|
183
|
+
const surface = document.createElement('canvas');
|
|
184
|
+
surface.width = width;
|
|
185
|
+
surface.height = height;
|
|
186
|
+
const ctx = surface.getContext('2d');
|
|
187
|
+
if (!ctx)
|
|
188
|
+
return null;
|
|
189
|
+
ctx.drawImage(image, left, top, width, height, 0, 0, width, height);
|
|
190
|
+
const { data } = ctx.getImageData(0, 0, width, height);
|
|
191
|
+
let lit = 0;
|
|
192
|
+
let sampled = 0;
|
|
193
|
+
const colors = new Set();
|
|
194
|
+
// Every 4th pixel per axis: ~16x less work than a full scan, and a frame that is empty
|
|
195
|
+
// only in the 15 pixels between samples is not a frame anyone needs warning about.
|
|
196
|
+
for (let y = 0; y < height; y += 4) {
|
|
197
|
+
for (let x = 0; x < width; x += 4) {
|
|
198
|
+
const i = (y * width + x) << 2;
|
|
199
|
+
// Threshold, not `> 0`: PNG capture of a near-black frame is not bit-exact.
|
|
200
|
+
if (data[i] > 4 || data[i + 1] > 4 || data[i + 2] > 4)
|
|
201
|
+
lit++;
|
|
202
|
+
// 5 bits per channel, so capture rounding and dithering do not read as detail.
|
|
203
|
+
colors.add(((data[i] >> 3) << 10) | ((data[i + 1] >> 3) << 5) | (data[i + 2] >> 3));
|
|
204
|
+
sampled++;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return sampled === 0 ? null : { litFraction: lit / sampled, distinctColors: colors.size };
|
|
208
|
+
/* eslint-enable no-undef */
|
|
209
|
+
}, {
|
|
210
|
+
dataUrl: `data:image/png;base64,${screenshot.toString('base64')}`,
|
|
211
|
+
left: rect.left,
|
|
212
|
+
top: rect.top,
|
|
213
|
+
width: rect.width,
|
|
214
|
+
height: rect.height,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// A page that is already unusable (crashed renderer, closed context) cannot be measured, and
|
|
219
|
+
// the run has more useful things to report about that than a missing pixel statistic.
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
109
223
|
const START_POLL_MS = 250;
|
|
110
224
|
function hasStartMarker(consoleLines, marker) {
|
|
111
225
|
return consoleLines.some((line) => line.includes(marker));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/verify/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAa,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/verify/browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,20 @@ export interface CanvasObservation {
|
|
|
13
13
|
bufferWidth: number;
|
|
14
14
|
bufferHeight: number;
|
|
15
15
|
}
|
|
16
|
+
/** What the canvas area of the captured screenshot contains. */
|
|
17
|
+
export interface CanvasFrameObservation {
|
|
18
|
+
/** Fraction of sampled pixels that are not black. */
|
|
19
|
+
litFraction: number;
|
|
20
|
+
/**
|
|
21
|
+
* How many distinct colours the sampled pixels fall into, quantised to 5 bits per channel.
|
|
22
|
+
*
|
|
23
|
+
* The load-bearing number, and the reason this is not just a brightness check: a canvas that
|
|
24
|
+
* rendered nothing composites as a FLAT area — black on a black page, but whatever the page's
|
|
25
|
+
* background happens to be otherwise. Flatness catches that in every case; darkness only catches
|
|
26
|
+
* it when the backdrop is black. A real rendered frame from a 3D scene has thousands.
|
|
27
|
+
*/
|
|
28
|
+
distinctColors: number;
|
|
29
|
+
}
|
|
16
30
|
/** What the start interaction (waiting for and clicking the engine Play button) observed. */
|
|
17
31
|
export interface StartObservations {
|
|
18
32
|
playButtonSeen: boolean;
|
|
@@ -29,6 +43,20 @@ export interface VerifyObservations {
|
|
|
29
43
|
canvas: CanvasObservation | null;
|
|
30
44
|
/** Byte length of the PNG screenshot taken after settling. */
|
|
31
45
|
screenshotBytes: number;
|
|
46
|
+
/**
|
|
47
|
+
* The page fired `webglcontextlost`. The renderer is dead from that moment: rAF keeps firing,
|
|
48
|
+
* the engine logs its usual lines, the DOM HUD still paints, and nothing else gives it away.
|
|
49
|
+
*/
|
|
50
|
+
webglContextLost: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* What the captured screenshot actually contains INSIDE the canvas rect, or null when it could
|
|
53
|
+
* not be measured (no canvas, decode failed).
|
|
54
|
+
*
|
|
55
|
+
* Neither the console nor the page can answer "did a frame actually come out": what matters is
|
|
56
|
+
* what the COMPOSITOR captured, because that image is also the game's published thumbnail when
|
|
57
|
+
* the project has no cover art. See `BLACK_FRAME_MAX_LIT_FRACTION` and `MIN_DISTINCT_COLORS`.
|
|
58
|
+
*/
|
|
59
|
+
canvasFrame: CanvasFrameObservation | null;
|
|
32
60
|
/** Only present when the run was asked to perform the start interaction (`bitmagic verify`);
|
|
33
61
|
* `publish`'s smoke check never sets it and classifies exactly as before. */
|
|
34
62
|
start?: StartObservations;
|
package/dist/verify/classify.js
CHANGED
|
@@ -26,6 +26,33 @@ const UNTOUCHED_CANVAS_HEIGHT = 150;
|
|
|
26
26
|
* real frame — a smoke signal, not a pixel assertion.
|
|
27
27
|
*/
|
|
28
28
|
const MIN_SCREENSHOT_BYTES = 20_000;
|
|
29
|
+
/**
|
|
30
|
+
* Below this fraction of non-black pixels in the canvas rect, the frame is treated as black.
|
|
31
|
+
*
|
|
32
|
+
* The byte-size check above does not catch this case: the black frame that prompted this still
|
|
33
|
+
* carried the DOM HUD, which compresses to far more than 20KB, so verify reported PASSED on an
|
|
34
|
+
* image that was solid black behind the panel. The measured cause was a lost WebGL context under
|
|
35
|
+
* headless GPU compositing (see `browser-gl.ts`), but the check is deliberately about the PIXELS
|
|
36
|
+
* rather than that cause: this screenshot becomes the game's published thumbnail when the project
|
|
37
|
+
* has no cover art, so whatever makes it black — a dead context, a renderer that drew nothing, a
|
|
38
|
+
* capture path that lost the layer — is worth failing over.
|
|
39
|
+
*
|
|
40
|
+
* 0.5% rather than zero because PNG capture of a nearly-black frame is not bit-exact, and a real
|
|
41
|
+
* frame clears this by orders of magnitude: a healthy voxel game measures upwards of 99% lit. A
|
|
42
|
+
* genuine full-black render (a fade-out caught at the settle instant) would fail here; that is the
|
|
43
|
+
* intended trade, because it is indistinguishable from the broken case in the artifact that ships.
|
|
44
|
+
*/
|
|
45
|
+
const BLACK_FRAME_MAX_LIT_FRACTION = 0.005;
|
|
46
|
+
/**
|
|
47
|
+
* Fewer distinct colours than this in the canvas area means nothing was drawn there.
|
|
48
|
+
*
|
|
49
|
+
* This is the check that does the work, because "black" is only how the failure looked on a black
|
|
50
|
+
* page: a canvas whose context died composites as a flat area of WHATEVER is behind it, and on a
|
|
51
|
+
* page with a dark-grey backdrop the brightness check above sails straight past it. Two rather than
|
|
52
|
+
* one leaves room for the capture's own rounding and a single antialiased edge; a rendered 3D frame
|
|
53
|
+
* measures in the thousands.
|
|
54
|
+
*/
|
|
55
|
+
const MIN_DISTINCT_COLORS = 3;
|
|
29
56
|
/**
|
|
30
57
|
* The engine's line when the player has fallen out of the world and is being put back.
|
|
31
58
|
*
|
|
@@ -92,6 +119,27 @@ export function classifyVerifyRun(observations) {
|
|
|
92
119
|
failures.push(`The screenshot is implausibly small (${observations.screenshotBytes} bytes) — the frame ` +
|
|
93
120
|
'looks blank.');
|
|
94
121
|
}
|
|
122
|
+
if (observations.webglContextLost) {
|
|
123
|
+
failures.push('The WebGL context was lost during the run, so the game rendered nothing after that point '
|
|
124
|
+
+ '(the page keeps running and the HUD keeps painting, which is why nothing else shows it). '
|
|
125
|
+
+ 'If this machine drives a GPU, re-run with BITMAGIC_BROWSER_SOFTWARE_GL=true to confirm.');
|
|
126
|
+
}
|
|
127
|
+
// Only when a canvas was found and measured: `null` means "not observed", never "empty".
|
|
128
|
+
const frame = observations.canvasFrame;
|
|
129
|
+
if (frame !== null) {
|
|
130
|
+
// Both land on the same remedy, so only the more specific one is reported.
|
|
131
|
+
const shipped = 'This image is also what publish uses as the game\'s thumbnail when the '
|
|
132
|
+
+ 'project has no cover art, so it cannot be shipped as-is.';
|
|
133
|
+
if (frame.litFraction <= BLACK_FRAME_MAX_LIT_FRACTION) {
|
|
134
|
+
failures.push('The screenshot is black where the game should be — nothing the renderer drew reached '
|
|
135
|
+
+ `the captured frame. ${shipped}`);
|
|
136
|
+
}
|
|
137
|
+
else if (frame.distinctColors < MIN_DISTINCT_COLORS) {
|
|
138
|
+
failures.push(`The screenshot shows a flat, featureless area where the game should be `
|
|
139
|
+
+ `(${frame.distinctColors} distinct colour${frame.distinctColors === 1 ? '' : 's'} across `
|
|
140
|
+
+ `the whole canvas) — the renderer's output never reached the captured frame. ${shipped}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
95
143
|
for (const line of observations.consoleLines) {
|
|
96
144
|
const fatal = FATAL_CONSOLE.find((f) => line.includes(f));
|
|
97
145
|
if (fatal)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/verify/classify.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/verify/classify.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitmagic/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"description": "Build Bitmagic games from your own agent tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"citty": "^0.2.2",
|
|
20
20
|
"playwright-core": "^1.59.1",
|
|
21
21
|
"tar": "^7.4.3",
|
|
22
|
-
"@bitmagic/
|
|
23
|
-
"@bitmagic/
|
|
22
|
+
"@bitmagic/asset-core": "0.1.1",
|
|
23
|
+
"@bitmagic/world-forger": "0.1.3"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@eslint/js": "^9.38.0",
|