@bitmagic/cli 0.1.37 → 0.1.39

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 (51) hide show
  1. package/README.md +41 -3
  2. package/dist/auth/subscribe-flow.d.ts +59 -0
  3. package/dist/auth/subscribe-flow.js +102 -0
  4. package/dist/auth/subscribe-flow.js.map +1 -0
  5. package/dist/browser-gl.d.ts +23 -0
  6. package/dist/browser-gl.js +50 -0
  7. package/dist/browser-gl.js.map +1 -1
  8. package/dist/cli.d.ts +41 -1
  9. package/dist/cli.js +4 -0
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands/login.js +33 -5
  12. package/dist/commands/login.js.map +1 -1
  13. package/dist/commands/subscribe.d.ts +14 -0
  14. package/dist/commands/subscribe.js +60 -0
  15. package/dist/commands/subscribe.js.map +1 -0
  16. package/dist/commands/usage.d.ts +76 -0
  17. package/dist/commands/usage.js +117 -0
  18. package/dist/commands/usage.js.map +1 -0
  19. package/dist/commands/verify.d.ts +13 -1
  20. package/dist/commands/verify.js +139 -15
  21. package/dist/commands/verify.js.map +1 -1
  22. package/dist/commands/whoami.d.ts +10 -4
  23. package/dist/commands/whoami.js +22 -7
  24. package/dist/commands/whoami.js.map +1 -1
  25. package/dist/forge/upload-proxy.js +1 -1
  26. package/dist/http/client.d.ts +10 -0
  27. package/dist/http/client.js +38 -2
  28. package/dist/http/client.js.map +1 -1
  29. package/dist/scaffold/claude-settings.js +4 -0
  30. package/dist/scaffold/claude-settings.js.map +1 -1
  31. package/dist/scaffold/project-files.d.ts +9 -0
  32. package/dist/scaffold/project-files.js +70 -9
  33. package/dist/scaffold/project-files.js.map +1 -1
  34. package/dist/scaffold/project.js +2 -1
  35. package/dist/scaffold/project.js.map +1 -1
  36. package/dist/verify/browser.d.ts +37 -1
  37. package/dist/verify/browser.js +389 -97
  38. package/dist/verify/browser.js.map +1 -1
  39. package/dist/verify/classify.d.ts +75 -2
  40. package/dist/verify/classify.js +48 -2
  41. package/dist/verify/classify.js.map +1 -1
  42. package/dist/verify/iterate.d.ts +47 -0
  43. package/dist/verify/iterate.js +162 -0
  44. package/dist/verify/iterate.js.map +1 -0
  45. package/dist/verify/result.d.ts +16 -3
  46. package/dist/verify/result.js +13 -3
  47. package/dist/verify/result.js.map +1 -1
  48. package/dist/verify/settle.d.ts +60 -0
  49. package/dist/verify/settle.js +90 -0
  50. package/dist/verify/settle.js.map +1 -0
  51. package/package.json +1 -1
@@ -27,6 +27,62 @@ export interface CanvasFrameObservation {
27
27
  */
28
28
  distinctColors: number;
29
29
  }
