@datamoc/mw_games 0.19.0 → 0.20.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 (44) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/ai/index.d.ts +2 -2
  3. package/dist/ai/index.d.ts.map +1 -1
  4. package/dist/ai/index.js +1 -1
  5. package/dist/ai/index.js.map +1 -1
  6. package/dist/ai/search.d.ts +49 -0
  7. package/dist/ai/search.d.ts.map +1 -1
  8. package/dist/ai/search.js +48 -0
  9. package/dist/ai/search.js.map +1 -1
  10. package/dist/audio/MidiRender.d.ts +39 -2
  11. package/dist/audio/MidiRender.d.ts.map +1 -1
  12. package/dist/audio/MidiRender.js +286 -199
  13. package/dist/audio/MidiRender.js.map +1 -1
  14. package/dist/audio/index.d.ts +2 -2
  15. package/dist/audio/index.d.ts.map +1 -1
  16. package/dist/audio/index.js +1 -1
  17. package/dist/audio/index.js.map +1 -1
  18. package/dist/board/Engine.d.ts +33 -0
  19. package/dist/board/Engine.d.ts.map +1 -1
  20. package/dist/board/Engine.js +448 -1
  21. package/dist/board/Engine.js.map +1 -1
  22. package/dist/board/index.d.ts +2 -2
  23. package/dist/board/index.d.ts.map +1 -1
  24. package/dist/board/index.js +1 -1
  25. package/dist/board/index.js.map +1 -1
  26. package/dist/mw_games.global.js +22 -22
  27. package/dist/threads/index.d.ts +3 -0
  28. package/dist/threads/index.d.ts.map +1 -0
  29. package/dist/threads/index.js +2 -0
  30. package/dist/threads/index.js.map +1 -0
  31. package/dist/threads/spawn.d.ts +41 -0
  32. package/dist/threads/spawn.d.ts.map +1 -0
  33. package/dist/threads/spawn.js +143 -0
  34. package/dist/threads/spawn.js.map +1 -0
  35. package/dist/threads/worker.d.ts +51 -0
  36. package/dist/threads/worker.d.ts.map +1 -0
  37. package/dist/threads/worker.js +84 -0
  38. package/dist/threads/worker.js.map +1 -0
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/package.json +6 -1
  42. package/sbom.cdx.json +54 -28
  43. package/tools/compress-dist.d.mts +8 -0
  44. package/tools/compress-dist.mjs +76 -30
