@datamoc/mw_games 0.37.0 → 0.39.1
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 +79 -0
- package/README.md +26 -0
- package/api-surface.json +650 -2
- package/dist/assets/SvgParts.d.ts +31 -0
- package/dist/assets/SvgParts.d.ts.map +1 -0
- package/dist/assets/SvgParts.js +54 -0
- package/dist/assets/SvgParts.js.map +1 -0
- package/dist/assets/SvgSource.d.ts +3 -0
- package/dist/assets/SvgSource.d.ts.map +1 -0
- package/dist/assets/SvgSource.js +22 -0
- package/dist/assets/SvgSource.js.map +1 -0
- package/dist/assets/index.d.ts +2 -0
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +1 -0
- package/dist/assets/index.js.map +1 -1
- package/dist/assets/svg.d.ts.map +1 -1
- package/dist/assets/svg.js +9 -5
- package/dist/assets/svg.js.map +1 -1
- package/dist/mw_games.global.js +110 -110
- package/dist/mwl/presentation.d.ts +70 -0
- package/dist/mwl/presentation.d.ts.map +1 -0
- package/dist/mwl/presentation.js +419 -0
- package/dist/mwl/presentation.js.map +1 -0
- package/dist/physics/PhysicsWorld.d.ts +49 -17
- package/dist/physics/PhysicsWorld.d.ts.map +1 -1
- package/dist/physics/PhysicsWorld.js +297 -59
- package/dist/physics/PhysicsWorld.js.map +1 -1
- package/dist/physics/XpbdWorld.d.ts +1 -1
- package/dist/physics/XpbdWorld.js +1 -1
- package/dist/physics/cloth/index.d.ts +98 -0
- package/dist/physics/cloth/index.d.ts.map +1 -0
- package/dist/physics/cloth/index.js +207 -0
- package/dist/physics/cloth/index.js.map +1 -0
- package/dist/physics/index.d.ts +4 -0
- package/dist/physics/index.d.ts.map +1 -1
- package/dist/physics/index.js +2 -0
- package/dist/physics/index.js.map +1 -1
- package/dist/physics/spring/index.d.ts +90 -0
- package/dist/physics/spring/index.d.ts.map +1 -0
- package/dist/physics/spring/index.js +97 -0
- package/dist/physics/spring/index.js.map +1 -0
- package/dist/skeleton/index.d.ts +94 -13
- package/dist/skeleton/index.d.ts.map +1 -1
- package/dist/skeleton/index.js +192 -73
- package/dist/skeleton/index.js.map +1 -1
- package/dist/two-d/presentation/MwlPresentation.d.ts +110 -0
- package/dist/two-d/presentation/MwlPresentation.d.ts.map +1 -0
- package/dist/two-d/presentation/MwlPresentation.js +755 -0
- package/dist/two-d/presentation/MwlPresentation.js.map +1 -0
- package/dist/two-d/presentation/index.d.ts +3 -0
- package/dist/two-d/presentation/index.d.ts.map +1 -0
- package/dist/two-d/presentation/index.js +2 -0
- package/dist/two-d/presentation/index.js.map +1 -0
- package/dist/two-d/render/ClothSprite.d.ts +83 -0
- package/dist/two-d/render/ClothSprite.d.ts.map +1 -0
- package/dist/two-d/render/ClothSprite.js +105 -0
- package/dist/two-d/render/ClothSprite.js.map +1 -0
- package/dist/two-d/render/HazeLayer.d.ts +112 -0
- package/dist/two-d/render/HazeLayer.d.ts.map +1 -0
- package/dist/two-d/render/HazeLayer.js +185 -0
- package/dist/two-d/render/HazeLayer.js.map +1 -0
- package/dist/two-d/render/NeonGlow.d.ts +100 -0
- package/dist/two-d/render/NeonGlow.d.ts.map +1 -0
- package/dist/two-d/render/NeonGlow.js +183 -0
- package/dist/two-d/render/NeonGlow.js.map +1 -0
- package/dist/two-d/render/SkeletalSprite.d.ts +5 -2
- package/dist/two-d/render/SkeletalSprite.d.ts.map +1 -1
- package/dist/two-d/render/SkeletalSprite.js +27 -3
- package/dist/two-d/render/SkeletalSprite.js.map +1 -1
- package/dist/two-d/render/SpringSprite.d.ts +40 -0
- package/dist/two-d/render/SpringSprite.d.ts.map +1 -0
- package/dist/two-d/render/SpringSprite.js +39 -0
- package/dist/two-d/render/SpringSprite.js.map +1 -0
- package/dist/two-d/render/SpriteDeform.d.ts +156 -0
- package/dist/two-d/render/SpriteDeform.d.ts.map +1 -0
- package/dist/two-d/render/SpriteDeform.js +278 -0
- package/dist/two-d/render/SpriteDeform.js.map +1 -0
- package/dist/two-d/render/SvgSkeleton.d.ts +54 -0
- package/dist/two-d/render/SvgSkeleton.d.ts.map +1 -0
- package/dist/two-d/render/SvgSkeleton.js +112 -0
- package/dist/two-d/render/SvgSkeleton.js.map +1 -0
- package/dist/two-d/render/index.d.ts +12 -0
- package/dist/two-d/render/index.d.ts.map +1 -1
- package/dist/two-d/render/index.js +6 -0
- package/dist/two-d/render/index.js.map +1 -1
- package/dist/two-d/ui/Bar.d.ts +3 -1
- package/dist/two-d/ui/Bar.d.ts.map +1 -1
- package/dist/two-d/ui/Bar.js +5 -0
- package/dist/two-d/ui/Bar.js.map +1 -1
- package/dist/two-d/ui/Button.d.ts +12 -0
- package/dist/two-d/ui/Button.d.ts.map +1 -1
- package/dist/two-d/ui/Button.js +16 -4
- package/dist/two-d/ui/Button.js.map +1 -1
- package/dist/two-d/ui/Window.d.ts +10 -0
- package/dist/two-d/ui/Window.d.ts.map +1 -1
- package/dist/two-d/ui/Window.js +17 -6
- package/dist/two-d/ui/Window.js.map +1 -1
- package/dist/two-d/ui/index.d.ts +2 -0
- package/dist/two-d/ui/index.d.ts.map +1 -1
- package/dist/two-d/ui/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +24 -7
- package/sbom.cdx.json +6 -58
- package/tools/asset-report.d.mts +37 -0
- package/tools/asset-report.mjs +157 -0
- package/tools/compile-resources.d.mts +2 -0
- package/tools/compile-resources.mjs +23 -2
- package/tools/emit-page.d.mts +2 -0
- package/tools/emit-page.mjs +11 -1
- package/tools/verify-scenarios.d.mts +15 -0
- package/tools/verify-scenarios.mjs +129 -0
- package/tools/vite.mjs +4 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
import { launchChrome } from './find-chrome.mjs';
|
|
6
|
+
|
|
7
|
+
/** Validate the serialisable, game-owned plan before running any scene steps. */
|
|
8
|
+
export function validateScenarioPlan(plan) {
|
|
9
|
+
if (!Array.isArray(plan) || plan.length === 0) throw new Error('scenario plan must be a nonempty array');
|
|
10
|
+
const ids = new Set();
|
|
11
|
+
for (const scenario of plan) {
|
|
12
|
+
if (!scenario || typeof scenario.id !== 'string' || !scenario.id || ids.has(scenario.id))
|
|
13
|
+
throw new Error('scenario IDs must be nonempty and unique');
|
|
14
|
+
ids.add(scenario.id);
|
|
15
|
+
for (const field of ['enter', 'verify'])
|
|
16
|
+
if (typeof scenario[field] !== 'string' || !scenario[field])
|
|
17
|
+
throw new Error(`${scenario.id}: ${field} needs a scene control name`);
|
|
18
|
+
if (
|
|
19
|
+
!Array.isArray(scenario.turn) ||
|
|
20
|
+
scenario.turn.length === 0 ||
|
|
21
|
+
scenario.turn.some((step) => typeof step !== 'string' || !step)
|
|
22
|
+
)
|
|
23
|
+
throw new Error(`${scenario.id}: turn needs a nonempty list of scene control names`);
|
|
24
|
+
if (scenario.leave !== undefined && (typeof scenario.leave !== 'string' || !scenario.leave))
|
|
25
|
+
throw new Error(`${scenario.id}: leave needs a scene control name`);
|
|
26
|
+
}
|
|
27
|
+
return plan;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Opens one local page and drives every scenario through its registered SceneControls. */
|
|
31
|
+
export async function verifyScenarios({ file, turns = 3, timeoutMs = 120000, only = '' }) {
|
|
32
|
+
if (!Number.isSafeInteger(turns) || turns < 1) throw new Error('turns must be a positive integer');
|
|
33
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1) throw new Error('timeoutMs must be a positive integer');
|
|
34
|
+
const browser = await launchChrome({ argsEnv: 'MWG_VISUAL_CHROME_ARGS' });
|
|
35
|
+
const failures = [];
|
|
36
|
+
const scenarios = [];
|
|
37
|
+
let expired = false;
|
|
38
|
+
const bounded = async (operation) => {
|
|
39
|
+
let timer;
|
|
40
|
+
try {
|
|
41
|
+
return await Promise.race([
|
|
42
|
+
operation,
|
|
43
|
+
new Promise((_, reject) => {
|
|
44
|
+
timer = setTimeout(() => {
|
|
45
|
+
expired = true;
|
|
46
|
+
reject(new Error(`scene step exceeded ${timeoutMs} ms`));
|
|
47
|
+
}, timeoutMs);
|
|
48
|
+
}),
|
|
49
|
+
]);
|
|
50
|
+
} finally {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
|
|
56
|
+
let current = 'startup';
|
|
57
|
+
page.on('pageerror', (error) => failures.push(`${current}: ${error.message}`));
|
|
58
|
+
page.on('console', (message) => {
|
|
59
|
+
if (message.type() === 'error') failures.push(`${current}: ${message.text()}`);
|
|
60
|
+
});
|
|
61
|
+
await page.goto(pathToFileURL(resolve(file)).href, { waitUntil: 'load', timeout: timeoutMs });
|
|
62
|
+
await page.waitForFunction(() => Boolean(window.__MWG_SCENARIOS__), undefined, { timeout: timeoutMs });
|
|
63
|
+
const plan = validateScenarioPlan(await page.evaluate(() => window.__MWG_SCENARIOS__.scenarios));
|
|
64
|
+
const selected = plan.filter((scenario) => scenario.id.startsWith(only));
|
|
65
|
+
if (!selected.length) throw new Error(`no scenarios match ${JSON.stringify(only)}`);
|
|
66
|
+
// Refuse a missing step before entering the first scenario.
|
|
67
|
+
await page.evaluate((plan) => {
|
|
68
|
+
const controls = window.__MWG_SCENARIOS__.controls;
|
|
69
|
+
for (const scenario of plan)
|
|
70
|
+
for (const step of [scenario.enter, ...scenario.turn, scenario.verify, scenario.leave].filter(Boolean))
|
|
71
|
+
if (!controls.has(step)) throw new Error(`scene control step is not registered: ${step}`);
|
|
72
|
+
}, selected);
|
|
73
|
+
for (const scenario of selected) {
|
|
74
|
+
current = scenario.id;
|
|
75
|
+
const before = failures.length;
|
|
76
|
+
let played = 0;
|
|
77
|
+
const run = (step) => bounded(page.evaluate((name) => window.__MWG_SCENARIOS__.controls.run(name), step));
|
|
78
|
+
try {
|
|
79
|
+
await run(scenario.enter);
|
|
80
|
+
for (; played < turns; played++) {
|
|
81
|
+
for (const step of scenario.turn) await run(step);
|
|
82
|
+
await run(scenario.verify);
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
failures.push(`${current}: ${error.message}`);
|
|
86
|
+
} finally {
|
|
87
|
+
if (scenario.leave && !expired) {
|
|
88
|
+
try {
|
|
89
|
+
await run(scenario.leave);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
failures.push(`${current}: cleanup: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
scenarios.push({ id: scenario.id, turns: played, ok: failures.length === before });
|
|
96
|
+
// A timed-out promise is still live in the page. Do not overlap another scenario with it.
|
|
97
|
+
if (expired) break;
|
|
98
|
+
}
|
|
99
|
+
return { ok: failures.length === 0, scenarios, failures };
|
|
100
|
+
} finally {
|
|
101
|
+
await browser.close();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
106
|
+
const [file, ...args] = process.argv.slice(2);
|
|
107
|
+
const options = { file };
|
|
108
|
+
try {
|
|
109
|
+
if (!file)
|
|
110
|
+
throw new Error('usage: mwg-scenarios <built index.html> [--turns N] [--timeout-ms N] [--only prefix]');
|
|
111
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
112
|
+
const name = { '--turns': 'turns', '--timeout-ms': 'timeoutMs', '--only': 'only' }[args[i]];
|
|
113
|
+
if (!name || args[i + 1] === undefined) throw new Error(`invalid option: ${args[i]}`);
|
|
114
|
+
options[name] = name === 'only' ? args[i + 1] : Number(args[i + 1]);
|
|
115
|
+
}
|
|
116
|
+
verifyScenarios(options)
|
|
117
|
+
.then((report) => {
|
|
118
|
+
console.log(JSON.stringify(report, null, 2));
|
|
119
|
+
process.exitCode = report.ok ? 0 : 1;
|
|
120
|
+
})
|
|
121
|
+
.catch((error) => {
|
|
122
|
+
console.error(error.message);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error(error.message);
|
|
127
|
+
process.exitCode = 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
package/tools/vite.mjs
CHANGED
|
@@ -11,7 +11,10 @@ import { buildArtifactSbom, serialize, shippedFiles } from './sbom.mjs';
|
|
|
11
11
|
* It sets the configuration that target needs (a classic IIFE bundle named `game.js`, relative
|
|
12
12
|
* urls through `base: './'`, no inlined or copied assets since they are compiled instead) and
|
|
13
13
|
* serves `assets` (default `'assets'`, `false` for none) as the public folder, so an asset path reads the same in `vite` dev as in the
|
|
14
|
-
* build.
|
|
14
|
+
* build. `assetFiles: ['characters/hero.svg', 'music/theme.ogg']` embeds only those paths;
|
|
15
|
+
* omit it for all assets, or pass [] for none. An unknown entry fails by name. Filtering
|
|
16
|
+
* affects the build only, so the dev server still serves the asset folder normally.
|
|
17
|
+
* At the end of the build it runs `emitPage` over the output folder with the same
|
|
15
18
|
* options. A setting the game's own config gives explicitly still wins, since vite merges a
|
|
16
19
|
* plugin's `config` result under the user's.
|
|
17
20
|
*
|