@datamoc/mw_games 0.42.0 → 0.43.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 (139) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +62 -1
  3. package/REFERENCE.md +57 -5
  4. package/api-surface.json +672 -0
  5. package/data/microgm-essential.PROVENANCE.md +13 -0
  6. package/data/microgm-essential.sf2 +0 -0
  7. package/data/soundfont-catalog.json +8 -0
  8. package/dist/audio/AudioLoader.d.ts +28 -0
  9. package/dist/audio/AudioLoader.d.ts.map +1 -0
  10. package/dist/audio/AudioLoader.js +53 -0
  11. package/dist/audio/AudioLoader.js.map +1 -0
  12. package/dist/audio/Channels.d.ts +1 -0
  13. package/dist/audio/Channels.d.ts.map +1 -1
  14. package/dist/audio/Channels.js +2 -1
  15. package/dist/audio/Channels.js.map +1 -1
  16. package/dist/audio/MidiRender.d.ts +16 -0
  17. package/dist/audio/MidiRender.d.ts.map +1 -1
  18. package/dist/audio/MidiRender.js +21 -0
  19. package/dist/audio/MidiRender.js.map +1 -1
  20. package/dist/audio/MusicPlayer.d.ts +80 -0
  21. package/dist/audio/MusicPlayer.d.ts.map +1 -0
  22. package/dist/audio/MusicPlayer.js +147 -0
  23. package/dist/audio/MusicPlayer.js.map +1 -0
  24. package/dist/audio/SampleDecoder.d.ts +16 -2
  25. package/dist/audio/SampleDecoder.d.ts.map +1 -1
  26. package/dist/audio/SampleDecoder.js +10 -6
  27. package/dist/audio/SampleDecoder.js.map +1 -1
  28. package/dist/audio/Sound.d.ts +2 -0
  29. package/dist/audio/Sound.d.ts.map +1 -1
  30. package/dist/audio/Sound.js +3 -0
  31. package/dist/audio/Sound.js.map +1 -1
  32. package/dist/audio/SoundFont.d.ts +5 -3
  33. package/dist/audio/SoundFont.d.ts.map +1 -1
  34. package/dist/audio/SoundFont.js +10 -8
  35. package/dist/audio/SoundFont.js.map +1 -1
  36. package/dist/audio/Synth.d.ts +1 -1
  37. package/dist/audio/Synth.d.ts.map +1 -1
  38. package/dist/audio/Synth.js +4 -0
  39. package/dist/audio/Synth.js.map +1 -1
  40. package/dist/audio/index.d.ts +6 -2
  41. package/dist/audio/index.d.ts.map +1 -1
  42. package/dist/audio/index.js +3 -1
  43. package/dist/audio/index.js.map +1 -1
  44. package/dist/mw_games.global.js +22 -22
  45. package/dist/mwl/index.d.ts +2 -0
  46. package/dist/mwl/index.d.ts.map +1 -1
  47. package/dist/mwl/index.js +1 -0
  48. package/dist/mwl/index.js.map +1 -1
  49. package/dist/mwl/objects.d.ts +31 -0
  50. package/dist/mwl/objects.d.ts.map +1 -0
  51. package/dist/mwl/objects.js +83 -0
  52. package/dist/mwl/objects.js.map +1 -0
  53. package/dist/mwl/schema.d.ts +1 -1
  54. package/dist/mwl/schema.d.ts.map +1 -1
  55. package/dist/mwl/tables.d.ts +2 -0
  56. package/dist/mwl/tables.d.ts.map +1 -1
  57. package/dist/mwl/tables.js +13 -0
  58. package/dist/mwl/tables.js.map +1 -1
  59. package/dist/physics/XpbdWorld.d.ts +23 -5
  60. package/dist/physics/XpbdWorld.d.ts.map +1 -1
  61. package/dist/physics/XpbdWorld.js +90 -6
  62. package/dist/physics/XpbdWorld.js.map +1 -1
  63. package/dist/testing/index.d.ts +16 -0
  64. package/dist/testing/index.d.ts.map +1 -1
  65. package/dist/testing/index.js +21 -0
  66. package/dist/testing/index.js.map +1 -1
  67. package/dist/three-d/Collision3D.d.ts +46 -2
  68. package/dist/three-d/Collision3D.d.ts.map +1 -1
  69. package/dist/three-d/Collision3D.js +46 -2
  70. package/dist/three-d/Collision3D.js.map +1 -1
  71. package/dist/three-d/Grid.d.ts +12 -1
  72. package/dist/three-d/Grid.d.ts.map +1 -1
  73. package/dist/three-d/Grid.js +12 -1
  74. package/dist/three-d/Grid.js.map +1 -1
  75. package/dist/three-d/Vox.d.ts +13 -1
  76. package/dist/three-d/Vox.d.ts.map +1 -1
  77. package/dist/three-d/Vox.js +13 -1
  78. package/dist/three-d/Vox.js.map +1 -1
  79. package/dist/two-d/Game.d.ts +21 -1
  80. package/dist/two-d/Game.d.ts.map +1 -1
  81. package/dist/two-d/Game.js +73 -6
  82. package/dist/two-d/Game.js.map +1 -1
  83. package/dist/two-d/Scene2D.d.ts +24 -0
  84. package/dist/two-d/Scene2D.d.ts.map +1 -1
  85. package/dist/two-d/Scene2D.js +30 -0
  86. package/dist/two-d/Scene2D.js.map +1 -1
  87. package/dist/two-d/render/SceneTransition.d.ts +272 -0
  88. package/dist/two-d/render/SceneTransition.d.ts.map +1 -0
  89. package/dist/two-d/render/SceneTransition.js +431 -0
  90. package/dist/two-d/render/SceneTransition.js.map +1 -0
  91. package/dist/two-d/render/index.d.ts +2 -0
  92. package/dist/two-d/render/index.d.ts.map +1 -1
  93. package/dist/two-d/render/index.js +1 -0
  94. package/dist/two-d/render/index.js.map +1 -1
  95. package/dist/two-d/ui/Button.d.ts +23 -0
  96. package/dist/two-d/ui/Button.d.ts.map +1 -1
  97. package/dist/two-d/ui/Button.js +37 -0
  98. package/dist/two-d/ui/Button.js.map +1 -1
  99. package/dist/two-d/ui/DropdownView.d.ts +49 -3
  100. package/dist/two-d/ui/DropdownView.d.ts.map +1 -1
  101. package/dist/two-d/ui/DropdownView.js +150 -9
  102. package/dist/two-d/ui/DropdownView.js.map +1 -1
  103. package/dist/two-d/ui/ScrollBox.d.ts +90 -0
  104. package/dist/two-d/ui/ScrollBox.d.ts.map +1 -1
  105. package/dist/two-d/ui/ScrollBox.js +206 -4
  106. package/dist/two-d/ui/ScrollBox.js.map +1 -1
  107. package/dist/two-d/ui/ScrollText.d.ts +122 -0
  108. package/dist/two-d/ui/ScrollText.d.ts.map +1 -0
  109. package/dist/two-d/ui/ScrollText.js +202 -0
  110. package/dist/two-d/ui/ScrollText.js.map +1 -0
  111. package/dist/two-d/ui/Tooltip.d.ts +70 -1
  112. package/dist/two-d/ui/Tooltip.d.ts.map +1 -1
  113. package/dist/two-d/ui/Tooltip.js +115 -3
  114. package/dist/two-d/ui/Tooltip.js.map +1 -1
  115. package/dist/two-d/ui/Window.d.ts +35 -2
  116. package/dist/two-d/ui/Window.d.ts.map +1 -1
  117. package/dist/two-d/ui/Window.js +58 -3
  118. package/dist/two-d/ui/Window.js.map +1 -1
  119. package/dist/two-d/ui/index.d.ts +7 -5
  120. package/dist/two-d/ui/index.d.ts.map +1 -1
  121. package/dist/two-d/ui/index.js +3 -2
  122. package/dist/two-d/ui/index.js.map +1 -1
  123. package/dist/version.d.ts +1 -1
  124. package/dist/version.js +1 -1
  125. package/package.json +7 -3
  126. package/sbom.cdx.json +19 -260
  127. package/tools/browser-driver.mjs +3 -3
  128. package/tools/browser-smoke.mjs +19 -2
  129. package/tools/build-tablebases.mjs +7 -3
  130. package/tools/compile-openings.mjs +7 -3
  131. package/tools/compile-resources.d.mts +2 -0
  132. package/tools/compile-resources.mjs +13 -1
  133. package/tools/emit-page.d.mts +6 -0
  134. package/tools/emit-page.mjs +43 -3
  135. package/tools/find-chrome.mjs +4 -2
  136. package/tools/init.mjs +11 -3
  137. package/tools/play-uci.mjs +9 -5
  138. package/tools/vite.mjs +9 -1
  139. package/tools.md +1 -0
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { readFile, writeFile, rm, stat } from 'node:fs/promises';
2
+ import { readFile, readdir, writeFile, rm, stat } from 'node:fs/promises';
3
3
  import { realpathSync } from 'node:fs';