@@ -0,0 +1,3 @@
1
+ export { spawn } from './spawn.ts';
2
+ export type { SpawnOptions } from './spawn.ts';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/threads/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { spawn } from './spawn.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/threads/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,41 @@
1
+ export interface SpawnOptions {
2
+ /** buffers posted to the task instead of copied: each is detached from this thread when spawn runs */
3
+ transfer?: Transferable[];
4
+ /** abandons the task after this many milliseconds; there is no default, because a render can legitimately take minutes */
5
+ timeout?: number;
6
+ /** aborting rejects the promise with the signal's reason and terminates the task's worker */
7
+ signal?: AbortSignal;
8
+ }
9
+ /**
10
+ * Runs `fn` on a worker thread and resolves with its value, keeping the caller's thread
11
+ * free: the one synchronous block a game can never get back is the frame it spends waiting.
12
+ *
13
+ * The task is serialized with `Function.prototype.toString` and rehydrated in the worker,
14
+ * so it must be **self-contained** - no closures over module scope, no imports, no bound or
15
+ * native functions. Everything it needs arrives as `args`, structured-cloned in; buffers
16
+ * listed in `options.transfer` are moved instead, detached from the caller's thread. The
17
+ * result comes back structured-cloned, with every ArrayBuffer inside it transferred rather
18
+ * than copied (the worker is finished with them). A task that throws, closes over
19
+ * something the worker cannot see, or exits its worker without answering rejects the
20
+ * promise - it never hangs - and `timeout` or `signal` abandon a task that never returns.
21
+ *
22
+ * One worker per task: construction costs a few milliseconds, which is nothing next to the
23
+ * work worth moving off the main thread (T161 measured break-even at roughly 10 ms per
24
+ * task in a batch, and far less against a dropped frame).
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { spawn } from '@datamoc/mw_games/threads';
29
+ *
30
+ * const sum = await spawn((a: number, b: number) => a + b, [40, 2]);
31
+ * console.log(sum); // 42
32
+ *
33
+ * //a task that never returns is abandoned rather than hanging the caller
34
+ * const controller = new AbortController();
35
+ * const pending = spawn(() => new Promise(() => {}), [], { signal: controller.signal });
36
+ * controller.abort();
37
+ * pending.catch((error: Error) => console.log(error.name)); // 'AbortError'
38
+ * ```
39
+ */
40
+ export declare function spawn<Args extends readonly unknown[], Result>(fn: (...args: Args) => Result, args?: Args, options?: SpawnOptions): Promise<Awaited<Result>>;
41
+ //# sourceMappingURL=spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../src/threads/spawn.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC5B,sGAAsG;IACtG,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,0HAA0H;IAC1H,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAsED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,KAAK,CAAC,IAAI,SAAS,SAAS,OAAO,EAAE,EAAE,MAAM,EAC5D,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,MAAM,EAC7B,IAAI,CAAC,EAAE,IAAI,EACX,OAAO,GAAE,YAAiB,GACxB,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CA0D1B"}
@@ -0,0 +1,143 @@
1
+ import { nodeThreads, workerMain } from './worker.js';
2
+ const browserWorker = (events) => {
3
+ if (typeof Worker === 'undefined' ||
4
+ typeof Blob === 'undefined' ||
5
+ typeof URL === 'undefined' ||
6
+ typeof URL.createObjectURL !== 'function') {
7
+ return null;
8
+ }
9
+ //a blob worker, because the shipped file:// page allows worker-src blob: and because a
10
+ //file:// module worker is blocked by CORS whatever the policy says
11
+ const url = URL.createObjectURL(new Blob([`(${workerMain.toString()})()`], { type: 'text/javascript' }));
12
+ let worker;
13
+ try {
14
+ worker = new Worker(url);
15
+ }
16
+ catch (error) {
17
+ URL.revokeObjectURL(url);
18
+ throw error;
19
+ }
20
+ worker.onmessage = (event) => events.reply(event.data);
21
+ worker.onerror = (event) => events.fail(new Error(event.message || 'the worker failed to run'));
22
+ worker.onmessageerror = () => events.fail(new Error('the worker sent a reply that could not be deserialized'));
23
+ return {
24
+ post: (message, transfer) => worker.postMessage(message, transfer),
25
+ terminate: () => {
26
+ worker.terminate();
27
+ URL.revokeObjectURL(url);
28
+ },
29
+ };
30
+ };
31
+ const nodeWorker = (events) => {
32
+ const threads = nodeThreads();
33
+ if (!threads)
34
+ return null;
35
+ //the same source string as the browser path, executed as CJS by Node's eval mode
36
+ const worker = new threads.Worker(`(${workerMain.toString()})()`, { eval: true });
37
+ worker.on('message', (reply) => events.reply(reply));
38
+ worker.on('error', (error) => events.fail(error instanceof Error ? error : new Error(String(error))));
39
+ worker.on('messageerror', () => events.fail(new Error('the worker sent a reply that could not be deserialized')));
40
+ //a task that kills its worker without answering must reject rather than hang forever -
41
+ //the failure the multithreading package's pool never reports (T161)
42
+ worker.on('exit', (code) => events.fail(new Error(`the worker exited with code ${code} before answering`)));
43
+ return {
44
+ post: (message, transfer) => worker.postMessage(message, transfer),
45
+ terminate: () => void worker.terminate(),
46
+ };
47
+ };
48
+ /** the error a failed reply carries, with the worker-side stack where there was one */
49
+ function taskError(reply) {
50
+ const error = new Error(reply.message);
51
+ if (reply.stack)
52
+ error.stack = reply.stack;
53
+ return error;
54
+ }
55
+ /**
56
+ * Runs `fn` on a worker thread and resolves with its value, keeping the caller's thread
57
+ * free: the one synchronous block a game can never get back is the frame it spends waiting.
58
+ *
59
+ * The task is serialized with `Function.prototype.toString` and rehydrated in the worker,
60
+ * so it must be **self-contained** - no closures over module scope, no imports, no bound or
61
+ * native functions. Everything it needs arrives as `args`, structured-cloned in; buffers
62
+ * listed in `options.transfer` are moved instead, detached from the caller's thread. The
63
+ * result comes back structured-cloned, with every ArrayBuffer inside it transferred rather
64
+ * than copied (the worker is finished with them). A task that throws, closes over
65
+ * something the worker cannot see, or exits its worker without answering rejects the
66
+ * promise - it never hangs - and `timeout` or `signal` abandon a task that never returns.
67
+ *
68
+ * One worker per task: construction costs a few milliseconds, which is nothing next to the
69
+ * work worth moving off the main thread (T161 measured break-even at roughly 10 ms per
70
+ * task in a batch, and far less against a dropped frame).
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * import { spawn } from '@datamoc/mw_games/threads';
75
+ *
76
+ * const sum = await spawn((a: number, b: number) => a + b, [40, 2]);
77
+ * console.log(sum); // 42
78
+ *
79
+ * //a task that never returns is abandoned rather than hanging the caller
80
+ * const controller = new AbortController();
81
+ * const pending = spawn(() => new Promise(() => {}), [], { signal: controller.signal });
82
+ * controller.abort();
83
+ * pending.catch((error: Error) => console.log(error.name)); // 'AbortError'
84
+ * ```
85
+ */
86
+ export function spawn(fn, args, options = {}) {
87
+ return new Promise((resolve, reject) => {
88
+ if (options.signal?.aborted) {
89
+ reject(options.signal.reason);
90
+ return;
91
+ }
92
+ let settled = false;
93
+ let timer;
94
+ let worker;
95
+ const settle = (finish) => {
96
+ if (settled)
97
+ return;
98
+ settled = true;
99
+ if (timer !== undefined)
100
+ clearTimeout(timer);
101
+ options.signal?.removeEventListener('abort', onAbort);
102
+ //one worker per task: whatever settled it, the worker is done
103
+ worker?.terminate();
104
+ finish();
105
+ };
106
+ function onAbort() {
107
+ settle(() => reject(options.signal.reason));
108
+ }
109
+ const events = {
110
+ reply: (reply) => settle(() => {
111
+ if (reply.ok)
112
+ resolve(reply.value);
113
+ else
114
+ reject(taskError(reply));
115
+ }),
116
+ fail: (error) => settle(() => reject(error)),
117
+ };
118
+ try {
119
+ //each host returns null when its environment is absent, so they are tried in order
120
+ worker = browserWorker(events) ?? nodeWorker(events);
121
+ }
122
+ catch (error) {
123
+ reject(error);
124
+ return;
125
+ }
126
+ if (!worker) {
127
+ reject(new Error('spawn() needs Web Workers or node:worker_threads, and this environment offers neither'));
128
+ return;
129
+ }
130
+ if (options.timeout !== undefined && options.timeout > 0) {
131
+ timer = setTimeout(() => settle(() => reject(new Error(`the task did not answer within ${options.timeout} ms`))), options.timeout);
132
+ }
133
+ options.signal?.addEventListener('abort', onAbort, { once: true });
134
+ try {
135
+ worker.post({ code: fn.toString(), args: args ?? [] }, options.transfer ?? []);
136
+ }
137
+ catch (error) {
138
+ //unclonable arguments, or a transfer list that does not match the message
139
+ settle(() => reject(error));
140
+ }
141
+ });
142
+ }
143
+ //# sourceMappingURL=spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../src/threads/spawn.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /** what the main thread posts to a fresh worker: the task's source and its arguments */
2
+ export interface WorkerRequest {
3
+ /** the stringified task function, rehydrated in the worker with `new Function` */
4
+ code: string;
5
+ args: readonly unknown[];
6
+ }
7
+ /** what the worker posts back: the value, or the failure that stopped it */
8
+ export type WorkerReply = {
9
+ ok: true;
10
+ value: unknown;
11
+ } | {
12
+ ok: false;
13
+ message: string;
14
+ stack?: string;
15
+ };
16
+ /**
17
+ * The slice of `node:worker_threads` this module uses, described structurally: `src/`
18
+ * compiles in a browser-only config (the `@example` fence compiler runs with `types: []`),
19
+ * so nothing here may name `process` or a `node:` type directly.
20
+ */
21
+ export interface NodeThreads {
22
+ parentPort: {
23
+ on(event: 'message', listener: (request: WorkerRequest) => void): void;
24
+ postMessage(message: WorkerReply, transfer?: ArrayBuffer[]): void;
25
+ } | null;
26
+ Worker: new (code: string, options: {
27
+ eval: true;
28
+ }) => {
29
+ on(event: 'message', listener: (reply: WorkerReply) => void): void;
30
+ on(event: 'error', listener: (error: unknown) => void): void;
31
+ on(event: 'messageerror', listener: () => void): void;
32
+ on(event: 'exit', listener: (code: number) => void): void;
33
+ postMessage(message: WorkerRequest, transfer?: readonly unknown[]): void;
34
+ terminate(): Promise<number> | void;
35
+ };
36
+ }
37
+ /** `node:worker_threads` reached through `globalThis`, so nothing here needs @types/node */
38
+ export declare function nodeThreads(): NodeThreads | undefined;
39
+ /**
40
+ * The worker half of `threads.spawn`: receives one task, runs it, replies, and goes no
41
+ * further (spawn terminates it as soon as the reply settles).
42
+ *
43
+ * **This function must stay self-contained.** `spawn` ships it to a worker with
44
+ * `workerMain.toString()`, so it may not reference imports, module-scope helpers or any
45
+ * outer binding - everything it needs is either a language global or nested inside it.
46
+ * The same body runs in both transports: a classic blob worker in the browser (where the
47
+ * shipped `file://` CSP allows only `worker-src blob:` and `new Function`, per the T161
48
+ * probe) and an eval'd `node:worker_threads` worker in Node, which has no `self`.
49
+ */
50
+ export declare function workerMain(): void;
51
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/threads/worker.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,WAAW,aAAa;IAC7B,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;CACzB;AAED,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExG;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC3B,UAAU,EAAE;QACX,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI,CAAC;QACvE,WAAW,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;KAClE,GAAG,IAAI,CAAC;IACT,MAAM,EAAE,KACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,KACnB;QACJ,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI,CAAC;QACnE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;QAC7D,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;QACtD,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;QAC1D,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAAC;QACzE,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;KACpC,CAAC;CACF;AAED,4FAA4F;AAC5F,wBAAgB,WAAW,IAAI,WAAW,GAAG,SAAS,CAMrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAoEjC"}
@@ -0,0 +1,84 @@
1
+ /** `node:worker_threads` reached through `globalThis`, so nothing here needs @types/node */
2
+ export function nodeThreads() {
3
+ return globalThis.process?.getBuiltinModule?.('node:worker_threads');
4
+ }
5
+ /**
6
+ * The worker half of `threads.spawn`: receives one task, runs it, replies, and goes no
7
+ * further (spawn terminates it as soon as the reply settles).
8
+ *
9
+ * **This function must stay self-contained.** `spawn` ships it to a worker with
10
+ * `workerMain.toString()`, so it may not reference imports, module-scope helpers or any
11
+ * outer binding - everything it needs is either a language global or nested inside it.
12
+ * The same body runs in both transports: a classic blob worker in the browser (where the
13
+ * shipped `file://` CSP allows only `worker-src blob:` and `new Function`, per the T161
14
+ * probe) and an eval'd `node:worker_threads` worker in Node, which has no `self`.
15
+ */
16
+ export function workerMain() {
17
+ //the same chain as nodeThreads(), inlined because workerMain is stringified into the
18
+ //worker and may not call anything module-scoped; the cast is a type, erased before then
19
+ const nodePort = globalThis.process?.getBuiltinModule?.('node:worker_threads')?.parentPort ?? null;
20
+ const scope = globalThis;
21
+ /** every ArrayBuffer reachable through plain objects and arrays, listed once each */
22
+ const transferablesOf = (value) => {
23
+ const out = [];
24
+ const seen = new Set();
25
+ const visit = (item) => {
26
+ if (item === null || typeof item !== 'object')
27
+ return;
28
+ if (seen.has(item))
29
+ return;
30
+ seen.add(item);
31
+ if (ArrayBuffer.isView(item)) {
32
+ //a view transfers its whole buffer; a SharedArrayBuffer cannot transfer, so it
33
+ //stays a copy (and does not exist in the shipped file:// target at all)
34
+ const buffer = item.buffer;
35
+ if (buffer instanceof ArrayBuffer && !out.includes(buffer))
36
+ out.push(buffer);
37
+ return;
38
+ }
39
+ if (item instanceof ArrayBuffer) {
40
+ if (!out.includes(item))
41
+ out.push(item);
42
+ return;
43
+ }
44
+ if (Array.isArray(item)) {
45
+ for (const entry of item)
46
+ visit(entry);
47
+ return;
48
+ }
49
+ const prototype = Object.getPrototypeOf(item);
50
+ if (prototype === Object.prototype || prototype === null) {
51
+ for (const entry of Object.values(item))
52
+ visit(entry);
53
+ }
54
+ };
55
+ visit(value);
56
+ return out;
57
+ };
58
+ const reply = (message, transfer = []) => {
59
+ if (nodePort)
60
+ nodePort.postMessage(message, transfer);
61
+ else
62
+ scope.postMessage(message, transfer);
63
+ };
64
+ const onRequest = async (request) => {
65
+ try {
66
+ const task = new Function(`return (${request.code});`)();
67
+ const value = await task(...request.args);
68
+ reply({ ok: true, value }, transferablesOf(value));
69
+ }
70
+ catch (error) {
71
+ const failure = error;
72
+ reply({
73
+ ok: false,
74
+ message: String(failure?.message ?? error),
75
+ stack: typeof failure?.stack === 'string' ? failure.stack : undefined,
76
+ });
77
+ }
78
+ };
79
+ if (nodePort)
80
+ nodePort.on('message', (request) => void onRequest(request));
81
+ else
82
+ scope.onmessage = (event) => void onRequest(event.data);
83
+ }
84
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/threads/worker.ts"],"names":[],"mappings":"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"}
package/dist/version.d.ts CHANGED
@@ -9,5 +9,5 @@
9
9
  * this from `package.json` automatically, chosen because this project's own build already
