@bitmagic/cli 0.1.83-dev.1 → 0.1.83-dev.3

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 (206) hide show
  1. package/README.md +69 -0
  2. package/assets/skills/publishing-to-steam/SKILL.md +218 -0
  3. package/dist/assets/vxl-summary.d.ts +6 -20
  4. package/dist/assets/vxl-summary.js +9 -100
  5. package/dist/assets/vxl-summary.js.map +1 -1
  6. package/dist/cli.d.ts +1 -0
  7. package/dist/cli.js +2 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/check.js +5 -0
  10. package/dist/commands/check.js.map +1 -1
  11. package/dist/commands/forge.js +1 -1
  12. package/dist/commands/forge.js.map +1 -1
  13. package/dist/commands/ios.js +88 -136
  14. package/dist/commands/ios.js.map +1 -1
  15. package/dist/commands/steam-build.d.ts +67 -0
  16. package/dist/commands/steam-build.js +286 -0
  17. package/dist/commands/steam-build.js.map +1 -0
  18. package/dist/commands/steam-portal.d.ts +1 -0
  19. package/dist/commands/steam-portal.js +319 -0
  20. package/dist/commands/steam-portal.js.map +1 -0
  21. package/dist/commands/steam-upload.d.ts +37 -0
  22. package/dist/commands/steam-upload.js +174 -0
  23. package/dist/commands/steam-upload.js.map +1 -0
  24. package/dist/commands/steam.d.ts +1 -0
  25. package/dist/commands/steam.js +504 -0
  26. package/dist/commands/steam.js.map +1 -0
  27. package/dist/commands/verify.js +6 -0
  28. package/dist/commands/verify.js.map +1 -1
  29. package/dist/editor/asset-viewer.js +5 -4
  30. package/dist/editor/asset-viewer.js.map +1 -1
  31. package/dist/editor/server.js +1 -1
  32. package/dist/editor/server.js.map +1 -1
  33. package/dist/editor/shell-page.js +3 -3
  34. package/dist/forge/browser-host.d.ts +1 -1
  35. package/dist/forge/browser-host.js +1 -1
  36. package/dist/forge/browser-host.js.map +1 -1
  37. package/dist/ios/native-overrides.d.ts +27 -0
  38. package/dist/ios/native-overrides.js +87 -0
  39. package/dist/ios/native-overrides.js.map +1 -0
  40. package/dist/ios/record-validate.d.ts +1 -33
  41. package/dist/ios/record-validate.js +2 -100
  42. package/dist/ios/record-validate.js.map +1 -1
  43. package/dist/ios/store-bridge.d.ts +26 -0
  44. package/dist/ios/store-bridge.js +224 -0
  45. package/dist/ios/store-bridge.js.map +1 -0
  46. package/dist/ios/storekit-config.d.ts +15 -0
  47. package/dist/ios/storekit-config.js +106 -0
  48. package/dist/ios/storekit-config.js.map +1 -0
  49. package/dist/ios/xcode-template.d.ts +12 -3
  50. package/dist/ios/xcode-template.js +158 -27
  51. package/dist/ios/xcode-template.js.map +1 -1
  52. package/dist/package/collect-urls.d.ts +1 -1
  53. package/dist/package/collect-urls.js +10 -2
  54. package/dist/package/collect-urls.js.map +1 -1
  55. package/dist/package/offline-payload.d.ts +35 -0
  56. package/dist/package/offline-payload.js +142 -0
  57. package/dist/package/offline-payload.js.map +1 -0
  58. package/dist/package/packaged-check.d.ts +39 -0
  59. package/dist/package/packaged-check.js +104 -0
  60. package/dist/package/packaged-check.js.map +1 -0
  61. package/dist/package/transform.d.ts +4 -4
  62. package/dist/package/transform.js +3 -3
  63. package/dist/project/published-three.d.ts +14 -0
  64. package/dist/project/published-three.js +91 -0
  65. package/dist/project/published-three.js.map +1 -0
  66. package/dist/publish/bundle.js +6 -0
  67. package/dist/publish/bundle.js.map +1 -1
  68. package/dist/scaffold/project-files.js +7 -4
  69. package/dist/scaffold/project-files.js.map +1 -1
  70. package/dist/scaffold/project.js +2 -0
  71. package/dist/scaffold/project.js.map +1 -1
  72. package/dist/scaffold/skill-files.js +3 -1
  73. package/dist/scaffold/skill-files.js.map +1 -1
  74. package/dist/scaffold/steam-skill-files.d.ts +2 -0
  75. package/dist/scaffold/steam-skill-files.js +7 -0
  76. package/dist/scaffold/steam-skill-files.js.map +1 -0
  77. package/dist/steam/art-generate.d.ts +55 -0
  78. package/dist/steam/art-generate.js +174 -0
  79. package/dist/steam/art-generate.js.map +1 -0
  80. package/dist/steam/art-render.d.ts +25 -0
  81. package/dist/steam/art-render.js +157 -0
  82. package/dist/steam/art-render.js.map +1 -0
  83. package/dist/steam/art-use.d.ts +20 -0
  84. package/dist/steam/art-use.js +84 -0
  85. package/dist/steam/art-use.js.map +1 -0
  86. package/dist/steam/art.d.ts +98 -0
  87. package/dist/steam/art.js +243 -0
  88. package/dist/steam/art.js.map +1 -0
  89. package/dist/steam/check.d.ts +21 -0
  90. package/dist/steam/check.js +113 -0
  91. package/dist/steam/check.js.map +1 -0
  92. package/dist/steam/config.d.ts +57 -0
  93. package/dist/steam/config.js +145 -0
  94. package/dist/steam/config.js.map +1 -0
  95. package/dist/steam/electron-cache.d.ts +11 -0
  96. package/dist/steam/electron-cache.js +105 -0
  97. package/dist/steam/electron-cache.js.map +1 -0
  98. package/dist/steam/fuses.d.ts +16 -0
  99. package/dist/steam/fuses.js +75 -0
  100. package/dist/steam/fuses.js.map +1 -0
  101. package/dist/steam/icon-files.d.ts +16 -0
  102. package/dist/steam/icon-files.js +70 -0
  103. package/dist/steam/icon-files.js.map +1 -0
  104. package/dist/steam/image-info.d.ts +16 -0
  105. package/dist/steam/image-info.js +89 -0
  106. package/dist/steam/image-info.js.map +1 -0
  107. package/dist/steam/launch-check.d.ts +12 -0
  108. package/dist/steam/launch-check.js +101 -0
  109. package/dist/steam/launch-check.js.map +1 -0
  110. package/dist/steam/media.d.ts +48 -0
  111. package/dist/steam/media.js +155 -0
  112. package/dist/steam/media.js.map +1 -0
  113. package/dist/steam/package-macos.d.ts +52 -0
  114. package/dist/steam/package-macos.js +268 -0
  115. package/dist/steam/package-macos.js.map +1 -0
  116. package/dist/steam/package-windows.d.ts +23 -0
  117. package/dist/steam/package-windows.js +83 -0
  118. package/dist/steam/package-windows.js.map +1 -0
  119. package/dist/steam/paths.d.ts +35 -0
  120. package/dist/steam/paths.js +44 -0
  121. package/dist/steam/paths.js.map +1 -0
  122. package/dist/steam/playtest-shots.d.ts +18 -0
  123. package/dist/steam/playtest-shots.js +83 -0
  124. package/dist/steam/playtest-shots.js.map +1 -0
  125. package/dist/steam/portal-recipes.d.ts +25 -0
  126. package/dist/steam/portal-recipes.js +61 -0
  127. package/dist/steam/portal-recipes.js.map +1 -0
  128. package/dist/steam/portal.d.ts +58 -0
  129. package/dist/steam/portal.js +340 -0
  130. package/dist/steam/portal.js.map +1 -0
  131. package/dist/steam/raster.d.ts +58 -0
  132. package/dist/steam/raster.js +285 -0
  133. package/dist/steam/raster.js.map +1 -0
  134. package/dist/steam/shell-files.d.ts +17 -0
  135. package/dist/steam/shell-files.js +268 -0
  136. package/dist/steam/shell-files.js.map +1 -0
  137. package/dist/steam/specs.d.ts +61 -0
  138. package/dist/steam/specs.js +103 -0
  139. package/dist/steam/specs.js.map +1 -0
  140. package/dist/steam/steampipe.d.ts +46 -0
  141. package/dist/steam/steampipe.js +170 -0
  142. package/dist/steam/steampipe.js.map +1 -0
  143. package/dist/steam/store-copy.d.ts +88 -0
  144. package/dist/steam/store-copy.js +214 -0
  145. package/dist/steam/store-copy.js.map +1 -0
  146. package/dist/steam/tags.d.ts +9 -0
  147. package/dist/steam/tags.js +52 -0
  148. package/dist/steam/tags.js.map +1 -0
  149. package/dist/steam/trailer.d.ts +34 -0
  150. package/dist/steam/trailer.js +141 -0
  151. package/dist/steam/trailer.js.map +1 -0
  152. package/dist/theme/kernel.d.ts +14 -0
  153. package/dist/theme/kernel.js +15 -0
  154. package/dist/theme/kernel.js.map +1 -0
  155. package/dist/theme/theme-io.js +11 -16
  156. package/dist/theme/theme-io.js.map +1 -1
  157. package/dist/vendor/hud-theme/catalog.js +71 -0
  158. package/dist/vendor/hud-theme/engine/hud/ThemeTokens.js +115 -0
  159. package/dist/{theme/theme-color-math.js → vendor/hud-theme/engine/hud/colorMath.js} +13 -14
  160. package/dist/vendor/hud-theme/engine/hud/decorations.js +179 -0
  161. package/dist/vendor/hud-theme/engine/hud/fonts.js +147 -0
  162. package/dist/vendor/hud-theme/engine/hud/presets.js +80 -0
  163. package/dist/vendor/hud-theme/engine/hud/resolveWorldTheme.js +66 -0
  164. package/dist/vendor/hud-theme/engine/hud/themes/bitmagic.js +29 -0
  165. package/dist/vendor/hud-theme/engine/hud/themes/nexus.js +69 -0
  166. package/dist/vendor/hud-theme/engine/hud/themes/racing.js +57 -0
  167. package/dist/vendor/hud-theme/engine/hud/themes/rift-raider.js +63 -0
  168. package/dist/vendor/hud-theme/engine/hud/themes/simulator.js +55 -0
  169. package/dist/vendor/hud-theme/engine/hud/themes/village-keep.js +108 -0
  170. package/dist/vendor/hud-theme/engine/hud/validateTheme.js +535 -0
  171. package/dist/vendor/hud-theme/index.js +26 -0
  172. package/dist/{theme/theme-merge.js → vendor/hud-theme/merge.js} +0 -1
  173. package/dist/{theme/theme-report.js → vendor/hud-theme/report.js} +8 -9
  174. package/dist/{theme/theme-stored.js → vendor/hud-theme/stored.js} +6 -9
  175. package/dist/{theme/theme-validate.js → vendor/hud-theme/validate-strict.js} +102 -164
  176. package/dist/verify/browser.d.ts +11 -0
  177. package/dist/verify/browser.js +222 -155
  178. package/dist/verify/browser.js.map +1 -1
  179. package/dist/verify/categories.d.ts +1 -1
  180. package/dist/verify/categories.js +1 -0
  181. package/dist/verify/categories.js.map +1 -1
  182. package/dist/verify/classify.d.ts +14 -2
  183. package/dist/verify/classify.js +23 -4
  184. package/dist/verify/classify.js.map +1 -1
  185. package/dist/verify/hud-theme.d.ts +19 -14
  186. package/dist/verify/hud-theme.js +23 -17
  187. package/dist/verify/hud-theme.js.map +1 -1
  188. package/dist/verify/mobile-parity.d.ts +1 -1
  189. package/dist/verify/mobile-parity.js +1 -1
  190. package/dist/verify/start.d.ts +3 -2
  191. package/dist/verify/start.js +1 -1
  192. package/dist/verify/start.js.map +1 -1
  193. package/package.json +8 -6
  194. package/dist/theme/hud-theme-catalog.d.ts +0 -235
  195. package/dist/theme/hud-theme-catalog.js +0 -389
  196. package/dist/theme/hud-theme-catalog.js.map +0 -1
  197. package/dist/theme/theme-color-math.d.ts +0 -21
  198. package/dist/theme/theme-color-math.js.map +0 -1
  199. package/dist/theme/theme-merge.d.ts +0 -36
  200. package/dist/theme/theme-merge.js.map +0 -1
  201. package/dist/theme/theme-report.d.ts +0 -28
  202. package/dist/theme/theme-report.js.map +0 -1
  203. package/dist/theme/theme-stored.d.ts +0 -21
  204. package/dist/theme/theme-stored.js.map +0 -1
  205. package/dist/theme/theme-validate.d.ts +0 -41
  206. package/dist/theme/theme-validate.js.map +0 -1
