@bitmagic/cli 0.1.64-dev.6 → 0.1.64-dev.8

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 (67) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/forge.js +70 -11
  3. package/dist/commands/forge.js.map +1 -1
  4. package/dist/commands/levels.d.ts +1 -1
  5. package/dist/commands/levels.js +14 -3
  6. package/dist/commands/levels.js.map +1 -1
  7. package/dist/commands/trailer.js +18 -4
  8. package/dist/commands/trailer.js.map +1 -1
  9. package/dist/editor/hud-replay-page.js +317 -22
  10. package/dist/editor/hud-replay-page.js.map +1 -1
  11. package/dist/forge/apply-modifications.js +70 -2
  12. package/dist/forge/apply-modifications.js.map +1 -1
  13. package/dist/forge/progress.d.ts +10 -0
  14. package/dist/forge/progress.js +7 -6
  15. package/dist/forge/progress.js.map +1 -1
  16. package/dist/forge/run-pipeline.d.ts +8 -0
  17. package/dist/forge/run-pipeline.js +37 -2
  18. package/dist/forge/run-pipeline.js.map +1 -1
  19. package/dist/forge/stream.d.ts +7 -1
  20. package/dist/forge/stream.js +5 -2
  21. package/dist/forge/stream.js.map +1 -1
  22. package/dist/forge/two-d-gate.d.ts +72 -0
  23. package/dist/forge/two-d-gate.js +198 -0
  24. package/dist/forge/two-d-gate.js.map +1 -0
  25. package/dist/publish/bundle.d.ts +12 -0
  26. package/dist/publish/bundle.js +32 -0
  27. package/dist/publish/bundle.js.map +1 -1
  28. package/dist/render/qr-png.d.ts +6 -3
  29. package/dist/render/qr-png.js +8 -8
  30. package/dist/render/qr-png.js.map +1 -1
  31. package/dist/scaffold/project-files.js +71 -6
  32. package/dist/scaffold/project-files.js.map +1 -1
  33. package/dist/scaffold/skill-files.js +1 -0
  34. package/dist/scaffold/skill-files.js.map +1 -1
  35. package/dist/trailer/hud-raster.d.ts +12 -12
  36. package/dist/trailer/hud-raster.js +85 -32
  37. package/dist/trailer/hud-raster.js.map +1 -1
  38. package/dist/trailer/hud-state.d.ts +75 -4
  39. package/dist/trailer/hud-state.js +132 -17
  40. package/dist/trailer/hud-state.js.map +1 -1
  41. package/dist/trailer/io.d.ts +2 -0
  42. package/dist/trailer/io.js +6 -1
  43. package/dist/trailer/io.js.map +1 -1
  44. package/dist/trailer/link.d.ts +1 -0
  45. package/dist/trailer/link.js +15 -0
  46. package/dist/trailer/link.js.map +1 -0
  47. package/dist/trailer/make.d.ts +2 -0
  48. package/dist/trailer/make.js +1 -0
  49. package/dist/trailer/make.js.map +1 -1
  50. package/dist/trailer/merge.js +32 -11
  51. package/dist/trailer/merge.js.map +1 -1
  52. package/dist/trailer/render.d.ts +2 -0
  53. package/dist/trailer/render.js +94 -48
  54. package/dist/trailer/render.js.map +1 -1
  55. package/dist/trailer/timeline-types.d.ts +19 -0
  56. package/dist/trailer/timeline-types.js +42 -2
  57. package/dist/trailer/timeline-types.js.map +1 -1
  58. package/dist/trailer/title-raster.js +2 -1
  59. package/dist/trailer/title-raster.js.map +1 -1
  60. package/dist/trailer/titles.js +6 -1
  61. package/dist/trailer/titles.js.map +1 -1
  62. package/dist/trailer/video-chain.d.ts +96 -8
  63. package/dist/trailer/video-chain.js +183 -0
  64. package/dist/trailer/video-chain.js.map +1 -1
  65. package/dist/verify/browser.js +4 -1
  66. package/dist/verify/browser.js.map +1 -1
  67. package/package.json +4 -4
@@ -13,12 +13,23 @@
13
13
  * default `server.cors` admits localhost origins, which is what makes the cross-origin module load
14
14
  * work.
15
15
  *