10
10
  * has no step before `tsc`/`vite` that this would need to run ahead of.
11
11
  */
12
- export declare const version = "0.19.0";
12
+ export declare const version = "0.20.0";
13
13
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -9,5 +9,5 @@
9
9
  * this from `package.json` automatically, chosen because this project's own build already
10
10
  * has no step before `tsc`/`vite` that this would need to run ahead of.
11
11
  */
12
- export const version = '0.19.0';
12
+ export const version = '0.20.0';
13
13
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datamoc/mw_games",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "A local-file game framework: renderer-free game logic, drawn through PixiJS in 2D or Babylon.js in 3D, with mobile and desktop packaging.",
5
5
  "license": "MPL-2.0",
6
6
  "repository": {
@@ -178,6 +178,10 @@
178
178
  "types": "./dist/testing/index.d.ts",
179
179
  "import": "./dist/testing/index.js"
180
180
  },
181
+ "./threads": {
182
+ "types": "./dist/threads/index.d.ts",
183
+ "import": "./dist/threads/index.js"
184
+ },
181
185
  "./tools/benchmark-browser": "./tools/benchmark-browser.mjs",
182
186
  "./tools/benchmark-history": "./tools/benchmark-history.mjs",
183
187
  "./tools/browser-smoke": "./tools/browser-smoke.mjs",
