@datamoc/mw_games 0.40.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.
Files changed (119) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/REFERENCE.md +2128 -0
  3. package/api-surface.json +72 -0
  4. package/data/microgm-opt.PROVENANCE.md +3 -0
  5. package/data/soundfont-catalog.json +183 -0
  6. package/dist/ai/index.d.ts +10 -0
  7. package/dist/ai/index.d.ts.map +1 -1
  8. package/dist/ai/index.js.map +1 -1
  9. package/dist/ai/lua.d.ts +6 -0
  10. package/dist/ai/lua.d.ts.map +1 -1
  11. package/dist/ai/lua.js.map +1 -1
  12. package/dist/audio/MidiRender.d.ts +3 -1
  13. package/dist/audio/MidiRender.d.ts.map +1 -1
  14. package/dist/audio/MidiRender.js +3 -1
  15. package/dist/audio/MidiRender.js.map +1 -1
  16. package/dist/audio/SampleDecoder.d.ts +41 -0
  17. package/dist/audio/SampleDecoder.d.ts.map +1 -0
  18. package/dist/audio/SampleDecoder.js +70 -0
  19. package/dist/audio/SampleDecoder.js.map +1 -0
  20. package/dist/audio/SoundFont.d.ts +93 -1
  21. package/dist/audio/SoundFont.d.ts.map +1 -1
  22. package/dist/audio/SoundFont.js +290 -8
  23. package/dist/audio/SoundFont.js.map +1 -1
  24. package/dist/audio/index.d.ts +4 -2
  25. package/dist/audio/index.d.ts.map +1 -1
  26. package/dist/audio/index.js +2 -1
  27. package/dist/audio/index.js.map +1 -1
  28. package/dist/core/DialogueText.d.ts +25 -0
  29. package/dist/core/DialogueText.d.ts.map +1 -1
  30. package/dist/core/DialogueText.js +25 -0
  31. package/dist/core/DialogueText.js.map +1 -1
  32. package/dist/core/Motion.d.ts +20 -0
  33. package/dist/core/Motion.d.ts.map +1 -1
  34. package/dist/core/Motion.js +20 -0
  35. package/dist/core/Motion.js.map +1 -1
  36. package/dist/core/Save.d.ts +31 -0
  37. package/dist/core/Save.d.ts.map +1 -1
  38. package/dist/core/Save.js +31 -0
  39. package/dist/core/Save.js.map +1 -1
  40. package/dist/core/Twee.d.ts +34 -0
  41. package/dist/core/Twee.d.ts.map +1 -1
  42. package/dist/core/Twee.js +34 -0
  43. package/dist/core/Twee.js.map +1 -1
  44. package/dist/mw_games.global.js +11 -11
  45. package/dist/mwl/presentation.d.ts +16 -0
  46. package/dist/mwl/presentation.d.ts.map +1 -1
  47. package/dist/mwl/presentation.js.map +1 -1
  48. package/dist/mwl/pyroeis-runtime.d.ts +7 -0
  49. package/dist/mwl/pyroeis-runtime.d.ts.map +1 -1
  50. package/dist/mwl/pyroeis-runtime.js.map +1 -1
  51. package/dist/mwl/pyroeis.d.ts +3 -0
  52. package/dist/mwl/pyroeis.d.ts.map +1 -1
  53. package/dist/mwl/pyroeis.js.map +1 -1
  54. package/dist/physics/PhysicsWorld.d.ts +3 -0
  55. package/dist/physics/PhysicsWorld.d.ts.map +1 -1
  56. package/dist/physics/PhysicsWorld.js.map +1 -1
  57. package/dist/physics/XpbdWorld.d.ts +56 -0
  58. package/dist/physics/XpbdWorld.d.ts.map +1 -1
  59. package/dist/physics/XpbdWorld.js +45 -0
  60. package/dist/physics/XpbdWorld.js.map +1 -1
  61. package/dist/physics/cloth/index.d.ts +20 -19
  62. package/dist/physics/cloth/index.d.ts.map +1 -1
  63. package/dist/physics/cloth/index.js +19 -0
  64. package/dist/physics/cloth/index.js.map +1 -1
  65. package/dist/physics/spring/index.d.ts +19 -18
  66. package/dist/physics/spring/index.d.ts.map +1 -1
  67. package/dist/physics/spring/index.js +23 -0
  68. package/dist/physics/spring/index.js.map +1 -1
  69. package/dist/three-d/Character3D.d.ts +8 -7
  70. package/dist/three-d/Character3D.d.ts.map +1 -1
  71. package/dist/three-d/Character3D.js +7 -0
  72. package/dist/three-d/Character3D.js.map +1 -1
  73. package/dist/three-d/Collision3D.d.ts +4 -0
  74. package/dist/three-d/Collision3D.d.ts.map +1 -1
  75. package/dist/three-d/Collision3D.js.map +1 -1
  76. package/dist/three-d/Engine3D.d.ts +2 -0
  77. package/dist/three-d/Engine3D.d.ts.map +1 -1
  78. package/dist/three-d/Engine3D.js.map +1 -1
  79. package/dist/three-d/Grid.d.ts +5 -0
  80. package/dist/three-d/Grid.d.ts.map +1 -1
  81. package/dist/three-d/Grid.js.map +1 -1
  82. package/dist/three-d/Heightmap.d.ts +3 -0
  83. package/dist/three-d/Heightmap.d.ts.map +1 -1
  84. package/dist/three-d/Heightmap.js.map +1 -1
  85. package/dist/three-d/TileGrid3D.d.ts +6 -0
  86. package/dist/three-d/TileGrid3D.d.ts.map +1 -1
  87. package/dist/three-d/TileGrid3D.js.map +1 -1
  88. package/dist/three-d/Vox.d.ts +7 -0
  89. package/dist/three-d/Vox.d.ts.map +1 -1
  90. package/dist/three-d/Vox.js.map +1 -1
  91. package/dist/two-d/presentation/MwlPresentation.d.ts +7 -0
  92. package/dist/two-d/presentation/MwlPresentation.d.ts.map +1 -1
  93. package/dist/two-d/presentation/MwlPresentation.js.map +1 -1
  94. package/dist/two-d/ui/theme.d.ts +18 -0
  95. package/dist/two-d/ui/theme.d.ts.map +1 -1
  96. package/dist/two-d/ui/theme.js.map +1 -1
  97. package/dist/version.d.ts +1 -1
  98. package/dist/version.js +1 -1
  99. package/package.json +23 -1
  100. package/sbom.cdx.json +599 -139
  101. package/tools/benchmark-browser.mjs +6 -0
  102. package/tools/bundle-size.mjs +4 -0
  103. package/tools/compile-resources.d.mts +6 -0
  104. package/tools/compile-resources.mjs +53 -16
  105. package/tools/emit-page.d.mts +3 -0
  106. package/tools/emit-page.mjs +15 -2
  107. package/tools/init.mjs +3 -1
  108. package/tools/make-example-assets.mjs +4 -0
  109. package/tools/multiplayer-server.mjs +4 -0
  110. package/tools/mwl.mjs +7 -0
  111. package/tools/play-uci.mjs +4 -0
  112. package/tools/sbom.mjs +4 -0
  113. package/tools/soundfonts.d.mts +79 -0
  114. package/tools/soundfonts.mjs +301 -0
  115. package/tools/svg-optimise.d.mts +8 -0
  116. package/tools/svg-optimise.mjs +45 -0
  117. package/tools/verify-scenarios.mjs +6 -2
  118. package/tools/vite.mjs +2 -1
  119. package/tools.md +218 -0