30
+ /** Which renderer the run asked for and what actually drove the canvas. */
31
+ export interface RendererObservation {
32
+ /** The `?renderer=` value verify booted the game with. */
33
+ requested: 'webgpu' | 'webgl';
34
+ /**
35
+ * `engine.getRendererType()` — which renderer CLASS the engine constructed (⇒ TSL vs GLSL
36
+ * material pipeline). Null when the engine or the getter could not be reached (older engine,
37
+ * probe failed): "not observed", never a failure by itself.
38
+ */
39
+ rendererType: string | null;
40
+ /**
41
+ * `engine.getActiveBackend()` — what is actually driving the canvas. The load-bearing one:
42
+ * a WebGPURenderer whose adapter never came up silently runs `'webgl2'`, and only this getter
43
+ * can tell. Null means "not observed", same contract as `rendererType`.
44
+ */
45
+ backend: string | null;
46
+ }
47
+ /**
48
+ * A snapshot of live engine state at the end of the run, read through `window.gameTemplate`.
49
+ *
50
+ * Every field is null when it could not be observed (older engine, probe failure) and classifies
51
+ * as "not observed", never as a failure — same contract as `RendererObservation`. What this buys
52
+ * over the console log: the console only says what the engine chose to print; this says what the
53
+ * running game actually IS.
54
+ */
55
+ export interface StateSnapshot {
56
+ /** `gameStateManager.getCurrentState()` — lowercase engine values ('playing', 'menu', …). */
57
+ gameState: string | null;
58
+ hasStarted: boolean | null;
59
+ /** Engine clock seconds since boot. */
60
+ elapsedTimeSeconds: number | null;
61
+ /** Rendered frames per second across the final settle polls — page rAF rate, so it proves the
62
+ * page's loop is alive rather than measuring render quality. */
63
+ fps: number | null;
64
+ player: {
65
+ x: number;
66
+ y: number;
67
+ z: number;
68
+ isFalling: boolean;
69
+ } | null;
70
+ physics: {
71
+ rigidBodyCount: number;
72
+ colliderCount: number;
73
+ } | null;
74
+ scene: {
75
+ objects: number;
76
+ meshes: number;
77
+ lights: number;
78
+ } | null;
79
+ npcCount: number | null;
80
+ }
81
+ /** How the settle ended: early because the game was provably stable, or at the `--timeout` cap. */
82
+ export interface SettleObservation {
83
+ durationMs: number;
84
+ reason: 'stable' | 'timeout';
85
+ }
30
86
  /** What the start interaction (waiting for and clicking the engine Play button) observed. */