16
- * Contract with `trailer/hud-raster.ts` (unchanged from the web lane's `game/hud-replay.html`):
16
+ * Contract with `trailer/hud-raster.ts` (the same one the web lane's `game/hud-replay.html` keeps):
17
17
  * window.__hudReplayReady === true when the HUD is constructed
18
18
  * window.__hudReplay.setViewport(w, h) size the surface to the recording
19
- * window.__hudReplay.render(state) apply a state snapshot (synchronous)
19
+ * window.__hudReplay.render(state, ms, clip) apply a state snapshot (synchronous); `ms` is the
20
+ * keyframe's position on the SOURCE clock and `clip` the EDL
21
+ * clip it belongs to — together they seed animation phase
22
+ * (both optional; an older raster passes neither)
23
+ * window.__hudReplay.settle() promise: fonts/images loaded, or 1.5 s
20
24
  *
21
- * Same template-literal rule as the shell page: **no backticks** in the emitted output.
25
+ * Same template-literal rules as the shell page: **no backticks** in the emitted output, and every
26
+ * backslash the emitted JS needs (regex escapes) is written doubled here, because an untagged
27
+ * template drops the backslash from an unknown escape like `\(`.
28
+ *
29
+ * The engine module is imported as a namespace and feature-detected: this page ships in the CLI
30
+ * and runs against whichever engine the project has vendored, so a named import of something that
31
+ * engine predates would fail the whole module load (and with it every overlay) rather than one
32
+ * optional nicety.
22
33
  */
23
34
  import { CDN_IMPORTS } from '../scaffold/project-files.js';
24
35
  import { importMapEntries } from '../scaffold/aliases.js';
@@ -34,6 +45,9 @@ export function renderHudReplayHtml(options) {
34
45
  <html lang="en">
35
46
  <head>
36
47
  <meta charset="utf-8">
48
+ <!-- The page is served from the sidecar, but captured game sheets and widget markup reference
49
+ assets root-relatively against the GAME origin; the import map is already absolute. -->
50
+ <base href="http://localhost:${options.gamePort}/">
37
51
  <title>HUD Replay (trailer overlay)</title>
38
52
  <style>
39
53
  /* Transparent everywhere — the renderer captures with omitBackground so only HUD pixels carry alpha. */
@@ -41,10 +55,16 @@ export function renderHudReplayHtml(options) {
41
55
  /* The HUD fades itself in only on GameState.PLAYING, which never happens here; force it visible and
42
56
  kill transitions so a screenshot taken immediately after render() is the settled frame. */
43
57
  .hud-root { opacity: 1 !important; transition: none !important; }
58
+ /* First layer: an !important declaration beats every normal one whatever its specificity, so this
59
+ kills any game animation declared normally. A game rule that is itself !important escapes it,
60
+ and freezeMotion() below is what makes THOSE deterministic. Keeping the rule still pays — the
61
+ animations it suppresses are never created at all. */
44
62
  .hud-root, .hud-root * { animation: none !important; transition: none !important; }
45
63
  /* Gameplay layer holds the reticle/ESC hint — not part of a trailer overlay. */
46
64
  .hud-gameplay-layer { display: none !important; }
47
65
  </style>
66
+ <!-- Kept last in <head> after every captured-sheet injection; see enforceTransparentPageBackground(). -->
67
+ <style id="hud-replay-transparent">html, body { background: transparent !important; }</style>
48
68
  <script type="importmap">
49
69
  ${JSON.stringify({ imports }, null, 2)}
50
70
  </script>
@@ -52,38 +72,56 @@ ${JSON.stringify({ imports }, null, 2)}
52
72
  <body>
53
73
  <script type="module">
54
74
  import { GameHUD } from 'engine/GameHUD.js';
55
- import { applyThemeGlobals } from 'engine/hud/index.js';
75
+ import * as hudApi from 'engine/hud/index.js';
56
76
 
77
+ /** Rebuilt whenever the element set changes; kept between same-shape frames. */
57
78
  var hud = null;
58
79
  var builtKey = '';
80
+ /** The recorded theme currently applied, as JSON — re-applied only on change. */
81
+ var lastThemeJson = 'null';
59
82
 
60
83
  function disposeHud() {
61
84
  if (hud) { hud.dispose(); hud = null; }
85
+ // dispose() removes .hud-root/.hud-gameplay-layer, and a previous frame's toast stacks live inside those.
62
86
  builtKey = '';
63
87
  }
64
88
 
65
- /** Identity of a state's STRUCTURE (not its values): unchanged means mutate, changed means rebuild. */
89
+ /**
90
+ * Identity of a state's STRUCTURE (not its values): unchanged means mutate, changed means rebuild.
91
+ * Custom css is deliberately NOT part of it: it rides custom-html updates too, and applyValues writes
92
+ * it onto the live container — a per-frame style tick must mutate, never tear the whole HUD down.
93
+ */
66
94
  function structureKey(state) {
67
95
  return JSON.stringify([
68
96
  (state.elements || []).map(function (e) {
69
97
  return [e.id, e.type, e.anchor, e.label == null ? null : e.label, e.icon == null ? null : e.icon,
70
98
  e.color == null ? null : e.color, e.width == null ? null : e.width, e.showText == null ? null : e.showText,
71
- e.iconSize == null ? null : e.iconSize, e.type === 'custom' ? (e.css == null ? null : e.css) : null];
99
+ e.iconSize == null ? null : e.iconSize];
72
100
  }),
73
101
  state.health ? 'health' : null,
74
102
  ]);
75
103
  }
76
104
 
105
+ /**
106
+ * setTheme, not applyThemeGlobals: the full path also writes the theme's decorations stylesheet, the
107
+ * data-hud-theme attribute and re-applies per-element overrides — applyThemeGlobals alone left the
108
+ * first build on the default theme's decorations and every element with the wrong overrides.
109
+ * Synchronous, so a screenshot right after render() sees the settled theme.
110
+ */
111
+ function applyRecordedTheme(state) {
112
+ var json = JSON.stringify(state.theme == null ? null : state.theme);
113
+ if (json === lastThemeJson) return;
114
+ if (state.theme) hud.setTheme(state.theme);
115
+ lastThemeJson = json;
116
+ }
117
+
77
118
  function buildFrom(state) {
78
119
  disposeHud();
79
120
  hud = new GameHUD();
80
- if (state.theme) {
81
- // applyThemeGlobals is what the HUD's private ThemeManager calls; re-applying is idempotent and
82
- // synchronous, so a screenshot right after render() never catches the HUD mid-theme.
83
- applyThemeGlobals(state.theme, { manageFontLink: true });
84
- }
121
+ lastThemeJson = 'null';
122
+ applyRecordedTheme(state);
85
123
  (state.elements || []).forEach(createElement);
86
- if (state.health) hud.showHealth();
124
+ if (state.health) hud.showHealth(state.health.width !== undefined ? { width: state.health.width } : undefined);
87
125
  builtKey = structureKey(state);
88
126
  }
89
127
 
@@ -117,6 +155,128 @@ function createElement(el) {
117
155
  }
118
156
  }
119
157
 
158
+ /**
159
+ * Mirror the recording's game-authored head stylesheets into this page. These are the selector
160
+ * rules custom-element markup depends on — without them the markup replays with browser-default
161
+ * styling. Synced by id (add/update/remove) rather than rebuilt: CSS applies live, so a sheet change
162
+ * never needs a HUD teardown, which is also why stylesheets stay out of structureKey.
163
+ */
164
+ function applyStylesheets(state) {
165
+ var wanted = new Map();
166
+ (state.stylesheets || []).forEach(function (sheet) { wanted.set(sheet.id, sheet.css); });
167
+ document.head.querySelectorAll('style[data-hud-replay-sheet]').forEach(function (node) {
168
+ var id = node.dataset.hudReplaySheet;
169
+ if (!wanted.has(id)) { node.remove(); return; }
170
+ var css = wanted.get(id);
171
+ if (node.textContent !== css) node.textContent = css;
172
+ wanted.delete(id);
173
+ });
174
+ wanted.forEach(function (css, id) {
175
+ var node = document.createElement('style');
176
+ node.dataset.hudReplaySheet = id;
177
+ node.textContent = css;
178
+ document.head.appendChild(node);
179
+ });
180
+ enforceTransparentPageBackground();
181
+ }
182
+
183
+ /**
184
+ * Keep the page background transparent whatever the captured sheets say.
185
+ *
186
+ * A game's head stylesheet is captured whole, and real ones carry page-level rules — body { background }
187
+ * is ordinary game theming. Injected here it paints the page, and Playwright's omitBackground does
188
+ * NOT override an author background (it only clears the base canvas colour), so every overlay PNG
189
+ * comes out as an opaque full-frame rectangle covering the footage. Measured, not theorised.
190
+ *
191
+ * Two layers, because one is not enough: the harness's own <style> re-appended LAST wins on document
192
+ * order against an equal-specificity rule; the style ATTRIBUTE outranks every author rule regardless
193
+ * of specificity, which "html body { ... !important }" (0,0,2) and ":root { ... !important }" (0,1,0)
194
+ * need, since both beat the harness sheet's "html, body" (0,0,1). Deliberate inline style: the
195
+ * cascade offers nothing else a captured sheet cannot outrank. The background shorthand is required
196
+ * (background-color alone leaves a captured background-image painting), and both the root element and
197
+ * body need it: a :root background propagates to the canvas when body is transparent.
198
+ */
199
+ function enforceTransparentPageBackground() {
200
+ var override = document.getElementById('hud-replay-transparent');
201
+ if (override && override !== document.head.lastElementChild) document.head.appendChild(override);
202
+ [document.documentElement, document.body].forEach(function (el) {
203
+ el.style.setProperty('background', 'transparent', 'important');
204
+ el.style.setProperty('background-image', 'none', 'important');
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Freeze every animation at a deterministic phase.
210
+ *
211
+ * The CSS kill rule in <head> only reaches normal declarations; a game rule that is itself
212
+ * !important ("#hud-custom-gauge .needle { animation: spin 2s linear infinite !important }") keeps
213
+ * running and screenshots would catch whatever phase wall-clock happened to be at.
214
+ *
215
+ * Phase is time since the animation STARTED, which the harness does not know — so it uses the
216
+ * keyframe at which it first saw the animation as the start (the op that inserted the markup lands
217
+ * at most one keyframe earlier), on the SOURCE clock so a speed ramp keeps footage and motion in
218
+ * lockstep. Animations already present at a clip's first keyframe get absolute source time instead:
219
+ * they began before the clip, and for a loop that is the phase the player saw. The first-seen map
220
+ * is reset per clip because keyframes are rasterized in EDL order, not source order. No paint flush
221
+ * is needed: page.screenshot() forces a commit.
222
+ */
223
+ var animationStarts = new Map();
224
+ /** The clip the previous render() belonged to; a change resets the first-seen map above. */
225
+ var lastClipIndex = null;
226
+
227
+ function animationKey(animation) {
228
+ var target = animation.effect ? animation.effect.target : null;
229
+ var container = target && target.closest ? target.closest('.hud-element') : null;
230
+ // Index path from the container to the target: several nodes in one container can run the same
231
+ // animation name at different times (a stack of "+100" popups).
232
+ var path = [];
233
+ var node = target;
234
+ while (node && node !== container && node.parentNode) {
235
+ path.unshift(Array.prototype.indexOf.call(node.parentNode.childNodes, node));
236
+ node = node.parentNode;
237
+ }
238
+ return (container ? container.id : '') + '|' + path.join('.') + '|' + animation.animationName;
239
+ }
240
+
241
+ function freezeMotion(frameTimeMs, clipStart) {
242
+ document.getAnimations().forEach(function (animation) {
243
+ if (typeof CSSTransition !== 'undefined' && animation instanceof CSSTransition) {
244
+ // A transition is a state change in flight, not a point on the replay timeline — land it on
245
+ // its end state. Seeding an early frameTimeMs would freeze it mid-fade instead.
246
+ try { animation.finish(); } catch (e) { animation.cancel(); }
247
+ return;
248
+ }
249
+ var key = animationKey(animation);
250
+ var start = animationStarts.get(key);
251
+ if (start === undefined) {
252
+ start = clipStart ? 0 : frameTimeMs;
253
+ animationStarts.set(key, start);
254
+ }
255
+ // pause() first: assigning currentTime to a running animation only shifts its start time.
256
+ // Seeking a pending animation cancels its pending task and lands it exactly.
257
+ animation.pause();
258
+ animation.currentTime = Math.max(0, frameTimeMs - start);
259
+ });
260
+ }
261
+
262
+ /**
263
+ * Show/hide without destroying a recorded display.
264
+ *
265
+ * Blanking display to '' looks harmless but is not: a custom element's css routinely carries
266
+ * "display: flex" (the docs' own example does), and clearing it drops the widget to the UA default
267
+ * block, so a two-column widget replays as two stacked rows. Only ever WRITE 'none', and only clear
268
+ * a 'none' when the state says visible — which the reducer decides from the most recent of the css
269
+ * snapshot's own display and the show/hide ops, in frame order (hud-state.ts), so clearing here
270
+ * happens exactly when the game itself cleared it.
271
+ */
272
+ function applyVisibility(root, visible) {
273
+ if (visible === false) {
274
+ root.style.display = 'none';
275
+ } else if (root.style.display === 'none') {
276
+ root.style.display = '';
277
+ }
278
+ }
279
+
120
280
  function setText(root, selector, value) {
121
281
  if (value === undefined || value === null) return;
122
282
  var node = root.querySelector(selector);
@@ -129,7 +289,7 @@ function applyValues(state) {
129
289
  var element = hud.getElement(el.id);
130
290
  if (!element) return;
131
291
  var root = element.container;
132
- root.style.display = el.visible === false ? 'none' : '';
292
+ applyVisibility(root, el.visible);
133
293
  switch (el.type) {
134
294
  case 'counter':
135
295
  setText(root, '.hud-counter__value', el.text);
@@ -153,6 +313,13 @@ function applyValues(state) {
153
313
  }
154
314
  case 'custom':
155
315
  if (typeof el.html === 'string' && root.innerHTML !== el.html) root.innerHTML = el.html;
316
+ // The recorded container cssText (create-time, and per-update since the recorder started
317
+ // carrying it). cssText overwrites wholesale — including any display captured while the
318
+ // widget was game-hidden — so the show/hide verdict is re-asserted after: it owns visibility.
319
+ if (typeof el.css === 'string' && root.style.cssText !== el.css) {
320
+ root.style.cssText = el.css;
321
+ applyVisibility(root, el.visible);
322
+ }
156
323
  break;
157
324
  default:
158
325
  break;
@@ -160,7 +327,7 @@ function applyValues(state) {
160
327
  });
161
328
 
162
329
  if (state.health) {
163
- hud.showHealth();
330
+ hud.showHealth(state.health.width !== undefined ? { width: state.health.width } : undefined);
164
331
  hud.updateHealth(state.health.current, state.health.max);
165
332
  var container = document.getElementById('hud-health-container');
166
333
  if (container) {
@@ -174,23 +341,135 @@ function applyValues(state) {
174
341
  if (hudRoot) hudRoot.style.display = state.hudVisible === false ? 'none' : '';
175
342
  }
176
343
 
177
- /** Toasts are transient and timer-managed in the engine; the replay owns visibility, so draw them plainly. */
344
+ /**
345
+ * Toasts are transient and stack-managed, and the engine's own showToast arms wall-clock removal
346
+ * timers. The replay owns visibility, so render them as plain toast-classed nodes in fresh stacks
347
+ * each frame — one stack per anchor, with the same newest-top rule and per-toast theme override the
348
+ * engine applies (GameHUD.showToast). Recordings that predate the anchor field land on top-center.
349
+ */
178
350
  function applyToasts(state) {
179
351
  document.querySelectorAll('.hud-replay-toasts').forEach(function (stale) { stale.remove(); });
180
352
  var toasts = state.toasts || [];
181
353
  if (!toasts.length) return;
182
- var anchorEl = document.querySelector('.hud-anchor-top-center') || document.querySelector('.hud-root');
183
- if (!anchorEl) return;
184
- var stack = document.createElement('div');
185
- stack.className = 'hud-toast-stack hud-toast-stack--newest-top hud-replay-toasts';
354
+ var override = state.theme && state.theme.elements ? state.theme.elements.toast : undefined;
355
+ // Base colours come from the APPLIED theme, exactly as the engine's own applyElementOverride passes
356
+ // this.current.colors: a partial override is resolved against the full palette. Feature-detected —
357
+ // an engine from before this export just gets unthemed toasts, not a dead page.
358
+ var applied = hud.getTheme();
359
+ var applyOverride = typeof hudApi.applyPartialThemeProperties === 'function' ? hudApi.applyPartialThemeProperties : null;
360
+ var stacks = new Map();
186
361
  toasts.forEach(function (toast) {
362
+ var anchor = toast.anchor || 'top-center';
363
+ var stack = stacks.get(anchor);
364
+ if (!stack) {
365
+ var anchorEl = document.querySelector('.hud-anchor-' + anchor)
366
+ || document.querySelector('.hud-anchor-top-center')
367
+ || document.querySelector('.hud-root');
368
+ if (!anchorEl) return;
369
+ stack = document.createElement('div');
370
+ stack.className = (anchor.indexOf('top') === 0 ? 'hud-toast-stack hud-toast-stack--newest-top' : 'hud-toast-stack') + ' hud-replay-toasts';
371
+ anchorEl.appendChild(stack);
372
+ stacks.set(anchor, stack);
373
+ }
187
374
  var node = document.createElement('div');
188
375
  node.className = 'hud-element hud-toast';
189
376
  node.dataset.kind = toast.variant || 'info';
190
377
  node.textContent = toast.message;
378
+ if (override && applied && applyOverride) applyOverride(node, override, applied.colors);
191
379
  stack.appendChild(node);
192
380
  });
193
- anchorEl.appendChild(stack);
381
+ }
382
+
383
+ /**
384
+ * Everything render() may have started loading, settled — or 1.5 s, whichever comes first. The
385
+ * screenshot that follows render() must not catch fallback type or a half-loaded image, but a dead
386
+ * URL must not stall the whole raster either, so image outcomes (success AND failure) are memoized:
387
+ * an unreachable image costs its timeout once, not once per keyframe.
388
+ */
389
+ var settledResources = new Set();
390
+ /** Hrefs whose wait hit the deadline — retried only when they can still help. */
391
+ var timedOutResources = new Set();
392
+ var SETTLE_TIMEOUT_MS = 1500;
393
+
394
+ /** Resolves 'settled' when the work finished, 'timeout' when the deadline won. */
395
+ function withDeadline(promise) {
396
+ var timer = null;
397
+ var deadline = new Promise(function (resolve) { timer = setTimeout(function () { resolve('timeout'); }, SETTLE_TIMEOUT_MS); });
398
+ return Promise.race([Promise.resolve(promise).then(function () { return 'settled'; }), deadline])
399
+ .finally(function () { if (timer !== null) clearTimeout(timer); });
400
+ }
401
+
402
+ function collectCssImageUrls() {
403
+ var urls = [];
404
+ var pattern = /url\\(\\s*(?:"([^"]*)"|'([^']*)'|([^)]*))\\s*\\)/g;
405
+ document.querySelectorAll('.hud-root, .hud-root *').forEach(function (node) {
406
+ var style = getComputedStyle(node);
407
+ ['background-image', 'border-image-source'].forEach(function (prop) {
408
+ var value = style.getPropertyValue(prop);
409
+ if (!value || value === 'none') return;
410
+ var match;
411
+ while ((match = pattern.exec(value)) !== null) {
412
+ var url = match[1] || match[2] || match[3] || '';
413
+ if (url && url.indexOf('data:') !== 0) urls.push(url);
414
+ }
415
+ });
416
+ });
417
+ return urls;
418
+ }
419
+
420
+ function settle() {
421
+ var waits = [];
422
+
423
+ // The font is keyed off the LIVE link's readiness, not a per-href memo: a deadline win means the
424
+ // font is slow, not absent (memoizing it would leave keyframe 0 on fallback type and every later
425
+ // one on the real face — a visible swap mid-trailer), and every structural rebuild disposes the
426
+ // HUD, which removes this link and creates a fresh one at the same href that has yet to parse.
427
+ // Only a genuine timeout is remembered, so a truly dead font costs 1.5 s once, not per keyframe.
428
+ var fontLink = document.getElementById('hud-active-font');
429
+ if (fontLink && !fontLink.sheet && !timedOutResources.has(fontLink.href)) {
430
+ var href = fontLink.href;
431
+ waits.push(withDeadline(new Promise(function (resolve) {
432
+ fontLink.addEventListener('load', resolve, { once: true });
433
+ fontLink.addEventListener('error', resolve, { once: true });
434
+ }).then(function () { return document.fonts.ready; })).then(function (outcome) {
435
+ if (outcome === 'timeout') timedOutResources.add(href);
436
+ }));
437
+ }
438
+
439
+ document.querySelectorAll('.hud-root img').forEach(function (img) {
440
+ var src = img.currentSrc || img.src;
441
+ if (!src || src.indexOf('data:') === 0 || settledResources.has(src)) return;
442
+ waits.push(withDeadline(img.decode().catch(function () {})).then(function () { settledResources.add(src); }));
443
+ });
444
+
445
+ collectCssImageUrls().forEach(function (url) {
446
+ if (settledResources.has(url)) return;
447
+ waits.push(withDeadline(new Promise(function (resolve) {
448
+ var probe = new Image();
449
+ probe.onload = resolve;
450
+ probe.onerror = resolve;
451
+ probe.src = url;
452
+ })).then(function () { settledResources.add(url); }));
453
+ });
454
+
455
+ // Fonts the injected game sheets bring in (@font-face, @import) load lazily once text using them
456
+ // is styled — the computed-style walk above just forced that — and only document.fonts.ready
457
+ // knows about them. Free when nothing is loading. A face that never arrives is remembered by
458
+ // family after one deadline, or every keyframe would pay the full timeout for it.
459
+ var unresolved = [];
460
+ document.fonts.forEach(function (face) {
461
+ if (face.status === 'loading' && !timedOutResources.has('font-family:' + face.family)) unresolved.push(face.family);
462
+ });
463
+ if (unresolved.length || document.fonts.status === 'loading') {
464
+ waits.push(withDeadline(document.fonts.ready).then(function (outcome) {
465
+ if (outcome !== 'timeout') return;
466
+ document.fonts.forEach(function (face) {
467
+ if (face.status === 'loading') timedOutResources.add('font-family:' + face.family);
468
+ });
469
+ }));
470
+ }
471
+
472
+ return waits.length ? Promise.all(waits).then(function () { return true; }) : Promise.resolve(true);
194
473
  }
195
474
 
196
475
  window.__hudReplay = {
@@ -198,18 +477,34 @@ window.__hudReplay = {
198
477
  document.body.style.width = width + 'px';
199
478
  document.body.style.height = height + 'px';
200
479
  },
201
- render: function (state) {
480
+ render: function (state, frameTimeMs, clipIndex) {
481
+ var clipStart = clipIndex !== lastClipIndex;
482
+ if (clipStart) {
483
+ lastClipIndex = clipIndex;
484
+ animationStarts.clear();
485
+ }
202
486
  var key = structureKey(state);
203
- if (!hud || key !== builtKey) buildFrom(state);
487
+ if (!hud || key !== builtKey) {
488
+ buildFrom(state);
489
+ } else {
490
+ // A theme change without a structure change (mid-recording setTheme) must not force a
491
+ // rebuild — setTheme re-applies the overrides on the live elements itself.
492
+ applyRecordedTheme(state);
493
+ }
494
+ applyStylesheets(state);
204
495
  applyValues(state);
205
496
  applyToasts(state);
497
+ // Last: every step above can create elements, and a replaced element brings a fresh running animation.
498
+ freezeMotion(typeof frameTimeMs === 'number' ? frameTimeMs : 0, clipStart);
206
499
  return true;
207
500
  },
501
+ settle: settle,
208
502
  };
209
503
 
210
504
  // Build an empty HUD up front so the first render() has styles injected and the anchor grid in place.
211
505
  hud = new GameHUD();
212
506
  builtKey = structureKey({ elements: [] });
507
+ enforceTransparentPageBackground();
213
508
  window.__hudReplayReady = true;
214
509
  </script>
215
510
  </body>
@@ -1 +1 @@
1
- {"version":3,"file":"hud-replay-page.js","sourceRoot":"","sources":["../../src/editor/hud-replay-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAO1D,oGAAoG;AACpG,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,MAAM,MAAM,GAAG,oBAAoB,QAAQ,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAC9B,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CACvF,CAAC;IACF,OAAO,EAAE,GAAG,WAAW,EAAE,GAAG,KAAK,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,MAAM,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrD,OAAO;;;;;;;;;;;;;;;;EAgBP,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwKrC,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"hud-replay-page.js","sourceRoot":"","sources":["../../src/editor/hud-replay-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAO1D,oGAAoG;AACpG,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,MAAM,MAAM,GAAG,oBAAoB,QAAQ,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAC9B,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CACvF,CAAC;IACF,OAAO,EAAE,GAAG,WAAW,EAAE,GAAG,KAAK,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,MAAM,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrD,OAAO;;;;;;+BAMsB,OAAO,CAAC,QAAQ;;;;;;;;;;;;;;;;;;;EAmB7C,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2brC,CAAC;AACF,CAAC"}
@@ -136,7 +136,57 @@ export function applyModificationsToWorld(worldPath, modifications, options = {}
136
136
  const world = readWorldJson(worldPath);
137
137
  // Baseline BEFORE applying: a violation already present in a legacy or imported world must not
138
138
  // block an unrelated write. Only violations this batch INTRODUCES are refused.
139
- const preExisting = new Set(validateWorldData(world));
139
+ //
140
+ // Compared by IDENTITY, not by the formatted string: an error embeds the array index it was found
141
+ // at (`/worldProfileData/doors/1: must have ...`), so a pre-existing violation that merely MOVES
142
+ // — because the batch removed an earlier element of the same array — reads as brand new and
143
+ // refuses a write that introduced nothing. That is a post-payment failure over a defect the
144
+ // creator already had, and every array-pruning batch can trigger it (the forge's own placeholder
145
+ // purge and the side-on lane's discarded-level cleanup both prune).
146
+ //
147
+ // Each array index is therefore rewritten to the ELEMENT'S OWN `id` — every constrained array
148
+ // item in the generated schema carries one — so the same offending element keeps one identity
149
+ // wherever it moves, while a DIFFERENT element reads as different and is still refused. Falling
150
+ // back to `#` for an element with no id keeps the old, coarser behaviour for that case only.
151
+ // The comparison is a count per identity, so a second violation of an already-violating shape
152
+ // still raises the count and is still refused.
153
+ const identify = (doc, error) => {
154
+ const split = error.indexOf(': ');
155
+ if (split < 0)
156
+ return error;
157
+ const instancePath = error.slice(0, split);
158
+ const rest = error.slice(split);
159
+ const segments = instancePath.split('/').slice(1)
160
+ .map((seg) => seg.replace(/~1/g, '/').replace(/~0/g, '~'));
161
+ let node = doc;
162
+ const out = [];
163
+ for (const segment of segments) {
164
+ const parent = node;
165
+ node = (typeof parent === 'object' && parent !== null)
166
+ ? parent[segment]
167
+ : undefined;
168
+ if (Array.isArray(parent) && /^\d+$/.test(segment)) {
169
+ const id = (typeof node === 'object' && node !== null)
170
+ ? node.id
171
+ : undefined;
172
+ out.push(typeof id === 'string' && id.length > 0 ? `id=${id}` : '#');
173
+ }
174
+ else {
175
+ out.push(segment);
176
+ }
177
+ }
178
+ return `/${out.join('/')}${rest}`;
179
+ };
180
+ const tally = (doc, errors) => {
181
+ const counts = new Map();
182
+ for (const error of errors) {
183
+ const key = identify(doc, error);
184
+ counts.set(key, (counts.get(key) ?? 0) + 1);
185
+ }
186
+ return counts;
187
+ };
188
+ const baselineErrors = validateWorldData(world);
189
+ const preExisting = tally(world, baselineErrors);
140
190
  const results = modifications.map((modification) => applyOne(world, modification));
141
191
  const summary = results.map((result) => result.summary);
142
192
  // The engine statically casts world.json to Partial<GameData> (game/src/bundle/WorldDataLoader.ts),
@@ -145,9 +195,27 @@ export function applyModificationsToWorld(worldPath, modifications, options = {}
145
195
  // PAID FOR, so a bad batch would leave the creator with a project that no longer builds and an
146
196
  // error pointing at the engine rather than at the offending value.
147
197
  //
198
+ // NOT in lockstep with the web lane, deliberately do not assume it is: game-play-agent's
199
+ // `updateWorldJson` still compares formatted strings (so it keeps the reindex false-refusal this
200
+ // one fixes), and `updateWorldJsonBatch` — the call the WEB forger's persist actually goes
201
+ // through — runs no schema gate at all.
202
+ //
148
203
  // Thrown BEFORE the rename, so world.json keeps whatever it had — the same guarantee every other
149
204
  // failure path in this function already makes.
150
- const introduced = validateWorldData(world).filter((error) => !preExisting.has(error));
205
+ const after = validateWorldData(world);
206
+ const afterCounts = tally(world, after);
207
+ const worsened = new Set();
208
+ for (const [identity, count] of afterCounts) {
209
+ if (count > (preExisting.get(identity) ?? 0))
210
+ worsened.add(identity);
211
+ }
212
+ // Reported errors exclude anything the document already said verbatim, so the
213
+ // message names what this batch is responsible for rather than the creator's
214
+ // pre-existing damage.
215
+ const baselineText = new Set(baselineErrors);
216
+ const introduced = after
217
+ .filter((error) => worsened.has(identify(world, error)))
218
+ .filter((error) => !baselineText.has(error));
151
219
  if (introduced.length > 0) {
152
220
  throw new CliError(`${subject} would make ${path.basename(worldPath)} invalid against the engine's GameData `
153
221
  + `schema, so nothing was written. Violations: ${introduced.join('; ')}`);
@@ -1 +1 @@
1
- {"version":3,"file":"apply-modifications.js","sourceRoot":"","sources":["../../src/forge/apply-modifications.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"apply-modifications.js","sourceRoot":"","sources":["../../src/forge/apply-modifications.ts"],"names":[],"mappings":"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"}
@@ -71,6 +71,16 @@ export interface ForgeProgressTrackerOptions {
71
71
  /** Overridden in tests. */
72
72
  logIntervalMs?: number;
73
73
  reportIntervalMs?: number;
74
+ /**
75
+ * The run's step list. Defaults to the 3D `FORGE_STEPS`; a side-on run passes
76
+ * `SIDEON_FORGE_STEPS` (4 steps, no level bake) so the `[n/4]` banners and the
77
+ * row model match what actually runs — with the default, a side-on run would
78
+ * print `[n/5]` and leave "Baking level" pending forever.
79
+ */
80
+ steps?: ReadonlyArray<{
81
+ readonly key: ForgeStepKey;
82
+ readonly label: string;
83
+ }>;
74
84
  }
75
85
  /**
76
86
  * Minimum gap between printed `update` lines. The bake is already throttled at its source
@@ -39,9 +39,9 @@ export const PROGRESS_LOG_MIN_INTERVAL_MS = 3000;
39
39
  * re-renders at 1 Hz anyway and a stale bar looks broken in a way a missing log line does not.
40
40
  */
41
41
  export const PROGRESS_REPORT_MIN_INTERVAL_MS = 1000;
42
- /** `[3/5]`, from the step's position in {@link FORGE_STEPS}. */
43
- function stepBanner(index) {
44
- return `[${index + 1}/${FORGE_STEPS.length}]`;
42
+ /** `[3/5]`, from the step's position in the run's own step list. */
43
+ function stepBanner(index, stepCount) {
44
+ return `[${index + 1}/${stepCount}]`;
45
45
  }
46
46
  /**
47
47
  * Merge a step's summary into the accumulated one.
@@ -60,7 +60,8 @@ export function createForgeProgressTracker(options) {
60
60
  const now = options.now ?? (() => Date.now());
61
61
  const logIntervalMs = options.logIntervalMs ?? PROGRESS_LOG_MIN_INTERVAL_MS;
62
62
  const reportIntervalMs = options.reportIntervalMs ?? PROGRESS_REPORT_MIN_INTERVAL_MS;
63
- const steps = FORGE_STEPS.map((step) => ({
63
+ const stepList = options.steps ?? FORGE_STEPS;
64
+ const steps = stepList.map((step) => ({
64
65
  key: step.key,
65
66
  label: step.label,
66
67
  status: 'pending',
@@ -141,7 +142,7 @@ export function createForgeProgressTracker(options) {
141
142
  const suffix = event.detail !== undefined && !event.message.includes(event.detail)
142
143
  ? ` — ${event.detail}`
143
144
  : '';
144
- print(`${stepBanner(index)} ${event.message}${suffix}`, terminal, at);
145
+ print(`${stepBanner(index, steps.length)} ${event.message}${suffix}`, terminal, at);
145
146
  // The reference-match previews are the one thing in a forge's progress worth a URL: they
146
147
  // are what the critic's verdict was about, and the creator cannot judge it otherwise.
147
148
  for (const url of event.previewUrls ?? [])
@@ -162,7 +163,7 @@ export function createForgeProgressTracker(options) {
162
163
  earlier.detail = undefined;
163
164
  }
164
165
  const at = now();
165
- print(`${stepBanner(index)} ${message}`, true, at);
166
+ print(`${stepBanner(index, steps.length)} ${message}`, true, at);
166
167
  report(true, at);
167
168
  };
168
169
  return { apply, skip, snapshot };