@bitmagic/cli 0.1.52-dev.10 → 0.1.52-dev.12

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 (75) hide show
  1. package/README.md +44 -8
  2. package/dist/assets/add.js +2 -1
  3. package/dist/assets/add.js.map +1 -1
  4. package/dist/assets/voxelize-glb.d.ts +17 -1
  5. package/dist/assets/voxelize-glb.js +43 -19
  6. package/dist/assets/voxelize-glb.js.map +1 -1
  7. package/dist/cli.d.ts +8 -0
  8. package/dist/cli.js +6 -6
  9. package/dist/cli.js.map +1 -1
  10. package/dist/commands/generate.js +36 -67
  11. package/dist/commands/generate.js.map +1 -1
  12. package/dist/commands/judge.d.ts +5 -0
  13. package/dist/commands/judge.js +28 -7
  14. package/dist/commands/judge.js.map +1 -1
  15. package/dist/commands/levels.d.ts +10 -17
  16. package/dist/commands/levels.js +48 -48
  17. package/dist/commands/levels.js.map +1 -1
  18. package/dist/commands/publish.d.ts +6 -1
  19. package/dist/commands/publish.js +29 -15
  20. package/dist/commands/publish.js.map +1 -1
  21. package/dist/commands/verify.d.ts +4 -0
  22. package/dist/commands/verify.js +94 -51
  23. package/dist/commands/verify.js.map +1 -1
  24. package/dist/editor/journal.js +13 -7
  25. package/dist/editor/journal.js.map +1 -1
  26. package/dist/generate/model.d.ts +8 -2
  27. package/dist/generate/model.js +69 -21
  28. package/dist/generate/model.js.map +1 -1
  29. package/dist/generate/prop.d.ts +2 -0
  30. package/dist/generate/prop.js +1 -1
  31. package/dist/generate/prop.js.map +1 -1
  32. package/dist/judge/record.d.ts +3 -0
  33. package/dist/judge/record.js +3 -0
  34. package/dist/judge/record.js.map +1 -1
  35. package/dist/levels/registry.d.ts +5 -2
  36. package/dist/levels/registry.js +32 -27
  37. package/dist/levels/registry.js.map +1 -1
  38. package/dist/project/jobs.js +16 -20
  39. package/dist/project/jobs.js.map +1 -1
  40. package/dist/project/platform-declaration.d.ts +51 -0
  41. package/dist/project/platform-declaration.js +81 -0
  42. package/dist/project/platform-declaration.js.map +1 -0
  43. package/dist/publish/platform.d.ts +4 -8
  44. package/dist/publish/platform.js +19 -53
  45. package/dist/publish/platform.js.map +1 -1
  46. package/dist/scaffold/project-files.js +19 -5
  47. package/dist/scaffold/project-files.js.map +1 -1
  48. package/dist/verify/artifacts.d.ts +19 -0
  49. package/dist/verify/artifacts.js +34 -0
  50. package/dist/verify/artifacts.js.map +1 -0
  51. package/dist/verify/browser.d.ts +10 -0
  52. package/dist/verify/browser.js +79 -9
  53. package/dist/verify/browser.js.map +1 -1
  54. package/dist/verify/classify.d.ts +37 -1
  55. package/dist/verify/classify.js +66 -2
  56. package/dist/verify/classify.js.map +1 -1
  57. package/dist/verify/device.d.ts +43 -0
  58. package/dist/verify/device.js +59 -0
  59. package/dist/verify/device.js.map +1 -0
  60. package/dist/verify/iterate.d.ts +21 -2
  61. package/dist/verify/iterate.js +43 -36
  62. package/dist/verify/iterate.js.map +1 -1
  63. package/dist/verify/mobile-parity.d.ts +42 -0
  64. package/dist/verify/mobile-parity.js +84 -0
  65. package/dist/verify/mobile-parity.js.map +1 -0
  66. package/dist/verify/platform.d.ts +47 -0
  67. package/dist/verify/platform.js +89 -0
  68. package/dist/verify/platform.js.map +1 -0
  69. package/dist/verify/result.d.ts +90 -6
  70. package/dist/verify/result.js +89 -10
  71. package/dist/verify/result.js.map +1 -1
  72. package/dist/verify/url.d.ts +9 -1
  73. package/dist/verify/url.js +10 -2
  74. package/dist/verify/url.js.map +1 -1
  75. package/package.json +4 -4