@@ -1,77 +1,37 @@
1
1
  /**
2
2
  * The strict write-time HUD theme validator.
3
3
  *
4
- * STRICTER than the engine's load-time validator on purpose, and that asymmetry
5
- * is the design: at WRITE time a bad per-element leaf is an error the agent can
6
- * fix in the same turn, while at LOAD time the engine must keep rendering
7
- * already-published themes, so it warns and drops the leaf instead. Everything
8
- * this validator accepts, the engine accepts; the reverse is not required.
4
+ * STRICTER than the engine's load-time validator (`validateAndResolveTheme`) on
5
+ * purpose, and that asymmetry is the design: at WRITE time a bad per-element leaf
6
+ * is an error the agent can fix in the same turn, while at LOAD time the engine
7
+ * must keep rendering already-published themes, so it warns and drops the leaf
8
+ * instead. Everything this validator accepts, the engine accepts; the reverse is
9
+ * not required. This package's tests hold that rule over a fixture corpus.
9
10
  *
10
- * This replaced `lightValidateTheme`, which skipped glow keys, every numeric
11
- * range, the whole `elements` block, decoration slot suitability and decoration
12
- * size/position/repeat — six ways a theme could pass the write tool and then
13
- * silently render as the DEFAULT look at engine load, which reads as "theming
14
- * is broken" rather than "the value was wrong". Parity with the engine is
15
- * enforced by scripts/check-hud-catalog.ts, which runs both validators over a
16
- * fixture corpus under tsx and compares verdicts.
17
- *
18
- * Validation rules mirror game/src/engine/hud/validateTheme.ts; vocabulary
19
- * comes from the drift-checked catalog next door.
11
+ * Both modes read the same vocabulary and ranges from the engine's ThemeTokens.ts
12
+ * and the same catalogues (fonts, decorations), and share the engine's
13
+ * `suggestClosest`, so they cannot disagree about what a key, a range or a
14
+ * "Closest match" is — only about how loudly to fail.
20
15
  */