4
4
  import { join, resolve as resolvePath } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
@@ -81,6 +81,7 @@ export async function emitPage({
81
81
  soundFont = true,
82
82
  soundFontUsage,
83
83
  csp = true,
84
+ maxPageBytes,
84
85
  }) {
85
86
  if (!dist) throw new Error('emitPage needs the dist folder of a vite build');
86
87
  if (!assets && assetFiles?.length) throw new Error(`unknown asset file ${assetFiles[0]}: no assets folder`);
@@ -97,7 +98,7 @@ export async function emitPage({
97
98
  soundFont,
98
99
  soundFontUsage,
99
100
  })
100
- : { groups: [], rawBytes: 0, embeddedBytes: 0, webpConverted: 0, svgOptimised: 0, soundFonts: [] };
101
+ : { groups: [], keys: [], rawBytes: 0, embeddedBytes: 0, webpConverted: 0, svgOptimised: 0, soundFonts: [] };
101
102
 
102
103
  const html = await readFile(join(dist, 'index.html'), 'utf8');
103
104
  const classic = toClassicScript(html);
@@ -124,7 +125,38 @@ export async function emitPage({
124
125
  const standalone = await readFile(single.path, 'utf8');
125
126
  await writeFile(single.path, withContentSecurityPolicy(standalone, { ...cspOptions, scripts }), 'utf8');
126
127
  }
127
- return { dist, entry: classic.src, ...compiled, compressed, single };
128
+ const unusedAssets = await unreferencedAssets(dist, classic.src, compiled.keys);
129
+ const pageBytes = await pageWeight(dist, single, compiled.groups);
130
+ if (maxPageBytes !== undefined && pageBytes > maxPageBytes) {
131
+ throw new Error(
132
+ `the page weighs ${kb(pageBytes)}, over the ${kb(maxPageBytes)} budget (maxPageBytes)` +
133
+ (unusedAssets.length
134
+ ? `; ${unusedAssets.length} embedded asset(s) are never named in the game code: ${unusedAssets.slice(0, 5).join(', ')}`
135
+ : ''),
136
+ );
137
+ }
138
+ return { dist, entry: classic.src, ...compiled, compressed, single, unusedAssets, pageBytes };
139
+ }
140
+
141
+ /** the embedded assets whose path appears nowhere in the game's script: a hint (a path built at run time is not seen), never an error */
142
+ async function unreferencedAssets(dist, entry, keys) {
143
+ if (keys.length === 0) return [];
144
+ const code = await readFile(join(dist, entry), 'utf8').catch(() => '');
145
+ return keys.filter((key) => !code.includes(key)).sort();
146
+ }
147
+
148
+ /** the bytes a player downloads to open the page: the single file when there is one, else the page, the game script and the asset scripts */
149
+ async function pageWeight(dist, single, groups) {
150
+ const files = single
151
+ ? [single.path]
152
+ : [
153
+ join(dist, 'index.html'),
154
+ ...(await readdir(dist)).filter((name) => name.endsWith('.js')).map((name) => join(dist, name)),
155
+ ...groups.map((g) => join(dist, 'assets', `${g.name}.js`)),
156
+ ];
157
+ let total = 0;
158
+ for (const file of files) total += (await stat(file)).size;
159
+ return total;
128
160
  }
129
161
 
130
162
  const kb = (n) => (n / 1024).toFixed(1) + ' KB';
@@ -170,6 +202,7 @@ async function main(argv) {
170
202
  svgo: argv.includes('--svgo'),
171
203
  soundFont: !argv.includes('--no-soundfont-subset'),
172
204
  soundFontUsage,
205
+ maxPageBytes: value('max-page-bytes') ? Number(value('max-page-bytes')) : undefined,
173
206
  csp: argv.includes('--no-csp')
174
207
  ? false
175
208
  : { connect: argv.filter((a) => a.startsWith('--connect=')).map((a) => a.slice('--connect='.length)) },
@@ -186,6 +219,13 @@ async function main(argv) {
186
219
  );
187
220
  }
188
221
  if (argv.includes('--svgo')) console.log(` ${result.svgOptimised} SVG(s) optimised`);
222
+ console.log(` page weight ${kb(result.pageBytes)}`);
223
+ if (result.unusedAssets.length > 0) {
224
+ console.log(
225
+ ` warning: ${result.unusedAssets.length} embedded asset(s) are never named in the game code, ` +
226
+ `so they only add weight (a path built at run time is not seen): ${result.unusedAssets.join(', ')}`,
227
+ );
228
+ }
189
229
  for (const font of result.soundFonts ?? []) {
190
230
  console.log(
191
231
  font.programs === null
@@ -17,9 +17,11 @@ async function playwright() {
17
17
  /**
18
18
  * The browser to drive: the platform's usual Chrome (or, on Windows, Edge), then a Chromium that
19
19
  * Playwright itself installed (`PLAYWRIGHT_BROWSERS_PATH`, common in CI images and containers),
20
- * then whatever is on PATH. `CHROME_PATH` overrides all of them in `launchChrome`.
20
+ * then whatever is on PATH. `CHROME_PATH` or `CHROME` overrides all of them.
21
21
  */
22
22
  export async function findChrome() {
23
+ const override = process.env.CHROME_PATH || process.env.CHROME;
24
+ if (override) return override;
23
25
  const candidates =
24
26
  process.platform === 'win32'
25
27
  ? [
@@ -93,7 +95,7 @@ async function playwrightChromium() {
93
95
  * page setup and assertions; this is the launch they all had in common.
94
96
  */
95
97
  export async function launchChrome({ argsEnv = 'MWG_BENCHMARK_CHROME_ARGS' } = {}) {
96
- const executablePath = process.env.CHROME_PATH ?? (await findChrome());
98
+ const executablePath = await findChrome();
97
99
  const extraArgs = (process.env[argsEnv] ?? '').split(' ').filter(Boolean);
98
100
  const { chromium } = await playwright();
99
101
  return chromium.launch({
package/tools/init.mjs CHANGED
@@ -26,7 +26,13 @@ export function initFiles(name) {
26
26
  private: true,
27
27
  version: '0.0.0',
28
28
  type: 'module',
29
- scripts: { dev: 'vite', build: 'vite build', check: 'tsc --noEmit' },
29
+ scripts: {
30
+ dev: 'vite',
31
+ build: 'vite build',
32
+ check: 'tsc --noEmit',
33
+ test: 'node --test',
34
+ 'hooks:install': 'git config core.hooksPath .githooks',
35
+ },
30
36
  dependencies: { '@datamoc/mw_games': `^${PACKAGE_VERSION}`, 'pixi.js': '^8.20.1' },
31
37
  devDependencies: { typescript: '^7.0.2', vite: '^8.2.2' },
32
38
  },
@@ -101,7 +107,9 @@ export function initFiles(name) {
101
107
  '',
102
108
  ].join('\n'),
103
109
  'assets/.gitkeep': '',
104
- 'README.md': `# ${name}\n\n\`npm install\`, then \`npm run dev\` to play it while you edit, \`npm run build\` to write \`dist/\`.\nOpen \`dist/index.html\` (or \`dist/standalone.html\`) by double-clicking it: no server needed.\n\nPut images and sounds under \`assets/\`; the build compiles them into the page. An \`assets/credits.json\`\n(\`[{ "name": "...", "license": "...", "author": "...", "url": "..." }]\`) becomes \`dist/CREDITS.txt\`.\n`,
110
+ '.gitignore': 'node_modules/\ndist/\n.opencode/\n.coord-session\n',
111
+ '.githooks/pre-commit': '#!/bin/sh\nset -e\nnpm run check\nnpm test\n',
112
+ 'README.md': `# ${name}\n\n\`npm install\`, then \`npm run dev\` to play it while you edit, \`npm run build\` to write \`dist/\`.\nOpen \`dist/index.html\` (or \`dist/standalone.html\`) by double-clicking it: no server needed.\n\nPut images and sounds under \`assets/\`; the build compiles them into the page. An \`assets/credits.json\`\n(\`[{ "name": "...", "license": "...", "author": "...", "url": "..." }]\`) becomes \`dist/CREDITS.txt\`.\n\nAfter \`git init\`, run \`npm run hooks:install\` to enable the included pre-commit hook.\nIt runs \`npm run check\` and \`npm test\`; add your tests under \`tests/\`.\n`,
105
113
  };
106
114
  }
107
115
 
@@ -121,7 +129,7 @@ export function initProject(folder) {
121
129
  )) {
122
130
  const file = join(target, path);
123
131
  mkdirSync(dirname(file), { recursive: true });
124
- writeFileSync(file, content, 'utf8');
132
+ writeFileSync(file, content, { encoding: 'utf8', mode: path === '.githooks/pre-commit' ? 0o755 : 0o644 });
125
133
  written.push(path);
126
134
  }
127
135
  return { folder: target, files: written };
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from 'node:url';
3
- import { readFileSync, readdirSync, realpathSync } from 'node:fs';
3
+ import { existsSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
4
4
  import { join, extname } from 'node:path';
5
5
  import { createInterface } from 'node:readline';
6
- import { applyMove, cloneChess, legalMoves, parseFen, squareName, startingChess } from '../src/board/chess.ts';
7
- import { parseUciMove, probeBook } from '../src/board/openings.ts';
8
- import { searchTourney } from '../src/board/tourney.ts';
9
- import { loadTablebaseEnding, probeTablebase } from '../src/board/tablebase.ts';
6
+ //the framework's own modules: the TypeScript sources inside this repository, the built `dist` inside an
7
+ //installed package, which ships no `src`
8
+ const fromSource = existsSync(new URL('../src/board/chess.ts', import.meta.url));
9
+ const load = (path) => import(fromSource ? `../src/${path}.ts` : `../dist/${path}.js`);
10
+ const { applyMove, cloneChess, legalMoves, parseFen, squareName, startingChess } = await load('board/chess');
11
+ const { parseUciMove, probeBook } = await load('board/openings');
12
+ const { searchTourney } = await load('board/tourney');
13
+ const { loadTablebaseEnding, probeTablebase } = await load('board/tablebase');
10
14
 
11
15
  /**
12
16
  * A UCI adapter over the tournament engine: `position`/`go` on stdin, `bestmove`
package/tools/vite.mjs CHANGED
@@ -80,7 +80,15 @@ export function mwgPage(options = {}) {
80
80
  const { assets: _assets, sbom = true, ...emitOptions } = options;
81
81
  const source = assets ? resolve(root, assets) : undefined;
82
82
  const dist = resolve(root, outDir);
83
- await emitPage({ ...emitOptions, dist, assets: source && existsSync(source) ? source : undefined });
83
+ const page = await emitPage({
84
+ ...emitOptions,
85
+ dist,
86
+ assets: source && existsSync(source) ? source : undefined,
87
+ });
88
+ if (page.unusedAssets.length > 0)
89
+ console.warn(
90
+ `mwg-page: ${page.unusedAssets.length} embedded asset(s) are never named in the game code: ${page.unusedAssets.join(', ')}`,
91
+ );
84
92
  if (sbom) {
85
93
  const manifest = join(root, 'package.json');
86
94
  const packageJson = existsSync(manifest)
package/tools.md CHANGED
@@ -201,6 +201,7 @@ same build.
201
201
  | --- | --- | --- |
202
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` |
203
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-bridge-tutorial` / `bins-smoke` | `build-bridge-tutorial` writes `webpage/tutorials/bridge/index.html` from `tools/templates/bridge-tutorial.html`, quoting the code blocks of `examples/bridge` so a test can check them. `bins-smoke` packs the project, installs the tarball in an empty folder and runs every `mwg-*` bin with `--help` on the Node that runs it; CI runs it on Node 20.19.0. Repository tools, not shipped | `npm run build:bridge-tutorial`, `npm run bins:smoke` |
204
205
  | `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]` |
205
206
  | `make-example-diagrams` / `make-architecture-diagrams` | Rebuild the generated diagrams under `webpage/assets` (never hand-drawn) | `npm run webpage:diagrams` |
206
207
  | `api-report` | Regenerates `API_REPORT.md` from the built declarations; `--check` compares instead. A CI gate | `npm run api:report`, `npm run api:check` |