@@ -136,6 +136,12 @@ export async function measurePage({
136
136
  * `benchmark-results/<page>.json`, named by the page so two pages keep separate histories.
137
137
  */
138
138
  async function main(argv) {
139
+ if (argv.includes('--help') || argv.includes('-h')) {
140
+ console.log(
141
+ 'usage: mwg-bench <page.html | dist folder> [--frames=180] [--min-fps=45] [--max-p95=40] [--history=<file>]',
142
+ );
143
+ return;
144
+ }
139
145
  const value = (name, env, fallback) =>
140
146
  argv.find((a) => a.startsWith(`--${name}=`))?.split('=')[1] ?? process.env[env] ?? fallback;
141
147
  const target = argv.find((arg) => !arg.startsWith('--')) ?? 'examples/dungeon/dist/index.html';
@@ -99,6 +99,10 @@ function measureFramework(root) {
99
99
  }
100
100
 
101
101
  function main(argv) {
102
+ if (argv.includes('--help') || argv.includes('-h')) {
103
+ console.log('usage: mwg-size <dist folder> [--baseline=<file>] [--tolerance=0.02] [--update]');
104
+ return;
105
+ }
102
106
  const tolerance = Number(
103
107
  argv.find((a) => a.startsWith('--tolerance='))?.slice(12) ?? process.env.MWG_SIZE_TOLERANCE ?? 0.02,
104
108
  );
@@ -12,6 +12,8 @@ export interface CompileResourcesOptions {
12
12
  toWebp?: boolean;
13
13
  webpLossless?: boolean;
14
14
  webpQuality?: number;
15
+ /** minify embedded `.svg` assets through the optional `svgo` package (version 4 or newer); false by default */
16
+ svgo?: boolean;
15
17
  /** Subset an embedded `.sf2` to the programs the embedded `.mid`/`.midi` files use. Default true; `false` embeds every SoundFont whole. */
16
18
  soundFont?: boolean;
17
19
  /** Explicit `{ bank, program }` list to subset a SoundFont to, overriding the MIDI-derived list. */
@@ -29,6 +31,8 @@ export interface CompileResourcesResult {
29
31
  rawBytes: number;
30
32
  embeddedBytes: number;
31
33
  webpConverted: number;
34
+ /** SVG assets embedded smaller than their source because of `svgo` */
35
+ svgOptimised: number;
32
36
  skipped: number;
33
37
  /** the `CREDITS.txt` text when the asset folder holds a `credits.json`, else null */
34
38
  credits: string | null;
@@ -50,6 +54,8 @@ export interface SoundFontEmbed {
50
54
  programs: number | null;
51
55
  rawBytes: number;
52
56
  embeddedBytes: number;
57
+ /** the programs the game's MIDI files ask for that this font does not carry; empty when none */
58
+ missing: SoundFontUsage[];
53
59
  }
54
60
 
55
61
  /** parses a `--soundfont-usage=bank:program,...` CLI value; undefined when the flag is absent */
@@ -41,9 +41,10 @@ export const DEFAULT_MIME = {
41
41
  '.mid': 'audio/midi',
42
42
  '.midi': 'audio/midi',
43
43
  '.sf2': 'application/octet-stream',
44
+ '.sf3': 'application/octet-stream',
44
45
  };
45
46
 
46
- const isSoundFontKey = (key) => key.toLowerCase().endsWith('.sf2');
47
+ const isSoundFontKey = (key) => /\.sf[23]$/i.test(key);
47
48
  const isMidiKey = (key) => /\.(mid|midi)$/i.test(key);
48
49
 
49
50
  /**
@@ -120,7 +121,11 @@ export async function* walk(dir) {
120
121
  * pixel-art tileset's exact colours survive; set false only with `webpQuality` to opt into
121
122
  * lossy re-encoding for photographic source art)
122
123
  * @param options.webpQuality 0-100, used only when `webpLossless` is false (default 90)
123
- * @param options.soundFont subset an embedded `.sf2` to the programs the embedded
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.
128
+ * @param options.soundFont subset an embedded `.sf2` or `.sf3` to the programs the embedded
124
129
  * `.mid`/`.midi` files use (default true); `false` embeds every SoundFont whole
125
130
  * @param options.soundFontUsage explicit `{ bank, program }` list to subset to, for a
126
131
  * game that voices notes no shipped MIDI file requests; overrides the MIDI-derived list
@@ -140,6 +145,7 @@ export async function compileResources({
140
145
  toWebp: convertToWebp = false,
141
146
  webpLossless = true,
142
147
  webpQuality = 90,
148
+ svgo: optimiseSvgFiles = false,
143
149
  soundFont = true,
144
150
  soundFontUsage,
145
151
  } = {}) {
@@ -204,10 +210,12 @@ export async function compileResources({
204
210
  let rawBytes = 0;
205
211
  let embeddedBytes = 0;
206
212
  let webpConverted = 0;
213
+ let svgOptimised = 0;
207
214
 
208
215
  // only paid when toWebp is actually used, so a build that never asks for it never imports
209
216
  // (or requires installing) sharp
210
217
  const toWebp = convertToWebp ? (await import('./webp-convert.mjs')).toWebp : null;
218
+ const optimiseSvg = optimiseSvgFiles ? (await import('./svg-optimise.mjs')).optimiseSvg : null;
211
219
 
212
220
  const creditsFile = join(from, 'credits.json');
213
221
  let credits = null;
@@ -239,6 +247,19 @@ export async function compileResources({
239
247
  }
240
248
  }
241
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
+
242
263
  if (isSoundFontKey(key)) {
243
264
  if (usage !== undefined && soundFontTools) {
244
265
  try {
@@ -246,7 +267,14 @@ export async function compileResources({
246
267
  } catch (error) {
247
268
  throw new Error(`cannot subset SoundFont asset ${key}: ${error.message ?? error}`);
248
269
  }
249
- soundFonts.push({ key, programs: usage.length, rawBytes: data.length, embeddedBytes: bytes.length });
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
+ });
250
278
  } else {
251
279
  soundFonts.push({ key, programs: null, rawBytes: data.length, embeddedBytes: data.length });
252
280
  }
@@ -272,7 +300,7 @@ export async function compileResources({
272
300
 
273
301
  if (credits) await writeFile(join(to, 'CREDITS.txt'), credits, 'utf8');
274
302
 
275
- return { groups: written, rawBytes, embeddedBytes, webpConverted, skipped, credits, soundFonts };
303
+ return { groups: written, rawBytes, embeddedBytes, webpConverted, svgOptimised, skipped, credits, soundFonts };
276
304
  }
277
305
 
278
306
  /** the plain-text credits for a manifest, one block per entry; throws on an entry that cannot be attributed */
@@ -301,7 +329,7 @@ async function runCli() {
301
329
  const [from, to] = args.filter((a) => !a.startsWith('--'));
302
330
  if (!from || !to) {
303
331
  console.error(
304
- '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,...]',
305
333
  );
306
334
  process.exit(1);
307
335
  }
@@ -314,17 +342,19 @@ async function runCli() {
314
342
  args.find((a) => a.startsWith('--soundfont-usage='))?.slice('--soundfont-usage='.length),
315
343
  );
316
344
 
317
- const { groups, rawBytes, embeddedBytes, webpConverted, skipped, soundFonts } = await compileResources({
318
- from,
319
- to,
320
- //a flat folder has no subdirectories to group by, so everything lands in one file
321
- groupBy: (key) => (key.includes('/') ? key.slice(0, key.indexOf('/')) : 'assets'),
322
- toWebp,
323
- webpLossless,
324
- webpQuality,
325
- soundFont: !args.includes('--no-soundfont-subset'),
326
- soundFontUsage,
327
- });
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
+ });
328
358
 
329
359
  const kb = (n) => (n / 1024).toFixed(1).padStart(8) + ' KB';
330
360
  for (const { name, count, size } of groups) {
@@ -337,7 +367,14 @@ async function runCli() {
337
367
  ? ` ${font.key}: SoundFont embedded whole (${kb(font.rawBytes)}), no shipped MIDI to subset against`
338
368
  : ` ${font.key}: SoundFont subset to ${font.programs} program(s), ${kb(font.rawBytes)} -> ${kb(font.embeddedBytes)}`,
339
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
+ }
340
376
  }
377
+ if (args.includes('--svgo')) console.log(` ${svgOptimised} SVG(s) optimised`);
341
378
  if (toWebp) {
342
379
  console.log(
343
380
  ` ${webpConverted} image(s) converted to WebP (${webpLossless ? 'lossless' : `lossy, quality ${webpQuality}`}), ` +
@@ -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;
@@ -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) {
package/tools/init.mjs CHANGED
@@ -130,7 +130,9 @@ export function initProject(folder) {
130
130
  //no top-level await: this module is also imported, and the real path covers an npm bin symlink
131
131
  if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
132
132
  const folder = process.argv[2];
133
- if (!folder) {
133
+ if (folder === '--help' || folder === '-h') {
134
+ console.log('usage: mwg-init <new folder>');
135
+ } else if (!folder) {
134
136
  console.error('usage: mwg-init <new folder>');
135
137
  process.exitCode = 2;
136
138
  } else {
@@ -717,6 +717,10 @@ if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.me
717
717
  const args = process.argv.slice(2);
718
718
  const folder = args.find((arg) => !arg.startsWith('--'));
719
719
  const inRepository = existsSync(join(here, '..', 'examples'));
720
+ if (args.includes('--help') || args.includes('-h')) {
721
+ console.log('usage: mwg-placeholder-assets <folder> [--force]');
722
+ process.exit(0);
723
+ }
720
724
  if (!folder && !inRepository) {
721
725
  console.error('usage: mwg-placeholder-assets <folder> [--force]');
722
726
  process.exit(1);
@@ -187,6 +187,10 @@ export function createLockstepServer(options = {}) {
187
187
  //realpath, since an npm `bin` shim reaches this file through a symlink
188
188
  if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
189
189
  const args = process.argv.slice(2);
190
+ if (args.includes('--help') || args.includes('-h')) {
191
+ console.log('usage: mwg-lockstep-server [port] [--host=<address>]');
192
+ process.exit(0);
193
+ }
190
194
  const port = Number(args.find((arg) => !arg.startsWith('--')) ?? 8787);
191
195
  const host = args.find((arg) => arg.startsWith('--host='))?.slice(7) ?? '127.0.0.1';
192
196
  const server = createLockstepServer({ port, host });
package/tools/mwl.mjs CHANGED
@@ -65,6 +65,13 @@ const assetRoot = assetRootFlag === -1 ? undefined : args[assetRootFlag + 1];
65
65
  const targetFlag = args.indexOf('--to');
66
66
  const target = targetFlag === -1 ? undefined : args[targetFlag + 1];
67
67
 
68
+ if (args.includes('--help') || args.includes('-h')) {
69
+ console.log(
70
+ 'usage: mwg-mwl <validate|compile|format|migrate|extract-i18n|assets|report|diagnostics|ci|hooks|build> input.mwl [-o output] [--to 1.0] [--manifest hooks.json] [--asset-root directory]',
71
+ );
72
+ process.exit(0);
73
+ }
74
+
68
75
  if (!command || !input || !commands.includes(command)) {
69
76
  console.error(
70
77
  'Usage: mwl <validate|compile|format|migrate|extract-i18n|assets|report|diagnostics|ci|hooks|build> input.mwl [-o output] [--to 1.0] [--manifest hooks.json] [--asset-root directory]',
@@ -171,6 +171,10 @@ const isMain = process.argv[1] && realpathSync(process.argv[1]) === fileURLToPat
171
171
 
172
172
  if (isMain) {
173
173
  const args = process.argv.slice(2);
174
+ if (args.includes('--help') || args.includes('-h')) {
175
+ console.log('usage: mwg-uci [--book <book.json>] [--tables <dir>] [--depth N]');
176
+ process.exit(0);
177
+ }
174
178
  const flag = (name) => {
175
179
  const at = args.indexOf(name);
176
180
  return at === -1 ? undefined : args[at + 1];
package/tools/sbom.mjs CHANGED
@@ -301,6 +301,10 @@ async function readJson(path) {
301
301
  * `application`, anything else as a `library`. `--check` compares instead of writing, for CI.
302
302
  */
303
303
  async function main(argv) {
304
+ if (argv.includes('--help') || argv.includes('-h')) {
305
+ console.log('usage: mwg-sbom [project folder] [--out=<file>] [--check]');
306
+ return;
307
+ }
304
308
  const root = resolve(argv.find((arg) => !arg.startsWith('--')) ?? '.');
305
309
  const out = resolve(root, argv.find((arg) => arg.startsWith('--out='))?.slice(6) ?? OUTPUT);
306
310
  const packageJson = await readJson(join(root, 'package.json'));
@@ -0,0 +1,79 @@
1
+ /** how freely a catalogue font may be shipped inside a game */
2
+ export type SoundFontLicenceClass =
3
+ 'public-domain' | 'permissive' | 'attribution' | 'share-alike' | 'copyleft' | 'check';
4
+
5
+ export interface SoundFontCatalogEntry {
6
+ name: string;
7
+ licence: string;
8
+ licenceClass: SoundFontLicenceClass;
9
+ web?: string;
10
+ /** instrument files the collection carries for this font (programs for a bundled bank) */
11
+ programs: number;
12
+ /** megabytes of a full General MIDI bank, SoundFont 3 compressed; absent for the bundled bank */
13
+ gmMB?: number;
14
+ /** megabytes of every instrument file, SoundFont 3 compressed (the bundled bank's own size) */
15
+ fullMB: number;
16
+ /** the file of a bundled bank, relative to the package root */
17
+ file?: string;
18
+ note?: string;
19
+ }
20
+
21
+ export interface SoundFontCatalog {
22
+ description: string;
23
+ /** where the per-instrument files are fetched from by default */
24
+ source: string;
25
+ /** the name of the bank the package ships and games use unless they choose another */
26
+ default: string;
27
+ bundled: SoundFontCatalogEntry[];
28
+ fonts: SoundFontCatalogEntry[];
29
+ }
30
+
31
+ export interface SoundFontProgramRef {
32
+ bank: number;
33
+ program: number;
34
+ }
35
+
36
+ export interface FetchSoundFontOptions {
37
+ /** a catalogue font name, from `mwg-soundfonts list` */
38
+ name: string;
39
+ programs: SoundFontProgramRef[];
40
+ catalog?: SoundFontCatalog;
41
+ /** a folder or `http(s)` URL laid out as `<font>/<bank>/<program>.sf3`; the catalogue's source by default */
42
+ source?: string;
43
+ /** required for a licence class that is not public-domain, permissive or attribution */
44
+ acceptLicence?: boolean;
45
+ /** the decoded font is a SoundFont 2 (`ffmpeg` needed) instead of a SoundFont 3; `sf3` by default */
46
+ format?: 'sf2' | 'sf3';
47
+ /** Ogg Vorbis bytes to mono 16-bit frames, for `format: 'sf2'`; `ffmpeg` by default */
48
+ decode?: (ogg: Uint8Array, rate: number) => Int16Array | Promise<Int16Array>;
49
+ /** replaces the file reader, mainly for tests */
50
+ read?: (path: string) => Promise<Uint8Array | null>;
51
+ }
52
+
53
+ export interface FetchedSoundFont {
54
+ /** one font holding the found programs, in `format` */
55
+ bytes: Uint8Array;
56
+ format: 'sf2' | 'sf3';
57
+ found: SoundFontProgramRef[];
58
+ /** requested programs the collection does not carry */
59
+ missing: SoundFontProgramRef[];
60
+ font: SoundFontCatalogEntry;
61
+ }
62
+
63
+ /** the licence-classified catalogue of fetchable fonts */
64
+ export declare const CATALOG_PATH: string;
65
+ export declare function readCatalog(path?: string): SoundFontCatalog;
66
+ export declare function findFont(catalog: SoundFontCatalog, name: string): SoundFontCatalogEntry | undefined;
67
+ /** `"0,24,128:0"`: a bare number is a program of bank 0, `bank:program` names both */
68
+ export declare function parseProgramList(text: string): SoundFontProgramRef[];
69
+ /** mono 16-bit frames from Ogg Vorbis bytes, through `ffmpeg` */
70
+ export declare function ffmpegDecode(ogg: Uint8Array): Int16Array;
71
+ /** a SoundFont 3 decoded into SoundFont 2 bytes (`ffmpeg` unless `decode` is given) */
72
+ export declare function sf3ToSf2(
73
+ bytes: Uint8Array,
74
+ decode?: (ogg: Uint8Array, rate: number) => Int16Array | Promise<Int16Array>,
75
+ ): Promise<Uint8Array>;
76
+ export declare function readCollectionFile(source: string, path: string): Promise<Uint8Array | null>;
77
+ export declare function fetchSoundFont(options: FetchSoundFontOptions): Promise<FetchedSoundFont>;
78
+ /** the credits note that travels beside a fetched font */
79
+ export declare function creditsFor(font: SoundFontCatalogEntry, programs: SoundFontProgramRef[]): string;