@@ -361,6 +365,7 @@
361
365
  "@capacitor/ios": "^7.4.4",
362
366
  "@types/node": "^26.4.1",
363
367
  "@types/ws": "^8.18.1",
368
+ "multithreading": "^0.3.52",
364
369
  "pixi.js": "^8.20.1",
365
370
  "playwright-core": "^1.62.1",
366
371
  "prettier": "^3.9.6",
package/sbom.cdx.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.6",
4
- "serialNumber": "urn:uuid:316ac220-52bf-5102-b492-e3d49b6d5244",
4
+ "serialNumber": "urn:uuid:224ae83a-9a61-53bd-ab61-d8f83739153f",
5
5
  "version": 1,
6
6
  "metadata": {
7
7
  "tools": {
@@ -10,16 +10,16 @@
10
10
  "type": "application",
11
11
  "group": "datamoc",
12
12
  "name": "tools/sbom.mjs",
13
- "version": "0.19.0"
13
+ "version": "0.20.0"
14
14
  }
15
15
  ]
16
16
  },
17
17
  "component": {
18
18
  "type": "library",
19
- "bom-ref": "pkg:npm/%40datamoc/mw_games@0.19.0",
19
+ "bom-ref": "pkg:npm/%40datamoc/mw_games@0.20.0",
20
20
  "name": "@datamoc/mw_games",
21
- "version": "0.19.0",
22
- "purl": "pkg:npm/%40datamoc/mw_games@0.19.0",
21
+ "version": "0.20.0",
22
+ "purl": "pkg:npm/%40datamoc/mw_games@0.20.0",
23
23
  "description": "A local-file game framework: renderer-free game logic, drawn through PixiJS in 2D or Babylon.js in 3D, with mobile and desktop packaging.",
24
24
  "licenses": [
25
25
  {
@@ -33,15 +33,15 @@
33
33
  "components": [
34
34
  {
35
35
  "type": "library",
36
- "bom-ref": "pkg:npm/%40babylonjs/core@9.26.0",
36
+ "bom-ref": "pkg:npm/%40babylonjs/core@9.27.1",
37
37
  "name": "@babylonjs/core",
38
- "version": "9.26.0",
39
- "purl": "pkg:npm/%40babylonjs/core@9.26.0",
38
+ "version": "9.27.1",
39
+ "purl": "pkg:npm/%40babylonjs/core@9.27.1",
40
40
  "scope": "excluded",
41
41
  "hashes": [
42
42
  {
43
43
  "alg": "SHA-512",
44
- "content": "6097bb661f6f8fa5a0f0d6b0268032728185161bb35bd28b67290d9b69ef94daecd8fe371d332fdd2be06dfc072a40e91dc81ae1caddc9536549fd39e4b2e45e"
44
+ "content": "6dc1de07b60dad84620fea448598c47f39bcb53fcde1d0bca1886510d1556bd7e73ff5e21c46e6b7b87653a12cfefb3cc7f023bbef6f829726ab6aa72effb71f"
45
45
  }
46
46
  ],
47
47
  "licenses": [
@@ -54,15 +54,15 @@
54
54
  },
55
55
  {
56
56
  "type": "library",
57
- "bom-ref": "pkg:npm/%40babylonjs/loaders@9.26.0",
57
+ "bom-ref": "pkg:npm/%40babylonjs/loaders@9.27.1",
58
58
  "name": "@babylonjs/loaders",
59
- "version": "9.26.0",
60
- "purl": "pkg:npm/%40babylonjs/loaders@9.26.0",
59
+ "version": "9.27.1",
60
+ "purl": "pkg:npm/%40babylonjs/loaders@9.27.1",
61
61
  "scope": "excluded",
62
62
  "hashes": [
63
63
  {
64
64
  "alg": "SHA-512",
65
- "content": "2207e634db293eeec2c421b40bcbef377d8cd1f737c1eda697047360c758ad380d1454113be09d3f71c5ebcb1295a6743f9855f604b8d87057fb79145c001fad"
65
+ "content": "042b671f83ae0a2abb5cf34436c38fc8d05118e0519556e5778f58c0c21a95b3638e070030cb64fb0c7c933135ff2c0a1771dfae3af5edb30b4f7aa0989678da"
66
66
  }
67
67
  ],
68
68
  "licenses": [
@@ -1356,15 +1356,15 @@
1356
1356
  },
1357
1357
  {
1358
1358
  "type": "library",
1359
- "bom-ref": "pkg:npm/%40types/node@26.5.1",
1359
+ "bom-ref": "pkg:npm/%40types/node@26.6.2",
1360
1360
  "name": "@types/node",
1361
- "version": "26.5.1",
1362
- "purl": "pkg:npm/%40types/node@26.5.1",
1361
+ "version": "26.6.2",
1362
+ "purl": "pkg:npm/%40types/node@26.6.2",
1363
1363
  "scope": "excluded",
1364
1364
  "hashes": [
1365
1365
  {
1366
1366
  "alg": "SHA-512",
1367
- "content": "0b3366d857b35b8551fcb8c6eb251d88480b13fac043d4a58f9802bbf0b656b75c5bb2346a1359f0345b1d3ef3399eabdce36077f6ec4087926a80c6addd42ea"
1367
+ "content": "5f53f6d6c70cbf8cc628b62a8dd1a368a6bb08e6b444a55861964dfcd7ef2d0d497a0c4585d869646fcbca7f005f1e820d46af280775d6fe81bdfa640c1c8af2"
1368
1368
  }
1369
1369
  ],
1370
1370
  "licenses": [
@@ -3202,6 +3202,27 @@
3202
3202
  }
3203
3203
  ]
3204
3204
  },
3205
+ {
3206
+ "type": "library",
3207
+ "bom-ref": "pkg:npm/multithreading@0.3.52",
3208
+ "name": "multithreading",
3209
+ "version": "0.3.52",
3210
+ "purl": "pkg:npm/multithreading@0.3.52",
3211
+ "scope": "excluded",
3212
+ "hashes": [
3213
+ {
3214
+ "alg": "SHA-512",
3215
+ "content": "5e0fd81f59393ee63e41659f3c215ec39c6c529945f5cf9726a6343df95239f0186f5b96b876339b6cf328f920de2e406caf8e254b20cd7ef0b41f6081d9b4c6"
3216
+ }
3217
+ ],
3218
+ "licenses": [
3219
+ {
3220
+ "license": {
3221
+ "id": "MIT"
3222
+ }
3223
+ }
3224
+ ]
3225
+ },
3205
3226
  {
3206
3227
  "type": "library",
3207
3228
  "bom-ref": "pkg:npm/nanoid@3.3.19",
@@ -4402,13 +4423,13 @@
4402
4423
  ],
4403
4424
  "dependencies": [
4404
4425
  {
4405
- "ref": "pkg:npm/%40babylonjs/core@9.26.0",
4426
+ "ref": "pkg:npm/%40babylonjs/core@9.27.1",
4406
4427
  "dependsOn": []
4407
4428
  },
4408
4429
  {
4409
- "ref": "pkg:npm/%40babylonjs/loaders@9.26.0",
4430
+ "ref": "pkg:npm/%40babylonjs/loaders@9.27.1",
4410
4431
  "dependsOn": [
4411
- "pkg:npm/%40babylonjs/core@9.26.0",
4432
+ "pkg:npm/%40babylonjs/core@9.27.1",
4412
4433
  "pkg:npm/babylonjs-gltf2interface@9.26.0"
4413
4434
  ]
4414
4435
  },
@@ -4453,18 +4474,19 @@
4453
4474
  ]
4454
4475
  },
4455
4476
  {
4456
- "ref": "pkg:npm/%40datamoc/mw_games@0.19.0",
4477
+ "ref": "pkg:npm/%40datamoc/mw_games@0.20.0",
4457
4478
  "dependsOn": [
4458
- "pkg:npm/%40babylonjs/core@9.26.0",
4459
- "pkg:npm/%40babylonjs/loaders@9.26.0",
4479
+ "pkg:npm/%40babylonjs/core@9.27.1",
4480
+ "pkg:npm/%40babylonjs/loaders@9.27.1",
4460
4481
  "pkg:npm/%40capacitor/android@7.6.9",
4461
4482
  "pkg:npm/%40capacitor/cli@7.6.9",
4462
4483
  "pkg:npm/%40capacitor/core@7.6.9",
4463
4484
  "pkg:npm/%40capacitor/ios@7.6.9",
4464
- "pkg:npm/%40types/node@26.5.1",
4485
+ "pkg:npm/%40types/node@26.6.2",
4465
4486
  "pkg:npm/%40types/ws@8.18.1",
4466
4487
  "pkg:npm/fengari@0.1.5",
4467
4488
  "pkg:npm/json5@2.2.3",
4489
+ "pkg:npm/multithreading@0.3.52",
4468
4490
  "pkg:npm/pixi.js@8.20.1",
4469
4491
  "pkg:npm/playwright-core@1.63.0",
4470
4492
  "pkg:npm/prettier@3.9.6",
@@ -4776,11 +4798,11 @@
4776
4798
  {
4777
4799
  "ref": "pkg:npm/%40types/fs-extra@8.1.5",
4778
4800
  "dependsOn": [
4779
- "pkg:npm/%40types/node@26.5.1"
4801
+ "pkg:npm/%40types/node@26.6.2"
4780
4802
  ]
4781
4803
  },
4782
4804
  {
4783
- "ref": "pkg:npm/%40types/node@26.5.1",
4805
+ "ref": "pkg:npm/%40types/node@26.6.2",
4784
4806
  "dependsOn": [
4785
4807
  "pkg:npm/undici-types@8.9.0"
4786
4808
  ]
@@ -4792,7 +4814,7 @@
4792
4814
  {
4793
4815
  "ref": "pkg:npm/%40types/ws@8.18.1",
4794
4816
  "dependsOn": [
4795
- "pkg:npm/%40types/node@26.5.1"
4817
+ "pkg:npm/%40types/node@26.6.2"
4796
4818
  ]
4797
4819
  },
4798
4820
  {
@@ -5196,6 +5218,10 @@
5196
5218
  "ref": "pkg:npm/ms@2.1.3",
5197
5219
  "dependsOn": []
5198
5220
  },
5221
+ {
5222
+ "ref": "pkg:npm/multithreading@0.3.52",
5223
+ "dependsOn": []
5224
+ },
5199
5225
  {
5200
5226
  "ref": "pkg:npm/nanoid@3.3.19",
5201
5227
  "dependsOn": []
@@ -5532,7 +5558,7 @@
5532
5558
  {
5533
5559
  "ref": "pkg:npm/vite@8.3.0",
5534
5560
  "dependsOn": [
5535
- "pkg:npm/%40types/node@26.5.1",
5561
+ "pkg:npm/%40types/node@26.6.2",
5536
5562
  "pkg:npm/fsevents@2.3.3",
5537
5563
  "pkg:npm/lightningcss@1.33.0",
5538
5564
  "pkg:npm/picomatch@4.0.7",
@@ -6,6 +6,8 @@ export interface CompressOptions {
6
6
  brotli?: boolean;
7
7
  /** Also write `.xz` siblings where smaller (default false, needs the system `xz`). */
8
8
  xz?: boolean;
9
+ /** Files to compress at once (default `compressJobs()`). */
10
+ jobs?: number;
9
11
  }
10
12
 
11
13
  /**
@@ -25,6 +27,12 @@ export interface CompressRow {
25
27
  /** True when the system `xz` binary (LZMA2) is available for the opt-in archive pass. */
26
28
  export function hasXz(): boolean;
27
29
 
30
+ /**
31
+ * How many files `compressDist` works on at once: `MWG_COMPRESS_JOBS` when it names
32
+ * a positive integer, else the machine's own thread count, else 4.
33
+ */
34
+ export function compressJobs(): number;
35
+
28
36
  /**
29
37
  * Writes compressed siblings for the large files in `dir`, *into that same folder*: the
30
38
  * originals are left untouched, and each `<file>` gains `<file>.gz`, `<file>.br` and, when