31
87
  export interface StartObservations {
32
88
  playButtonSeen: boolean;
@@ -41,8 +97,9 @@ export interface VerifyObservations {
41
97
  consoleLines: string[];
42
98
  pageErrors: string[];
43
99
  canvas: CanvasObservation | null;
44
- /** Byte length of the PNG screenshot taken after settling. */
45
- screenshotBytes: number;
100
+ /** Byte length of the PNG screenshot taken after settling, or null when the run was asked not
101
+ * to capture one (`--fast`) — "not observed", never "blank". */
102
+ screenshotBytes: number | null;
46
103
  /**
47
104
  * The page fired `webglcontextlost`. The renderer is dead from that moment: rAF keeps firing,
48
105
  * the engine logs its usual lines, the DOM HUD still paints, and nothing else gives it away.
@@ -60,10 +117,26 @@ export interface VerifyObservations {
60
117
  /** Only present when the run was asked to perform the start interaction (`bitmagic verify`);
61
118
  * `publish`'s smoke check never sets it and classifies exactly as before. */
62
119
  start?: StartObservations;
120
+ /** Only present when the run was told which renderer it booted (`bitmagic verify` passes its
121
+ * `--renderer` choice); `publish`'s smoke check never sets it and classifies exactly as before. */
122
+ renderer?: RendererObservation;
123
+ /** End-of-run engine state. Present on `bitmagic verify` runs against a reachable page; null
124
+ * when the probe failed outright, absent on the smoke check. */
125
+ snapshot?: StateSnapshot | null;
126
+ /** How the settle ended. Only present when the run settled adaptively (`bitmagic verify`). */
127
+ settle?: SettleObservation;
63
128
  }
64
129
  export interface VerifyResult {
65
130
  ok: boolean;
66
131
  failures: string[];
67
132
  warnings: string[];
68
133
  }
134
+ /**
135
+ * The engine's line when the player has fallen out of the world and is being put back.
136
+ *
137
+ * A game the player cannot stand up in is not verified, however cleanly it renders. This was not
138
+ * theoretical: a forged level whose terrain never loaded rendered a perfect frame, threw nothing,
139
+ * and reported PASSED, while the player fell and respawned 14 times in a 45s settle.
140
+ */
141
+ export declare const RESPAWN_LINE = "Fell below -100";
69
142
  export declare function classifyVerifyRun(observations: VerifyObservations): VerifyResult;
@@ -60,7 +60,7 @@ const MIN_DISTINCT_COLORS = 3;
60
60
  * theoretical: a forged level whose terrain never loaded rendered a perfect frame, threw nothing,
61
61
  * and reported PASSED, while the player fell and respawned 14 times in a 45s settle.
62
62
  */
63
- const RESPAWN_LINE = 'Fell below -100';
63
+ export const RESPAWN_LINE = 'Fell below -100';
64
64
  /**
65
65
  * One or two falls can be the spawn beating the chunk colliders — the controller says as much
66
66
  * ("waiting for colliders…") and recovers, so those warn. Three is no longer a settle; it is a
@@ -74,6 +74,13 @@ const MAX_TOLERATED_RESPAWNS = 2;
74
74
  * only evidence was this line, which nothing was reading.
75
75
  */
76
76
  const CORS_BLOCK = 'has been blocked by CORS policy';
77
+ /**
78
+ * A player below this and still falling when the run ends is out of the world, not exploring a
79
+ * deep cave: the engine's own kill plane respawns at -100, so a sustained fall past -50 means
80
+ * the respawn keeps landing on nothing. Complements the `RESPAWN_LINE` count — this one fires
81
+ * even when a game's custom controller never prints that line.
82
+ */
83
+ const FREEFALL_FAILURE_Y = -50;
77
84
  export function classifyVerifyRun(observations) {
78
85
  const failures = [];
79
86
  const warnings = [];
@@ -102,6 +109,18 @@ export function classifyVerifyRun(observations) {
102
109
  warnings.push('The game started but the start menu was still visible when the run ended.');
103
110
  }
104
111
  }
112
+ const { renderer } = observations;
113
+ if (renderer && renderer.requested === 'webgpu') {
114
+ if (renderer.backend === 'webgl2') {
115
+ failures.push('WebGPU was requested but the renderer silently fell back to WebGL2 — the WebGPU path '
116
+ + 'was not verified. Run `bitmagic verify --renderer webgl` to verify the WebGL path '
117
+ + 'deliberately, or fix WebGPU availability in this environment.');
118
+ }
119
+ else if (renderer.backend === null) {
120
+ warnings.push('Could not observe which backend drove the canvas (older engine or probe failure) — '
121
+ + 'the WebGPU path may or may not have been exercised.');
122
+ }
123
+ }
105
124
  const { canvas } = observations;
106
125
  if (!canvas) {
107
126
  failures.push('No <canvas> was rendered inside #game-container — the game did not start.');
@@ -115,7 +134,7 @@ export function classifyVerifyRun(observations) {
115
134
  `${UNTOUCHED_CANVAS_HEIGHT} HTML default) — nothing claimed it.`);
116
135
  }
117
136
  }
118
- if (observations.screenshotBytes < MIN_SCREENSHOT_BYTES) {
137
+ if (observations.screenshotBytes !== null && observations.screenshotBytes < MIN_SCREENSHOT_BYTES) {
119
138
  failures.push(`The screenshot is implausibly small (${observations.screenshotBytes} bytes) — the frame ` +
120
139
  'looks blank.');
121
140
  }
@@ -164,6 +183,33 @@ export function classifyVerifyRun(observations) {
164
183
  warnings.push(`Expected "${marker}" in the console and did not see it.`);
165
184
  }
166
185
  }