21
- import { HUD_DECORATIONS, HUD_DECORATION_SLOTS, HUD_ELEMENT_CLASSES, HUD_FONTS, HUD_GLOW_KEYS, HUD_ICON_KEYS, } from './hud-theme-catalog.js';
22
- const HEX_COLOR = /^#[0-9a-fA-F]{6}$/;
23
- // Mirrors the engine's TILE_SIZE: "<n>px|% <n>px|%", decimals allowed.
24
- const TILE_SIZE = /^\d{1,4}(?:\.\d{1,2})?(?:px|%) \d{1,4}(?:\.\d{1,2})?(?:px|%)$/;
25
- const POSITION_VALUES = [
26
- 'top', 'top-right', 'right', 'bottom-right', 'bottom', 'bottom-left', 'left', 'top-left', 'center',
27
- ];
28
- const REPEAT_VALUES = ['no-repeat', 'repeat', 'repeat-x', 'repeat-y'];
29
- const BORDER_REPEAT_VALUES = ['stretch', 'repeat', 'round', 'space'];
30
- const COLOR_KEYS = [
31
- 'primary', 'danger', 'warning', 'success', 'background', 'surface', 'text', 'textMuted', 'outline',
32
- ];
33
- const REQUIRED_COLOR_KEYS = ['primary', 'danger', 'background', 'surface', 'text'];
34
- // Levenshtein, matching the engine: suggest only when the distance is small
35
- // relative to the input. A wild guess gets the full candidate list from the
36
- // error message, never false confidence in one arbitrary name.
37
- function editDistance(a, b) {
38
- let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
39
- for (let i = 1; i <= a.length; i++) {
40
- const cur = [i];
41
- for (let j = 1; j <= b.length; j++) {
42
- const deletion = (prev[j] ?? 0) + 1;
43
- const insertion = (cur[j - 1] ?? 0) + 1;
44
- const substitution = (prev[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1);
45
- cur[j] = Math.min(deletion, insertion, substitution);
46
- }
47
- prev = cur;
48
- }
49
- return prev[b.length] ?? 0;
50
- }
51
- export function suggestClosest(value, candidates) {
52
- const v = value.toLowerCase();
53
- let best;
54
- let bestScore = Infinity;
55
- for (const c of candidates) {
56
- const score = editDistance(v, c.toLowerCase());
57
- if (score < bestScore) {
58
- bestScore = score;
59
- best = c;
60
- }
61
- }
62
- const maxDistance = Math.max(2, Math.ceil(v.length / 3));
63
- return bestScore <= maxDistance ? best : undefined;
64
- }
16
+ import { BACKGROUND_POSITIONS, BACKGROUND_REPEATS, BACKGROUND_SIZE_KEYWORDS, BACKGROUND_TILE_SIZE, BORDER_IMAGE_REPEATS, DECORATION_SLOT_KINDS, FONT_BODY_WEIGHTS, FONT_HEADING_WEIGHTS, GLOW_KEYS, HUD_ELEMENT_CLASSES, IMAGE_RENDERINGS, LABEL_CASES, MAX_INLINE_SVG_LENGTH, REQUIRED_THEME_COLOR_KEYS, THEME_COLOR_KEYS, THEME_ICON_KEYS, THEME_NUMBER_RANGES, isHexColor, } from './engine/hud/ThemeTokens.js';
17
+ import { HUD_FONTS } from './engine/hud/fonts.js';
18
+ import { HUD_DECORATIONS } from './engine/hud/decorations.js';
19
+ import { suggestClosest } from './engine/hud/validateTheme.js';
20
+ export { suggestClosest };
65
21
  function isObject(v) {
66
22
  return typeof v === 'object' && v !== null && !Array.isArray(v);
67
23
  }
68
- function checkNumber(ctx, path, value, min, max) {
24
+ function includes(list, value) {
25
+ return list.includes(value);
26
+ }
27
+ function checkNumber(ctx, path, value, token) {
28
+ const [min, max] = THEME_NUMBER_RANGES[token];
69
29
  if (typeof value !== 'number' || !Number.isFinite(value) || value < min || value > max) {
70
30
  ctx.errors.push({ path, message: `${JSON.stringify(value)} is not a number in [${min}, ${max}]` });
71
31
  }
72
32
  }
73
33
  function checkHexAt(ctx, path, value) {
74
- if (typeof value !== 'string' || !HEX_COLOR.test(value)) {
34
+ if (!isHexColor(value)) {
75
35
  ctx.errors.push({
76
36
  path,
77
37
  message: `${JSON.stringify(value)} is not a 6-digit hex color`,
@@ -89,6 +49,14 @@ function checkKeyed(ctx, path, value, allowed) {
89
49
  ...(close ? { suggestion: `Closest match: "${close}"` } : {}),
90
50
  });
91
51
  }
52
+ function unknownKey(ctx, path, message, value, known) {
53
+ const close = suggestClosest(value, known);
54
+ ctx.errors.push({
55
+ path,
56
+ message,
57
+ suggestion: close ? `Closest match: "${close}". Known: ${known.join(', ')}` : `Known: ${known.join(', ')}`,
58
+ });
59
+ }
92
60
  function checkFontBlock(ctx, path, raw, required) {
93
61
  if (raw === undefined) {
94
62
  if (required)
@@ -119,15 +87,11 @@ function checkFontBlock(ctx, path, raw, required) {
119
87
  // corrected object is what gets written, and the warning explains the
120
88
  // swap so the agent can mention it to the user.
121
89
  //
122
- // Only a VALID case value is eligible: the auto-correct used to run
123
- // first and mutate any string (including "lower"/"uppercase") to
124
- // suitsCase[0], so the enum check below then saw the corrected value and
125
- // the kit passed with a warning where the engine hard-rejects the whole
126
- // theme — kit-ok/engine-reject, the exact parity direction the gate
127
- // exists to forbid.
90
+ // Only a VALID case value is eligible: correcting any string (including
91
+ // "lower"/"uppercase") would let the enum check below see the corrected
92
+ // value and pass a theme the engine hard-rejects.
128
93
  const entry = HUD_FONTS[raw.key];
129
- if ((raw.case === 'upper' || raw.case === 'normal')
130
- && !entry.suitsCase.includes(raw.case)) {
94
+ if (includes(LABEL_CASES, raw.case) && !includes(entry.suitsCase, raw.case)) {
131
95
  const originalCase = raw.case;
132
96
  const correctedCase = entry.suitsCase[0];
133
97
  raw.case = correctedCase;
@@ -138,21 +102,21 @@ function checkFontBlock(ctx, path, raw, required) {
138
102
  });
139
103
  }
140
104
  }
141
- if (raw.case !== undefined && raw.case !== 'upper' && raw.case !== 'normal') {
105
+ if (raw.case !== undefined && !includes(LABEL_CASES, raw.case)) {
142
106
  ctx.errors.push({ path: `${path}.case`, message: `${JSON.stringify(raw.case)} is not "upper" or "normal"` });
143
107
  }
144
- if (raw.weightBody !== undefined && ![300, 400, 500, 700].includes(raw.weightBody)) {
145
- ctx.errors.push({ path: `${path}.weightBody`, message: `${JSON.stringify(raw.weightBody)} is not one of 300, 400, 500, 700` });
108
+ if (raw.weightBody !== undefined && !includes(FONT_BODY_WEIGHTS, raw.weightBody)) {
109
+ ctx.errors.push({ path: `${path}.weightBody`, message: `${JSON.stringify(raw.weightBody)} is not one of ${FONT_BODY_WEIGHTS.join(', ')}` });
146
110
  }
147
- if (raw.weightHeading !== undefined && ![400, 500, 700, 900].includes(raw.weightHeading)) {
148
- ctx.errors.push({ path: `${path}.weightHeading`, message: `${JSON.stringify(raw.weightHeading)} is not one of 400, 500, 700, 900` });
111
+ if (raw.weightHeading !== undefined && !includes(FONT_HEADING_WEIGHTS, raw.weightHeading)) {
112
+ ctx.errors.push({ path: `${path}.weightHeading`, message: `${JSON.stringify(raw.weightHeading)} is not one of ${FONT_HEADING_WEIGHTS.join(', ')}` });
149
113
  }
150
114
  if (raw.trackingLabel !== undefined)
151
- checkNumber(ctx, `${path}.trackingLabel`, raw.trackingLabel, 0, 4);
115
+ checkNumber(ctx, `${path}.trackingLabel`, raw.trackingLabel, 'trackingLabel');
152
116
  if (raw.outlineWidth !== undefined)
153
- checkNumber(ctx, `${path}.outlineWidth`, raw.outlineWidth, 0, 3);
117
+ checkNumber(ctx, `${path}.outlineWidth`, raw.outlineWidth, 'outlineWidth');
154
118
  if (raw.sizeScale !== undefined)
155
- checkNumber(ctx, `${path}.sizeScale`, raw.sizeScale, 0.85, 1.3);
119
+ checkNumber(ctx, `${path}.sizeScale`, raw.sizeScale, 'sizeScale');
156
120
  }
157
121
  function checkColorsBlock(ctx, path, raw, required) {
158
122
  if (raw === undefined) {
@@ -165,20 +129,15 @@ function checkColorsBlock(ctx, path, raw, required) {
165
129
  return;
166
130
  }
167
131
  if (required) {
168
- for (const key of REQUIRED_COLOR_KEYS) {
132
+ for (const key of REQUIRED_THEME_COLOR_KEYS) {
169
133
  if (raw[key] === undefined) {
170
134
  ctx.errors.push({ path: `${path}.${key}`, message: `colors.${key} is required (hex string)` });
171
135
  }
172
136
  }
173
137
  }
174
138
  for (const key of Object.keys(raw)) {
175
- if (!COLOR_KEYS.includes(key)) {
176
- const close = suggestClosest(key, COLOR_KEYS);
177
- ctx.errors.push({
178
- path: `${path}.${key}`,
179
- message: `unknown color token "${key}"`,
180
- suggestion: close ? `Closest match: "${close}". Known: ${COLOR_KEYS.join(', ')}` : `Known: ${COLOR_KEYS.join(', ')}`,
181
- });
139
+ if (!includes(THEME_COLOR_KEYS, key)) {
140
+ unknownKey(ctx, `${path}.${key}`, `unknown color token "${key}"`, key, THEME_COLOR_KEYS);
182
141
  continue;
183
142
  }
184
143
  checkHexAt(ctx, `${path}.${key}`, raw[key]);
@@ -191,21 +150,15 @@ function checkShapeBlock(ctx, path, raw) {
191
150
  ctx.errors.push({ path, message: 'shape must be an object' });
192
151
  return;
193
152
  }
194
- if (raw.radiusPill !== undefined)
195
- checkNumber(ctx, `${path}.radiusPill`, raw.radiusPill, 0, 500);
196
- if (raw.radiusCard !== undefined)
197
- checkNumber(ctx, `${path}.radiusCard`, raw.radiusCard, 0, 32);
198
- if (raw.borderWidth !== undefined)
199
- checkNumber(ctx, `${path}.borderWidth`, raw.borderWidth, 0, 6);
200
- if (raw.bevel !== undefined)
201
- checkNumber(ctx, `${path}.bevel`, raw.bevel, 0, 1);
202
- if (raw.gloss !== undefined)
203
- checkNumber(ctx, `${path}.gloss`, raw.gloss, 0, 1);
153
+ for (const token of ['radiusPill', 'radiusCard', 'borderWidth', 'bevel', 'gloss']) {
154
+ if (raw[token] !== undefined)
155
+ checkNumber(ctx, `${path}.${token}`, raw[token], token);
156
+ }
204
157
  if (raw.glow !== undefined)
205
- checkKeyed(ctx, `${path}.glow`, raw.glow, HUD_GLOW_KEYS);
158
+ checkKeyed(ctx, `${path}.glow`, raw.glow, GLOW_KEYS);
206
159
  if (raw.glowColor !== undefined)
207
160
  checkHexAt(ctx, `${path}.glowColor`, raw.glowColor);
208
- if (raw.imageRendering !== undefined && raw.imageRendering !== 'auto' && raw.imageRendering !== 'pixelated') {
161
+ if (raw.imageRendering !== undefined && !includes(IMAGE_RENDERINGS, raw.imageRendering)) {
209
162
  ctx.errors.push({ path: `${path}.imageRendering`, message: `${JSON.stringify(raw.imageRendering)} is not "auto" or "pixelated"` });
210
163
  }
211
164
  }
@@ -226,7 +179,7 @@ function checkDecorationValue(ctx, path, slotName, value) {
226
179
  // Suitability is a WARNING, matching the engine: an unsuited pairing
227
180
  // renders, it just usually looks wrong.
228
181
  const entry = HUD_DECORATIONS[value];
229
- if (!entry.suitsSlots.includes(slotName)) {
182
+ if (!includes(entry.suitsSlots, slotName)) {
230
183
  ctx.warnings.push({
231
184
  path,
232
185
  message: `decoration "${value}" isn't designed for slot "${slotName}" (suits: ${entry.suitsSlots.join(', ')})`,
@@ -239,18 +192,18 @@ function checkDecorationValue(ctx, path, slotName, value) {
239
192
  ctx.errors.push({ path: `${path}.svg`, message: 'inline decoration must have an svg string' });
240
193
  return;
241
194
  }
242
- if (value.svg.length > 16384) {
195
+ if (value.svg.length > MAX_INLINE_SVG_LENGTH) {
243
196
  ctx.errors.push({ path: `${path}.svg`, message: 'inline SVG exceeds 16kb limit' });
244
197
  }
245
198
  if (slotName === 'borderImage') {
246
199
  if (value.borderSlice !== undefined)
247
- checkNumber(ctx, `${path}.borderSlice`, value.borderSlice, 0, 200);
200
+ checkNumber(ctx, `${path}.borderSlice`, value.borderSlice, 'borderSlice');
248
201
  if (value.repeat !== undefined)
249
- checkKeyed(ctx, `${path}.repeat`, value.repeat, BORDER_REPEAT_VALUES);
202
+ checkKeyed(ctx, `${path}.repeat`, value.repeat, BORDER_IMAGE_REPEATS);
250
203
  }
251
204
  else {
252
- if (value.size !== undefined && value.size !== 'cover' && value.size !== 'contain' && value.size !== 'auto'
253
- && !(typeof value.size === 'string' && TILE_SIZE.test(value.size))) {
205
+ if (value.size !== undefined && !includes(BACKGROUND_SIZE_KEYWORDS, value.size)
206
+ && !(typeof value.size === 'string' && BACKGROUND_TILE_SIZE.test(value.size))) {
254
207
  ctx.errors.push({
255
208
  path: `${path}.size`,
256
209
  message: `${JSON.stringify(value.size)} is not "cover", "contain", "auto", or an explicit tile size`,
@@ -258,14 +211,20 @@ function checkDecorationValue(ctx, path, slotName, value) {
258
211
  });
259
212
  }
260
213
  if (value.position !== undefined)
261
- checkKeyed(ctx, `${path}.position`, value.position, POSITION_VALUES);
214
+ checkKeyed(ctx, `${path}.position`, value.position, BACKGROUND_POSITIONS);
262
215
  if (value.repeat !== undefined)
263
- checkKeyed(ctx, `${path}.repeat`, value.repeat, REPEAT_VALUES);
216
+ checkKeyed(ctx, `${path}.repeat`, value.repeat, BACKGROUND_REPEATS);
264
217
  }
265
218
  return;
266
219
  }
267
220
  ctx.errors.push({ path, message: 'expected curated decoration key string or inline SVG object' });
268
221
  }
222
+ function checkElementClassKey(ctx, path, elementClass) {
223
+ if (includes(HUD_ELEMENT_CLASSES, elementClass))
224
+ return true;
225
+ unknownKey(ctx, `${path}.${elementClass}`, `unknown HUD element class "${elementClass}"`, elementClass, HUD_ELEMENT_CLASSES);
226
+ return false;
227
+ }
269
228
  function checkDecorationsBlock(ctx, path, raw) {
270
229
  if (raw === undefined || raw === null)
271
230
  return;
@@ -274,30 +233,14 @@ function checkDecorationsBlock(ctx, path, raw) {
274
233
  return;
275
234
  }
276
235
  for (const elementClass of Object.keys(raw)) {
277
- if (!HUD_ELEMENT_CLASSES.includes(elementClass)) {
278
- const close = suggestClosest(elementClass, HUD_ELEMENT_CLASSES);
279
- ctx.errors.push({
280
- path: `${path}.${elementClass}`,
281
- message: `unknown HUD element class "${elementClass}"`,
282
- suggestion: close
283
- ? `Closest match: "${close}". Known: ${HUD_ELEMENT_CLASSES.join(', ')}`
284
- : `Known: ${HUD_ELEMENT_CLASSES.join(', ')}`,
285
- });
236
+ if (!checkElementClassKey(ctx, path, elementClass))
286
237
  continue;
287
- }
288
238
  const slots = raw[elementClass];
289
239
  if (!isObject(slots))
290
240
  continue;
291
241
  for (const slotName of Object.keys(slots)) {
292
- if (!HUD_DECORATION_SLOTS.includes(slotName)) {
293
- const close = suggestClosest(slotName, HUD_DECORATION_SLOTS);
294
- ctx.errors.push({
295
- path: `${path}.${elementClass}.${slotName}`,
296
- message: `unknown slot "${slotName}"`,
297
- suggestion: close
298
- ? `Closest match: "${close}". Known: ${HUD_DECORATION_SLOTS.join(', ')}`
299
- : `Known: ${HUD_DECORATION_SLOTS.join(', ')}`,
300
- });
242
+ if (!includes(DECORATION_SLOT_KINDS, slotName)) {
243
+ unknownKey(ctx, `${path}.${elementClass}.${slotName}`, `unknown slot "${slotName}"`, slotName, DECORATION_SLOT_KINDS);
301
244
  continue;
302
245
  }
303
246
  checkDecorationValue(ctx, `${path}.${elementClass}.${slotName}`, slotName, slots[slotName]);
@@ -312,17 +255,8 @@ function checkElementsBlock(ctx, path, raw) {
312
255
  return;
313
256
  }
314
257
  for (const elementClass of Object.keys(raw)) {
315
- if (!HUD_ELEMENT_CLASSES.includes(elementClass)) {
316
- const close = suggestClosest(elementClass, HUD_ELEMENT_CLASSES);
317
- ctx.errors.push({
318
- path: `${path}.${elementClass}`,
319
- message: `unknown HUD element class "${elementClass}"`,
320
- suggestion: close
321
- ? `Closest match: "${close}". Known: ${HUD_ELEMENT_CLASSES.join(', ')}`
322
- : `Known: ${HUD_ELEMENT_CLASSES.join(', ')}`,
323
- });
258
+ if (!checkElementClassKey(ctx, path, elementClass))
324
259
  continue;
325
- }
326
260
  const override = raw[elementClass];
327
261
  if (!isObject(override)) {
328
262
  ctx.errors.push({ path: `${path}.${elementClass}`, message: 'expected a partial theme object' });
@@ -339,6 +273,34 @@ function checkElementsBlock(ctx, path, raw) {
339
273
  checkDecorationsBlock(ctx, `${path}.${elementClass}.decorations`, override.decorations);
340
274
  }
341
275
  }
276
+ function checkIconsBlock(ctx, raw) {
277
+ if (raw === undefined || raw === null)
278
+ return;
279
+ if (!isObject(raw)) {
280
+ ctx.errors.push({ path: 'icons', message: `icons must be an object keyed by slot name (${THEME_ICON_KEYS.join(', ')})` });
281
+ return;
282
+ }
283
+ for (const key of Object.keys(raw)) {
284
+ if (!includes(THEME_ICON_KEYS, key)) {
285
+ ctx.errors.push({
286
+ path: `icons.${key}`,
287
+ message: `unknown icon slot "${key}"`,
288
+ suggestion: `Known: ${THEME_ICON_KEYS.join(', ')}`,
289
+ });
290
+ continue;
291
+ }
292
+ const value = raw[key];
293
+ if (value === undefined || value === null)
294
+ continue;
295
+ if (typeof value !== 'string') {
296
+ ctx.errors.push({ path: `icons.${key}`, message: 'icon SVG must be a string' });
297
+ continue;
298
+ }
299
+ if (value.length > MAX_INLINE_SVG_LENGTH) {
300
+ ctx.errors.push({ path: `icons.${key}`, message: 'icon SVG exceeds 16kb limit' });
301
+ }
302
+ }
303
+ }
342
304
  /**
343
305
  * Whether an issue would make the ENGINE reject the whole theme, or only drop a
344
306
  * leaf while the theme keeps rendering.
@@ -358,6 +320,11 @@ export function isEngineFatalIssue(issue) {
358
320
  return false;
359
321
  return true;
360
322
  }
323
+ /**
324
+ * Validate a whole inline theme at write time. Mutates `theme` in one place: a
325
+ * font case the font is not designed for is auto-corrected (with a warning), and
326
+ * the corrected object is what the caller writes.
327
+ */
361
328
  export function validateThemeStrict(theme) {
362
329
  const ctx = { errors: [], warnings: [] };
363
330
  if (!isObject(theme)) {
@@ -372,35 +339,6 @@ export function validateThemeStrict(theme) {
372
339
  checkShapeBlock(ctx, 'shape', theme.shape);
373
340
  checkDecorationsBlock(ctx, 'decorations', theme.decorations);
374
341
  checkElementsBlock(ctx, 'elements', theme.elements);
375
- // icons
376
- const icons = theme.icons;
377
- if (icons !== undefined && icons !== null) {
378
- if (!isObject(icons)) {
379
- ctx.errors.push({ path: 'icons', message: 'icons must be an object keyed by slot name (heart, mouse, arrow)' });
380
- }
381
- else {
382
- for (const key of Object.keys(icons)) {
383
- if (!HUD_ICON_KEYS.includes(key)) {
384
- ctx.errors.push({
385
- path: `icons.${key}`,
386
- message: `unknown icon slot "${key}"`,
387
- suggestion: `Known: ${HUD_ICON_KEYS.join(', ')}`,
388
- });
389
- continue;
390
- }
391
- const value = icons[key];
392
- if (value === undefined || value === null)
393
- continue;
394
- if (typeof value !== 'string') {
395
- ctx.errors.push({ path: `icons.${key}`, message: 'icon SVG must be a string' });
396
- continue;
397
- }
398
- if (value.length > 16384) {
399
- ctx.errors.push({ path: `icons.${key}`, message: 'icon SVG exceeds 16kb limit' });
400
- }
401
- }
402
- }
403
- }
342
+ checkIconsBlock(ctx, theme.icons);
404
343
  return { ok: ctx.errors.length === 0, errors: ctx.errors, warnings: ctx.warnings };
405
344
  }
406
- //# sourceMappingURL=theme-validate.js.map
@@ -102,3 +102,14 @@ export declare class VerifyBrowserSession {
102
102
  close(): Promise<void>;
103
103
  check(options: HeadlessCheckOptions): Promise<VerifyObservations>;
104
104
  }
105
+ /**
106
+ * One check on a page that already exists: arm the probes, `navigate` (a `goto` for verify, a
107
+ * `reload` for a packaged desktop app that loaded its own page before anything could attach),
108
+ * start the game the way a player does, settle, and observe. Split from
109
+ * {@link VerifyBrowserSession.check} so `bitmagic steam build` can hold the real Electron app to
110
+ * the same verdict a browser tab gets.
111
+ */
112
+ export declare function observeGamePage(page: Page, options: HeadlessCheckOptions, viewport: {
113
+ width: number;
114
+ height: number;
115
+ }, navigate: (page: Page) => Promise<unknown>): Promise<VerifyObservations>;