@moku-labs/game 0.14.12 → 0.14.13
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/README.md +13 -12
- package/dist/app/page.d.mts +8 -3
- package/dist/app/page.mjs +57 -12
- package/dist/app/system.d.mts +5 -3
- package/dist/app/system.mjs +2 -0
- package/dist/app.d.mts +15 -7
- package/dist/app.mjs +131 -27
- package/dist/{assets-BrKSg1O6.mjs → assets-CEMfBogh.mjs} +471 -120
- package/dist/assets.d.mts +30 -13
- package/dist/assets.mjs +1 -1
- package/dist/cli.d.mts +105 -8
- package/dist/cli.mjs +1036 -128
- package/dist/{commands-BaCx2B9j.d.mts → commands-DAETmK86.d.mts} +43 -7
- package/dist/{config-CdARrrhU.mjs → config-B87aFTW-.mjs} +25 -4
- package/dist/control.d.mts +130 -17
- package/dist/control.mjs +4 -4
- package/dist/{define-Ce8SVEN-.d.mts → define-BASd9ZUh.d.mts} +32 -4
- package/dist/dev-DbHgx-R-.mjs +89 -0
- package/dist/{fake-MpzwZhIZ.mjs → fake-6mazmHK3.mjs} +20 -2
- package/dist/features-DowEilZm.mjs +266 -0
- package/dist/{headless-CMHNsKjW.d.mts → headless-cFveciuY.d.mts} +1 -1
- package/dist/hot.d.mts +4 -2
- package/dist/hot.mjs +7 -4
- package/dist/{index-CLQ8NOTw.d.mts → index-DedbED3T.d.mts} +317 -69
- package/dist/index.d.mts +5 -6
- package/dist/index.mjs +3 -3
- package/dist/inspect.d.mts +12 -7
- package/dist/inspect.mjs +2 -2
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/lint.d.mts +8 -3
- package/dist/lint.mjs +432 -20
- package/dist/project.d.mts +15 -1
- package/dist/project.mjs +1 -1
- package/dist/{run-Bf2hKyjZ.mjs → run-BbipFRc6.mjs} +250 -51
- package/dist/{runtime-BCnKn0xf.d.mts → runtime-DoSlRx8X.d.mts} +1 -1
- package/dist/{session-DgukhR_7.mjs → session-CeKgO2hy.mjs} +30 -2
- package/dist/{sources-Duzj6lhV.mjs → sources-Cdvb0rOS.mjs} +33 -6
- package/dist/{src-CtC8w3_z.mjs → src-BEbJ_sSx.mjs} +35536 -17789
- package/dist/testing.d.mts +23 -13
- package/dist/testing.mjs +10 -6
- package/dist/{types-DOlpeuGx.d.mts → types-7GXcXxxv.d.mts} +174 -242
- package/dist/{types-BjTtGSFb.d.mts → types-BGcda5Le.d.mts} +4459 -732
- package/dist/{types-gisvSHoC.d.mts → types-C73hbmxi.d.mts} +129 -17
- package/dist/{types-CAPv9l29.d.mts → types-DngFQJro.d.mts} +43 -5
- package/dist/{types-Y_rCuXtj.d.mts → types-GHE4J5Uf.d.mts} +1025 -191
- package/dist/{visual-dMW8iqAE.mjs → visual-2m4wJL_z.mjs} +111 -7
- package/dist/visual-_XhoGeIC.d.mts +345 -0
- package/dist/visual.d.mts +1 -332
- package/dist/visual.mjs +130 -20
- package/dist/{watch-CWI_on4T.mjs → watch-BEdrp80i.mjs} +149 -30
- package/llms.txt +99 -48
- package/package.json +8 -6
- package/dist/dev-Cc50Dk7H.mjs +0 -47
- package/dist/memory-pUQ7YZWC.d.mts +0 -50
- package/dist/types-BX4Ge_j5.d.mts +0 -656
package/dist/visual.d.mts
CHANGED
|
@@ -1,333 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as UiApi, x as AnimApi } from "./types-Y_rCuXtj.mjs";
|
|
3
|
-
import { A as InputApi } from "./types-DOlpeuGx.mjs";
|
|
4
|
-
import { r as Repro } from "./headless-CMHNsKjW.mjs";
|
|
5
|
-
import { a as InputOf, r as ControlApp } from "./types-CAPv9l29.mjs";
|
|
6
|
-
import { t as commands } from "./commands-BaCx2B9j.mjs";
|
|
7
|
-
|
|
8
|
-
//#region src/visual/types.d.ts
|
|
9
|
-
/** The base commands of the `/control` door, keyed by their short names. */
|
|
10
|
-
type Catalogue = typeof commands;
|
|
11
|
-
/**
|
|
12
|
-
* The commands a step may name: the catalogue of `/control` minus `bookmark` (it only reads),
|
|
13
|
-
* `capture` (the runner captures by itself) and `debug` (debug drawing would enter a baseline). A
|
|
14
|
-
* command the catalogue gains is a step with no change to the runner.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```ts
|
|
18
|
-
* const name: StepCommand = "tap"; // also "answer", "walk", "step", "reducedMotion", ...
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
type StepCommand = Exclude<keyof Catalogue, "bookmark" | "capture" | "debug">;
|
|
22
|
-
/**
|
|
23
|
-
* Where a visual test starts: what `game.restore` loads, a repro without its route. The player
|
|
24
|
-
* is required; the session, the rng and the checkpoint are optional, as in a repro.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```ts
|
|
28
|
-
* // A save with a plank ready for the first order, entered at Home.
|
|
29
|
-
* const start: VisualStart = { player: { coins: 40, orders: [] }, checkpoint: "home" };
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
type VisualStart = Omit<Repro, "route">;
|
|
33
|
-
/**
|
|
34
|
-
* One step of a visual test: a `/control` command by its short name with that command's input,
|
|
35
|
-
* typed from the catalogue, or a checkpoint by its name.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```ts
|
|
39
|
-
* // Tap Play, answer the order on the board, then compare the screen with its baseline.
|
|
40
|
-
* const steps: VisualStep[] = [
|
|
41
|
-
* { tap: { key: "play" } },
|
|
42
|
-
* { answer: { intent: "deliver", payload: { orderId: "o1" } } },
|
|
43
|
-
* { checkpoint: "open" }
|
|
44
|
-
* ];
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
type VisualStep = {
|
|
48
|
-
checkpoint: string;
|
|
49
|
-
} | { [K in StepCommand]: { [P in K]: InputOf<Catalogue[K]["input"]> } }[StepCommand];
|
|
50
|
-
/**
|
|
51
|
-
* A visual test as `defineVisualTest` answers it: its name, where it starts, its steps, and
|
|
52
|
-
* `webgl`, true when it also runs in the WebGL leg. Frozen data; nothing runs at definition.
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
|
-
* ```ts
|
|
56
|
-
* const test: VisualTest = defineVisualTest("reward-popup", {
|
|
57
|
-
* start: { player: { coins: 40 }, checkpoint: "home" },
|
|
58
|
-
* steps: [{ tap: { key: "play" } }, { checkpoint: "board" }]
|
|
59
|
-
* });
|
|
60
|
-
* test.name; // "reward-popup"
|
|
61
|
-
* test.webgl; // false
|
|
62
|
-
* Object.isFrozen(test.steps); // true
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
type VisualTest = {
|
|
66
|
-
readonly name: string;
|
|
67
|
-
readonly start: Readonly<VisualStart>;
|
|
68
|
-
readonly steps: readonly VisualStep[];
|
|
69
|
-
readonly webgl: boolean;
|
|
70
|
-
};
|
|
71
|
-
/**
|
|
72
|
-
* What the runner needs of an app: the control app, the plugins the base commands act on and
|
|
73
|
-
* the plugins the readers read. Every app composed with the screen set fits.
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* ```ts
|
|
77
|
-
* const app: VisualApp = createApp({ plugins: [...screen, boardFeature], pluginConfigs });
|
|
78
|
-
* ```
|
|
79
|
-
*/
|
|
80
|
-
type VisualApp = ControlApp & {
|
|
81
|
-
readonly anim: AnimApi;
|
|
82
|
-
readonly ui: UiApi;
|
|
83
|
-
readonly renderer: Api;
|
|
84
|
-
readonly world: Api$1;
|
|
85
|
-
readonly model: Api$2;
|
|
86
|
-
readonly input: InputApi;
|
|
87
|
-
readonly lifecycle: Api$3;
|
|
88
|
-
};
|
|
89
|
-
/**
|
|
90
|
-
* The dev page the browser leg opens: its URL, the viewport width in CSS px (390 by default), the
|
|
91
|
-
* device scale (2 by default) and which Chrome to launch. The page height follows the inert frame
|
|
92
|
-
* of the renderer: 520 for a 390 wide portrait game at the default 4:3. Without `browser` the leg
|
|
93
|
-
* launches Playwright's own Chromium, and the system Chrome when that one is not installed.
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* ```ts
|
|
97
|
-
* // The fixture's dev server, a 390 px wide phone at 2x, the system Chrome.
|
|
98
|
-
* const page: VisualPage = { url: "http://localhost:3000/", browser: { channel: "chrome" } };
|
|
99
|
-
* ```
|
|
100
|
-
*/
|
|
101
|
-
type VisualPage = {
|
|
102
|
-
url: string;
|
|
103
|
-
width?: number;
|
|
104
|
-
deviceScaleFactor?: number;
|
|
105
|
-
browser?: {
|
|
106
|
-
channel?: string;
|
|
107
|
-
executablePath?: string;
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
/**
|
|
111
|
-
* How the runner builds a game: a fresh app per test, not started, with the screen set
|
|
112
|
-
* composed; the renderer stays inert in Bun. `page` is the dev page of the pixel leg.
|
|
113
|
-
*
|
|
114
|
-
* @example
|
|
115
|
-
* ```ts
|
|
116
|
-
* // tests/visual/run.ts of a game.
|
|
117
|
-
* const setup: VisualSetup = { app: () => createScreenGame().app, page: { url: "http://localhost:3000/" } };
|
|
118
|
-
* ```
|
|
119
|
-
*/
|
|
120
|
-
type VisualSetup = {
|
|
121
|
-
app: () => VisualApp;
|
|
122
|
-
page?: VisualPage;
|
|
123
|
-
};
|
|
124
|
-
/**
|
|
125
|
-
* How far a pixel may differ: `threshold` is the largest channel delta that still counts as the
|
|
126
|
-
* same pixel, `ratio` the share of differing pixels a checkpoint tolerates.
|
|
127
|
-
*
|
|
128
|
-
* @example
|
|
129
|
-
* ```ts
|
|
130
|
-
* const tolerance: VisualTolerance = { ratio: 0.001, threshold: 24 }; // the defaults
|
|
131
|
-
* ```
|
|
132
|
-
*/
|
|
133
|
-
type VisualTolerance = {
|
|
134
|
-
ratio: number;
|
|
135
|
-
threshold: number;
|
|
136
|
-
};
|
|
137
|
-
/**
|
|
138
|
-
* The backend the browser leg draws with: `"webgpu"`, or `"webgl"` for the WebGL leg.
|
|
139
|
-
*
|
|
140
|
-
* @example
|
|
141
|
-
* ```ts
|
|
142
|
-
* const renderer: VisualRenderer = "webgl"; // what --webgl asks for
|
|
143
|
-
* ```
|
|
144
|
-
*/
|
|
145
|
-
type VisualRenderer = "webgpu" | "webgl";
|
|
146
|
-
/**
|
|
147
|
-
* The options of a run. Each one left out comes from the command line (`argv`), then from its
|
|
148
|
-
* default: `dir` `"tests/visual"`, `update` false, `pixels` true with a `page` on a Mac,
|
|
149
|
-
* `renderer` `"webgpu"`, `settleFrames` 600, `tolerance` `{ ratio: 0.001, threshold: 24 }`,
|
|
150
|
-
* `argv` `process.argv.slice(2)`. An option given here wins over a flag. `renderer: "webgl"`
|
|
151
|
-
* runs only the tests with `webgl: true`, on the page with `?renderer=webgl`, and compares their
|
|
152
|
-
* pictures with `screen.webgl.webp`.
|
|
153
|
-
*
|
|
154
|
-
* @example
|
|
155
|
-
* ```ts
|
|
156
|
-
* // The same as `bun tests/visual/run.ts --only reward-popup --no-pixels`.
|
|
157
|
-
* const options: VisualOptions = { only: ["reward-popup"], pixels: false };
|
|
158
|
-
* // The same as `bun tests/visual/run.ts --webgl`.
|
|
159
|
-
* const webgl: VisualOptions = { renderer: "webgl" };
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
type VisualOptions = {
|
|
163
|
-
dir?: string;
|
|
164
|
-
update?: boolean;
|
|
165
|
-
pixels?: boolean;
|
|
166
|
-
renderer?: VisualRenderer;
|
|
167
|
-
only?: readonly string[];
|
|
168
|
-
settleFrames?: number;
|
|
169
|
-
tolerance?: VisualTolerance;
|
|
170
|
-
argv?: readonly string[];
|
|
171
|
-
};
|
|
172
|
-
/**
|
|
173
|
-
* What a comparison with a baseline file found: the same content, a different one, or no
|
|
174
|
-
* baseline (or `--update`), so the file was written.
|
|
175
|
-
*
|
|
176
|
-
* @example
|
|
177
|
-
* ```ts
|
|
178
|
-
* const outcome: Outcome = "written"; // the first run of a new checkpoint
|
|
179
|
-
* ```
|
|
180
|
-
*/
|
|
181
|
-
type Outcome = "same" | "different" | "written";
|
|
182
|
-
/**
|
|
183
|
-
* The outcome of one checkpoint. `first` is the JSON path of the first difference, the state's
|
|
184
|
-
* before the describe's. `pixels` is `"skipped"` when the pixel leg did not run; `pixelRatio`
|
|
185
|
-
* and `verdict` come with a pixel difference.
|
|
186
|
-
*
|
|
187
|
-
* @example
|
|
188
|
-
* ```ts
|
|
189
|
-
* // The player's coins differ from the baseline; the layout is the same.
|
|
190
|
-
* const result: CheckpointResult = {
|
|
191
|
-
* name: "claimed", state: "different", describe: "same", pixels: "skipped", first: "player.coins"
|
|
192
|
-
* };
|
|
193
|
-
* ```
|
|
194
|
-
*/
|
|
195
|
-
type CheckpointResult = {
|
|
196
|
-
name: string;
|
|
197
|
-
state: Outcome;
|
|
198
|
-
describe: Outcome;
|
|
199
|
-
pixels: Outcome | "skipped";
|
|
200
|
-
pixelRatio?: number;
|
|
201
|
-
first?: string;
|
|
202
|
-
verdict?: "rendering" | "behaviour";
|
|
203
|
-
};
|
|
204
|
-
/**
|
|
205
|
-
* The outcome of one visual test: every checkpoint it reached, and the error that ended it early.
|
|
206
|
-
*
|
|
207
|
-
* @example
|
|
208
|
-
* ```ts
|
|
209
|
-
* const result: VisualTestResult = {
|
|
210
|
-
* name: "reward-popup",
|
|
211
|
-
* checkpoints: [{ name: "open", state: "same", describe: "same", pixels: "skipped" }]
|
|
212
|
-
* };
|
|
213
|
-
* ```
|
|
214
|
-
*/
|
|
215
|
-
type VisualTestResult = {
|
|
216
|
-
name: string;
|
|
217
|
-
checkpoints: readonly CheckpointResult[];
|
|
218
|
-
error?: string;
|
|
219
|
-
};
|
|
220
|
-
/**
|
|
221
|
-
* What `runVisualTests` answers: `ok` is true when no test failed and no outcome is
|
|
222
|
-
* `"different"`.
|
|
223
|
-
*
|
|
224
|
-
* @example
|
|
225
|
-
* ```ts
|
|
226
|
-
* const report: VisualReport = await runVisualTests(setup, [rewardPopup], { argv: [] });
|
|
227
|
-
* process.exitCode = report.ok ? 0 : 1;
|
|
228
|
-
* ```
|
|
229
|
-
*/
|
|
230
|
-
type VisualReport = {
|
|
231
|
-
ok: boolean;
|
|
232
|
-
tests: readonly VisualTestResult[];
|
|
233
|
-
};
|
|
234
|
-
/** What the command line asked for: the flags it carried, nothing else. */
|
|
235
|
-
type VisualFlags = {
|
|
236
|
-
dir?: string;
|
|
237
|
-
update?: boolean;
|
|
238
|
-
pixels?: boolean;
|
|
239
|
-
renderer?: VisualRenderer;
|
|
240
|
-
only?: readonly string[];
|
|
241
|
-
};
|
|
242
|
-
//#endregion
|
|
243
|
-
//#region src/visual/define.d.ts
|
|
244
|
-
/**
|
|
245
|
-
* Defines a visual test: where it starts, what `game.restore` loads, and its steps. A step is a
|
|
246
|
-
* `/control` command by its short name with that command's input, or `{ checkpoint: name }`,
|
|
247
|
-
* where the runner saves `state.json` and `describe.json` next to the test. `walk` runs fast: a
|
|
248
|
-
* popup its last node opens never mounts, so the step before a checkpoint that shows a node's
|
|
249
|
-
* result is `answer` or `tap`. With `webgl: true` the test also runs in the WebGL leg
|
|
250
|
-
* (`--webgl`), whose pictures go to `screen.webgl.webp`: set it on a screen with a custom filter.
|
|
251
|
-
* The answer is a frozen copy; nothing runs here.
|
|
252
|
-
*
|
|
253
|
-
* @param name - The test name, also its folder: lowercase letters, digits and dashes.
|
|
254
|
-
* @param test - Where it starts, its steps and its WebGL leg.
|
|
255
|
-
* @param test.start - The player, and optionally the session, the rng and the checkpoint.
|
|
256
|
-
* @param test.steps - The commands and the checkpoints, in order.
|
|
257
|
-
* @param test.webgl - True to run the test in the WebGL leg too; false by default.
|
|
258
|
-
* @returns The test, frozen.
|
|
259
|
-
* @throws {Error} When the name or a checkpoint name is not a file name, a checkpoint name is
|
|
260
|
-
* used twice, or a step is not one step command.
|
|
261
|
-
* @example
|
|
262
|
-
* ```ts
|
|
263
|
-
* // tests/visual/home.visual.ts: Home with the glowing Play plank, in both legs.
|
|
264
|
-
* const home = defineVisualTest("home", { start: atHome, steps: [{ checkpoint: "rest" }], webgl: true });
|
|
265
|
-
* home.webgl; // true
|
|
266
|
-
* ```
|
|
267
|
-
*/
|
|
268
|
-
declare function defineVisualTest(name: string, test: {
|
|
269
|
-
start: VisualStart;
|
|
270
|
-
steps: readonly VisualStep[];
|
|
271
|
-
webgl?: boolean;
|
|
272
|
-
}): VisualTest;
|
|
273
|
-
//#endregion
|
|
274
|
-
//#region src/visual/run.d.ts
|
|
275
|
-
/**
|
|
276
|
-
* Reads the flags of the visual tests from a command line: `--update`, `--no-pixels`, `--pixels`,
|
|
277
|
-
* `--webgl`, `--only <name>` (repeatable) and `--dir <path>`. `--pixels` forces the pixel leg on
|
|
278
|
-
* off a Mac; the last of `--pixels` and `--no-pixels` wins. An argument it does not know is left
|
|
279
|
-
* to the script that got it, so a test runner's own arguments pass through. `moku-game visual`
|
|
280
|
-
* reads its flags with it.
|
|
281
|
-
*
|
|
282
|
-
* @param argv - The arguments after the script name.
|
|
283
|
-
* @returns The flags that were given, and nothing else.
|
|
284
|
-
* @throws {Error} When `--only` or `--dir` has no value.
|
|
285
|
-
* @example
|
|
286
|
-
* ```ts
|
|
287
|
-
* // `bun tests/visual/run.ts --update --only reward-popup`
|
|
288
|
-
* parseVisualArgv(["--update", "--only", "reward-popup"]); // { update: true, only: ["reward-popup"] }
|
|
289
|
-
* parseVisualArgv(["--no-pixels", "--dir", "shots"]); // { pixels: false, dir: "shots" }
|
|
290
|
-
* parseVisualArgv(["--webgl"]); // { renderer: "webgl" }
|
|
291
|
-
* parseVisualArgv(["--pixels"]); // { pixels: true }
|
|
292
|
-
* ```
|
|
293
|
-
*/
|
|
294
|
-
declare function parseVisualArgv(argv: readonly string[]): VisualFlags;
|
|
295
|
-
/**
|
|
296
|
-
* Runs visual tests: the headless leg over every selected test, then the browser leg over the
|
|
297
|
-
* same tests when `pixels` is on (by default only with a `page` on a Mac). With `renderer:
|
|
298
|
-
* "webgl"` (`--webgl`) only the tests with `webgl: true` run, the page opens with
|
|
299
|
-
* `?renderer=webgl` and its pictures go to `screen.webgl.webp`; `state.json` and `describe.json`
|
|
300
|
-
* are shared with the WebGPU leg and compared as always. A checkpoint saves
|
|
301
|
-
* `state.json` and `describe.json` in `<dir>/<test>/<checkpoint>/`, and the browser leg
|
|
302
|
-
* `screen.webp`, lossless: a missing file is written, `--update` rewrites every file of the tests
|
|
303
|
-
* run, a JSON file is compared exactly. The browser leg opens the dev page in Chrome with WebGPU
|
|
304
|
-
* through `playwright-core`, pauses its game, plays each test there on stepped frames of 1000/60
|
|
305
|
-
* ms, so two runs see the same game time, checks the page's state against `state.json` and
|
|
306
|
-
* compares the picture in the page: a pixel differs above `tolerance.threshold`, and the screen
|
|
307
|
-
* differs when more than `tolerance.ratio` of its pixels do; `screen.actual.webp` and
|
|
308
|
-
* `screen.diff.webp` are written beside the baseline then. A pixel difference over the same state
|
|
309
|
-
* and describe is a rendering regression (`verdict: "rendering"`). One line per checkpoint goes
|
|
310
|
-
* to the branded console; the script sets the exit code from `ok`.
|
|
311
|
-
*
|
|
312
|
-
* @param setup - How to build a game: a fresh, unstarted app per test, and the dev page.
|
|
313
|
-
* @param tests - Every visual test of the game.
|
|
314
|
-
* @param options - Overrides of the flags of `argv` and of the defaults.
|
|
315
|
-
* @returns The report: `ok` when no test failed and nothing is `"different"`.
|
|
316
|
-
* @throws {Error} When a test is not well formed, two tests share a name, `only` names no test,
|
|
317
|
-
* a flag misses its value, or the browser leg fails as a whole: no `page`, no
|
|
318
|
-
* `playwright-core`, no Chrome, or a page that does not open, expose `game` and `doors`, start,
|
|
319
|
-
* draw with the requested backend or stop loading. In the WebGL leg, also when `only` names a
|
|
320
|
-
* test without `webgl: true`.
|
|
321
|
-
* @example
|
|
322
|
-
* ```ts
|
|
323
|
-
* // tests/visual/run.ts of a game, `bun tests/visual/run.ts --update` on a Mac
|
|
324
|
-
* const report = await runVisualTests(
|
|
325
|
-
* { app: () => createScreenGame().app, page: { url: "http://localhost:3000/" } },
|
|
326
|
-
* [rewardPopup]
|
|
327
|
-
* );
|
|
328
|
-
* report.tests[0]?.checkpoints[0]; // { name: "open", state: "written", describe: "written", pixels: "written" }
|
|
329
|
-
* ```
|
|
330
|
-
*/
|
|
331
|
-
declare function runVisualTests(setup: VisualSetup, tests: readonly VisualTest[], options?: VisualOptions): Promise<VisualReport>;
|
|
332
|
-
//#endregion
|
|
1
|
+
import { a as VisualApp, c as VisualRenderer, d as VisualStart, f as VisualStep, h as VisualTolerance, i as CheckpointResult, l as VisualReport, m as VisualTestResult, n as runVisualTests, o as VisualOptions, p as VisualTest, r as defineVisualTest, s as VisualPage, t as parseVisualArgv, u as VisualSetup } from "./visual-_XhoGeIC.mjs";
|
|
333
2
|
export { type CheckpointResult, type VisualApp, type VisualOptions, type VisualPage, type VisualRenderer, type VisualReport, type VisualSetup, type VisualStart, type VisualStep, type VisualTest, type VisualTestResult, type VisualTolerance, defineVisualTest, parseVisualArgv, runVisualTests };
|
package/dist/visual.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as read, t as sources } from "./sources-
|
|
2
|
-
import { n as commands, t as run } from "./run-
|
|
1
|
+
import { n as read, t as sources } from "./sources-Cdvb0rOS.mjs";
|
|
2
|
+
import { n as commands, t as run } from "./run-BbipFRc6.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createBrandConsole } from "@moku-labs/common/cli";
|
|
5
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
@@ -1120,9 +1120,88 @@ async function pagePause(argument) {
|
|
|
1120
1120
|
}
|
|
1121
1121
|
}
|
|
1122
1122
|
/**
|
|
1123
|
+
* Tells whether a log line fails a visual test wherever the page logged it, its load included: a
|
|
1124
|
+
* filter broken for good (`effects:broken`), a warm-up probe whose render threw inside Pixi
|
|
1125
|
+
* (`renderer:warm-failed`), a read of the frame oracles that failed (`renderer: pixel read failed`).
|
|
1126
|
+
* No frame oracle can see them: the oracles compare the engine with itself.
|
|
1127
|
+
*
|
|
1128
|
+
* @param event - The event of the line.
|
|
1129
|
+
* @returns True for one of the three.
|
|
1130
|
+
* @example
|
|
1131
|
+
* ```ts
|
|
1132
|
+
* pageBroke("effects:broken"); // true
|
|
1133
|
+
* ```
|
|
1134
|
+
*/
|
|
1135
|
+
function pageBroke(event) {
|
|
1136
|
+
return event === "effects:broken" || event === "renderer:warm-failed" || event === "renderer: pixel read failed";
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Tells whether a log line is a finding of the frame oracles: a skipped frame that differed from
|
|
1140
|
+
* the frame drawn live, a view written without a mark, a kept picture that no longer matches its
|
|
1141
|
+
* live frame (spec 23).
|
|
1142
|
+
*
|
|
1143
|
+
* @param event - The event of the line.
|
|
1144
|
+
* @returns True for one of the three.
|
|
1145
|
+
* @example
|
|
1146
|
+
* ```ts
|
|
1147
|
+
* pageOracle("renderer:stale-picture"); // true
|
|
1148
|
+
* ```
|
|
1149
|
+
*/
|
|
1150
|
+
function pageOracle(event) {
|
|
1151
|
+
return event === "renderer:missed-frame" || event === "renderer:missed-mark" || event === "renderer:stale-picture";
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Turns on the pixel check of the frames the renderer skips, through the door `game.debug`, and
|
|
1155
|
+
* starts reading the log of the page from its end: from here on, `pageMissed` fails a step on the
|
|
1156
|
+
* first line of the frame oracles or of `pageBroke`. A `pageBroke` line the page logged while it
|
|
1157
|
+
* loaded, before the check, fails the start.
|
|
1158
|
+
*
|
|
1159
|
+
* @param _argument - The test and the step, for the leg's message.
|
|
1160
|
+
* @returns Nothing, or why the page refused the switch or broke while it loaded.
|
|
1161
|
+
*/
|
|
1162
|
+
async function pageVerify(_argument) {
|
|
1163
|
+
try {
|
|
1164
|
+
const { game, doors } = pageHandles();
|
|
1165
|
+
await doors.run(game, doors.commands.debug, { verifyFrames: true });
|
|
1166
|
+
const entries = game.log.trace();
|
|
1167
|
+
Reflect.set(globalThis, "__mokuVisualTrace", entries.length);
|
|
1168
|
+
for (const entry of entries) if (pageBroke(entry.event)) return {
|
|
1169
|
+
ok: false,
|
|
1170
|
+
reason: `${entry.event} ${JSON.stringify(entry.data)}`
|
|
1171
|
+
};
|
|
1172
|
+
return {
|
|
1173
|
+
ok: true,
|
|
1174
|
+
value: void 0
|
|
1175
|
+
};
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
return {
|
|
1178
|
+
ok: false,
|
|
1179
|
+
reason: pageMessage(error)
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Reads the log of the page since the last read, once `pageVerify` turned the check on, for the
|
|
1185
|
+
* first line of the frame oracles (`pageOracle`) or of an error no oracle can see (`pageBroke`).
|
|
1186
|
+
*
|
|
1187
|
+
* @param handles - The game and the doors.
|
|
1188
|
+
* @returns The line, as its event and its data, or `undefined` when none was logged.
|
|
1189
|
+
*/
|
|
1190
|
+
function pageMissed(handles) {
|
|
1191
|
+
const from = Reflect.get(globalThis, "__mokuVisualTrace");
|
|
1192
|
+
if (typeof from !== "number") return void 0;
|
|
1193
|
+
const entries = handles.game.log.trace();
|
|
1194
|
+
Reflect.set(globalThis, "__mokuVisualTrace", entries.length);
|
|
1195
|
+
for (let at = from; at < entries.length; at += 1) {
|
|
1196
|
+
const entry = entries[at];
|
|
1197
|
+
if (entry !== void 0 && (pageOracle(entry.event) || pageBroke(entry.event))) return `${entry.event} ${JSON.stringify(entry.data)}`;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1123
1201
|
* Runs one `/control` command through the doors of the page, the way an editor does, with frames
|
|
1124
1202
|
* stepped while it waits for them, then settles. The leg restores the start of a test with it
|
|
1125
|
-
* too, as the command `restore`.
|
|
1203
|
+
* too, as the command `restore`. With the frame check on, a missed frame or mark logged in the
|
|
1204
|
+
* meantime fails the step.
|
|
1126
1205
|
*
|
|
1127
1206
|
* @param argument - The test and the step, the command by its short name, its input, and the most
|
|
1128
1207
|
* frames to settle.
|
|
@@ -1135,12 +1214,17 @@ async function pageStep(argument) {
|
|
|
1135
1214
|
try {
|
|
1136
1215
|
const handles = pageHandles();
|
|
1137
1216
|
const unsettled = await pageRun(handles, argument, argument.settleFrames, argument) ?? await pageSettle(handles, argument.settleFrames, argument);
|
|
1138
|
-
|
|
1217
|
+
if (unsettled !== void 0) return {
|
|
1218
|
+
ok: false,
|
|
1219
|
+
error: unsettled
|
|
1220
|
+
};
|
|
1221
|
+
const missed = pageMissed(handles);
|
|
1222
|
+
return missed === void 0 ? {
|
|
1139
1223
|
ok: true,
|
|
1140
1224
|
value: void 0
|
|
1141
1225
|
} : {
|
|
1142
1226
|
ok: false,
|
|
1143
|
-
|
|
1227
|
+
reason: missed
|
|
1144
1228
|
};
|
|
1145
1229
|
} catch (error) {
|
|
1146
1230
|
return {
|
|
@@ -1152,7 +1236,8 @@ async function pageStep(argument) {
|
|
|
1152
1236
|
/**
|
|
1153
1237
|
* Plays a checkpoint in the page: settle, land every track and loop, two stepped frames, then the
|
|
1154
1238
|
* picture of the whole canvas as lossless WebP and the state through the doors. The game is
|
|
1155
|
-
* paused, so the picture is the stage after the last stepped frame.
|
|
1239
|
+
* paused, so the picture is the stage after the last stepped frame. With the frame check on, a
|
|
1240
|
+
* missed frame or mark logged in the meantime fails the checkpoint.
|
|
1156
1241
|
*
|
|
1157
1242
|
* @param argument - The test and the step, and the most frames to settle.
|
|
1158
1243
|
* @param argument.settleFrames - The most frames to settle.
|
|
@@ -1172,13 +1257,19 @@ async function pageCheckpoint(argument) {
|
|
|
1172
1257
|
await pageTick(handles);
|
|
1173
1258
|
const captured = await game.renderer.capture();
|
|
1174
1259
|
const screen = captured === void 0 ? void 0 : await pageWebp(captured.png);
|
|
1260
|
+
const state = {
|
|
1261
|
+
path: doors.read(game, doors.sources.position).path,
|
|
1262
|
+
...doors.read(game, doors.sources.model)
|
|
1263
|
+
};
|
|
1264
|
+
const missed = pageMissed(handles);
|
|
1265
|
+
if (missed !== void 0) return {
|
|
1266
|
+
ok: false,
|
|
1267
|
+
reason: missed
|
|
1268
|
+
};
|
|
1175
1269
|
return {
|
|
1176
1270
|
ok: true,
|
|
1177
1271
|
value: {
|
|
1178
|
-
state
|
|
1179
|
-
path: doors.read(game, doors.sources.position).path,
|
|
1180
|
-
...doors.read(game, doors.sources.model)
|
|
1181
|
-
},
|
|
1272
|
+
state,
|
|
1182
1273
|
screen
|
|
1183
1274
|
}
|
|
1184
1275
|
};
|
|
@@ -1380,6 +1471,10 @@ const pageFunctions = [
|
|
|
1380
1471
|
pageRun,
|
|
1381
1472
|
pageStart,
|
|
1382
1473
|
pagePause,
|
|
1474
|
+
pageBroke,
|
|
1475
|
+
pageOracle,
|
|
1476
|
+
pageVerify,
|
|
1477
|
+
pageMissed,
|
|
1383
1478
|
pageStep,
|
|
1384
1479
|
pageCheckpoint,
|
|
1385
1480
|
channelDelta,
|
|
@@ -1750,9 +1845,10 @@ async function checkpoint(play, name, where) {
|
|
|
1750
1845
|
};
|
|
1751
1846
|
}
|
|
1752
1847
|
/**
|
|
1753
|
-
* Plays the start and the steps of a test in the open page, the way the headless leg does:
|
|
1754
|
-
* the
|
|
1755
|
-
* stepped frames, and check each
|
|
1848
|
+
* Plays the start and the steps of a test in the open page, the way the headless leg does: turn
|
|
1849
|
+
* on the check of skipped frames (unless `--no-verify-frames`), pause the game and step it to the
|
|
1850
|
+
* start time, restore the start, run each command and settle on stepped frames, and check each
|
|
1851
|
+
* checkpoint. A missed frame or mark the page logs fails the step it happened in.
|
|
1756
1852
|
*
|
|
1757
1853
|
* @param play - The test being played.
|
|
1758
1854
|
* @param found - Where what each checkpoint found goes, as soon as it is known.
|
|
@@ -1767,6 +1863,10 @@ async function playSteps(play, found) {
|
|
|
1767
1863
|
test: test.name,
|
|
1768
1864
|
settleFrames: run.settleFrames
|
|
1769
1865
|
};
|
|
1866
|
+
if (run.verifyFrames) await callPage(play.page, pageVerify, {
|
|
1867
|
+
test: test.name,
|
|
1868
|
+
where: "the start"
|
|
1869
|
+
});
|
|
1770
1870
|
await callPage(play.page, pagePause, {
|
|
1771
1871
|
test: test.name,
|
|
1772
1872
|
where: "the start",
|
|
@@ -1891,6 +1991,14 @@ const DEFAULT_TOLERANCE = {
|
|
|
1891
1991
|
ratio: .001,
|
|
1892
1992
|
threshold: 24
|
|
1893
1993
|
};
|
|
1994
|
+
/** The flags without a value, and what each one sets. */
|
|
1995
|
+
const SWITCHES = {
|
|
1996
|
+
"--update": { update: true },
|
|
1997
|
+
"--no-pixels": { pixels: false },
|
|
1998
|
+
"--pixels": { pixels: true },
|
|
1999
|
+
"--webgl": { renderer: "webgl" },
|
|
2000
|
+
"--no-verify-frames": { verifyFrames: false }
|
|
2001
|
+
};
|
|
1894
2002
|
/**
|
|
1895
2003
|
* Reads the value after a flag that takes one.
|
|
1896
2004
|
*
|
|
@@ -1908,8 +2016,9 @@ function valueAfter(argv, index, placeholder) {
|
|
|
1908
2016
|
}
|
|
1909
2017
|
/**
|
|
1910
2018
|
* Reads the flags of the visual tests from a command line: `--update`, `--no-pixels`, `--pixels`,
|
|
1911
|
-
* `--webgl`, `--only <name>` (repeatable) and `--dir <path>`. `--pixels`
|
|
1912
|
-
* off a Mac; the last of `--pixels` and `--no-pixels` wins.
|
|
2019
|
+
* `--webgl`, `--no-verify-frames`, `--only <name>` (repeatable) and `--dir <path>`. `--pixels`
|
|
2020
|
+
* forces the pixel leg on off a Mac; the last of `--pixels` and `--no-pixels` wins.
|
|
2021
|
+
* `--no-verify-frames` keeps the browser leg from checking the frames the renderer skips. An argument it does not know is left
|
|
1913
2022
|
* to the script that got it, so a test runner's own arguments pass through. `moku-game visual`
|
|
1914
2023
|
* reads its flags with it.
|
|
1915
2024
|
*
|
|
@@ -1923,16 +2032,15 @@ function valueAfter(argv, index, placeholder) {
|
|
|
1923
2032
|
* parseVisualArgv(["--no-pixels", "--dir", "shots"]); // { pixels: false, dir: "shots" }
|
|
1924
2033
|
* parseVisualArgv(["--webgl"]); // { renderer: "webgl" }
|
|
1925
2034
|
* parseVisualArgv(["--pixels"]); // { pixels: true }
|
|
2035
|
+
* parseVisualArgv(["--no-verify-frames"]); // { verifyFrames: false }
|
|
1926
2036
|
* ```
|
|
1927
2037
|
*/
|
|
1928
2038
|
function parseVisualArgv(argv) {
|
|
1929
2039
|
const flags = {};
|
|
1930
2040
|
const only = [];
|
|
1931
2041
|
for (const [index, argument] of argv.entries()) {
|
|
1932
|
-
|
|
1933
|
-
if (
|
|
1934
|
-
if (argument === "--pixels") flags.pixels = true;
|
|
1935
|
-
if (argument === "--webgl") flags.renderer = "webgl";
|
|
2042
|
+
const switched = SWITCHES[argument];
|
|
2043
|
+
if (switched !== void 0) Object.assign(flags, switched);
|
|
1936
2044
|
if (argument === "--only") only.push(valueAfter(argv, index, "<name>"));
|
|
1937
2045
|
if (argument === "--dir") flags.dir = valueAfter(argv, index, "<path>");
|
|
1938
2046
|
}
|
|
@@ -1943,7 +2051,8 @@ function parseVisualArgv(argv) {
|
|
|
1943
2051
|
}
|
|
1944
2052
|
/**
|
|
1945
2053
|
* Fills in the options of a run: an explicit option wins over a flag of `argv`, a flag over the
|
|
1946
|
-
* default. `pixels` is on by default only with a `page` on a Mac; `renderer` is `"webgpu"
|
|
2054
|
+
* default. `pixels` is on by default only with a `page` on a Mac; `renderer` is `"webgpu"`;
|
|
2055
|
+
* `verifyFrames` is on.
|
|
1947
2056
|
*
|
|
1948
2057
|
* @param setup - The setup, for its `page`.
|
|
1949
2058
|
* @param options - The options the script passed.
|
|
@@ -1957,6 +2066,7 @@ function resolveVisualOptions(setup, options = {}) {
|
|
|
1957
2066
|
dir: options.dir ?? flags.dir ?? DEFAULT_DIR,
|
|
1958
2067
|
update: options.update ?? flags.update ?? false,
|
|
1959
2068
|
pixels: options.pixels ?? flags.pixels ?? (setup.page !== void 0 && process.platform === "darwin"),
|
|
2069
|
+
verifyFrames: options.verifyFrames ?? flags.verifyFrames ?? true,
|
|
1960
2070
|
renderer: options.renderer ?? flags.renderer ?? "webgpu",
|
|
1961
2071
|
settleFrames: options.settleFrames ?? DEFAULT_SETTLE_FRAMES,
|
|
1962
2072
|
tolerance: options.tolerance ?? { ...DEFAULT_TOLERANCE }
|