186
+ // Live-state checks. All skipped when the snapshot is null/absent — "not observed" is never a
187
+ // failure, and everything above already covers the page that could not be probed at all.
188
+ const snapshot = observations.snapshot;
189
+ if (snapshot) {
190
+ const { player } = snapshot;
191
+ if (player !== null && player.isFalling && player.y < FREEFALL_FAILURE_Y) {
192
+ failures.push(`The player was still falling when the run ended (at y=${Math.round(player.y)}) — the `
193
+ + 'level renders but cannot be stood on. Its terrain most likely never loaded.');
194
+ }
195
+ if (snapshot.fps === 0) {
196
+ // Software WebGPU is the measured way to get here without a broken game: SwiftShader
197
+ // brings the adapter up and then freezes at game start, so the backend probe looks fine.
198
+ const hint = observations.renderer?.requested === 'webgpu'
199
+ ? ' On a machine without a usable GPU (CI), the WebGPU path can freeze under software '
200
+ + 'rendering — run with --renderer webgl there.'
201
+ : '';
202
+ failures.push('The page\'s render loop stalled — no frames were produced across the final stretch of '
203
+ + `the run, however healthy the console looks.${hint}`);
204
+ }
205
+ if (observations.start?.gameStartDetected === true
206
+ && snapshot.gameState !== null
207
+ && snapshot.gameState !== 'playing'
208
+ && snapshot.gameState !== 'end') {
209
+ warnings.push(`The game started but ended the run in state "${snapshot.gameState}" instead of playing `
210
+ + '— it bounced back out of gameplay.');
211
+ }
212
+ }
167
213
  return { ok: failures.length === 0, failures, warnings };
168
214
  }
169
215
  //# sourceMappingURL=classify.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/verify/classify.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/verify/classify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { type RendererChoice } from '../browser-gl.js';