@@ -9,14 +9,15 @@
9
9
  * agent's five-file edit burst becomes one re-verify, and a red build reports its type errors
10
10
  * instead of wasting a browser run on the previous emit.
11
11
  */
12
- import * as path from 'path';
13
12
  import { browserLaunchPlan, shouldRetrySoftwareGl, softwareGlLaunchPlan, } from '../browser-gl.js';
14
13
  import { ReloadCoordinator, pipeTscWatch, watchProject } from '../editor/watch.js';
15
14
  import { computeFingerprint } from '../publish/fingerprint.js';
16
15
  import { VerifyBrowserSession, DEFAULT_START_INTERACTION } from './browser.js';
17
16
  import { classifyVerifyRun } from './classify.js';
18
- import { decideVerifyOutcome, writeVerifyRecord } from './result.js';
17
+ import { decideVerifyOutcome, mergeVerifyPlatformRuns, toVerifyPlatformRun, writeVerifyRecord, } from './result.js';
19
18
  import { buildVerifyUrl } from './url.js';
19
+ import { verifyArtifactPaths } from './artifacts.js';
20
+ import { mobileContextOptions } from './device.js';
20
21
  /**
21
22
  * Runs verify iterations one at a time. A change that lands mid-iteration queues exactly one
22
23
  * follow-up — not one per change, because the follow-up will see all of them anyway.
@@ -81,48 +82,54 @@ export async function runVerifyWatch(options) {
81
82
  // that changes mid-run makes the record stale, and the coordinator immediately queues the
82
83
  // iteration that will re-verify it.
83
84
  const fingerprint = computeFingerprint(options.root);
84
- const checkOptions = {
85
- url: buildVerifyUrl(options.port, options.renderer),
86
- screenshotPath: options.screenshotPath,
87
- consolePath: options.consolePath,
88
- settleMs: options.settleMs,
89
- startInteraction: DEFAULT_START_INTERACTION,
90
- requestedRenderer: options.renderer,
91
- captureScreenshot: !options.fast,
92
- };
93
- let observations = await session.check(checkOptions);
94
- let result = classifyVerifyRun(observations);
95
- let retriedWithSoftwareGl = false;
96
- if (!result.ok && !onSoftwareGl && shouldRetrySoftwareGl(observations, process.env)) {
97
- output.info('The WebGL context was lost — switching this watch to software rendering…');
98
- await session.close();
99
- session = await VerifyBrowserSession.launch(softwareGlLaunchPlan(process.env, options.renderer));
100
- onSoftwareGl = true;
101
- retriedWithSoftwareGl = true;
102
- observations = await session.check({
103
- ...checkOptions,
104
- consolePath: path.join(options.artifactDir, 'console.retry.log'),
105
- });
106
- result = classifyVerifyRun(observations);
85
+ const classifyContext = { mobilePrimary: options.mobilePrimary };
86
+ const runs = [];
87
+ const states = [];
88
+ let mobileParity;
89
+ for (const platform of options.platforms) {
90
+ const paths = verifyArtifactPaths(options.artifactDir, platform);
91
+ const checkOptions = {
92
+ url: buildVerifyUrl(options.port, options.renderer, platform),
93
+ screenshotPath: paths.screenshotPath,
94
+ consolePath: paths.consolePath,
95
+ settleMs: options.settleMs,
96
+ startInteraction: DEFAULT_START_INTERACTION,
97
+ requestedRenderer: options.renderer,
98
+ captureScreenshot: !options.fast,
99
+ ...(platform === 'mobile' ? { device: mobileContextOptions(options.orientation) } : {}),
100
+ };
101
+ let observations = await session.check(checkOptions);
102
+ let result = classifyVerifyRun(observations, { ...classifyContext, platform });
103
+ let retriedWithSoftwareGl = false;
104
+ if (!result.ok && !onSoftwareGl && shouldRetrySoftwareGl(observations, process.env)) {
105
+ output.info('The WebGL context was lost — switching this watch to software rendering…');
106
+ await session.close();
107
+ session = await VerifyBrowserSession.launch(softwareGlLaunchPlan(process.env, options.renderer));
108
+ onSoftwareGl = true;
109
+ retriedWithSoftwareGl = true;
110
+ observations = await session.check({ ...checkOptions, consolePath: paths.retryConsolePath });
111
+ result = classifyVerifyRun(observations, { ...classifyContext, platform });
112
+ }
113
+ if (mobileParity === undefined)
114
+ mobileParity = observations.mobileParity;
115
+ runs.push(toVerifyPlatformRun(platform, result, observations, retriedWithSoftwareGl, paths, options.fast));
116
+ const state = options.describeRunState(observations);
117
+ states.push(options.platforms.length > 1 ? `${platform}: ${state}` : state);
107
118
  }
108
- const outcome = decideVerifyOutcome(result, fingerprint, new Date().toISOString(), {
109
- renderer: observations.renderer,
110
- settle: observations.settle,
111
- snapshot: observations.snapshot,
112
- events: observations.events,
113
- ...(retriedWithSoftwareGl ? { retriedWithSoftwareGl } : {}),
114
- });
119
+ const merged = mergeVerifyPlatformRuns(runs, mobileParity);
120
+ const outcome = decideVerifyOutcome(merged.result, fingerprint, new Date().toISOString(), merged.extras);
115
121
  // Recorded but never thrown: a failing iteration is information for the next edit, not the
116
122
  // end of the loop. Publish still gates on this record exactly as it does after one-shot.
117
123
  writeVerifyRecord(options.artifactDir, outcome.record);
124
+ const { failures } = merged.result;
118
125
  const seconds = ((Date.now() - startedAt) / 1000).toFixed(1);
119
- if (result.ok) {
120
- output.info(`✓ verified in ${seconds}s (${reason}) — ${options.describeRunState(observations)}`);
126
+ if (merged.result.ok) {
127
+ output.info(`✓ verified in ${seconds}s (${reason}) — ${states.join(' | ')}`);
121
128
  }
122
129
  else {
123
- for (const failure of result.failures)
130
+ for (const failure of failures)
124
131
  output.info(`✗ ${failure}`);
125
- output.info(`✗ ${result.failures.length} failure${result.failures.length === 1 ? '' : 's'} in `
132
+ output.info(`✗ ${failures.length} failure${failures.length === 1 ? '' : 's'} in `
126
133
  + `${seconds}s (${reason})`);
127
134
  }
128
135
  }, reportIterationError);
@@ -1 +1 @@
1
- {"version":3,"file":"iterate.js","sourceRoot":"","sources":["../../src/verify/iterate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"iterate.js","sourceRoot":"","sources":["../../src/verify/iterate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Reading the engine's mobile-parity verdict out of the console verify already captures.
3
+ *
4
+ * `GameTemplate` calls `playerController.verifyMobileParity()` on every game start — desktop runs
5
+ * included — and when it finds gaps the engine logs one `console.error` listing every one of
6
+ * them (`game/src/engine/PlayerController.ts`, documented in
7
+ * `game/agent-docs/control-system.md`). Nothing consumed it, so a game with actions no phone can
8
+ * reach passed verify silently.
9
+ *
10
+ * Parsing that line rather than listening for the `aitopia:mobile-parity-gap` postMessage the
11
+ * same check also fires is deliberate on two counts: the console is already captured, so this
12
+ * needs no new browser plumbing; and only the console line carries the `(source)` annotation
13
+ * naming which system registered an orphan key, which is the difference between "KeyF is
14
+ * unreachable" and "the attack system left KeyF unreachable".
15
+ *
16
+ * The line's format is a documented contract, but this parser still degrades rather than
17
+ * insisting on it: anything it cannot read comes back null, which classifies as "not observed"
18
+ * and never as a failure. Two legitimate ways to get null: a clean game (the engine logs nothing
19
+ * when parity holds) and a genre whose controller has no parity check at all — the 2D genres'
20
+ * `Physics2DPlayerController` deliberately has no `verifyMobileParity`, so `GameTemplate`'s
21
+ * duck-typed guard skips it.
22
+ */
23
+ /** The engine's marker. Also what a reader greps the console log for. */
24
+ export declare const MOBILE_PARITY_MARKER = "[mobile-parity]";
25
+ export interface MobileParityGap {
26
+ /**
27
+ * Unpaired desktop keys exactly as the engine names them, source annotation included —
28
+ * `KeyF(CombatSystem)`, or `KeyF((anonymous))` when the registration passed no `source`. The
29
+ * annotation is the actionable half: it points at the call site to fix.
30
+ */
31
+ unpairedDesktopKeys: string[];
32
+ /** Mobile buttons with no desktop key, as bare action names. */
33
+ unpairedMobileActions: string[];
34
+ }
35
+ /**
36
+ * The gap the engine reported during this run, or null when it reported none.
37
+ *
38
+ * The last matching line wins: a run that starts gameplay more than once (a restart during the
39
+ * settle) logs the check once per start, and the newest verdict is the one about the game as it
40
+ * ended up.
41
+ */
42
+ export declare function parseMobileParityGap(consoleLines: string[]): MobileParityGap | null;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Reading the engine's mobile-parity verdict out of the console verify already captures.
3
+ *
4
+ * `GameTemplate` calls `playerController.verifyMobileParity()` on every game start — desktop runs
5
+ * included — and when it finds gaps the engine logs one `console.error` listing every one of
6
+ * them (`game/src/engine/PlayerController.ts`, documented in
7
+ * `game/agent-docs/control-system.md`). Nothing consumed it, so a game with actions no phone can
8
+ * reach passed verify silently.
9
+ *
10
+ * Parsing that line rather than listening for the `aitopia:mobile-parity-gap` postMessage the
11
+ * same check also fires is deliberate on two counts: the console is already captured, so this
12
+ * needs no new browser plumbing; and only the console line carries the `(source)` annotation
13
+ * naming which system registered an orphan key, which is the difference between "KeyF is
14
+ * unreachable" and "the attack system left KeyF unreachable".
15
+ *
16
+ * The line's format is a documented contract, but this parser still degrades rather than
17
+ * insisting on it: anything it cannot read comes back null, which classifies as "not observed"
18
+ * and never as a failure. Two legitimate ways to get null: a clean game (the engine logs nothing
19
+ * when parity holds) and a genre whose controller has no parity check at all — the 2D genres'
20
+ * `Physics2DPlayerController` deliberately has no `verifyMobileParity`, so `GameTemplate`'s
21
+ * duck-typed guard skips it.
22
+ */
23
+ /** The engine's marker. Also what a reader greps the console log for. */
24
+ export const MOBILE_PARITY_MARKER = '[mobile-parity]';
25
+ /** The engine's literal for "this side has none", on either list. */
26
+ const NONE = '(none)';
27
+ const DESKTOP_LABEL = 'Desktop keys with no mobile button:';
28
+ const MOBILE_LABEL = 'Mobile actions with no desktop key:';
29
+ /**
30
+ * The gap the engine reported during this run, or null when it reported none.
31
+ *
32
+ * The last matching line wins: a run that starts gameplay more than once (a restart during the
33
+ * settle) logs the check once per start, and the newest verdict is the one about the game as it
34
+ * ended up.
35
+ */
36
+ export function parseMobileParityGap(consoleLines) {
37
+ for (let i = consoleLines.length - 1; i >= 0; i--) {
38
+ const gap = parseLine(consoleLines[i]);
39
+ if (gap !== null)
40
+ return gap;
41
+ }
42
+ return null;
43
+ }
44
+ function parseLine(line) {
45
+ if (!line.includes(MOBILE_PARITY_MARKER))
46
+ return null;
47
+ const desktop = between(line, DESKTOP_LABEL, MOBILE_LABEL);
48
+ const mobile = between(line, MOBILE_LABEL, 'Use playerController');
49
+ if (desktop === null && mobile === null)
50
+ return null;
51
+ const gap = {
52
+ unpairedDesktopKeys: splitList(desktop),
53
+ unpairedMobileActions: splitList(mobile),
54
+ };
55
+ // Both sides empty means the line was recognised but says nothing actionable — the engine
56
+ // never logs that, so treat it as unobserved rather than inventing a gap with no contents.
57
+ return gap.unpairedDesktopKeys.length === 0 && gap.unpairedMobileActions.length === 0
58
+ ? null
59
+ : gap;
60
+ }
61
+ /** The text between `label` and whatever comes next, or null when `label` is absent. */
62
+ function between(line, label, next) {
63
+ const start = line.indexOf(label);
64
+ if (start === -1)
65
+ return null;
66
+ const from = start + label.length;
67
+ const end = line.indexOf(next, from);
68
+ return line.slice(from, end === -1 ? undefined : end);
69
+ }
70
+ /**
71
+ * One comma-separated engine list. Safe to split on a bare comma even though every desktop entry
72
+ * carries a parenthesised source: `source` strings are identifiers naming the registering system,
73
+ * never prose with commas in it.
74
+ */
75
+ function splitList(raw) {
76
+ if (raw === null)
77
+ return [];
78
+ return raw
79
+ .replace(/\.\s*$/, '')
80
+ .split(',')
81
+ .map((entry) => entry.trim())
82
+ .filter((entry) => entry.length > 0 && entry !== NONE);
83
+ }
84
+ //# sourceMappingURL=mobile-parity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile-parity.js","sourceRoot":"","sources":["../../src/verify/mobile-parity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,yEAAyE;AACzE,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AAEtD,qEAAqE;AACrE,MAAM,IAAI,GAAG,QAAQ,CAAC;AAEtB,MAAM,aAAa,GAAG,qCAAqC,CAAC;AAC5D,MAAM,YAAY,GAAG,qCAAqC,CAAC;AAa3D;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,YAAsB;IACzD,KAAK,IAAI,CAAC,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAClD,MAAM,GAAG,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,GAAG,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,sBAAsB,CAAC,CAAC;IACnE,IAAI,OAAO,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,GAAG,GAAG;QACV,mBAAmB,EAAE,SAAS,CAAC,OAAO,CAAC;QACvC,qBAAqB,EAAE,SAAS,CAAC,MAAM,CAAC;KACzC,CAAC;IACF,0FAA0F;IAC1F,2FAA2F;IAC3F,OAAO,GAAG,CAAC,mBAAmB,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,qBAAqB,CAAC,MAAM,KAAK,CAAC;QACnF,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,GAAG,CAAC;AACV,CAAC;AAED,wFAAwF;AACxF,SAAS,OAAO,CAAC,IAAY,EAAE,KAAa,EAAE,IAAY;IACxD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACxD,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,GAAkB;IACnC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC5B,OAAO,GAAG;SACP,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,47 @@
1
+ import type { MobileOrientation, PlatformDeclaration } from '../project/platform-declaration.js';
2
+ export type VerifyPlatform = 'desktop' | 'mobile';
3
+ /** What `--platform` accepts. `both` is a request for two runs, not a platform. */
4
+ export type PlatformRequest = 'desktop' | 'mobile' | 'both';
5
+ export interface ResolvedPlatforms {
6
+ /**
7
+ * The platforms to run, in order. `platforms[0]` is the LEAD: the run whose observations land
8
+ * at the top level of `result.json`, which is what `bitmagic judge` summarises and what the
9
+ * one-line run state reports. A `both` run leads with the platform the game is FOR, so the
10
+ * headline numbers describe the game as it is meant to be played; the other run is in
11
+ * `platforms[]`. Artifact names are per platform, never per lead, so this ordering never moves
12
+ * a file.
13
+ */
14
+ platforms: VerifyPlatform[];
15
+ /** How to orient a mobile run. Portrait unless world.json locks the game to landscape. */
16
+ orientation: MobileOrientation;
17
+ /**
18
+ * The project declares itself mobile-primary. This is what turns a mobile-parity gap from a
19
+ * warning into a failure: an action a phone cannot reach is a broken game when the phone is
20
+ * the platform.
21
+ */
22
+ mobilePrimary: boolean;
23
+ /** One line for the creator naming what will run and why it was chosen. */
24
+ note: string;
25
+ /** Values the declaration carried that could not be used, and what was done instead. */
26
+ warnings: string[];
27
+ }
28
+ /**
29
+ * Validate `--platform`, or undefined when it was not passed. Mirrors `parseRendererArg` — an
30
+ * unrecognised value is a mistake worth stopping for, not something to silently default.
31
+ */
32
+ export declare function parsePlatformArg(raw: string | undefined): PlatformRequest | undefined;
33
+ export declare function resolveVerifyPlatforms(requested: PlatformRequest | undefined, declaration: PlatformDeclaration): ResolvedPlatforms;
34
+ /**
35
+ * Validate `bitmagic judge --platform`. No `both`: the judge sends ONE frame and returns one
36
+ * scorecard, and a verdict averaged over two screens would be a verdict about neither.
37
+ */
38
+ export declare function parseJudgePlatformArg(raw: string | undefined): VerifyPlatform | undefined;
39
+ /**
40
+ * Which frame `bitmagic judge` grades.
41
+ *
42
+ * Explicit wins. Otherwise mobile, but only when BOTH halves hold: the game says it is a phone
43
+ * game, and a phone frame actually exists. Requiring the file is what keeps the auto-default from
44
+ * becoming a dead end — a mobile-primary project whose last verify was `--platform desktop` gets
45
+ * its desktop frame graded rather than an error about a screenshot nobody asked for.
46
+ */
47
+ export declare function resolveJudgePlatform(explicit: VerifyPlatform | undefined, declaration: PlatformDeclaration, mobileScreenshotExists: boolean): VerifyPlatform;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Which platform(s) a verify run boots, and why.
3
+ *
4
+ * Pure, so the decision is testable without a browser: `commands/verify.ts` reads the project's
5
+ * declaration off disk, this decides, and `browser.ts` obeys.
6
+ *
7
+ * The default is the project's own answer rather than a fixed 'desktop'. A game whose
8
+ * `src/work/game.json` says `primaryPlatform: "mobile"` is a phone game, and verifying it on a
9
+ * 1280x720 desktop viewport answers a question nobody asked — it exercises neither the touch HUD
10
+ * nor the mobile pixel-ratio clamp, level-detail tier or warmup path the player will actually
11
+ * run. An explicit `--platform` always wins, and the chosen platform is always reported, so an
12
+ * auto-selected mobile run is never a surprise.
13
+ */
14
+ import { CliError } from '../errors.js';
15
+ /**
16
+ * Validate `--platform`, or undefined when it was not passed. Mirrors `parseRendererArg` — an
17
+ * unrecognised value is a mistake worth stopping for, not something to silently default.
18
+ */
19
+ export function parsePlatformArg(raw) {
20
+ if (raw === undefined)
21
+ return undefined;
22
+ if (raw === 'desktop' || raw === 'mobile' || raw === 'both')
23
+ return raw;
24
+ throw new CliError(`--platform must be "desktop", "mobile" or "both", got ${JSON.stringify(raw)}.`);
25
+ }
26
+ export function resolveVerifyPlatforms(requested, declaration) {
27
+ const mobilePrimary = declaration.primaryPlatform === 'mobile';
28
+ const warnings = [];
29
+ if (declaration.rejected.primaryPlatform !== undefined) {
30
+ warnings.push(`game.json primaryPlatform ${JSON.stringify(declaration.rejected.primaryPlatform)} is not `
31
+ + '"desktop" or "mobile" — verifying as a desktop game.');
32
+ }
33
+ if (declaration.rejected.mobileOrientation !== undefined) {
34
+ warnings.push(`worldProfileData.mobileOrientation ${JSON.stringify(declaration.rejected.mobileOrientation)} `
35
+ + 'is not "portrait" or "landscape" — verifying the mobile run in portrait.');
36
+ }
37
+ const effective = requested ?? (mobilePrimary ? 'mobile' : 'desktop');
38
+ const platforms = effective !== 'both'
39
+ ? [effective]
40
+ : mobilePrimary ? ['mobile', 'desktop'] : ['desktop', 'mobile'];
41
+ const orientation = declaration.mobileOrientation ?? 'portrait';
42
+ return {
43
+ platforms,
44
+ orientation,
45
+ mobilePrimary,
46
+ note: describe(effective, orientation, requested !== undefined, mobilePrimary),
47
+ warnings,
48
+ };
49
+ }
50
+ /**
51
+ * Validate `bitmagic judge --platform`. No `both`: the judge sends ONE frame and returns one
52
+ * scorecard, and a verdict averaged over two screens would be a verdict about neither.
53
+ */
54
+ export function parseJudgePlatformArg(raw) {
55
+ if (raw === undefined)
56
+ return undefined;
57
+ if (raw === 'desktop' || raw === 'mobile')
58
+ return raw;
59
+ throw new CliError(`--platform must be "desktop" or "mobile", got ${JSON.stringify(raw)}.`
60
+ + (raw === 'both' ? ' The judge grades one frame; run it once per platform.' : ''));
61
+ }
62
+ /**
63
+ * Which frame `bitmagic judge` grades.
64
+ *
65
+ * Explicit wins. Otherwise mobile, but only when BOTH halves hold: the game says it is a phone
66
+ * game, and a phone frame actually exists. Requiring the file is what keeps the auto-default from
67
+ * becoming a dead end — a mobile-primary project whose last verify was `--platform desktop` gets
68
+ * its desktop frame graded rather than an error about a screenshot nobody asked for.
69
+ */
70
+ export function resolveJudgePlatform(explicit, declaration, mobileScreenshotExists) {
71
+ if (explicit !== undefined)
72
+ return explicit;
73
+ return declaration.primaryPlatform === 'mobile' && mobileScreenshotExists ? 'mobile' : 'desktop';
74
+ }
75
+ function describe(effective, orientation, explicit, mobilePrimary) {
76
+ const includesMobile = effective !== 'desktop';
77
+ const what = effective === 'both'
78
+ ? `desktop and mobile (${orientation})`
79
+ : effective === 'mobile'
80
+ ? `mobile (${orientation})`
81
+ : 'desktop';
82
+ if (explicit)
83
+ return `Platform: ${what}.`;
84
+ return mobilePrimary && includesMobile
85
+ ? `Platform: ${what} — game.json declares primaryPlatform "mobile". `
86
+ + 'Pass --platform desktop to verify the desktop path instead.'
87
+ : `Platform: ${what}.`;
88
+ }
89
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/verify/platform.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
- import type { RendererObservation, SettleObservation, StateSnapshot, VerifyResult } from './classify.js';
1
+ import type { RendererObservation, SettleObservation, StateSnapshot, TouchControlsObservation, VerifyObservations, VerifyResult } from './classify.js';
2
2
  import type { EventsObservation } from './events.js';
