@datamoc/mw_games 0.41.0 → 0.42.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/CHANGELOG.md +46 -0
- package/REFERENCE.md +16 -3
- package/api-surface.json +24 -0
- package/dist/audio/MidiRender.d.ts +3 -1
- package/dist/audio/MidiRender.d.ts.map +1 -1
- package/dist/audio/MidiRender.js +3 -1
- package/dist/audio/MidiRender.js.map +1 -1
- package/dist/audio/SoundFont.d.ts +35 -2
- package/dist/audio/SoundFont.d.ts.map +1 -1
- package/dist/audio/SoundFont.js +43 -7
- package/dist/audio/SoundFont.js.map +1 -1
- package/dist/audio/index.d.ts +2 -2
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +1 -1
- package/dist/audio/index.js.map +1 -1
- package/dist/core/DialogueText.d.ts +25 -0
- package/dist/core/DialogueText.d.ts.map +1 -1
- package/dist/core/DialogueText.js +25 -0
- package/dist/core/DialogueText.js.map +1 -1
- package/dist/core/Motion.d.ts +20 -0
- package/dist/core/Motion.d.ts.map +1 -1
- package/dist/core/Motion.js +20 -0
- package/dist/core/Motion.js.map +1 -1
- package/dist/core/Save.d.ts +31 -0
- package/dist/core/Save.d.ts.map +1 -1
- package/dist/core/Save.js +31 -0
- package/dist/core/Save.js.map +1 -1
- package/dist/core/Twee.d.ts +34 -0
- package/dist/core/Twee.d.ts.map +1 -1
- package/dist/core/Twee.js +34 -0
- package/dist/core/Twee.js.map +1 -1
- package/dist/mw_games.global.js +11 -11
- package/dist/physics/XpbdWorld.d.ts +45 -0
- package/dist/physics/XpbdWorld.d.ts.map +1 -1
- package/dist/physics/XpbdWorld.js +45 -0
- package/dist/physics/XpbdWorld.js.map +1 -1
- package/dist/two-d/ui/theme.d.ts +18 -0
- package/dist/two-d/ui/theme.d.ts.map +1 -1
- package/dist/two-d/ui/theme.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -1
- package/sbom.cdx.json +466 -6
- package/tools/compile-resources.d.mts +6 -0
- package/tools/compile-resources.mjs +50 -14
- package/tools/emit-page.d.mts +3 -0
- package/tools/emit-page.mjs +15 -2
- package/tools/svg-optimise.d.mts +8 -0
- package/tools/svg-optimise.mjs +45 -0
- package/tools/vite.mjs +1 -0
- package/tools.md +4 -2
|
@@ -121,6 +121,10 @@ export async function* walk(dir) {
|
|
|
121
121
|
* pixel-art tileset's exact colours survive; set false only with `webpQuality` to opt into
|
|
122
122
|
* lossy re-encoding for photographic source art)
|
|
123
123
|
* @param options.webpQuality 0-100, used only when `webpLossless` is false (default 90)
|
|
124
|
+
* @param options.svgo minify embedded `.svg` assets through the optional `svgo` package (default
|
|
125
|
+
* false - see `tools/svg-optimise.mjs`). The key never changes, only the embedded bytes, and
|
|
126
|
+
* only when the optimised file is actually smaller. A file svgo cannot parse fails the build by
|
|
127
|
+
* name rather than shipping unoptimised in silence.
|
|
124
128
|
* @param options.soundFont subset an embedded `.sf2` or `.sf3` to the programs the embedded
|
|
125
129
|
* `.mid`/`.midi` files use (default true); `false` embeds every SoundFont whole
|
|
126
130
|
* @param options.soundFontUsage explicit `{ bank, program }` list to subset to, for a
|
|
@@ -141,6 +145,7 @@ export async function compileResources({
|
|
|
141
145
|
toWebp: convertToWebp = false,
|
|
142
146
|
webpLossless = true,
|
|
143
147
|
webpQuality = 90,
|
|
148
|
+
svgo: optimiseSvgFiles = false,
|
|
144
149
|
soundFont = true,
|
|
145
150
|
soundFontUsage,
|
|
146
151
|
} = {}) {
|
|
@@ -205,10 +210,12 @@ export async function compileResources({
|
|
|
205
210
|
let rawBytes = 0;
|
|
206
211
|
let embeddedBytes = 0;
|
|
207
212
|
let webpConverted = 0;
|
|
213
|
+
let svgOptimised = 0;
|
|
208
214
|
|
|
209
215
|
// only paid when toWebp is actually used, so a build that never asks for it never imports
|
|
210
216
|
// (or requires installing) sharp
|
|
211
217
|
const toWebp = convertToWebp ? (await import('./webp-convert.mjs')).toWebp : null;
|
|
218
|
+
const optimiseSvg = optimiseSvgFiles ? (await import('./svg-optimise.mjs')).optimiseSvg : null;
|
|
212
219
|
|
|
213
220
|
const creditsFile = join(from, 'credits.json');
|
|
214
221
|
let credits = null;
|
|
@@ -240,6 +247,19 @@ export async function compileResources({
|
|
|
240
247
|
}
|
|
241
248
|
}
|
|
242
249
|
|
|
250
|
+
if (optimiseSvg && ext === '.svg') {
|
|
251
|
+
let optimised;
|
|
252
|
+
try {
|
|
253
|
+
optimised = await optimiseSvg(data);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
throw new Error(`cannot optimise SVG asset ${key}: ${error.message ?? error}`);
|
|
256
|
+
}
|
|
257
|
+
if (optimised.length < data.length) {
|
|
258
|
+
bytes = optimised;
|
|
259
|
+
svgOptimised++;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
243
263
|
if (isSoundFontKey(key)) {
|
|
244
264
|
if (usage !== undefined && soundFontTools) {
|
|
245
265
|
try {
|
|
@@ -247,7 +267,14 @@ export async function compileResources({
|
|
|
247
267
|
} catch (error) {
|
|
248
268
|
throw new Error(`cannot subset SoundFont asset ${key}: ${error.message ?? error}`);
|
|
249
269
|
}
|
|
250
|
-
soundFonts.push({
|
|
270
|
+
soundFonts.push({
|
|
271
|
+
key,
|
|
272
|
+
programs: usage.length,
|
|
273
|
+
rawBytes: data.length,
|
|
274
|
+
embeddedBytes: bytes.length,
|
|
275
|
+
//the programs the MIDI files ask for that this font does not carry
|
|
276
|
+
missing: soundFontTools.missingSoundFontPrograms(data, usage),
|
|
277
|
+
});
|
|
251
278
|
} else {
|
|
252
279
|
soundFonts.push({ key, programs: null, rawBytes: data.length, embeddedBytes: data.length });
|
|
253
280
|
}
|
|
@@ -273,7 +300,7 @@ export async function compileResources({
|
|
|
273
300
|
|
|
274
301
|
if (credits) await writeFile(join(to, 'CREDITS.txt'), credits, 'utf8');
|
|
275
302
|
|
|
276
|
-
return { groups: written, rawBytes, embeddedBytes, webpConverted, skipped, credits, soundFonts };
|
|
303
|
+
return { groups: written, rawBytes, embeddedBytes, webpConverted, svgOptimised, skipped, credits, soundFonts };
|
|
277
304
|
}
|
|
278
305
|
|
|
279
306
|
/** the plain-text credits for a manifest, one block per entry; throws on an entry that cannot be attributed */
|
|
@@ -302,7 +329,7 @@ async function runCli() {
|
|
|
302
329
|
const [from, to] = args.filter((a) => !a.startsWith('--'));
|
|
303
330
|
if (!from || !to) {
|
|
304
331
|
console.error(
|
|
305
|
-
'usage: node compile-resources.mjs <asset folder> <output folder> [--to-webp] [--webp-lossy[=quality]] [--no-soundfont-subset] [--soundfont-usage=bank:program,...]',
|
|
332
|
+
'usage: node compile-resources.mjs <asset folder> <output folder> [--to-webp] [--webp-lossy[=quality]] [--svgo] [--no-soundfont-subset] [--soundfont-usage=bank:program,...]',
|
|
306
333
|
);
|
|
307
334
|
process.exit(1);
|
|
308
335
|
}
|
|
@@ -315,17 +342,19 @@ async function runCli() {
|
|
|
315
342
|
args.find((a) => a.startsWith('--soundfont-usage='))?.slice('--soundfont-usage='.length),
|
|
316
343
|
);
|
|
317
344
|
|
|
318
|
-
const { groups, rawBytes, embeddedBytes, webpConverted, skipped, soundFonts } =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
345
|
+
const { groups, rawBytes, embeddedBytes, webpConverted, svgOptimised, skipped, soundFonts } =
|
|
346
|
+
await compileResources({
|
|
347
|
+
from,
|
|
348
|
+
to,
|
|
349
|
+
//a flat folder has no subdirectories to group by, so everything lands in one file
|
|
350
|
+
groupBy: (key) => (key.includes('/') ? key.slice(0, key.indexOf('/')) : 'assets'),
|
|
351
|
+
toWebp,
|
|
352
|
+
webpLossless,
|
|
353
|
+
webpQuality,
|
|
354
|
+
svgo: args.includes('--svgo'),
|
|
355
|
+
soundFont: !args.includes('--no-soundfont-subset'),
|
|
356
|
+
soundFontUsage,
|
|
357
|
+
});
|
|
329
358
|
|
|
330
359
|
const kb = (n) => (n / 1024).toFixed(1).padStart(8) + ' KB';
|
|
331
360
|
for (const { name, count, size } of groups) {
|
|
@@ -338,7 +367,14 @@ async function runCli() {
|
|
|
338
367
|
? ` ${font.key}: SoundFont embedded whole (${kb(font.rawBytes)}), no shipped MIDI to subset against`
|
|
339
368
|
: ` ${font.key}: SoundFont subset to ${font.programs} program(s), ${kb(font.rawBytes)} -> ${kb(font.embeddedBytes)}`,
|
|
340
369
|
);
|
|
370
|
+
if (font.missing?.length > 0) {
|
|
371
|
+
console.log(
|
|
372
|
+
` warning: ${font.key} does not carry ${font.missing.map(({ bank, program }) => `${bank}:${program}`).join(', ')}: ` +
|
|
373
|
+
"they play the font's piano, or the built-in synthesizer with `missingProgram: 'synth'`",
|
|
374
|
+
);
|
|
375
|
+
}
|
|
341
376
|
}
|
|
377
|
+
if (args.includes('--svgo')) console.log(` ${svgOptimised} SVG(s) optimised`);
|
|
342
378
|
if (toWebp) {
|
|
343
379
|
console.log(
|
|
344
380
|
` ${webpConverted} image(s) converted to WebP (${webpLossless ? 'lossless' : `lossy, quality ${webpQuality}`}), ` +
|
package/tools/emit-page.d.mts
CHANGED
|
@@ -20,6 +20,8 @@ export interface EmitPageOptions {
|
|
|
20
20
|
toWebp?: boolean;
|
|
21
21
|
webpLossless?: boolean;
|
|
22
22
|
webpQuality?: number;
|
|
23
|
+
/** minify embedded `.svg` assets through the optional `svgo` package; false by default */
|
|
24
|
+
svgo?: boolean;
|
|
23
25
|
/** Subset an embedded `.sf2` to the programs the embedded `.mid`/`.midi` files use. Default true; `false` embeds every SoundFont whole. */
|
|
24
26
|
soundFont?: boolean;
|
|
25
27
|
/** Explicit `{ bank, program }` list to subset a SoundFont to, overriding the MIDI-derived list. */
|
|
@@ -42,6 +44,7 @@ export interface EmitPageResult {
|
|
|
42
44
|
rawBytes: number;
|
|
43
45
|
embeddedBytes: number;
|
|
44
46
|
webpConverted: number;
|
|
47
|
+
svgOptimised: number;
|
|
45
48
|
/** one entry per embedded `.sf2`: the programs kept (null when embedded whole) and the byte saving */
|
|
46
49
|
soundFonts: SoundFontEmbed[];
|
|
47
50
|
compressed: CompressRow[] | null;
|
package/tools/emit-page.mjs
CHANGED
|
@@ -29,7 +29,7 @@ import { withContentSecurityPolicy } from './csp.mjs';
|
|
|
29
29
|
* usage: mwg-emit <project folder> [--dist=<dir>] [--assets=<dir>] [--no-csp] [--connect=<origin>]...
|
|
30
30
|
* [--no-compress] [--xz]
|
|
31
31
|
* [--single-file] [--single-file-compress[=level]]
|
|
32
|
-
* [--single-file-brotli[=quality]] [--to-webp] [--webp-lossy[=quality]]
|
|
32
|
+
* [--single-file-brotli[=quality]] [--to-webp] [--webp-lossy[=quality]] [--svgo]
|
|
33
33
|
* [--no-soundfont-subset] [--soundfont-usage=bank:program,...]
|
|
34
34
|
*
|
|
35
35
|
* `--dist` defaults to `<project folder>/dist` and `--assets` to `<project folder>/assets`; with
|
|
@@ -53,6 +53,9 @@ import { withContentSecurityPolicy } from './csp.mjs';
|
|
|
53
53
|
* `--to-webp` converts `.png`/`.jpg`/`.jpeg` example assets to WebP before embedding (see
|
|
54
54
|
* `tools/webp-convert.mjs`), lossless by default; `--webp-lossy[=quality]` opts into lossy
|
|
55
55
|
* re-encoding instead. Needs the optional `sharp` devDependency installed.
|
|
56
|
+
*
|
|
57
|
+
* `--svgo` minifies embedded `.svg` assets (see `tools/svg-optimise.mjs`), keeping the `viewBox` and
|
|
58
|
+
* every id. Needs the optional `svgo` package installed.
|
|
56
59
|
*/
|
|
57
60
|
|
|
58
61
|
/**
|
|
@@ -74,6 +77,7 @@ export async function emitPage({
|
|
|
74
77
|
toWebp = false,
|
|
75
78
|
webpLossless = true,
|
|
76
79
|
webpQuality = 90,
|
|
80
|
+
svgo = false,
|
|
77
81
|
soundFont = true,
|
|
78
82
|
soundFontUsage,
|
|
79
83
|
csp = true,
|
|
@@ -89,10 +93,11 @@ export async function emitPage({
|
|
|
89
93
|
toWebp,
|
|
90
94
|
webpLossless,
|
|
91
95
|
webpQuality,
|
|
96
|
+
svgo,
|
|
92
97
|
soundFont,
|
|
93
98
|
soundFontUsage,
|
|
94
99
|
})
|
|
95
|
-
: { groups: [], rawBytes: 0, embeddedBytes: 0, webpConverted: 0, soundFonts: [] };
|
|
100
|
+
: { groups: [], rawBytes: 0, embeddedBytes: 0, webpConverted: 0, svgOptimised: 0, soundFonts: [] };
|
|
96
101
|
|
|
97
102
|
const html = await readFile(join(dist, 'index.html'), 'utf8');
|
|
98
103
|
const classic = toClassicScript(html);
|
|
@@ -162,6 +167,7 @@ async function main(argv) {
|
|
|
162
167
|
toWebp: argv.includes('--to-webp'),
|
|
163
168
|
webpLossless: !webpLossyFlag,
|
|
164
169
|
webpQuality: webpLossyFlag?.includes('=') ? Number(webpLossyFlag.split('=')[1]) : 90,
|
|
170
|
+
svgo: argv.includes('--svgo'),
|
|
165
171
|
soundFont: !argv.includes('--no-soundfont-subset'),
|
|
166
172
|
soundFontUsage,
|
|
167
173
|
csp: argv.includes('--no-csp')
|
|
@@ -179,12 +185,19 @@ async function main(argv) {
|
|
|
179
185
|
`${kb(result.rawBytes)} -> ${kb(result.embeddedBytes)} embedded`,
|
|
180
186
|
);
|
|
181
187
|
}
|
|
188
|
+
if (argv.includes('--svgo')) console.log(` ${result.svgOptimised} SVG(s) optimised`);
|
|
182
189
|
for (const font of result.soundFonts ?? []) {
|
|
183
190
|
console.log(
|
|
184
191
|
font.programs === null
|
|
185
192
|
? ` ${font.key}: SoundFont embedded whole (${kb(font.rawBytes)}), no shipped MIDI to subset against`
|
|
186
193
|
: ` ${font.key}: SoundFont subset to ${font.programs} program(s), ${kb(font.rawBytes)} -> ${kb(font.embeddedBytes)}`,
|
|
187
194
|
);
|
|
195
|
+
if (font.missing?.length > 0) {
|
|
196
|
+
console.log(
|
|
197
|
+
` warning: ${font.key} does not carry ${font.missing.map(({ bank, program }) => `${bank}:${program}`).join(', ')}: ` +
|
|
198
|
+
"they play the font's piano, or the built-in synthesizer with `missingProgram: 'synth'`",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
188
201
|
}
|
|
189
202
|
console.log(' open index.html directly - no server needed');
|
|
190
203
|
if (result.compressed) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** the svgo configuration `optimiseSvg` runs */
|
|
2
|
+
export declare const SVGO_CONFIG: {
|
|
3
|
+
multipass: boolean;
|
|
4
|
+
plugins: { name: string; params: { overrides: Record<string, boolean> } }[];
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/** the SVG bytes minified; needs the optional `svgo` package, and throws for a file that is not valid SVG */
|
|
8
|
+
export declare function optimiseSvg(buffer: Buffer): Promise<Buffer>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minifies an SVG for embedding, the SVG half of what `webp-convert` is for raster images, kept as
|
|
3
|
+
* its own module so `tools/compile-resources.mjs` never has to import an optimiser itself: it
|
|
4
|
+
* dynamically imports `svgo`, which a game that opts in installs in its own project the way it does
|
|
5
|
+
* `sharp` for WebP (`npm install --save-dev svgo`). A game that never passes `svgo: true` carries
|
|
6
|
+
* no new dependency at all.
|
|
7
|
+
*
|
|
8
|
+
* Optimising must not change what the game can see or name. Ids are left alone (`cleanupIds` is off:
|
|
9
|
+
* a `<use href="#id">`, a gradient reference or a style selector inside the file, or an id the game
|
|
10
|
+
* looks up, would break if minified), and the `viewBox` stays because svgo 4's default preset no
|
|
11
|
+
* longer removes it (a game scales a texture by it), which is why this needs svgo 4 or newer.
|
|
12
|
+
* Everything else in the default preset applies: comments, metadata, editor data, default
|
|
13
|
+
* attributes, redundant groups, path precision.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** the svgo configuration `optimiseSvg` runs, exposed so a game can see exactly what it opts into */
|
|
17
|
+
export const SVGO_CONFIG = {
|
|
18
|
+
multipass: true,
|
|
19
|
+
plugins: [{ name: 'preset-default', params: { overrides: { cleanupIds: false } } }],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let svgoModule;
|
|
23
|
+
async function loadSvgo() {
|
|
24
|
+
if (svgoModule) return svgoModule;
|
|
25
|
+
try {
|
|
26
|
+
svgoModule = await import('svgo');
|
|
27
|
+
} catch (err) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
'SVG optimisation needs the optional `svgo` package, which is not installed. ' +
|
|
30
|
+
'Run `npm install --save-dev svgo` in your project to use `svgo: true`, or leave ' +
|
|
31
|
+
'it off (the default) to embed SVG files as they are.',
|
|
32
|
+
{ cause: err },
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return svgoModule;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {Buffer} buffer the SVG file's bytes (UTF-8)
|
|
40
|
+
* @returns {Promise<Buffer>} the optimised SVG; svgo throws for a file that is not valid SVG
|
|
41
|
+
*/
|
|
42
|
+
export async function optimiseSvg(buffer) {
|
|
43
|
+
const { optimize } = await loadSvgo();
|
|
44
|
+
return Buffer.from(optimize(buffer.toString('utf8'), SVGO_CONFIG).data, 'utf8');
|
|
45
|
+
}
|
package/tools/vite.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { buildArtifactSbom, serialize, shippedFiles } from './sbom.mjs';
|
|
|
16
16
|
* affects the build only, so the dev server still serves the asset folder normally.
|
|
17
17
|
* An embedded `.sf2` or `.sf3` is subset at build time to the programs the embedded `.mid` files
|
|
18
18
|
* use (`soundFont: false` embeds it whole, `soundFontUsage` names the programs instead).
|
|
19
|
+
* `svgo: true` minifies embedded `.svg` assets (needs the optional `svgo` package).
|
|
19
20
|
* At the end of the build it runs `emitPage` over the output folder with the same
|
|
20
21
|
* options. A setting the game's own config gives explicitly still wins, since vite merges a
|
|
21
22
|
* plugin's `config` result under the user's.
|
package/tools.md
CHANGED
|
@@ -23,7 +23,8 @@ by double-clicking. Run from the repo root.
|
|
|
23
23
|
|
|
24
24
|
| tool | what it does | run |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| `compile-resources` | Turns an asset folder into `data:` URI scripts under `window.__MWG_ASSETS__`, grouped by top-level folder. `toWebp: true` converts `.png`/`.jpg`/`.jpeg` to WebP first (lossless by default, only kept when actually smaller, key unchanged) via the optional `sharp` devDependency, dynamically imported only when used (item 350) | called by the example builds; `node tools/compile-resources.mjs <asset folder> <output folder> [--to-webp] [--webp-lossy[=quality]]` |
|
|
26
|
+
| `compile-resources` | Turns an asset folder into `data:` URI scripts under `window.__MWG_ASSETS__`, grouped by top-level folder. `toWebp: true` converts `.png`/`.jpg`/`.jpeg` to WebP first (lossless by default, only kept when actually smaller, key unchanged) via the optional `sharp` devDependency, dynamically imported only when used (item 350). `svgo: true` minifies `.svg` assets through the optional `svgo` package (version 4 or newer, ids and `viewBox` kept, only kept when smaller). Each subset SoundFont's result lists the programs the game's MIDI files ask for that the font does not carry, and `mwg-emit` warns about them | called by the example builds; `node tools/compile-resources.mjs <asset folder> <output folder> [--to-webp] [--webp-lossy[=quality]] [--svgo]` |
|
|
27
|
+
| `svg-optimise` | Minifies one SVG file's bytes through `svgo` with ids and the `viewBox` left alone; the standalone half of `compile-resources`'s `svgo` option (item 428). On the repository's own 30 SVGs: 13.6% smaller, worst rendered pixel difference 1 in 255 | `optimiseSvg` from `@datamoc/mw_games/tools/svg-optimise`, or `mwg-emit <folder> --svgo` |
|
|
27
28
|
| `webp-convert` | Converts one raster image buffer (PNG/JPEG) to WebP, lossless by default; the standalone half of `compile-resources`'s `toWebp` option (item 350) | `node tools/webp-convert.mjs <file.png\|file.jpg> [--lossy[=quality]]` |
|
|
28
29
|
| `emit-page` | Finishes a vite build so it opens from `file://` with no server: compiles the assets, rewrites the entry to a classic deferred script with the asset scripts before it, then optionally precompresses and writes a single-file variant (item 381) | `emitPage(options)` from `@datamoc/mw_games/tools/emit-page`, the `mwg-emit <folder> [--dist=] [--assets=]` command, or called by each `example:*:build` script here |
|
|
29
30
|
| `init` | `mwg-init <folder>` writes a starter project (vite config using `mwgPage`, one scene, tsconfig, scripts) that builds to a double-clickable page | `npx mwg-init my-game` |
|
|
@@ -199,7 +200,8 @@ same build.
|
|
|
199
200
|
| tool | what it does | run |
|
|
200
201
|
| --- | --- | --- |
|
|
201
202
|
| `roadmap-progress` | Reports CLOSED.md + ROADMAP.md completion: numbered items in batches of 25, plus the 1.0 exit checklist on its own row (checks to run, not capabilities, so they stay out of the numbered totals), optionally in a browser | `npm run roadmap:progress` |
|
|
202
|
-
| `build-webpage-examples` / `build-webpage-docs` | Generate the live examples and Documentation page under `webpage/` (`REFERENCE.md` guide + TypeDoc under `documentation/api/`) | `npm run webpage:examples`, `npm run webpage:docs` |
|
|
203
|
+
| `build-webpage-examples` / `build-webpage-docs` | Generate the live examples and Documentation page under `webpage/` (`REFERENCE.md` guide + TypeDoc under `documentation/api/` + the examples index) | `npm run webpage:examples`, `npm run webpage:docs` |
|
|
204
|
+
| `build-example-index` | Writes `documentation/examples.html`, read from source text (no typedoc, no renderer): for each public module the symbols that carry an `@example`, each linked to its typedoc page when one exists, with the runnable `examples/` that import the module. Part of `webpage:docs`; a repository tool, not shipped (item 430) | `npm run webpage:docs`, or `node tools/build-example-index.mjs [outFile]` |
|
|
203
205
|
| `make-example-diagrams` / `make-architecture-diagrams` | Rebuild the generated diagrams under `webpage/assets` (never hand-drawn) | `npm run webpage:diagrams` |
|
|
204
206
|
| `api-report` | Regenerates `API_REPORT.md` from the built declarations; `--check` compares instead. A CI gate | `npm run api:report`, `npm run api:check` |
|
|
205
207
|
| `project-stats` | Writes `PROJECT_STATS.json`/`PROJECT_STATS.md` and the website statistics page; `--check` compares. A CI gate | `npm run stats:write`, `npm run stats:check` |
|