2
+ import type { Output } from '../output.js';
3
+ import { type VerifyObservations } from './classify.js';
4
+ /**
5
+ * Runs verify iterations one at a time. A change that lands mid-iteration queues exactly one
6
+ * follow-up — not one per change, because the follow-up will see all of them anyway.
7
+ *
8
+ * Extracted pure (no timers, no I/O beyond the injected runner) so the rerun-on-change-during-run
9
+ * behaviour is testable the way `ReloadCoordinator`'s is.
10
+ */
11
+ export declare class VerifyIterationScheduler {
12
+ private readonly run;
13
+ /** Where a detached follow-up's failure goes — `request` only rethrows for its own caller. */
14
+ private readonly onError;
15
+ private running;
16
+ private queued;
17
+ constructor(run: (reason: string) => Promise<void>,
18
+ /** Where a detached follow-up's failure goes — `request` only rethrows for its own caller. */
19
+ onError?: (error: unknown) => void);
20
+ /**
21
+ * Resolves when THIS request's iteration completes (or immediately, if it was queued behind a
22
+ * running one). A queued follow-up runs detached — awaiting it here would make the first
23
+ * caller's promise hostage to work it never asked for.
24
+ */
25
+ request(reason: string): Promise<void>;
26
+ }
27
+ export interface VerifyWatchOptions {
28
+ root: string;
29
+ /** Path to the project's `tsc` binary (for `tsc --watch`). */
30
+ tsc: string;
31
+ artifactDir: string;
32
+ consolePath: string;
33
+ screenshotPath: string;
34
+ /** The vite dev server's port — already up; the loop only builds URLs from it. */
35
+ port: number;
36
+ renderer: RendererChoice;
37
+ settleMs: number;
38
+ /** Skip the screenshot pipeline (default in watch mode). */
39
+ fast: boolean;
40
+ output: Output;
41
+ describeRunState: (observations: VerifyObservations) => string;
42
+ }
43
+ /**
44
+ * Run the watch loop until SIGINT/SIGTERM. The caller owns vite; everything spawned here —
45
+ * `tsc --watch`, the file watcher, the browser session — is cleaned up before returning.
46
+ */
47
+ export declare function runVerifyWatch(options: VerifyWatchOptions): Promise<void>;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `bitmagic verify --watch`: the warm inner loop.
3
+ *
4
+ * One-shot verify pays Chrome launch + full tsc build + vite spawn on every run — most of a
5
+ * minute before the game is even observed. The watch loop pays each of those once: `tsc --watch`
6
+ * keeps the build incremental, vite stays up (the command owns it, like one-shot), and one
7
+ * launched Chrome runs every iteration in a fresh browser context. What triggers an iteration is
8
+ * `editor/watch.ts`'s coordinator — the same debounce + compile gate `bitmagic dev` uses, so an
9
+ * agent's five-file edit burst becomes one re-verify, and a red build reports its type errors
10
+ * instead of wasting a browser run on the previous emit.
11
+ */
12
+ import * as path from 'path';
13
+ import { browserRendererArgs, shouldRetrySoftwareGl } from '../browser-gl.js';
14
+ import { ReloadCoordinator, pipeTscWatch, watchProject } from '../editor/watch.js';
15
+ import { computeFingerprint } from '../publish/fingerprint.js';
16
+ import { VerifyBrowserSession, DEFAULT_START_INTERACTION } from './browser.js';
17
+ import { classifyVerifyRun } from './classify.js';
18
+ import { decideVerifyOutcome, writeVerifyRecord } from './result.js';
19
+ /**
20
+ * Runs verify iterations one at a time. A change that lands mid-iteration queues exactly one
21
+ * follow-up — not one per change, because the follow-up will see all of them anyway.
22
+ *
23
+ * Extracted pure (no timers, no I/O beyond the injected runner) so the rerun-on-change-during-run
24
+ * behaviour is testable the way `ReloadCoordinator`'s is.
25
+ */
26
+ export class VerifyIterationScheduler {
27
+ run;
28
+ onError;
29
+ running = false;
30
+ queued = null;
31
+ constructor(run,
32
+ /** Where a detached follow-up's failure goes — `request` only rethrows for its own caller. */
33
+ onError = () => { }) {
34
+ this.run = run;
35
+ this.onError = onError;
36
+ }
37
+ /**
38
+ * Resolves when THIS request's iteration completes (or immediately, if it was queued behind a
39
+ * running one). A queued follow-up runs detached — awaiting it here would make the first
40
+ * caller's promise hostage to work it never asked for.
41
+ */
42
+ async request(reason) {
43
+ if (this.running) {
44
+ this.queued = reason;
45
+ return;
46
+ }
47
+ this.running = true;
48
+ try {
49
+ await this.run(reason);
50
+ }
51
+ finally {
52
+ this.running = false;
53
+ const queued = this.queued;
54
+ this.queued = null;
55
+ if (queued !== null) {
56
+ void this.request(queued).catch((error) => this.onError(error));
57
+ }
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * Run the watch loop until SIGINT/SIGTERM. The caller owns vite; everything spawned here —
63
+ * `tsc --watch`, the file watcher, the browser session — is cleaned up before returning.
64
+ */
65
+ export async function runVerifyWatch(options) {
66
+ const { output } = options;
67
+ let session = await VerifyBrowserSession.launch(browserRendererArgs(process.env, options.renderer));
68
+ // Sticky across iterations: once one run loses its context and the software re-run works,
69
+ // every following iteration goes straight to software instead of failing GPU-first each time.
70
+ let onSoftwareGl = false;
71
+ const reportIterationError = (error) => {
72
+ // An iteration that throws (page never loaded, browser died) must not kill the loop — the
73
+ // next edit deserves its verify. Say what happened and keep watching.
74
+ const message = error instanceof Error ? error.message : String(error);
75
+ output.info(`✗ verify iteration failed: ${message}`);
76
+ };
77
+ const scheduler = new VerifyIterationScheduler(async (reason) => {
78
+ const startedAt = Date.now();
79
+ // Before the check, not after: this names the state the browser is about to load. A file
80
+ // that changes mid-run makes the record stale, and the coordinator immediately queues the
81
+ // iteration that will re-verify it.
82
+ const fingerprint = computeFingerprint(options.root);
83
+ const checkOptions = {
84
+ url: `http://localhost:${options.port}/?renderer=${options.renderer}`,
85
+ screenshotPath: options.screenshotPath,
86
+ consolePath: options.consolePath,
87
+ settleMs: options.settleMs,
88
+ startInteraction: DEFAULT_START_INTERACTION,
89
+ requestedRenderer: options.renderer,
90
+ captureScreenshot: !options.fast,
91
+ };
92
+ let observations = await session.check(checkOptions);
93
+ let result = classifyVerifyRun(observations);
94
+ let retriedWithSoftwareGl = false;
95
+ if (!result.ok && !onSoftwareGl && shouldRetrySoftwareGl(observations, process.env)) {
96
+ output.info('The WebGL context was lost — switching this watch to software rendering…');
97
+ await session.close();
98
+ session = await VerifyBrowserSession.launch(browserRendererArgs({ ...process.env, BITMAGIC_BROWSER_SOFTWARE_GL: 'true' }, options.renderer));
99
+ onSoftwareGl = true;
100
+ retriedWithSoftwareGl = true;
101
+ observations = await session.check({
102
+ ...checkOptions,
103
+ consolePath: path.join(options.artifactDir, 'console.retry.log'),
104
+ });
105
+ result = classifyVerifyRun(observations);
106
+ }
107
+ const outcome = decideVerifyOutcome(result, fingerprint, new Date().toISOString(), {
108
+ renderer: observations.renderer,
109
+ settle: observations.settle,
110
+ snapshot: observations.snapshot,
111
+ ...(retriedWithSoftwareGl ? { retriedWithSoftwareGl } : {}),
112
+ });
113
+ // Recorded but never thrown: a failing iteration is information for the next edit, not the
114
+ // end of the loop. Publish still gates on this record exactly as it does after one-shot.
115
+ writeVerifyRecord(options.artifactDir, outcome.record);
116
+ const seconds = ((Date.now() - startedAt) / 1000).toFixed(1);
117
+ if (result.ok) {
118
+ output.info(`✓ verified in ${seconds}s (${reason}) — ${options.describeRunState(observations)}`);
119
+ }
120
+ else {
121
+ for (const failure of result.failures)
122
+ output.info(`✗ ${failure}`);
123
+ output.info(`✗ ${result.failures.length} failure${result.failures.length === 1 ? '' : 's'} in `
124
+ + `${seconds}s (${reason})`);
125
+ }
126
+ }, reportIterationError);
127
+ let firstBuildSeen = false;
128
+ const coordinator = new ReloadCoordinator({
129
+ onReload: (reason) => {
130
+ void scheduler.request(reason).catch(reportIterationError);
131
+ },
132
+ onBuild: (status, message) => {
133
+ if (status === 'failed' && message !== undefined) {
134
+ output.info(`✗ ${message}`);
135
+ return;
136
+ }
137
+ if (status === 'ok' && !firstBuildSeen) {
138
+ // The first clean compile is the loop's starting gun; after that, only file changes
139
+ // (gated on their own compiles) trigger iterations.
140
+ firstBuildSeen = true;
141
+ coordinator.noteFileChange('initial build');
142
+ }
143
+ },
144
+ });
145
+ const tscWatch = pipeTscWatch(options.tsc, options.root, coordinator);
146
+ const watcher = watchProject(options.root, coordinator, (message) => output.info(message));
147
+ output.info('Watching for changes — Ctrl-C to stop.');
148
+ try {
149
+ await new Promise((resolve) => {
150
+ const stop = () => resolve();
151
+ process.once('SIGINT', stop);
152
+ process.once('SIGTERM', stop);
153
+ });
154
+ }
155
+ finally {
156
+ coordinator.stop();
157
+ watcher.close();
158
+ tscWatch.kill('SIGTERM');
159
+ await session.close();
160
+ }
161
+ }
162
+ //# sourceMappingURL=iterate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iterate.js","sourceRoot":"","sources":["../../src/verify/iterate.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { VerifyResult } from './classify.js';
1
+ import type { RendererObservation, SettleObservation, StateSnapshot, VerifyResult } from './classify.js';
2
2
  export declare const VERIFY_RESULT_FILENAME = "result.json";
3
3
  /**
4
4
  * What `bitmagic verify` observed, written so `bitmagic publish` can gate on it.
@@ -13,6 +13,19 @@ export interface VerifyRunRecord {
13
13
  fingerprint: string;
14
14
  /** ISO 8601. Reported to the creator; never used to decide staleness. */
15
15
  at: string;
16
+ /**
17
+ * Which renderer was requested and what actually drove the canvas. Optional — `isRecord` must
18
+ * not require it, so records written by older CLI versions keep parsing (publish gates on this
19
+ * file across versions).
20
+ */
21
+ renderer?: RendererObservation;
22
+ /** The run failed on a lost WebGL context and was automatically re-run once on software GL;
23
+ * the recorded verdict is the retry's. Optional for the same cross-version reason. */
24
+ retriedWithSoftwareGl?: boolean;
25
+ /** How the settle ended: early on provable stability, or at the `--timeout` cap. */
26
+ settle?: SettleObservation;
27
+ /** Live engine state at the end of the run — what the agent reads instead of pixels. */
28
+ snapshot?: StateSnapshot | null;
16
29
  }
17
30
  export declare function writeVerifyRecord(artifactDir: string, record: VerifyRunRecord): void;
18
31
  /**
@@ -33,7 +46,7 @@ export declare function readVerifyRecord(artifactDir: string): VerifyRunRecord |
33
46
  * different messages and different remedies. Inside `run()` that ordering is only observable by
34
47
  * spawning vite and a browser, which is why it went unguarded.
35
48
  */
36
- export declare function decideVerifyOutcome(result: VerifyResult, fingerprint: string, at: string): {
49
+ export declare function decideVerifyOutcome(result: VerifyResult, fingerprint: string, at: string, extras?: Pick<VerifyRunRecord, 'renderer' | 'retriedWithSoftwareGl' | 'settle' | 'snapshot'>): {
37
50
  record: VerifyRunRecord;
38
51
  shouldThrow: boolean;
39
52
  };
@@ -56,5 +69,5 @@ export declare function recordAndGateVerify(artifactDir: string, outcome: {
56
69
  shouldThrow: boolean;
57
70
  }, paths: {
58
71
  consolePath: string;
59
- screenshotPath: string;
72
+ screenshotPath?: string;
60
73
  }, reporter: VerifyReporter): void;
@@ -44,9 +44,16 @@ export function readVerifyRecord(artifactDir) {
44
44
  * different messages and different remedies. Inside `run()` that ordering is only observable by
45
45
  * spawning vite and a browser, which is why it went unguarded.
46
46
  */
47
- export function decideVerifyOutcome(result, fingerprint, at) {
47
+ export function decideVerifyOutcome(result, fingerprint, at, extras) {
48
48
  return {
49
- record: { ok: result.ok, failures: result.failures, warnings: result.warnings, fingerprint, at },
49
+ record: {
50
+ ok: result.ok,
51
+ failures: result.failures,
52
+ warnings: result.warnings,
53
+ fingerprint,
54
+ at,
55
+ ...extras,
56
+ },
50
57
  shouldThrow: !result.ok,
51
58
  };
52
59
  }
@@ -66,7 +73,10 @@ export function recordAndGateVerify(artifactDir, outcome, paths, reporter) {
66
73
  if (outcome.shouldThrow) {
67
74
  for (const failure of outcome.record.failures)
68
75
  reporter.error(`✗ ${failure}`);
69
- reporter.error(`\nArtifacts: ${paths.consolePath}, ${paths.screenshotPath}`);
76
+ // screenshotPath is absent on a `--fast` run, which captures none — listing it would point
77
+ // the reader at a file that does not exist (or worse, a stale one from an earlier run).
78
+ const artifacts = [paths.consolePath, paths.screenshotPath].filter((p) => p !== undefined);
79
+ reporter.error(`\nArtifacts: ${artifacts.join(', ')}`);
70
80
  throw new CliError('Verification failed.');
71
81
  }
72
82
  reporter.info(`Verification passed. Artifacts in ${artifactDir}`);
@@ -1 +1 @@
1
- {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/verify/result.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/verify/result.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The decision "has the game settled enough to observe", extracted pure so it is testable
3
+ * without a browser.
4
+ *
5
+ * Verify used to wait a fixed 15 seconds regardless of what the game was doing. The samples the
6
+ * browser loop feeds this function come from the live engine (`window.gameTemplate`), so a
7
+ * healthy game that reaches gameplay and holds steady is observed after ~4-5 seconds, while the
8
+ * fixed `--timeout` stays as the hard cap for games that never stabilise. An engine too old to
9
+ * answer the probes never settles early — it gets exactly the old fixed-wait behaviour.
10
+ */
11
+ /** One poll of the live page, taken every `SETTLE_POLL_MS` during the settle. */
12
+ export interface SettleSample {
13
+ /** ms since the settle began. */
14
+ atMs: number;
15
+ /** `window.__bitmagicFrameCount` — rAF ticks since page load, injected by verify's init
16
+ * script. Null when the page could not be evaluated. */
17
+ frameCount: number | null;
18
+ /** `engine.gameStateManager.getCurrentState()` — lowercase engine values ('menu', 'loading',
19
+ * 'ready', 'playing', 'paused', 'end'). Null when unobservable (older engine, no manager). */
20
+ gameState: string | null;
21
+ /** `engine.gameStateManager.hasStarted()`. Null when unobservable. */
22
+ hasStarted: boolean | null;
23
+ /** Player world-Y. Null when there is no player to ask (menu, loading, engine too old). */
24
+ playerY: number | null;
25
+ /** Console error lines + player-fell-out-of-world lines seen so far. Monotonic; a rise
26
+ * between samples means the game is still misbehaving. */
27
+ errorEventCount: number;
28
+ }
29
+ export interface SettleConfig {
30
+ /** Never settle before this — engines finish async spawns (NPCs, deferred GLBs) well after
31
+ * gameplay starts, and a too-early observation would miss them. */
32
+ minSettleMs: number;
33
+ /** Frames the page must have rendered — proves a live render loop, not just a live DOM. */
34
+ minFrames: number;
35
+ /** How long the error counters must have been flat. */
36
+ quietMs: number;
37
+ }
38
+ export declare const DEFAULT_SETTLE_CONFIG: SettleConfig;
39
+ export declare const SETTLE_POLL_MS = 500;
40
+ /**
41
+ * The player is in sustained freefall at the end of `samples`.
42
+ *
43
+ * Judged over the trailing samples that HAVE a player position; unobservable positions mean
44
+ * "cannot tell", never "falling". Exported because the final state snapshot reports the same
45
+ * judgement as `player.isFalling`.
46
+ */
47
+ export declare function isFreefalling(samples: SettleSample[]): boolean;
48
+ /**
49
+ * True when the run has seen enough to stop settling early.
50
+ *
51
+ * Every condition must hold on the latest sample:
52
+ * - the minimum settle floor has passed;
53
+ * - gameplay is actually running — state 'playing' or 'end' (a run-to-completion minigame), or
54
+ * `hasStarted` when the state manager is not observable. Both unobservable → never settle
55
+ * early; the fixed timeout is the correct behaviour for an engine that cannot be asked;
56
+ * - the page has rendered enough frames to prove a live render loop;
57
+ * - the error counters have been flat for the quiet window;
58
+ * - the player is not falling out of the world.
59
+ */
60
+ export declare function settleVerdict(samples: SettleSample[], config: SettleConfig): 'settled' | 'keep-waiting';