3
+ import type { MobileParityGap } from './mobile-parity.js';
4
+ import type { VerifyPlatform } from './platform.js';
3
5
  export declare const VERIFY_RESULT_FILENAME = "result.json";
4
6
  /**
5
7
  * What `bitmagic verify` observed, written so `bitmagic publish` can gate on it.
@@ -30,6 +32,53 @@ export interface VerifyRunRecord {
30
32
  /** Semantic gameplay events (deaths, pickups, match-end) from the engine's `?eventlog=1`
31
33
  * session; null when the engine predates it. Optional for the same cross-version reason. */
32
34
  events?: EventsObservation | null;
35
+ /**
36
+ * Which platform the fields above describe — the LEAD platform, `platforms[0]` of the run.
37
+ * Absent on records written before `--platform` existed, which were all desktop.
38
+ */
39
+ platform?: VerifyPlatform;
40
+ /**
41
+ * Touch affordances the engine rendered — the direct evidence that the mobile branch ran.
42
+ * Absent on a desktop lead run, where the engine never builds MobileControls at all, so
43
+ * absent is not the same as zero.
44
+ */
45
+ touchControls?: TouchControlsObservation | null;
46
+ /**
47
+ * The engine's mobile-parity gaps, as it reported them at game start. Null means it reported
48
+ * none: a clean game, an engine too old to have the check, or a genre whose controller does
49
+ * not implement it. A property of the game's CODE rather than of the emulated device, so it is
50
+ * reported once whatever the run covered.
51
+ */
52
+ mobileParity?: MobileParityGap | null;
53
+ /**
54
+ * One entry per platform, in run order, present ONLY on a run that covered more than one.
55
+ * A single-platform record keeps exactly the shape it has always had — nothing that reads
56
+ * these records today has a new key to learn.
57
+ */
58
+ platforms?: VerifyPlatformRun[];
59
+ }
60
+ /** One platform's half of a multi-platform run. */
61
+ export interface VerifyPlatformRun {
62
+ platform: VerifyPlatform;
63
+ ok: boolean;
64
+ /** Unprefixed — the platform is the sibling key, so the prefixing the merged top-level
65
+ * `failures`/`warnings` need would only be noise here. */
66
+ failures: string[];
67
+ warnings: string[];
68
+ renderer?: RendererObservation;
69
+ retriedWithSoftwareGl?: boolean;
70
+ settle?: SettleObservation;
71
+ snapshot?: StateSnapshot | null;
72
+ events?: EventsObservation | null;
73
+ /** Touch affordances the engine rendered. Only on a mobile run. */
74
+ touchControls?: TouchControlsObservation | null;
75
+ /**
76
+ * File names, not paths: every artifact is a sibling of the `result.json` these appear in, and
77
+ * an absolute path baked into a persisted record describes the machine that wrote it rather
78
+ * than the run.
79
+ */
80
+ screenshotFile?: string;
81
+ consoleFile: string;
33
82
  }
34
83
  export declare function writeVerifyRecord(artifactDir: string, record: VerifyRunRecord): void;
35
84
  /**
@@ -50,7 +99,38 @@ export declare function readVerifyRecord(artifactDir: string): VerifyRunRecord |
50
99
  * different messages and different remedies. Inside `run()` that ordering is only observable by
51
100
  * spawning vite and a browser, which is why it went unguarded.
52
101
  */
53
- export declare function decideVerifyOutcome(result: VerifyResult, fingerprint: string, at: string, extras?: Pick<VerifyRunRecord, 'renderer' | 'retriedWithSoftwareGl' | 'settle' | 'snapshot' | 'events'>): {
102
+ export type VerifyRunExtras = Pick<VerifyRunRecord, 'renderer' | 'retriedWithSoftwareGl' | 'settle' | 'snapshot' | 'events' | 'platform' | 'touchControls' | 'mobileParity' | 'platforms'>;
103
+ /**
104
+ * One platform's finished run, ready to merge.
105
+ *
106
+ * Shared by the one-shot command and the watch loop because both assemble exactly this from
107
+ * exactly these parts, and the only interesting thing about the assembly — which observations
108
+ * belong to the platform and which to the run as a whole — is the thing that would drift if it
109
+ * were written twice.
110
+ */
111
+ export declare function toVerifyPlatformRun(platform: VerifyPlatform, result: VerifyResult, observations: Pick<VerifyObservations, 'renderer' | 'settle' | 'snapshot' | 'events' | 'touchControls' | 'mobileParity'>, retriedWithSoftwareGl: boolean, paths: {
112
+ screenshotPath: string;
113
+ consolePath: string;
114
+ }, fast: boolean): VerifyPlatformRun;
115
+ /**
116
+ * One verdict, and one set of top-level fields, from however many platforms the run covered.
117
+ *
118
+ * The prefixing rule is what keeps a `both` run readable. A finding EVERY platform produced is
119
+ * one problem, not two — a missing `Loaded world.json` is the same missing file whichever
120
+ * viewport asked for it — so it is reported once, unprefixed. A finding only some platforms
121
+ * produced is prefixed with the platform, because there the platform IS the information. A
122
+ * single-platform run therefore comes out byte-identical to what it produced before this
123
+ * existed: every finding is in every run, so nothing is prefixed.
124
+ *
125
+ * The top-level observations are the LEAD run's (`runs[0]`), which `resolveVerifyPlatforms`
126
+ * chooses as the platform the game is actually for. `platforms` is only attached for a genuine
127
+ * multi-platform run, so a single-platform record grows no keys.
128
+ */
129
+ export declare function mergeVerifyPlatformRuns(runs: VerifyPlatformRun[], mobileParity: MobileParityGap | null | undefined): {
130
+ result: VerifyResult;
131
+ extras: VerifyRunExtras;
132
+ };
133
+ export declare function decideVerifyOutcome(result: VerifyResult, fingerprint: string, at: string, extras?: VerifyRunExtras): {
54
134
  record: VerifyRunRecord;
55
135
  shouldThrow: boolean;
56
136
  };
@@ -71,7 +151,11 @@ export interface VerifyReporter {
71
151
  export declare function recordAndGateVerify(artifactDir: string, outcome: {
72
152
  record: VerifyRunRecord;
73
153
  shouldThrow: boolean;
74
- }, paths: {
75
- consolePath: string;
76
- screenshotPath?: string;
77
- }, reporter: VerifyReporter): void;
154
+ },
155
+ /**
156
+ * Every file this run actually wrote, in the order to list them. A list rather than a
157
+ * console/screenshot pair because a run can now cover two platforms and write four files, and
158
+ * because a `--fast` run captures no screenshot at all — naming a file that does not exist
159
+ * (or worse, a stale one from an earlier run) is exactly what this avoids.
160
+ */
161
+ artifacts: string[], reporter: VerifyReporter): void;