@moku-labs/game 0.15.19 → 0.15.23

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 (37) hide show
  1. package/dist/app/page.d.mts +1 -1
  2. package/dist/app/page.mjs +1 -1
  3. package/dist/app/system.d.mts +3 -3
  4. package/dist/app.d.mts +3 -3
  5. package/dist/app.mjs +1 -1
  6. package/dist/assets.d.mts +1 -1
  7. package/dist/{capture-D7XvoHdH.mjs → capture-PTJXDpxK.mjs} +117 -16
  8. package/dist/{commands-G9maNqTK.d.mts → commands-CV6Deuaj.d.mts} +5 -5
  9. package/dist/control.d.mts +4 -4
  10. package/dist/control.mjs +1 -1
  11. package/dist/{define-BAuGN0UM.d.mts → define-42KUe-al.d.mts} +2 -2
  12. package/dist/{headless-BA6ffzqD.mjs → headless-Bxxl_mQX.mjs} +1 -1
  13. package/dist/{headless-uUUH54Ij.d.mts → headless-DIbfzWkr.d.mts} +1 -1
  14. package/dist/{index-Cku9aRz_.d.mts → index-CHI7dTc9.d.mts} +26 -4
  15. package/dist/index.d.mts +5 -5
  16. package/dist/index.mjs +3 -3
  17. package/dist/inspect.d.mts +6 -6
  18. package/dist/inspect.mjs +1 -1
  19. package/dist/jsx-dev-runtime.d.mts +1 -1
  20. package/dist/jsx-runtime.d.mts +1 -1
  21. package/dist/{run-BqYmcNo7.mjs → run-DRcc2SKF.mjs} +2 -2
  22. package/dist/{runtime-CuMDZhii.d.mts → runtime-BcMjHiib.d.mts} +1 -1
  23. package/dist/{sources-BYKXYPhd.mjs → sources-Dpc4NXvG.mjs} +1 -1
  24. package/dist/{src-CXfBuKpl.mjs → src-hVe3f8GV.mjs} +1380 -302
  25. package/dist/testing.d.mts +4 -4
  26. package/dist/testing.mjs +2 -2
  27. package/dist/{types-rr56N0Ab.d.mts → types-9xAh1wGm.d.mts} +8 -5
  28. package/dist/{types-bwefoJES.d.mts → types-CHmCFReJ.d.mts} +219 -24
  29. package/dist/{types-4T4QTU48.d.mts → types-DmGs6x0c.d.mts} +24 -6
  30. package/dist/{types-DQtBMqRE.d.mts → types-DwREs5vb.d.mts} +4 -4
  31. package/dist/{types-DMsvVZZT.d.mts → types-L4nVF9Ny.d.mts} +2 -2
  32. package/dist/{visual-B-c5OIFB.d.mts → visual-CjXAp0DW.d.mts} +6 -6
  33. package/dist/{visual-DNM5oFYx.mjs → visual-kEC9nb-o.mjs} +20 -2
  34. package/dist/visual.d.mts +1 -1
  35. package/dist/visual.mjs +2 -2
  36. package/llms.txt +2 -2
  37. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
2
  import { r as systemSource, t as memory } from "./memory-CWlqhS7l.mjs";
3
3
  import { a as finishDrafts, i as enableDraftPatches, n as deepFreeze$1, o as openDrafts, r as dropDrafts } from "./drafts-BawpSaVp.mjs";
4
- import { d as graphHash, f as isRestCheckpoint, h as pushEntry, i as servePaused, l as findNode, m as compact, n as captureFrame, o as collectFlows, p as slotNames, r as serveCaptures, s as describeGraph, t as cancelCaptures, u as framePath } from "./capture-D7XvoHdH.mjs";
5
- import { $ as component, A as localPoseOf, B as OverlayLevel, C as builtInStyles, D as textFor, E as readStyle, F as rootPoseOf, G as arcDefaults, H as Shape, I as Display, J as Exiting, K as componentsFor, L as HudPart, M as poseUnder, N as rootPointOf, O as applyPose, P as rootPoseInto, Q as Tree, R as NineSlice, U as Sprite, V as Parent, W as Transform, X as Now, Y as Layer, Z as Order, _ as uiFor, a as visualOf$1, b as Countdown, c as Covered, d as LocalWrite, f as Scroll, h as UiCounters, i as samePose, it as tag, j as parentOf$1, k as isLeaving, l as DebugTrigger, m as UI_OWNER, n as drawnRect, nt as system, o as visualRectOf, p as ScrollState, r as fitScaleOf, rt as systemsFor, s as Box, t as CONTENT$1, tt as resource, u as Escapable, x as Text, z as Overlay } from "./visual-DNM5oFYx.mjs";
4
+ import { _ as pushEntry, c as collectFlows, d as findNode, f as framePath, g as compact, h as slotNames, i as servePaused, l as describeGraph, m as isRestCheckpoint, n as captureFrame, o as epochOf, p as graphHash, r as serveCaptures, s as pageNow$1, t as cancelCaptures } from "./capture-PTJXDpxK.mjs";
5
+ import { $ as component, A as localPoseOf, B as OverlayLevel, C as builtInStyles, D as textFor, E as readStyle, F as rootPoseOf, G as arcDefaults, H as Shape, I as Display, J as Exiting, K as componentsFor, L as HudPart, M as poseUnder, N as rootPointOf, O as applyPose, P as rootPoseInto, Q as Tree, R as NineSlice, U as Sprite, V as Parent, W as Transform, X as Now, Y as Layer, Z as Order, _ as uiFor, a as visualOf$1, b as Countdown, c as Covered, d as LocalWrite, f as Scroll, h as UiCounters, i as samePose, it as tag, j as parentOf$1, k as isLeaving, l as DebugTrigger, m as UI_OWNER, n as drawnRect, nt as system, o as visualRectOf, p as ScrollState, r as fitScaleOf, rt as systemsFor, s as Box, t as CONTENT$1, tt as resource, u as Escapable, x as Text, z as Overlay } from "./visual-kEC9nb-o.mjs";
6
6
  import { a as isDev } from "./dev-BNndV-hU.mjs";
7
7
  import { n as isComponentDefinition } from "./component-DGg5DqKK.mjs";
8
8
  import { n as flatten } from "./flatten-nUX9PpGo.mjs";
@@ -252,7 +252,7 @@ function createMeasureState() {
252
252
  *
253
253
  * @returns The page clock, ms.
254
254
  */
255
- function pageNow$1() {
255
+ function pageNow() {
256
256
  return globalThis.performance.now();
257
257
  }
258
258
  /**
@@ -277,7 +277,7 @@ function timePhase(ctx, record, callbacks, phase, start) {
277
277
  error
278
278
  });
279
279
  }
280
- const end = pageNow$1();
280
+ const end = pageNow();
281
281
  record.phaseMs[phase] = end - start;
282
282
  return end;
283
283
  }
@@ -294,7 +294,7 @@ function timePhase(ctx, record, callbacks, phase, start) {
294
294
  function runMeasuredFrame(ctx, measure, at, stepped) {
295
295
  const { record } = measure;
296
296
  const phases = capturePhases(ctx);
297
- let mark = pageNow$1();
297
+ let mark = pageNow();
298
298
  record.startMs = stepped ? 0 : mark - at;
299
299
  mark = timePhase(ctx, record, phases[0], 0, mark);
300
300
  mark = timePhase(ctx, record, phases[1], 1, mark);
@@ -324,7 +324,7 @@ function runMeasuredFrame(ctx, measure, at, stepped) {
324
324
  } catch (error) {
325
325
  ctx.log.error("time: measure listener failed", { error });
326
326
  }
327
- measure.listenersMs = pageNow$1() - mark;
327
+ measure.listenersMs = pageNow() - mark;
328
328
  measure.lastAt = at;
329
329
  measure.skipped = 0;
330
330
  }
@@ -595,6 +595,11 @@ const CAP_SLACK_SHARE = .12;
595
595
  const DISPLAY_SHARE = .25;
596
596
  /** …and at least this share of the window's drawn intervals: the p90 rule written as a count. */
597
597
  const DROP_SHARE = .1;
598
+ /**
599
+ * The drop window stays late this long before the rate drops, ms (F64): longer than the window, so one burst of late
600
+ * frames (a mount, an upload, a GC) never drops the rate by itself; only a sustained slow run does.
601
+ */
602
+ const DROP_HOLD_MS = 2e3;
598
603
  /** The least time at a rate before a return, ms. */
599
604
  const DWELL_BASE_MS = 5e3;
600
605
  /** The dwell never grows past this, ms. */
@@ -901,6 +906,7 @@ function createGovernor(floor) {
901
906
  returnDrawn: 0,
902
907
  returnMissed: 0,
903
908
  returnHeavy: 0,
909
+ lateSince: NaN,
904
910
  switchedAt: Number.NEGATIVE_INFINITY,
905
911
  lastReturnAt: Number.NEGATIVE_INFINITY,
906
912
  climbed: false,
@@ -977,7 +983,7 @@ function createRateState(config) {
977
983
  * @returns The page clock, ms.
978
984
  */
979
985
  function nowOf() {
980
- return typeof globalThis.performance === "undefined" ? 0 : pageNow$1();
986
+ return typeof globalThis.performance === "undefined" ? 0 : pageNow();
981
987
  }
982
988
  /**
983
989
  * Empties both windows: evidence of one rate never judges another, and a resume is a new situation.
@@ -993,6 +999,7 @@ function emptyWindows(g) {
993
999
  g.returnDrawn = 0;
994
1000
  g.returnMissed = 0;
995
1001
  g.returnHeavy = 0;
1002
+ g.lateSince = NaN;
996
1003
  }
997
1004
  /**
998
1005
  * Takes the oldest sample of the drop window out of its counts.
@@ -1326,15 +1333,36 @@ function isSample(ctx, g, intervalMs) {
1326
1333
  return g.lastDrawn && !g.lastIdle && !g.lastProbed && intervalMs <= 500 && g.lastFps === rate.fps && rate.mode === "auto" && rate.display.hz !== null;
1327
1334
  }
1328
1335
  /**
1329
- * Tells whether the drop window is late enough to step down: late intervals reach `max(8, 10 %)` of the second's.
1336
+ * Tells whether the drop window is late: late intervals reach `max(8, 10 %)` of the second's.
1330
1337
  *
1331
1338
  * @param g - The governor.
1332
- * @returns True when the rate should drop one step.
1339
+ * @returns True when the window is late.
1333
1340
  */
1334
- function shouldDrop(g) {
1341
+ function isLate(g) {
1335
1342
  return g.dropMissed >= Math.max(8, Math.ceil(DROP_SHARE * g.dropDrawn));
1336
1343
  }
1337
1344
  /**
1345
+ * Tells whether the rate should step down (F64): the drop window has been late for 2 s in a row, so one burst of late
1346
+ * frames never drops it. Within 3 s of a step of the rule itself the first late window is enough: a return on
1347
+ * probation that fails, and the next step of a descent already under way. Notes when the window turned late.
1348
+ *
1349
+ * @param ctx - Domain context of the time plugin.
1350
+ * @param g - The governor.
1351
+ * @param at - The page time of the sample.
1352
+ * @returns True when the rate should drop one step.
1353
+ */
1354
+ function shouldDrop(ctx, g, at) {
1355
+ if (!isLate(g)) {
1356
+ g.lateSince = NaN;
1357
+ return false;
1358
+ }
1359
+ if (Number.isNaN(g.lateSince)) g.lateSince = at;
1360
+ const { reason } = ctx.state.rate;
1361
+ const afterRuleStep = (reason === "missed" || reason === "headroom") && at - g.switchedAt < 3e3;
1362
+ const heldLate = at - g.lateSince >= DROP_HOLD_MS;
1363
+ return afterRuleStep || heldLate;
1364
+ }
1365
+ /**
1338
1366
  * Tells whether the late frames of the drop window are GPU-bound: fewer than half of them kept the main thread longer
1339
1367
  * than their frame.
1340
1368
  *
@@ -1357,8 +1385,8 @@ function canReturn(g, at, scaleStep) {
1357
1385
  return thermalRank(g.thermal) < HEAT_LIMIT_RANK && at - g.switchedAt >= (scaleStep ? 2 * g.dwellMs : g.dwellMs) && g.returnDrawn >= 90 && g.returnMissed <= 1 && (scaleStep || g.returnHeavy <= Math.floor(.1 * g.returnDrawn));
1358
1386
  }
1359
1387
  /**
1360
- * Decides on the windows after a sample. Down one step when late intervals reach `max(8, 10 %)` of the second's: the
1361
- * render scale when the window is GPU-bound and a lower scale is allowed, the rate otherwise. Up one step in the
1388
+ * Decides on the windows after a sample. Down one step when late intervals reach `max(8, 10 %)` of the second's
1389
+ * for 2 s in a row (at once within 3 s of a step of the rule): the render scale when the window is GPU-bound and a lower scale is allowed, the rate otherwise. Up one step in the
1362
1390
  * reverse of the way down, with 90 steady drawn frames in three seconds, at most one late and no heat: the rate first,
1363
1391
  * at the scale it dropped at, after the dwell and with a tenth heavy at most, then the scale after twice the dwell. A
1364
1392
  * probe of the higher rate so never starts at full scale, and its failure changes no resolution.
@@ -1369,13 +1397,14 @@ function canReturn(g, at, scaleStep) {
1369
1397
  */
1370
1398
  function decide(ctx, g, at) {
1371
1399
  const { fps, scale } = ctx.state.rate;
1372
- if (shouldDrop(g)) {
1400
+ if (shouldDrop(ctx, g, at)) {
1373
1401
  const smaller = gpuBound(g) ? stepBelow(g.scales, scale) : void 0;
1374
1402
  const lower = stepBelow(g.ladder, fps);
1375
1403
  if (smaller !== void 0) switchRate(ctx, fps, smaller, "auto", "missed", at);
1376
1404
  else if (lower !== void 0) switchRate(ctx, lower, scale, "auto", "missed", at);
1377
1405
  return;
1378
1406
  }
1407
+ if (!Number.isNaN(g.lateSince)) return;
1379
1408
  const higher = stepAbove(g.ladder, fps);
1380
1409
  if (higher !== void 0 && canReturn(g, at, false)) {
1381
1410
  switchRate(ctx, higher, scale, "auto", "headroom", at);
@@ -1650,12 +1679,12 @@ function runFrameWork(ctx, at) {
1650
1679
  if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) {
1651
1680
  const { measure } = ctx.state;
1652
1681
  if (measure !== void 0 && measure.listeners.length > 0) {
1653
- runMeasuredFrame(ctx, measure, at ?? pageNow$1(), at === void 0);
1682
+ runMeasuredFrame(ctx, measure, at ?? pageNow(), at === void 0);
1654
1683
  return measure.record.workMs;
1655
1684
  }
1656
1685
  }
1657
1686
  runPhases(ctx);
1658
- return at !== void 0 && ctx.state.rate.governor !== void 0 ? pageNow$1() - at : 0;
1687
+ return at !== void 0 && ctx.state.rate.governor !== void 0 ? pageNow() - at : 0;
1659
1688
  }
1660
1689
  /**
1661
1690
  * Runs exactly one frame: advances `Time` by the scaled delta, then calls the six phases.
@@ -1849,7 +1878,7 @@ function createTimeApi(ctx) {
1849
1878
  }
1850
1879
  return noMeasure;
1851
1880
  },
1852
- agentClock: () => typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? pageNow$1 : void 0,
1881
+ agentClock: () => typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? pageNow : void 0,
1853
1882
  chargeAgent: (ms) => {
1854
1883
  if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) {
1855
1884
  const { measure } = ctx.state;
@@ -3050,7 +3079,7 @@ async function effectValue(state, log, descriptor, signal) {
3050
3079
  * @param work - The promise of the running effect.
3051
3080
  * @returns A promise that settles in the next `signals` phase after `work` settled.
3052
3081
  */
3053
- function enqueue$1(state, work) {
3082
+ function enqueue$2(state, work) {
3054
3083
  return new Promise((resolve, reject) => {
3055
3084
  const pending = {
3056
3085
  ready: false,
@@ -3191,7 +3220,7 @@ function createFxApi(ctx, deps) {
3191
3220
  const shown = descriptor.kind === "guide" ? beginGuide(state, signal) : signal;
3192
3221
  const work = effectValue(state, ctx.log, descriptor, shown);
3193
3222
  if (state.mode === "fast" || !ctx.deps.time.isRunning()) return work;
3194
- return enqueue$1(state, work);
3223
+ return enqueue$2(state, work);
3195
3224
  },
3196
3225
  buffer: (item) => {
3197
3226
  if (state.mode === "fast") return;
@@ -5507,7 +5536,7 @@ function startDeadline(timeoutMs) {
5507
5536
  * @param running - The promise of `run()`.
5508
5537
  * @returns `true` when the loop settled, `false` when the deadline won and the loop still runs.
5509
5538
  */
5510
- async function settle$2(ctx, running) {
5539
+ async function settle$3(ctx, running) {
5511
5540
  const quiet = running.then(noop, noop);
5512
5541
  const deadline = startDeadline(ctx.config.settleTimeoutMs);
5513
5542
  try {
@@ -5536,7 +5565,7 @@ async function stopRunner(ctx) {
5536
5565
  stop.abort("stop");
5537
5566
  state.abort = stop;
5538
5567
  }
5539
- const settled = running === void 0 || await settle$2(ctx, running);
5568
+ const settled = running === void 0 || await settle$3(ctx, running);
5540
5569
  state.running = void 0;
5541
5570
  ctx.state.gate.held = void 0;
5542
5571
  if (settled) state.abort = void 0;
@@ -14939,6 +14968,11 @@ function createHostApi(ctx, deps) {
14939
14968
  });
14940
14969
  return true;
14941
14970
  },
14971
+ initSource: (source) => {
14972
+ const app = state.app;
14973
+ if (!state.ready || app === void 0) return;
14974
+ app.renderer.texture.initSource(source);
14975
+ },
14942
14976
  clearsCanvas: () => state.ready && state.app?.renderer.background.clearBeforeRender === true,
14943
14977
  setResolution: (value, reason) => askResolution(state, value, reason),
14944
14978
  overrideResolution: (value) => {
@@ -15058,7 +15092,10 @@ const KEY = {
15058
15092
  qc: 25,
15059
15093
  qd: 26,
15060
15094
  qtx: 27,
15061
- qty: 28
15095
+ qty: 28,
15096
+ /** The canvas size at the take, CSS px: a picture cut to it is taken again when it changes (F53). */
15097
+ sw: 29,
15098
+ sh: 30
15062
15099
  };
15063
15100
  /** A whole number of texels within this much (P2.5). */
15064
15101
  const ON_GRID = 1 / 64;
@@ -15123,6 +15160,27 @@ function gridSpan(start, end, resolution) {
15123
15160
  };
15124
15161
  }
15125
15162
  /**
15163
+ * A span on the canvas cut to the canvas and the padding around it (F53). Pixi's live filter pass cuts its target to
15164
+ * the viewport and pads it after, so it draws what lies within the padding of the canvas too: a glow just off the
15165
+ * screen still reaches in. A span wholly outside comes back empty: its end is not after its start.
15166
+ *
15167
+ * @param start - The span's start on the canvas, CSS px.
15168
+ * @param end - Its end.
15169
+ * @param size - The canvas size on that axis, CSS px.
15170
+ * @param padding - The padding of the filters, CSS px.
15171
+ * @returns The part of the span kept.
15172
+ * @example
15173
+ * ```ts
15174
+ * cutSpan(-40, 120, 100, 10); // { start: -10, end: 110 }
15175
+ * ```
15176
+ */
15177
+ function cutSpan(start, end, size, padding) {
15178
+ return {
15179
+ start: Math.max(start, -padding),
15180
+ end: Math.min(end, size + padding)
15181
+ };
15182
+ }
15183
+ /**
15126
15184
  * The padding of a stack on the grid of its resolution: what stage 3 adds on each side of the subtree picture, so the
15127
15185
  * picture's bounds stay on that grid inside the output texture.
15128
15186
  *
@@ -15925,6 +15983,123 @@ function flushRanges(kctx) {
15925
15983
  }
15926
15984
  }
15927
15985
  //#endregion
15986
+ //#region src/plugins/renderer/kept/drawn-bounds.ts
15987
+ /** Every flag of a node's display status: visible, renderable, not culled (Pixi's `localDisplayStatus`). */
15988
+ const SHOWN = 7;
15989
+ /** The effects of a node that holds none. */
15990
+ const NO_EFFECTS = [];
15991
+ /** The matrix of each depth of the walk, from the top's space to the node's. Grown once, then reused. */
15992
+ const MATRICES = [];
15993
+ /** The bounds a masked node's subtree is gathered in, one per depth. Grown once, then reused. */
15994
+ const MASKED = [];
15995
+ /** The bounds the last walk answered, made once. */
15996
+ const RESULT = [];
15997
+ /**
15998
+ * The matrix of a depth, made the first time the walk reaches it.
15999
+ *
16000
+ * @param pixi - The module that draws.
16001
+ * @param depth - The depth under the top.
16002
+ * @returns The matrix.
16003
+ */
16004
+ function matrixAt(pixi, depth) {
16005
+ const known = MATRICES[depth];
16006
+ if (known !== void 0) return known;
16007
+ const made = new pixi.Matrix();
16008
+ MATRICES[depth] = made;
16009
+ return made;
16010
+ }
16011
+ /**
16012
+ * The bounds a masked node of a depth gathers its subtree in, made the first time the walk needs it.
16013
+ *
16014
+ * @param pixi - The module that draws.
16015
+ * @param depth - The depth under the top.
16016
+ * @returns The bounds, cleared.
16017
+ */
16018
+ function maskedAt(pixi, depth) {
16019
+ const known = MASKED[depth];
16020
+ if (known !== void 0) return known.clear();
16021
+ const made = new pixi.Bounds();
16022
+ MASKED[depth] = made;
16023
+ return made;
16024
+ }
16025
+ /**
16026
+ * Tells whether Pixi skips a node and its subtree: hidden, not renderable, culled, or not measured (a mask).
16027
+ *
16028
+ * @param node - A node under the top.
16029
+ * @returns True when it adds nothing.
16030
+ */
16031
+ function skipped(node) {
16032
+ return node.localDisplayStatus !== SHOWN || !node.measurable;
16033
+ }
16034
+ /**
16035
+ * Adds what a node draws itself and what its children draw to the bounds, under the node's matrix.
16036
+ *
16037
+ * @param pixi - The module that draws.
16038
+ * @param node - The node, shown.
16039
+ * @param into - The bounds so far.
16040
+ * @param depth - Its depth under the top.
16041
+ * @param top - The top.
16042
+ */
16043
+ function addContent(pixi, node, into, depth, top) {
16044
+ const matrix = matrixAt(pixi, depth);
16045
+ if (node.boundsArea) {
16046
+ into.addRect(node.boundsArea, matrix);
16047
+ return;
16048
+ }
16049
+ if (node.renderPipeId !== void 0) {
16050
+ const own = node.bounds;
16051
+ into.addFrame(own.minX, own.minY, own.maxX, own.maxY, matrix);
16052
+ }
16053
+ const children = node.children;
16054
+ for (let index = 0; index < children.length; index += 1) {
16055
+ const child = children[index];
16056
+ if (child !== void 0) addDrawn$1(pixi, child, into, depth + 1, top);
16057
+ }
16058
+ }
16059
+ /**
16060
+ * Adds what one node and its subtree draw to the bounds, in the top's space. The top itself is never skipped: it is
16061
+ * the root of the picture's render, drawn whatever its own flags say. A masked node gathers its subtree apart, then
16062
+ * the mask cuts it.
16063
+ *
16064
+ * @param pixi - The module that draws.
16065
+ * @param node - The node.
16066
+ * @param into - The bounds so far.
16067
+ * @param depth - Its depth under the top, 0 for the top.
16068
+ * @param top - The top: the space of the bounds, and the root a mask is measured from.
16069
+ */
16070
+ function addDrawn$1(pixi, node, into, depth, top) {
16071
+ if (depth > 0 && skipped(node)) return;
16072
+ const matrix = matrixAt(pixi, depth);
16073
+ if (depth === 0) matrix.identity();
16074
+ else {
16075
+ node.updateLocalTransform();
16076
+ matrix.appendFrom(node.localTransform, matrixAt(pixi, depth - 1));
16077
+ }
16078
+ const effects = node.effects ?? NO_EFFECTS;
16079
+ if (effects.length === 0) {
16080
+ addContent(pixi, node, into, depth, top);
16081
+ return;
16082
+ }
16083
+ const masked = maskedAt(pixi, depth);
16084
+ addContent(pixi, node, masked, depth, top);
16085
+ for (const effect of effects) effect.addLocalBounds?.(masked, top);
16086
+ if (masked.isValid) into.addFrame(masked.minX, masked.minY, masked.maxX, masked.maxY);
16087
+ }
16088
+ /**
16089
+ * The bounds of what a subtree draws, in its top's own space: no node Pixi skips, masks applied.
16090
+ *
16091
+ * @param pixi - The module that draws.
16092
+ * @param top - The top of the subtree.
16093
+ * @returns The bounds, one object the next walk writes again; empty (`isValid` false) when nothing is drawn.
16094
+ */
16095
+ function drawnBounds(pixi, top) {
16096
+ const into = RESULT[0] ?? new pixi.Bounds();
16097
+ RESULT[0] = into;
16098
+ into.clear();
16099
+ addDrawn$1(pixi, top, into, 0, top);
16100
+ return into;
16101
+ }
16102
+ //#endregion
15928
16103
  //#region src/plugins/renderer/kept/state.ts
15929
16104
  /**
15930
16105
  * @file renderer/kept — the state of the kept-picture cache, and its stop from state alone.
@@ -16081,6 +16256,7 @@ const NOW = {
16081
16256
  enabled: 0,
16082
16257
  rs: 1,
16083
16258
  P: 0,
16259
+ alpha: NaN,
16084
16260
  r: 1,
16085
16261
  a: 1,
16086
16262
  b: 0,
@@ -16109,27 +16285,42 @@ function resolutionOf$1(filter, resolution) {
16109
16285
  return own === "inherit" ? resolution : own;
16110
16286
  }
16111
16287
  /**
16112
- * Reads what the enabled filters of a stack draw with: how many, the smallest resolution, and the padding sum Pixi
16113
- * pads by (whole pixels).
16288
+ * Reads what the enabled filters of a stack draw with: how many, the smallest resolution, the padding sum Pixi
16289
+ * pads by (whole pixels), and the alpha they multiply by while every one is an `AlphaFilter`.
16114
16290
  *
16115
16291
  * @param slots - The record's slots.
16116
16292
  * @param resolution - The renderer resolution.
16293
+ * @param alphaFilter - Pixi's `AlphaFilter` class.
16117
16294
  * @param into - Where the numbers go.
16118
16295
  */
16119
- function readStack(slots, resolution, into) {
16296
+ function readStack(slots, resolution, alphaFilter, into) {
16120
16297
  let enabled = 0;
16121
16298
  let rs = Number.POSITIVE_INFINITY;
16122
16299
  let padding = 0;
16300
+ let alpha = 1;
16123
16301
  for (let index = 0; index < slots.length; index += 1) {
16124
16302
  const filter = slots[index]?.filter;
16125
16303
  if (filter === void 0 || !filter.enabled) continue;
16126
16304
  enabled += 1;
16127
16305
  rs = Math.min(rs, resolutionOf$1(filter, resolution));
16128
16306
  padding += filter.padding;
16307
+ alpha *= filter instanceof alphaFilter ? filter.alpha : NaN;
16129
16308
  }
16130
16309
  into.enabled = enabled;
16131
16310
  into.rs = enabled === 0 ? resolution : rs;
16132
16311
  into.P = Math.trunc(padding);
16312
+ into.alpha = alpha;
16313
+ }
16314
+ /**
16315
+ * Tells whether a stack draws as its subtree picture at an alpha (F54): every enabled filter an `AlphaFilter`, no
16316
+ * padding, at the renderer resolution. The quad then draws the picture itself at that alpha, as Pixi's alpha pass
16317
+ * draws its input: no stage 3, no pass when the alpha changes.
16318
+ *
16319
+ * @param now - This frame's numbers.
16320
+ * @returns True when it does.
16321
+ */
16322
+ function drawsDirect(now) {
16323
+ return !Number.isNaN(now.alpha) && now.P === 0 && now.rs === now.r;
16133
16324
  }
16134
16325
  /**
16135
16326
  * Tells whether a blend mode draws over what is under it as a picture does.
@@ -16283,7 +16474,7 @@ function holdsDisplay(kctx, top) {
16283
16474
  */
16284
16475
  function createRecord$1(kctx, pixi, entity, view, slots, category) {
16285
16476
  const top = view.wrapper ?? view.object;
16286
- const key = (/* @__PURE__ */ new Float64Array(29)).fill(NaN);
16477
+ const key = (/* @__PURE__ */ new Float64Array(31)).fill(NaN);
16287
16478
  const record = {
16288
16479
  entity,
16289
16480
  top,
@@ -16316,6 +16507,8 @@ function createRecord$1(kctx, pixi, entity, view, slots, category) {
16316
16507
  bytes: 0,
16317
16508
  settle: false,
16318
16509
  refused: 0,
16510
+ cut: false,
16511
+ direct: false,
16319
16512
  displays: void 0
16320
16513
  };
16321
16514
  const { state } = kctx;
@@ -16441,7 +16634,8 @@ function linearDiffers(key, now, base, rsAt) {
16441
16634
  */
16442
16635
  function neededBytes(record, now, box) {
16443
16636
  const { w1, h1, Ps } = box;
16444
- let bytes = textureBytes(w1, h1, now.rs) + textureBytes(w1 + 2 * Ps, h1 + 2 * Ps, now.r);
16637
+ let bytes = textureBytes(w1, h1, now.rs);
16638
+ if (!drawsDirect(now)) bytes += textureBytes(w1 + 2 * Ps, h1 + 2 * Ps, now.r);
16445
16639
  const first = firstInputs(record.slots);
16446
16640
  if (first !== void 0) for (let index = 0; index < first.inputs.count; index += 1) {
16447
16641
  const scale = first.inputs.scaleOf(index) ?? 0;
@@ -16508,14 +16702,22 @@ function writeTakeKeys(record, now, box) {
16508
16702
  function takePicture(kctx, pixi, matrices, record, view, now) {
16509
16703
  const { top } = record;
16510
16704
  if (!kctx.ctx.config.onDemand && holdsDisplay(kctx, top)) return false;
16511
- const local = clippedToArea(top.getLocalBounds(), top.filterArea);
16705
+ const drawn = drawnBounds(pixi, top);
16706
+ if (!drawn.isValid) return false;
16707
+ const local = clippedToArea(drawn, top.filterArea);
16512
16708
  if (isEmptyBounds(local)) return false;
16513
16709
  const box = linearBounds(now.a, now.b, now.c, now.d, local);
16514
- const x = gridSpan(now.tx + box.x0, now.tx + box.x1, now.rs);
16515
- const y = gridSpan(now.ty + box.y0, now.ty + box.y1, now.rs);
16710
+ const screen = kctx.host.screen();
16711
+ const width = screen?.width ?? Number.POSITIVE_INFINITY;
16712
+ const height = screen?.height ?? Number.POSITIVE_INFINITY;
16713
+ const Ps = paddingOnGrid(now.P, now.rs);
16714
+ const across = cutSpan(now.tx + box.x0, now.tx + box.x1, width, Ps);
16715
+ const down = cutSpan(now.ty + box.y0, now.ty + box.y1, height, Ps);
16716
+ if (isEmptySpan(across) || isEmptySpan(down)) return false;
16717
+ const x = gridSpan(across.start, across.end, now.rs);
16718
+ const y = gridSpan(down.start, down.end, now.rs);
16516
16719
  const w1 = x.texels / now.rs;
16517
16720
  const h1 = y.texels / now.rs;
16518
- const Ps = paddingOnGrid(now.P, now.rs);
16519
16721
  if (!fitsTexture(w1, h1, now.rs) || !fitsTexture(w1 + 2 * Ps, h1 + 2 * Ps, now.r)) return false;
16520
16722
  const needed = neededBytes(record, now, {
16521
16723
  w1,
@@ -16528,6 +16730,11 @@ function takePicture(kctx, pixi, matrices, record, view, now) {
16528
16730
  return false;
16529
16731
  }
16530
16732
  record.refused = 0;
16733
+ const cutAcross = across.start > now.tx + box.x0 || across.end < now.tx + box.x1;
16734
+ const cutDown = down.start > now.ty + box.y0 || down.end < now.ty + box.y1;
16735
+ record.cut = cutAcross || cutDown;
16736
+ record.key[KEY.sw] = width;
16737
+ record.key[KEY.sh] = height;
16531
16738
  writeTakeKeys(record, now, {
16532
16739
  x,
16533
16740
  y,
@@ -16540,6 +16747,21 @@ function takePicture(kctx, pixi, matrices, record, view, now) {
16540
16747
  return ok;
16541
16748
  }
16542
16749
  /**
16750
+ * Tells whether a span holds nothing: its end is not after its start. Pure.
16751
+ *
16752
+ * @param span - The span.
16753
+ * @param span.start - Its start.
16754
+ * @param span.end - Its end.
16755
+ * @returns True when it is empty.
16756
+ * @example
16757
+ * ```ts
16758
+ * isEmptySpan({ start: -10, end: -10 }); // true
16759
+ * ```
16760
+ */
16761
+ function isEmptySpan(span) {
16762
+ return span.end <= span.start;
16763
+ }
16764
+ /**
16543
16765
  * The local bounds of a top cut to its filter area, both in the top's own units: its filters draw nothing outside
16544
16766
  * the area. With no area the bounds are returned as they are. Pure.
16545
16767
  *
@@ -16591,6 +16813,13 @@ function renderStages(kctx, pixi, matrices, record, view, now) {
16591
16813
  if (live) handFilters(kctx, pixi, record, view);
16592
16814
  const first = firstInputs(record.slots);
16593
16815
  let ok = renderStage1(kctx, pixi, matrices, record);
16816
+ if (drawsDirect(now)) {
16817
+ goDirect(kctx, record);
16818
+ account(kctx, record);
16819
+ if (!ok && live) putFiltersBack(kctx, record, view);
16820
+ return ok;
16821
+ }
16822
+ record.direct = false;
16594
16823
  if (ok && first !== void 0) renderStage2(kctx, pixi, matrices, record, {
16595
16824
  inputs: first.inputs,
16596
16825
  resolution: now.r,
@@ -16599,13 +16828,36 @@ function renderStages(kctx, pixi, matrices, record, view, now) {
16599
16828
  else if (record.s2Of !== void 0) releaseInputs(kctx, record);
16600
16829
  ok = ok && renderStage3(kctx, pixi, matrices, record, now);
16601
16830
  account(kctx, record);
16602
- if (!ok && live) {
16603
- if (record.standIn !== void 0) record.standIn.filters = null;
16604
- kctx.sync.applyFilters(view, filtersOf$1(kctx.ctx.state.sync, record.entity), noMark$1, record.top);
16605
- }
16831
+ if (!ok && live) putFiltersBack(kctx, record, view);
16606
16832
  return ok;
16607
16833
  }
16608
16834
  /**
16835
+ * Puts the filters of a live record whose take failed back on its top: it draws live again.
16836
+ *
16837
+ * @param kctx - The kept context.
16838
+ * @param record - The record.
16839
+ * @param view - Its view.
16840
+ */
16841
+ function putFiltersBack(kctx, record, view) {
16842
+ if (record.standIn !== void 0) record.standIn.filters = null;
16843
+ kctx.sync.applyFilters(view, filtersOf$1(kctx.ctx.state.sync, record.entity), noMark$1, record.top);
16844
+ }
16845
+ /**
16846
+ * Lets a record draw its subtree picture itself (F54): the pre-pass results and the filter output go, and the quad
16847
+ * shows the picture at the stack's alpha from now on.
16848
+ *
16849
+ * @param kctx - The kept context.
16850
+ * @param record - The record.
16851
+ */
16852
+ function goDirect(kctx, record) {
16853
+ if (record.s2Of !== void 0) releaseInputs(kctx, record);
16854
+ if (record.s3 !== void 0) {
16855
+ dropTexture(kctx, record.s3);
16856
+ record.s3 = void 0;
16857
+ }
16858
+ record.direct = true;
16859
+ }
16860
+ /**
16609
16861
  * Ends the loan of stage 2 when the first filter with inputs left the stack: the results are destroyed.
16610
16862
  *
16611
16863
  * @param kctx - The kept context.
@@ -16641,7 +16893,8 @@ function account(kctx, record) {
16641
16893
  function placeQuad(sync, pixi, matrices, record, view) {
16642
16894
  const { top, key } = record;
16643
16895
  const parent = top.parent;
16644
- const output = record.s3 ?? pixi.Texture.EMPTY;
16896
+ const output = (record.direct ? record.s1 : record.s3) ?? pixi.Texture.EMPTY;
16897
+ const alpha = record.direct ? NOW.alpha : 1;
16645
16898
  if (record.quad === void 0 || record.quad.destroyed) {
16646
16899
  record.quad = new pixi.Sprite(output);
16647
16900
  record.quad.label = `kept:${labelOf$3(top)}`;
@@ -16655,8 +16908,9 @@ function placeQuad(sync, pixi, matrices, record, view) {
16655
16908
  }
16656
16909
  if (quad.zIndex !== top.zIndex) quad.zIndex = top.zIndex;
16657
16910
  if (quad.blendMode !== top.blendMode) quad.blendMode = top.blendMode;
16911
+ if (quad.alpha !== alpha) quad.alpha = alpha;
16658
16912
  const inverse = parent.getGlobalTransform(matrices.parent, false).invert();
16659
- const Ps = key[KEY.Ps] ?? 0;
16913
+ const Ps = record.direct ? 0 : key[KEY.Ps] ?? 0;
16660
16914
  const x = (key[KEY.X0] ?? 0) - Ps + NOW.tx - (key[KEY.tx] ?? 0);
16661
16915
  const y = (key[KEY.Y0] ?? 0) - Ps + NOW.ty - (key[KEY.ty] ?? 0);
16662
16916
  const { local } = matrices;
@@ -16800,6 +17054,17 @@ function noteMove(kctx, record) {
16800
17054
  if (record.settle) kctx.demand.request();
16801
17055
  }
16802
17056
  /**
17057
+ * Tells whether the canvas has another size than at the take.
17058
+ *
17059
+ * @param kctx - The kept context.
17060
+ * @param key - The record's key.
17061
+ * @returns True when it does.
17062
+ */
17063
+ function canvasChanged(kctx, key) {
17064
+ const screen = kctx.host.screen();
17065
+ return screen !== void 0 && (screen.width !== key[KEY.sw] || screen.height !== key[KEY.sh]);
17066
+ }
17067
+ /**
16803
17068
  * A kept record's frame (P4.3): a stage-1 change retakes (three in a row go live and calm); otherwise a new chain
16804
17069
  * version redoes stages 2 and 3, a filter value, the resolution, the padding or (placed) a move stage 3; at rest after
16805
17070
  * an off-grid move one settle retake is owed.
@@ -16816,8 +17081,12 @@ function stepKept$1(kctx, pixi, matrices, record, view) {
16816
17081
  churn(kctx, pixi, matrices, record, view);
16817
17082
  return;
16818
17083
  }
16819
- record.churn = 0;
16820
17084
  const moved = NOW.tx !== key[KEY.lastTx] || NOW.ty !== key[KEY.lastTy];
17085
+ if (record.cut && (moved || canvasChanged(kctx, key))) {
17086
+ churn(kctx, pixi, matrices, record, view);
17087
+ return;
17088
+ }
17089
+ record.churn = 0;
16821
17090
  if (!moved && record.settle) {
16822
17091
  retakeOrGoLive(kctx, pixi, matrices, record, view);
16823
17092
  return;
@@ -16835,6 +17104,12 @@ function stepKept$1(kctx, pixi, matrices, record, view) {
16835
17104
  */
16836
17105
  function refresh$1(kctx, pixi, matrices, record) {
16837
17106
  const { key } = record;
17107
+ if (drawsDirect(NOW)) {
17108
+ if (record.direct) return;
17109
+ goDirect(kctx, record);
17110
+ account(kctx, record);
17111
+ return;
17112
+ }
16838
17113
  const inputs = firstInputs(record.slots)?.inputs;
16839
17114
  const stageTwoOwed = inputs !== void 0 && (record.dirtyS2 || inputs !== record.s2Of || inputs.version() !== record.s2InputsVersion || record.s1Version !== record.s2PictureVersion);
16840
17115
  const inputsLeft = inputs === void 0 && record.s2Of !== void 0;
@@ -16846,6 +17121,7 @@ function refresh$1(kctx, pixi, matrices, record) {
16846
17121
  padding: NOW.P
16847
17122
  });
16848
17123
  if (!stageThreeOwed) return;
17124
+ record.direct = false;
16849
17125
  renderStage3(kctx, pixi, matrices, record, NOW);
16850
17126
  account(kctx, record);
16851
17127
  if (!stageTwoOwed) kctx.state.refreshesOpen += 1;
@@ -16915,7 +17191,7 @@ function stepCalm(kctx, record) {
16915
17191
  function stepRecord(kctx, pixi, matrices, record, view) {
16916
17192
  noteDrawing(kctx, record, BEFORE);
16917
17193
  NOW.r = kctx.host.resolution();
16918
- readStack(record.slots, NOW.r, NOW);
17194
+ readStack(record.slots, NOW.r, pixi.AlphaFilter, NOW);
16919
17195
  if (eligible(kctx, record, view, NOW)) stepEligible(kctx, pixi, matrices, record, view);
16920
17196
  else stepIneligible(kctx, record, view);
16921
17197
  if (drawingChanged(kctx, record, BEFORE)) dirtyAbove(kctx, record);
@@ -17581,31 +17857,6 @@ function stopDemand(state) {
17581
17857
  state.verify.reference = void 0;
17582
17858
  }
17583
17859
  //#endregion
17584
- //#region src/plugins/renderer/monitor/clock.ts
17585
- /**
17586
- * @file renderer/monitor — the one renderer file that reads the page clock; lint L3 ignores it (eslint `ignores`). It
17587
- * measures the page in agent builds: the agent phase, a line's `t`, an event's time between frames. Never a game's
17588
- * state, whose time is `clock`.
17589
- */
17590
- /**
17591
- * Reads `globalThis.performance.now()` at call time, so a test stubs it.
17592
- *
17593
- * @returns Page-clock milliseconds.
17594
- */
17595
- function pageNow() {
17596
- return globalThis.performance.now();
17597
- }
17598
- /**
17599
- * Turns page-clock milliseconds into page epoch milliseconds, `performance.timeOrigin + ms`, rounded to 0.01 ms: the
17600
- * `t` of every log line.
17601
- *
17602
- * @param pageMs - Page-clock milliseconds.
17603
- * @returns Epoch milliseconds.
17604
- */
17605
- function epochOf(pageMs) {
17606
- return Math.round((globalThis.performance.timeOrigin + pageMs) * 100) / 100;
17607
- }
17608
- //#endregion
17609
17860
  //#region src/plugins/renderer/reasons.ts
17610
17861
  /**
17611
17862
  * @file renderer plugin — the reasons a frame is drawn, in ring order: the monitor stores a reason by its index. A
@@ -19265,9 +19516,9 @@ function wrapDevice(pc, device) {
19265
19516
  * @returns The pipeline.
19266
19517
  */
19267
19518
  value: function countedPipeline(descriptor) {
19268
- const started = pageNow();
19519
+ const started = pageNow$1();
19269
19520
  const made = createRenderPipeline.call(this, descriptor);
19270
- recordCompile(sinks, pc.perf.frame, descriptor.label ?? null, pageNow() - started);
19521
+ recordCompile(sinks, pc.perf.frame, descriptor.label ?? null, pageNow$1() - started);
19271
19522
  return made;
19272
19523
  }
19273
19524
  });
@@ -19282,9 +19533,9 @@ function wrapDevice(pc, device) {
19282
19533
  * @returns The module.
19283
19534
  */
19284
19535
  value: function countedModule(descriptor) {
19285
- const started = pageNow();
19536
+ const started = pageNow$1();
19286
19537
  const made = createShaderModule.call(this, descriptor);
19287
- recordModule(sinks, pc.perf.frame, descriptor.label ?? null, pageNow() - started);
19538
+ recordModule(sinks, pc.perf.frame, descriptor.label ?? null, pageNow$1() - started);
19288
19539
  return made;
19289
19540
  }
19290
19541
  });
@@ -19312,9 +19563,9 @@ function wrapGl(pc, gl) {
19312
19563
  * @param program - The program.
19313
19564
  */
19314
19565
  value: function countedLink(program) {
19315
- const started = pageNow();
19566
+ const started = pageNow$1();
19316
19567
  linkProgram.call(this, program);
19317
- recordCompile(sinks, pc.perf.frame, null, pageNow() - started);
19568
+ recordCompile(sinks, pc.perf.frame, null, pageNow$1() - started);
19318
19569
  }
19319
19570
  });
19320
19571
  return () => {
@@ -19980,7 +20231,7 @@ function headerLine(pc, thermal) {
19980
20231
  return {
19981
20232
  v: 1,
19982
20233
  kind: "header",
19983
- t: epochOf(pageNow()),
20234
+ t: epochOf(pageNow$1()),
19984
20235
  build: buildId(),
19985
20236
  timeOrigin: globalThis.performance.timeOrigin,
19986
20237
  engine: typeof __MOKU_GAME_ENGINE__ === "string" ? __MOKU_GAME_ENGINE__ : null,
@@ -20500,7 +20751,7 @@ function eventLine(name, detail, frame) {
20500
20751
  return {
20501
20752
  v: 1,
20502
20753
  kind: "event",
20503
- t: epochOf(pageNow()),
20754
+ t: epochOf(pageNow$1()),
20504
20755
  build: buildId(),
20505
20756
  frame,
20506
20757
  name,
@@ -20518,7 +20769,7 @@ function markLine(label, auto) {
20518
20769
  return {
20519
20770
  v: 1,
20520
20771
  kind: "mark",
20521
- t: epochOf(pageNow()),
20772
+ t: epochOf(pageNow$1()),
20522
20773
  build: buildId(),
20523
20774
  label,
20524
20775
  auto
@@ -20535,7 +20786,7 @@ function thermalLine(reading) {
20535
20786
  return {
20536
20787
  v: 1,
20537
20788
  kind: "thermal",
20538
- t: epochOf(pageNow()),
20789
+ t: epochOf(pageNow$1()),
20539
20790
  build: buildId(),
20540
20791
  status: reading.status,
20541
20792
  headroom: reading.headroom,
@@ -20677,7 +20928,7 @@ function buildStartup(pc) {
20677
20928
  return {
20678
20929
  v: 1,
20679
20930
  kind: "startup",
20680
- t: epochOf(pageNow()),
20931
+ t: epochOf(pageNow$1()),
20681
20932
  build: buildId(),
20682
20933
  htmlMs: responseEnd === void 0 ? null : roundTo(responseEnd, 2),
20683
20934
  script,
@@ -21104,7 +21355,7 @@ function writeKept(pc, slot) {
21104
21355
  * @param section - The `COST` index the time goes to.
21105
21356
  */
21106
21357
  function lap(perf, section) {
21107
- const now = pageNow();
21358
+ const now = pageNow$1();
21108
21359
  perf.sectionsMs[section] = cellF64(perf.sectionsMs, section) + now - cellF64(perf.staged, 2);
21109
21360
  perf.staged[2] = now;
21110
21361
  }
@@ -21496,7 +21747,7 @@ function commitFrame(pc, measure) {
21496
21747
  const sections = sinks.sections;
21497
21748
  const slot = ring.written % ring.capacity;
21498
21749
  const previous = ring.size === 0 ? -1 : ring.newest;
21499
- perf.staged[2] = pageNow();
21750
+ perf.staged[2] = pageNow$1();
21500
21751
  stageBudget(perf, measure);
21501
21752
  writeMeasure(pc, slot, measure);
21502
21753
  writeKept(pc, slot);
@@ -22116,7 +22367,7 @@ function notePauses(pc, reasons, paused, resumed) {
22116
22367
  const detail = { reasons: [...reasons] };
22117
22368
  if (paused) {
22118
22369
  writeEvent(pc, "pause", detail);
22119
- pc.perf.staged[1] = pageNow();
22370
+ pc.perf.staged[1] = pageNow$1();
22120
22371
  flushPerf(pc);
22121
22372
  } else if (resumed) {
22122
22373
  writeEvent(pc, "resume", detail);
@@ -22138,7 +22389,7 @@ function noteChange(perf, change) {
22138
22389
  * @param perf - The perf state.
22139
22390
  */
22140
22391
  function beginAgent(perf) {
22141
- if (Number.isNaN(perf.agentStart)) perf.agentStart = pageNow();
22392
+ if (Number.isNaN(perf.agentStart)) perf.agentStart = pageNow$1();
22142
22393
  }
22143
22394
  /**
22144
22395
  * Closes the open agent span: its time goes into the agent phase of the frame interval it falls in, and to
@@ -22149,7 +22400,7 @@ function beginAgent(perf) {
22149
22400
  function endAgent(pc) {
22150
22401
  const { perf } = pc;
22151
22402
  if (Number.isNaN(perf.agentStart)) return;
22152
- const span = pageNow() - perf.agentStart;
22403
+ const span = pageNow$1() - perf.agentStart;
22153
22404
  perf.agentStart = NaN;
22154
22405
  perf.agentMs += span;
22155
22406
  pc.ctx.deps.time.chargeAgent(span);
@@ -22192,7 +22443,7 @@ function attachFirst(pc) {
22192
22443
  writeEvent(pc, "device-restored", { kind: host.kind() });
22193
22444
  });
22194
22445
  host.onTrim((reason, freedMb) => {
22195
- perf.problems.trimAt = pageNow();
22446
+ perf.problems.trimAt = pageNow$1();
22196
22447
  writeEvent(pc, "pool-trim", {
22197
22448
  reason,
22198
22449
  freedMb
@@ -22534,6 +22785,17 @@ function createMonitorApi(ctx, deps) {
22534
22785
  }
22535
22786
  if (pc === void 0) sinks.inFrame = false;
22536
22787
  };
22788
+ /**
22789
+ * The agent span: `beginAgent` and `endAgent`, also the span the captures are taken in.
22790
+ */
22791
+ const agentSpan = {
22792
+ begin: () => {
22793
+ if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) beginAgent(pc.perf);
22794
+ },
22795
+ end: () => {
22796
+ if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) endAgent(pc);
22797
+ }
22798
+ };
22537
22799
  return {
22538
22800
  stats: () => {
22539
22801
  const usage = deps.host.textures();
@@ -22587,12 +22849,8 @@ function createMonitorApi(ctx, deps) {
22587
22849
  event: (name, detail = NO_DETAIL) => {
22588
22850
  if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) writeEvent(pc, name, detail);
22589
22851
  },
22590
- beginAgent: () => {
22591
- if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) beginAgent(pc.perf);
22592
- },
22593
- endAgent: () => {
22594
- if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) endAgent(pc);
22595
- },
22852
+ beginAgent: agentSpan.begin,
22853
+ endAgent: agentSpan.end,
22596
22854
  sections: (next) => {
22597
22855
  if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) {
22598
22856
  if (next === void 0) return pc.perf.sections;
@@ -22626,7 +22884,7 @@ function createMonitorApi(ctx, deps) {
22626
22884
  state.lastDrawn = drawn;
22627
22885
  notePasses(drawn);
22628
22886
  if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) pc.perf.drawn = drawn;
22629
- if (drawn) serveCaptures(mctx);
22887
+ if (drawn) serveCaptures(mctx, agentSpan);
22630
22888
  },
22631
22889
  waiting: () => state.captures.length > 0,
22632
22890
  changed: (change) => {
@@ -22647,7 +22905,7 @@ function createMonitorApi(ctx, deps) {
22647
22905
  },
22648
22906
  flush: () => {
22649
22907
  if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && pc !== void 0) {
22650
- pc.perf.staged[1] = pageNow();
22908
+ pc.perf.staged[1] = pageNow$1();
22651
22909
  flushPerf(pc);
22652
22910
  }
22653
22911
  },
@@ -23728,7 +23986,7 @@ function variantsOf(sctx) {
23728
23986
  * @param subject - The subject.
23729
23987
  * @param variants - Its variants.
23730
23988
  */
23731
- function enqueue(warm, subject, variants) {
23989
+ function enqueue$1(warm, subject, variants) {
23732
23990
  for (const variant of variants) warm.queue.push({
23733
23991
  subject,
23734
23992
  variant
@@ -23770,7 +24028,7 @@ function abandon(warm) {
23770
24028
  *
23771
24029
  * @param sctx - Domain context of the sync module.
23772
24030
  */
23773
- function startWarm$1(sctx) {
24031
+ function startWarm$2(sctx) {
23774
24032
  const state = sctx.ctx.state.sync;
23775
24033
  const { warm } = state;
23776
24034
  dropObjects(warm);
@@ -23781,12 +24039,12 @@ function startWarm$1(sctx) {
23781
24039
  warm.failed = false;
23782
24040
  if (!canWarm(sctx)) return;
23783
24041
  const variants = variantsOf(sctx);
23784
- for (const subject of CONTENT) enqueue(warm, subject, variants);
24042
+ for (const subject of CONTENT) enqueue$1(warm, subject, variants);
23785
24043
  const [font] = state.fonts.keys();
23786
24044
  if (font !== void 0) {
23787
24045
  warm.textFont = font;
23788
24046
  warm.textQueued = true;
23789
- enqueue(warm, "text", variants);
24047
+ enqueue$1(warm, "text", variants);
23790
24048
  }
23791
24049
  for (const filter of warm.filters) {
23792
24050
  const entry = warm.waiting.get(filter);
@@ -23795,7 +24053,7 @@ function startWarm$1(sctx) {
23795
24053
  resolve: []
23796
24054
  });
23797
24055
  else entry.left = variants.length;
23798
- enqueue(warm, filter, variants);
24056
+ enqueue$1(warm, filter, variants);
23799
24057
  }
23800
24058
  sctx.deps.demand.request();
23801
24059
  }
@@ -23843,7 +24101,7 @@ function warmFilter(sctx, filter) {
23843
24101
  entry.left = variants.length;
23844
24102
  warm.filters.add(filter);
23845
24103
  warm.waiting.set(filter, entry);
23846
- enqueue(warm, filter, variants);
24104
+ enqueue$1(warm, filter, variants);
23847
24105
  sctx.deps.demand.request();
23848
24106
  }
23849
24107
  return new Promise((resolve) => {
@@ -24153,6 +24411,419 @@ function clearFonts(state) {
24153
24411
  state.fontCache = void 0;
24154
24412
  }
24155
24413
  //#endregion
24414
+ //#region src/plugins/renderer/sync/outline.ts
24415
+ /** Above every child of the wrapper, whatever `Order` a child carries. */
24416
+ const OUTLINE_Z = Number.MAX_SAFE_INTEGER;
24417
+ const CYAN = 65535;
24418
+ const RED = 16711680;
24419
+ /**
24420
+ * Writes the slice widths a texture asks for, in the units it is drawn in: its default borders divided
24421
+ * by the resolution of its source. A texture without borders gets 0 on every side, never Pixi's 10. Written in place,
24422
+ * so a nine-slice write allocates nothing.
24423
+ *
24424
+ * @param texture - The texture a nine-slice draws, or `undefined` while nothing can draw.
24425
+ * @param out - The widths to write.
24426
+ * @returns `out`.
24427
+ */
24428
+ function bordersInto(texture, out) {
24429
+ const borders = texture?.defaultBorders;
24430
+ if (texture === void 0 || borders === void 0) {
24431
+ out.left = 0;
24432
+ out.top = 0;
24433
+ out.right = 0;
24434
+ out.bottom = 0;
24435
+ return out;
24436
+ }
24437
+ const resolution = texture.source.resolution > 0 ? texture.source.resolution : 1;
24438
+ out.left = borders.left / resolution;
24439
+ out.top = borders.top / resolution;
24440
+ out.right = borders.right / resolution;
24441
+ out.bottom = borders.bottom / resolution;
24442
+ return out;
24443
+ }
24444
+ /**
24445
+ * The factor Pixi shrinks the corners of a nine-slice by when two of them would overlap.
24446
+ *
24447
+ * @param size - The drawn size.
24448
+ * @param size.width - Drawn width.
24449
+ * @param size.height - Drawn height.
24450
+ * @param borders - The slice widths.
24451
+ * @returns 1 when the corners fit, less when they overlap.
24452
+ * @example
24453
+ * ```ts
24454
+ * sliceScale({ width: 100, height: 200 }, { left: 72, top: 76, right: 72, bottom: 76 }); // 0.6944
24455
+ * ```
24456
+ */
24457
+ function sliceScale(size, borders) {
24458
+ const across = borders.left + borders.right;
24459
+ const down = borders.top + borders.bottom;
24460
+ const scaleX = across > 0 && size.width < across ? size.width / across : 1;
24461
+ const scaleY = down > 0 && size.height < down ? size.height / down : 1;
24462
+ return Math.min(scaleX, scaleY);
24463
+ }
24464
+ /**
24465
+ * Strokes the bounds of a nine-slice and the four lines Pixi cuts it at, into the wrapper of its
24466
+ * view: cyan, red when the corners overlap or the texture is missing. The graphics is kept on the
24467
+ * view and redrawn in place.
24468
+ *
24469
+ * @param pixi - The Pixi module the renderer loaded.
24470
+ * @param wrapper - The wrapper of the view, which carries its pose.
24471
+ * @param view - The view of the nine-slice.
24472
+ * @param shape - Size, borders and whether the texture is missing.
24473
+ */
24474
+ function drawOutline(pixi, wrapper, view, shape) {
24475
+ const outline = view.outline ?? new pixi.Graphics();
24476
+ const { width, height, borders } = shape;
24477
+ const scale = sliceScale(shape, borders);
24478
+ const left = borders.left * scale;
24479
+ const right = width - borders.right * scale;
24480
+ const top = borders.top * scale;
24481
+ const bottom = height - borders.bottom * scale;
24482
+ outline.label = `outline#${shape.entity}`;
24483
+ outline.zIndex = OUTLINE_Z;
24484
+ outline.clear();
24485
+ outline.rect(0, 0, width, height).moveTo(left, 0).lineTo(left, height).moveTo(right, 0).lineTo(right, height).moveTo(0, top).lineTo(width, top).moveTo(0, bottom).lineTo(width, bottom);
24486
+ outline.stroke({
24487
+ color: shape.missing || scale < 1 ? RED : CYAN,
24488
+ width: 1,
24489
+ pixelLine: true
24490
+ });
24491
+ if (outline.parent !== wrapper) wrapper.addChild(outline);
24492
+ view.outline = outline;
24493
+ }
24494
+ //#endregion
24495
+ //#region src/plugins/renderer/sync/hollow.ts
24496
+ /** The vertices across and down: the four cut lines of a nine-slice on each axis. */
24497
+ const SIDE = 4;
24498
+ /** The cells of the 3 × 3 slicing on each axis. */
24499
+ const CELLS = 3;
24500
+ /** The cell left out, row by row: the centre, (1, 1). */
24501
+ const CENTRE = 4;
24502
+ /** Indices of one quad: two triangles. */
24503
+ const QUAD = 6;
24504
+ /**
24505
+ * The index buffer of the rim: the two triangles of every cell of the 3 × 3 slicing but the centre, in Pixi's
24506
+ * `PlaneGeometry` winding. Static: written once per geometry.
24507
+ *
24508
+ * @returns 48 indices into the 16 vertices.
24509
+ * @example
24510
+ * ```ts
24511
+ * rimIndices().length; // 48
24512
+ * ```
24513
+ */
24514
+ function rimIndices() {
24515
+ const indices = /* @__PURE__ */ new Uint32Array(48);
24516
+ let at = 0;
24517
+ for (let cell = 0; cell < 9; cell += 1) {
24518
+ if (cell === CENTRE) continue;
24519
+ const topLeft = Math.floor(cell / CELLS) * SIDE + cell % CELLS;
24520
+ const bottomLeft = topLeft + SIDE;
24521
+ indices.set([
24522
+ topLeft,
24523
+ topLeft + 1,
24524
+ bottomLeft,
24525
+ topLeft + 1,
24526
+ bottomLeft + 1,
24527
+ bottomLeft
24528
+ ], at);
24529
+ at += QUAD;
24530
+ }
24531
+ return indices;
24532
+ }
24533
+ /**
24534
+ * Writes one row of the grid: its four vertices, one per cut line across, at the same `y`.
24535
+ *
24536
+ * @param target - The 32 numbers of the grid: x and y of each vertex, row by row.
24537
+ * @param row - The row, 0 to 3.
24538
+ * @param y - The row's line down.
24539
+ * @param across - The cut lines across.
24540
+ */
24541
+ function writeRow(target, row, y, across) {
24542
+ const at = row * SIDE * 2;
24543
+ target[at] = across.start;
24544
+ target[at + 1] = y;
24545
+ target[at + 2] = across.near;
24546
+ target[at + 3] = y;
24547
+ target[at + 4] = across.far;
24548
+ target[at + 5] = y;
24549
+ target[at + 6] = across.end;
24550
+ target[at + 7] = y;
24551
+ }
24552
+ /**
24553
+ * Writes the 4 × 4 grid of the cut lines in place: Pixi's vertex order, row by row.
24554
+ *
24555
+ * @param target - The 32 numbers of the grid.
24556
+ * @param across - The cut lines across.
24557
+ * @param down - The cut lines down.
24558
+ */
24559
+ function writeGrid(target, across, down) {
24560
+ writeRow(target, 0, down.start, across);
24561
+ writeRow(target, 1, down.near, across);
24562
+ writeRow(target, 2, down.far, across);
24563
+ writeRow(target, 3, down.end, across);
24564
+ }
24565
+ /**
24566
+ * Makes the class of the rim-only nine-slice over one loaded Pixi module.
24567
+ *
24568
+ * @param pixi - The Pixi module the renderer loaded.
24569
+ * @returns The class.
24570
+ */
24571
+ function createHollowClass(pixi) {
24572
+ /** A nine-slice that draws its rim alone: Pixi's mesh over the 16 vertices of the slicing and its eight border cells. */
24573
+ class HollowNineSlice extends pixi.Mesh {
24574
+ /** Tells the rim-only object from Pixi's `NineSliceSprite`. */
24575
+ hollow = true;
24576
+ /** The slice widths, in the units the panel is drawn in. */
24577
+ borders = {
24578
+ left: 0,
24579
+ top: 0,
24580
+ right: 0,
24581
+ bottom: 0
24582
+ };
24583
+ /** The cut lines across, scratch of the next write. */
24584
+ across = {
24585
+ start: 0,
24586
+ near: 0,
24587
+ far: 0,
24588
+ end: 0
24589
+ };
24590
+ /** The cut lines down, scratch of the next write. */
24591
+ down = {
24592
+ start: 0,
24593
+ near: 0,
24594
+ far: 0,
24595
+ end: 0
24596
+ };
24597
+ /** The panel's width the positions are laid out for. */
24598
+ boxWidth = 0;
24599
+ /** The panel's height the positions are laid out for. */
24600
+ boxHeight = 0;
24601
+ /** False while Pixi's constructor writes the first texture: the geometry does not exist yet. */
24602
+ built = false;
24603
+ /**
24604
+ * Builds the mesh over a geometry of its own, 0 × 0 with no border, its uvs cut for the texture.
24605
+ *
24606
+ * @param texture - The texture it draws first.
24607
+ */
24608
+ constructor(texture) {
24609
+ super({
24610
+ geometry: new pixi.MeshGeometry({
24611
+ positions: /* @__PURE__ */ new Float32Array(32),
24612
+ uvs: /* @__PURE__ */ new Float32Array(32),
24613
+ indices: rimIndices()
24614
+ }),
24615
+ texture
24616
+ });
24617
+ this.built = true;
24618
+ this.layoutUvs();
24619
+ }
24620
+ /**
24621
+ * The width of the left border column.
24622
+ *
24623
+ * @returns Units.
24624
+ */
24625
+ get leftWidth() {
24626
+ return this.borders.left;
24627
+ }
24628
+ /**
24629
+ * Sets the width of the left border column: the positions and the uvs are written again.
24630
+ *
24631
+ * @param value - Units.
24632
+ */
24633
+ set leftWidth(value) {
24634
+ if (this.borders.left === value) return;
24635
+ this.borders.left = value;
24636
+ this.layoutAll();
24637
+ }
24638
+ /**
24639
+ * The height of the top border row.
24640
+ *
24641
+ * @returns Units.
24642
+ */
24643
+ get topHeight() {
24644
+ return this.borders.top;
24645
+ }
24646
+ /**
24647
+ * Sets the height of the top border row: the positions and the uvs are written again.
24648
+ *
24649
+ * @param value - Units.
24650
+ */
24651
+ set topHeight(value) {
24652
+ if (this.borders.top === value) return;
24653
+ this.borders.top = value;
24654
+ this.layoutAll();
24655
+ }
24656
+ /**
24657
+ * The width of the right border column.
24658
+ *
24659
+ * @returns Units.
24660
+ */
24661
+ get rightWidth() {
24662
+ return this.borders.right;
24663
+ }
24664
+ /**
24665
+ * Sets the width of the right border column: the positions and the uvs are written again.
24666
+ *
24667
+ * @param value - Units.
24668
+ */
24669
+ set rightWidth(value) {
24670
+ if (this.borders.right === value) return;
24671
+ this.borders.right = value;
24672
+ this.layoutAll();
24673
+ }
24674
+ /**
24675
+ * The height of the bottom border row.
24676
+ *
24677
+ * @returns Units.
24678
+ */
24679
+ get bottomHeight() {
24680
+ return this.borders.bottom;
24681
+ }
24682
+ /**
24683
+ * Sets the height of the bottom border row: the positions and the uvs are written again.
24684
+ *
24685
+ * @param value - Units.
24686
+ */
24687
+ set bottomHeight(value) {
24688
+ if (this.borders.bottom === value) return;
24689
+ this.borders.bottom = value;
24690
+ this.layoutAll();
24691
+ }
24692
+ /**
24693
+ * The width of the panel, as on a `NineSliceSprite`: its box, never a scale.
24694
+ *
24695
+ * @returns Units.
24696
+ */
24697
+ get width() {
24698
+ return this.boxWidth;
24699
+ }
24700
+ /**
24701
+ * Sets the width of the panel: the positions are written again.
24702
+ *
24703
+ * @param value - Units.
24704
+ */
24705
+ set width(value) {
24706
+ if (this.boxWidth === value) return;
24707
+ this.boxWidth = value;
24708
+ this.layoutPositions();
24709
+ }
24710
+ /**
24711
+ * The height of the panel, as on a `NineSliceSprite`: its box, never a scale.
24712
+ *
24713
+ * @returns Units.
24714
+ */
24715
+ get height() {
24716
+ return this.boxHeight;
24717
+ }
24718
+ /**
24719
+ * Sets the height of the panel: the positions are written again.
24720
+ *
24721
+ * @param value - Units.
24722
+ */
24723
+ set height(value) {
24724
+ if (this.boxHeight === value) return;
24725
+ this.boxHeight = value;
24726
+ this.layoutPositions();
24727
+ }
24728
+ /**
24729
+ * The texture the rim is cut from.
24730
+ *
24731
+ * @returns The texture.
24732
+ */
24733
+ get texture() {
24734
+ return super.texture;
24735
+ }
24736
+ /**
24737
+ * Sets the texture as Pixi's mesh does; the uvs are cut for its frame.
24738
+ *
24739
+ * @param value - The texture.
24740
+ */
24741
+ set texture(value) {
24742
+ super.texture = value;
24743
+ if (this.built) this.layoutUvs();
24744
+ }
24745
+ /**
24746
+ * Frees the mesh as Pixi does, and its own geometry with it.
24747
+ *
24748
+ * @param options - Pixi's destroy options; a texture is freed only when they ask.
24749
+ */
24750
+ destroy(options) {
24751
+ if (this.destroyed) return;
24752
+ const geometry = this.geometry;
24753
+ super.destroy(options);
24754
+ geometry.destroy(true);
24755
+ }
24756
+ /** Writes the positions and the uvs again: a border changed. */
24757
+ layoutAll() {
24758
+ this.layoutPositions();
24759
+ this.layoutUvs();
24760
+ }
24761
+ /**
24762
+ * Writes the 16 positions in place: the cut lines at the borders, all four shrunk by one factor when two corners
24763
+ * would overlap, as Pixi's `NineSliceGeometry` shrinks them.
24764
+ */
24765
+ layoutPositions() {
24766
+ const across = this.across;
24767
+ const down = this.down;
24768
+ const borders = this.borders;
24769
+ const scale = sliceScale(this, borders);
24770
+ across.start = 0;
24771
+ across.near = borders.left * scale;
24772
+ across.far = this.boxWidth - borders.right * scale;
24773
+ across.end = this.boxWidth;
24774
+ down.start = 0;
24775
+ down.near = borders.top * scale;
24776
+ down.far = this.boxHeight - borders.bottom * scale;
24777
+ down.end = this.boxHeight;
24778
+ writeGrid(this.geometry.positions, across, down);
24779
+ this.geometry.getBuffer("aPosition").update();
24780
+ }
24781
+ /**
24782
+ * Writes the 16 uvs in place: the texture's frame (its trim, when it has one) cut at the unscaled borders, as
24783
+ * Pixi's `NineSliceGeometry` cuts it. Pixi maps them into an atlas page when it batches the mesh.
24784
+ */
24785
+ layoutUvs() {
24786
+ const across = this.across;
24787
+ const down = this.down;
24788
+ const borders = this.borders;
24789
+ const texture = this.texture;
24790
+ const width = texture.width;
24791
+ const height = texture.height;
24792
+ const trim = texture.trim;
24793
+ const x = trim?.x ?? 0;
24794
+ const y = trim?.y ?? 0;
24795
+ const u0 = x / width;
24796
+ const v0 = y / height;
24797
+ const u1 = (x + (trim?.width ?? width)) / width;
24798
+ const v1 = (y + (trim?.height ?? height)) / height;
24799
+ const texelU = 1 / width;
24800
+ const texelV = 1 / height;
24801
+ across.start = u0;
24802
+ across.near = u0 + texelU * borders.left;
24803
+ across.far = u1 - texelU * borders.right;
24804
+ across.end = u1;
24805
+ down.start = v0;
24806
+ down.near = v0 + texelV * borders.top;
24807
+ down.far = v1 - texelV * borders.bottom;
24808
+ down.end = v1;
24809
+ writeGrid(this.geometry.uvs, across, down);
24810
+ this.geometry.getBuffer("aUV").update();
24811
+ }
24812
+ }
24813
+ return HollowNineSlice;
24814
+ }
24815
+ /**
24816
+ * Builds a rim-only nine-slice of Pixi's white texture, its class made at the first one over the loaded module.
24817
+ *
24818
+ * @param state - The sync branch of the plugin state, which keeps the class.
24819
+ * @param pixi - The Pixi module the renderer loaded.
24820
+ * @returns The object.
24821
+ */
24822
+ function makeHollow(state, pixi) {
24823
+ state.hollowClass ??= createHollowClass(pixi);
24824
+ return new state.hollowClass(pixi.Texture.WHITE);
24825
+ }
24826
+ //#endregion
24156
24827
  //#region src/plugins/renderer/sync/labels.ts
24157
24828
  /**
24158
24829
  * Builds the label of one view.
@@ -24189,15 +24860,25 @@ function isGraphics(object) {
24189
24860
  return "roundRect" in object;
24190
24861
  }
24191
24862
  /**
24192
- * Tells whether a display object is a nine-slice sprite.
24863
+ * Tells whether a display object draws a nine-slice: Pixi's `NineSliceSprite`, or the engine's rim-only mesh of a
24864
+ * `hollow` one. Both carry the slice widths.
24193
24865
  *
24194
24866
  * @param object - The display object.
24195
- * @returns True for a nine-slice sprite.
24867
+ * @returns True for either nine-slice object.
24196
24868
  */
24197
24869
  function isNineSlice(object) {
24198
24870
  return "leftWidth" in object;
24199
24871
  }
24200
24872
  /**
24873
+ * Tells whether a display object is the engine's rim-only nine-slice (F59).
24874
+ *
24875
+ * @param object - The display object.
24876
+ * @returns True for the hollow mesh, false for Pixi's `NineSliceSprite` and every other object.
24877
+ */
24878
+ function isHollow(object) {
24879
+ return "hollow" in object;
24880
+ }
24881
+ /**
24201
24882
  * Takes a display object out of the Pixi tree. Pixi containers are not DOM nodes, so the DOM
24202
24883
  * helper the linter suggests does not exist on them.
24203
24884
  *
@@ -24248,18 +24929,20 @@ function dropOutline(view) {
24248
24929
  view.outline = void 0;
24249
24930
  }
24250
24931
  /**
24251
- * The pool an object belongs to: its class and the texture it draws.
24932
+ * The pool an object belongs to: its class and the texture it draws. A rim-only nine-slice has a pool of its own, so
24933
+ * a filled panel never gets a hollow object back, nor the other way round.
24252
24934
  *
24253
24935
  * @param kind - Which component gave the entity its display object.
24254
24936
  * @param textureKey - The asset key.
24937
+ * @param hollow - True for the rim-only object of a `NineSlice` with `hollow: true`.
24255
24938
  * @returns The pool key.
24256
24939
  * @example
24257
24940
  * ```ts
24258
- * poolKeyOf("Sprite", "board.cell"); // "Sprite:board.cell"
24941
+ * poolKeyOf("NineSlice", "glass.frost", true); // "NineSlice.hollow:glass.frost"
24259
24942
  * ```
24260
24943
  */
24261
- function poolKeyOf(kind, textureKey) {
24262
- return `${kind}:${textureKey}`;
24944
+ function poolKeyOf(kind, textureKey, hollow) {
24945
+ return hollow ? `${kind}.hollow:${textureKey}` : `${kind}:${textureKey}`;
24263
24946
  }
24264
24947
  /**
24265
24948
  * Brings the object of a view that left back to the state a fresh one has: detached, drawn, opaque, unmoved, white.
@@ -24269,9 +24952,17 @@ function poolKeyOf(kind, textureKey) {
24269
24952
  * @param view - The view whose object goes back to its pool; its wrapper is gone already.
24270
24953
  */
24271
24954
  function resetObject(view) {
24272
- const object = view.object;
24273
- detach$1(object);
24955
+ detach$1(view.object);
24274
24956
  clearHidden(view);
24957
+ resetNode(view.object);
24958
+ }
24959
+ /**
24960
+ * Brings a detached object back to a fresh one's pose and look: drawn, opaque, unmoved, white for a sprite and a
24961
+ * graphics.
24962
+ *
24963
+ * @param object - The object, out of the tree.
24964
+ */
24965
+ function resetNode(object) {
24275
24966
  object.visible = true;
24276
24967
  object.alpha = 1;
24277
24968
  object.rotation = 0;
@@ -24321,7 +25012,6 @@ function oldestPool(state) {
24321
25012
  * @param view - The view that left.
24322
25013
  */
24323
25014
  function release$1(sctx, view) {
24324
- const state = sctx.ctx.state.sync;
24325
25015
  dropMask(view);
24326
25016
  dropOutline(view);
24327
25017
  clearFilters(view.object);
@@ -24344,15 +25034,50 @@ function release$1(sctx, view) {
24344
25034
  view.display?.adapter.destroy(view.object);
24345
25035
  return;
24346
25036
  }
24347
- const groups = sctx.ctx.state.kept.madeGroups;
24348
- if (groups.has(view.object)) {
24349
- view.object.disableRenderGroup();
24350
- groups.delete(view.object);
24351
- }
25037
+ dropGroup(sctx, view.object);
24352
25038
  resetObject(view);
24353
- const pool = state.pools.get(view.poolKey) ?? emptyPool();
24354
- pool.push(view.object);
24355
- state.pools.set(view.poolKey, pool);
25039
+ store$1(sctx, view.object, view.poolKey);
25040
+ }
25041
+ /**
25042
+ * Puts an object a view no longer draws with back into a pool, while the view goes on with another one: the
25043
+ * nine-slice whose `hollow` flipped (F59). Reset as `release` resets a view's object; its view keeps its hidden
25044
+ * reasons, which hang on the view's wrapper.
25045
+ *
25046
+ * @param sctx - Domain context of the sync module.
25047
+ * @param object - The object, still in the view's wrapper.
25048
+ * @param poolKey - The pool it belongs to.
25049
+ */
25050
+ function releaseObject(sctx, object, poolKey) {
25051
+ dropGroup(sctx, object);
25052
+ detach$1(object);
25053
+ resetNode(object);
25054
+ store$1(sctx, object, poolKey);
25055
+ }
25056
+ /**
25057
+ * Takes away the render group the kept cache made of an object (spec 23 P9.3), before it is pooled.
25058
+ *
25059
+ * @param sctx - Domain context of the sync module.
25060
+ * @param object - The object.
25061
+ */
25062
+ function dropGroup(sctx, object) {
25063
+ const groups = sctx.ctx.state.kept.madeGroups;
25064
+ if (!groups.has(object)) return;
25065
+ object.disableRenderGroup();
25066
+ groups.delete(object);
25067
+ }
25068
+ /**
25069
+ * Pushes a reset object into its pool and evicts the one that waited longest while the pools hold more than
25070
+ * `poolLimit`.
25071
+ *
25072
+ * @param sctx - Domain context of the sync module.
25073
+ * @param object - The reset object.
25074
+ * @param poolKey - Its pool.
25075
+ */
25076
+ function store$1(sctx, object, poolKey) {
25077
+ const state = sctx.ctx.state.sync;
25078
+ const pool = state.pools.get(poolKey) ?? emptyPool();
25079
+ pool.push(object);
25080
+ state.pools.set(poolKey, pool);
24356
25081
  state.pooled += 1;
24357
25082
  while (state.pooled > sctx.ctx.config.poolLimit) {
24358
25083
  const oldest = oldestPool(state);
@@ -24363,21 +25088,32 @@ function release$1(sctx, view) {
24363
25088
  }
24364
25089
  /**
24365
25090
  * Destroys the pooled objects that draw one of these keys, so a bundle that left frees its GPU
24366
- * memory instead of waiting for a reuse that resolves nothing.
25091
+ * memory instead of waiting for a reuse that resolves nothing: sprites, nine-slices and rim-only nine-slices.
24367
25092
  *
24368
25093
  * @param state - The sync branch of the plugin state.
24369
25094
  * @param keys - The asset keys that went stale.
24370
25095
  */
24371
25096
  function dropPooled(state, keys) {
24372
- for (const key of keys) for (const kind of ["Sprite", "NineSlice"]) {
24373
- const pool = state.pools.get(poolKeyOf(kind, key));
24374
- if (pool === void 0) continue;
24375
- for (const object of pool) object.destroy({ texture: false });
24376
- state.pooled -= pool.length;
24377
- state.pools.delete(poolKeyOf(kind, key));
25097
+ for (const key of keys) {
25098
+ dropPool(state, poolKeyOf("Sprite", key, false));
25099
+ dropPool(state, poolKeyOf("NineSlice", key, false));
25100
+ dropPool(state, poolKeyOf("NineSlice", key, true));
24378
25101
  }
24379
25102
  }
24380
25103
  /**
25104
+ * Destroys one pool and everything in it.
25105
+ *
25106
+ * @param state - The sync branch of the plugin state.
25107
+ * @param poolKey - The pool.
25108
+ */
25109
+ function dropPool(state, poolKey) {
25110
+ const pool = state.pools.get(poolKey);
25111
+ if (pool === void 0) return;
25112
+ for (const object of pool) object.destroy({ texture: false });
25113
+ state.pooled -= pool.length;
25114
+ state.pools.delete(poolKey);
25115
+ }
25116
+ /**
24381
25117
  * Destroys everything the pools hold. Works on the state alone, because `onStop` has no context.
24382
25118
  *
24383
25119
  * @param state - The sync branch of the plugin state.
@@ -24543,85 +25279,6 @@ function clearLayers(state) {
24543
25279
  state.hudLayer = void 0;
24544
25280
  }
24545
25281
  //#endregion
24546
- //#region src/plugins/renderer/sync/outline.ts
24547
- /** Above every child of the wrapper, whatever `Order` a child carries. */
24548
- const OUTLINE_Z = Number.MAX_SAFE_INTEGER;
24549
- const CYAN = 65535;
24550
- const RED = 16711680;
24551
- /**
24552
- * The slice widths a texture asks for, in the units it is drawn in: its default borders divided
24553
- * by the resolution of its source. A texture without borders gets 0 on every side, never Pixi's 10.
24554
- *
24555
- * @param texture - The texture a nine-slice draws, or `undefined` while nothing can draw.
24556
- * @returns The four widths.
24557
- */
24558
- function bordersOf(texture) {
24559
- const borders = texture?.defaultBorders;
24560
- if (texture === void 0 || borders === void 0) return {
24561
- left: 0,
24562
- top: 0,
24563
- right: 0,
24564
- bottom: 0
24565
- };
24566
- const resolution = texture.source.resolution > 0 ? texture.source.resolution : 1;
24567
- return {
24568
- left: borders.left / resolution,
24569
- top: borders.top / resolution,
24570
- right: borders.right / resolution,
24571
- bottom: borders.bottom / resolution
24572
- };
24573
- }
24574
- /**
24575
- * The factor Pixi shrinks the corners of a nine-slice by when two of them would overlap.
24576
- *
24577
- * @param size - The drawn size.
24578
- * @param size.width - Drawn width.
24579
- * @param size.height - Drawn height.
24580
- * @param borders - The slice widths.
24581
- * @returns 1 when the corners fit, less when they overlap.
24582
- * @example
24583
- * ```ts
24584
- * sliceScale({ width: 100, height: 200 }, { left: 72, top: 76, right: 72, bottom: 76 }); // 0.6944
24585
- * ```
24586
- */
24587
- function sliceScale(size, borders) {
24588
- const across = borders.left + borders.right;
24589
- const down = borders.top + borders.bottom;
24590
- const scaleX = across > 0 && size.width < across ? size.width / across : 1;
24591
- const scaleY = down > 0 && size.height < down ? size.height / down : 1;
24592
- return Math.min(scaleX, scaleY);
24593
- }
24594
- /**
24595
- * Strokes the bounds of a nine-slice and the four lines Pixi cuts it at, into the wrapper of its
24596
- * view: cyan, red when the corners overlap or the texture is missing. The graphics is kept on the
24597
- * view and redrawn in place.
24598
- *
24599
- * @param pixi - The Pixi module the renderer loaded.
24600
- * @param wrapper - The wrapper of the view, which carries its pose.
24601
- * @param view - The view of the nine-slice.
24602
- * @param shape - Size, borders and whether the texture is missing.
24603
- */
24604
- function drawOutline(pixi, wrapper, view, shape) {
24605
- const outline = view.outline ?? new pixi.Graphics();
24606
- const { width, height, borders } = shape;
24607
- const scale = sliceScale(shape, borders);
24608
- const left = borders.left * scale;
24609
- const right = width - borders.right * scale;
24610
- const top = borders.top * scale;
24611
- const bottom = height - borders.bottom * scale;
24612
- outline.label = `outline#${shape.entity}`;
24613
- outline.zIndex = OUTLINE_Z;
24614
- outline.clear();
24615
- outline.rect(0, 0, width, height).moveTo(left, 0).lineTo(left, height).moveTo(right, 0).lineTo(right, height).moveTo(0, top).lineTo(width, top).moveTo(0, bottom).lineTo(width, bottom);
24616
- outline.stroke({
24617
- color: shape.missing || scale < 1 ? RED : CYAN,
24618
- width: 1,
24619
- pixelLine: true
24620
- });
24621
- if (outline.parent !== wrapper) wrapper.addChild(outline);
24622
- view.outline = outline;
24623
- }
24624
- //#endregion
24625
25282
  //#region src/plugins/renderer/sync/overlay-range.ts
24626
25283
  /** A range retaken for a mark in this many frames in a row is drawn live instead (O8.4; spec 23's `CHURN_LIMIT`). */
24627
25284
  const RETAKE_STREAK = 3;
@@ -26085,6 +26742,144 @@ function destroyTexture(state, texture) {
26085
26742
  texture.destroy(true);
26086
26743
  }
26087
26744
  //#endregion
26745
+ //#region src/plugins/renderer/sync/upload.ts
26746
+ /** The calls of a source nobody waits for: none. */
26747
+ const NOBODY = Object.freeze([]);
26748
+ /**
26749
+ * The resolve of a waiter before its promise exists: answers nobody.
26750
+ */
26751
+ function unanswered() {}
26752
+ /**
26753
+ * Creates the set of the sources uploaded on a device. It lives in its own non-exported function because lint rule L5
26754
+ * refuses a collection built inside an exported declaration.
26755
+ *
26756
+ * @returns An empty weak set.
26757
+ */
26758
+ function emptyDone() {
26759
+ return /* @__PURE__ */ new WeakSet();
26760
+ }
26761
+ /**
26762
+ * Counts one source for a call: queued once whoever names it first, waited for once by each call, however many of its
26763
+ * textures share it. A source uploaded on this device, or destroyed, is not waited for.
26764
+ *
26765
+ * @param upload - The upload branch of the sync state.
26766
+ * @param source - The source of one texture of the call.
26767
+ * @param waiter - The call.
26768
+ */
26769
+ function enqueue(upload, source, waiter) {
26770
+ if (source.destroyed || upload.done.has(source)) return;
26771
+ let waiters = upload.waiting.get(source);
26772
+ if (waiters === void 0) {
26773
+ waiters = [];
26774
+ upload.waiting.set(source, waiters);
26775
+ upload.queue.push(source);
26776
+ }
26777
+ if (waiters.at(-1) === waiter) return;
26778
+ waiters.push(waiter);
26779
+ waiter.left += 1;
26780
+ }
26781
+ /**
26782
+ * Queues the sources of textures for the GPU; resolves true once each went through `initSource` (the body of
26783
+ * `TexturesApi.upload`). Answers false at once while nothing draws or with `uploads: 0`, and true at once when every
26784
+ * source is on this device already. Asks for a frame.
26785
+ *
26786
+ * @param sctx - Domain context of the sync module.
26787
+ * @param textures - The textures.
26788
+ * @returns Whether every source is on the GPU.
26789
+ */
26790
+ function uploadTextures(sctx, textures) {
26791
+ const { upload } = sctx.ctx.state.sync;
26792
+ if (sctx.ctx.config.uploads === 0 || !sctx.deps.host.ready()) return Promise.resolve(false);
26793
+ const waiter = {
26794
+ left: 0,
26795
+ resolve: unanswered
26796
+ };
26797
+ for (const texture of textures) enqueue(upload, texture.source, waiter);
26798
+ if (waiter.left === 0) return Promise.resolve(true);
26799
+ sctx.deps.demand.request();
26800
+ return new Promise((resolve) => {
26801
+ waiter.resolve = resolve;
26802
+ });
26803
+ }
26804
+ /**
26805
+ * Uploads one source, the error of a throw logged: the source counts as done either way, so a failing one is logged
26806
+ * once and never stops the queue.
26807
+ *
26808
+ * @param sctx - Domain context of the sync module.
26809
+ * @param upload - The upload branch of the sync state.
26810
+ * @param source - The source.
26811
+ */
26812
+ function put(sctx, upload, source) {
26813
+ try {
26814
+ sctx.deps.host.initSource(source);
26815
+ } catch (error) {
26816
+ sctx.ctx.log.warn("renderer:upload-failed", {
26817
+ source: source.label,
26818
+ error
26819
+ });
26820
+ }
26821
+ upload.done.add(source);
26822
+ }
26823
+ /**
26824
+ * Counts a source off for the calls that wait for it; a call whose last source it was answers true.
26825
+ *
26826
+ * @param upload - The upload branch of the sync state.
26827
+ * @param source - The source uploaded or skipped.
26828
+ */
26829
+ function settle$2(upload, source) {
26830
+ const waiters = upload.waiting.get(source) ?? NOBODY;
26831
+ upload.waiting.delete(source);
26832
+ for (const waiter of waiters) {
26833
+ waiter.left -= 1;
26834
+ if (waiter.left === 0) waiter.resolve(true);
26835
+ }
26836
+ }
26837
+ /**
26838
+ * Uploads up to `renderer.uploads` queued sources before the stage pass of a drawn frame; a source destroyed while it
26839
+ * waited is skipped and costs nothing of the budget. A queue that still holds sources asks for the next frame; one
26840
+ * that ran out empties. Reads two numbers and returns while the queue is empty.
26841
+ *
26842
+ * @param sctx - Domain context of the sync module.
26843
+ * @returns True when a source went to the GPU in this frame, so the frame is no sample of the adaptive rate.
26844
+ */
26845
+ function uploadStep(sctx) {
26846
+ const { upload } = sctx.ctx.state.sync;
26847
+ if (upload.head >= upload.queue.length) return false;
26848
+ const budget = sctx.ctx.config.uploads;
26849
+ let made = 0;
26850
+ while (made < budget && upload.head < upload.queue.length) {
26851
+ const source = upload.queue[upload.head];
26852
+ upload.head += 1;
26853
+ if (source === void 0) continue;
26854
+ if (!source.destroyed) {
26855
+ put(sctx, upload, source);
26856
+ made += 1;
26857
+ }
26858
+ settle$2(upload, source);
26859
+ }
26860
+ if (upload.head < upload.queue.length) {
26861
+ sctx.deps.demand.request();
26862
+ return made > 0;
26863
+ }
26864
+ upload.queue.length = 0;
26865
+ upload.head = 0;
26866
+ return made > 0;
26867
+ }
26868
+ /**
26869
+ * Ends the queue of a device that goes, from state alone (`onStop` has no context): every waiting call answers false,
26870
+ * the queue empties, and the sources uploaded on it are forgotten, since a new device holds none of them.
26871
+ *
26872
+ * @param state - The sync branch of the plugin state.
26873
+ */
26874
+ function stopUploads(state) {
26875
+ const { upload } = state;
26876
+ for (const waiters of upload.waiting.values()) for (const waiter of waiters) waiter.resolve(false);
26877
+ upload.waiting.clear();
26878
+ upload.queue.length = 0;
26879
+ upload.head = 0;
26880
+ upload.done = emptyDone();
26881
+ }
26882
+ //#endregion
26088
26883
  //#region src/plugins/renderer/sync/views.ts
26089
26884
  /**
26090
26885
  * @file renderer/sync — one view per entity: build it from the components, write every value,
@@ -26104,6 +26899,23 @@ const PLACEHOLDER_BOX = Object.freeze({
26104
26899
  * sync pass).
26105
26900
  */
26106
26901
  const LAYOUT = createSpriteLayout();
26902
+ /** The slice widths of the nine-slice being written: per write, reused, as `LAYOUT`. */
26903
+ const BORDERS = {
26904
+ left: 0,
26905
+ top: 0,
26906
+ right: 0,
26907
+ bottom: 0
26908
+ };
26909
+ /** What the outline of the nine-slice being written is drawn from, written in place, as `LAYOUT`. */
26910
+ const OUTLINE = {
26911
+ entity: 0,
26912
+ width: 0,
26913
+ height: 0,
26914
+ borders: BORDERS,
26915
+ missing: false
26916
+ };
26917
+ /** The box a clipping nine-slice masks its children with, as the shape value `applyClip` reads, written in place. */
26918
+ const NINE_CLIP = { ...Shape.defaults };
26107
26919
  /**
26108
26920
  * The depth of a wrapper's own visual among the wrapper's children: a child at `Order` 0 or more draws above it, one
26109
26921
  * at −1 or less below it, and a ui child at `zIndex` 0 (an `Order` between −0.5 and 0) above it too (spec 16 U1.8).
@@ -26204,26 +27016,31 @@ function visualOf(sctx, entity) {
26204
27016
  if (sprite !== void 0) found.push({
26205
27017
  kind: "Sprite",
26206
27018
  textureKey: sprite.texture,
27019
+ hollow: false,
26207
27020
  display: void 0
26208
27021
  });
26209
27022
  if (nine !== void 0) found.push({
26210
27023
  kind: "NineSlice",
26211
27024
  textureKey: nine.texture,
27025
+ hollow: nine.hollow === true,
26212
27026
  display: void 0
26213
27027
  });
26214
27028
  if (ecs.has(entity, Shape)) found.push({
26215
27029
  kind: "Shape",
26216
27030
  textureKey: "",
27031
+ hollow: false,
26217
27032
  display: void 0
26218
27033
  });
26219
27034
  if (ecs.has(entity, Display)) found.push({
26220
27035
  kind: "Display",
26221
27036
  textureKey: "",
27037
+ hollow: false,
26222
27038
  display: void 0
26223
27039
  });
26224
27040
  for (const entry of adaptersOn(sctx, entity)) found.push({
26225
27041
  kind: "adapter",
26226
27042
  textureKey: "",
27043
+ hollow: false,
26227
27044
  display: entry
26228
27045
  });
26229
27046
  if (found.length > 1) sctx.ctx.log.warn("renderer: entity has more than one visual", {
@@ -26244,29 +27061,10 @@ function retargetKey(state, entity, view, key) {
26244
27061
  if (view.textureKey === key) return;
26245
27062
  untrackKey(state, entity, view.textureKey);
26246
27063
  view.textureKey = key;
26247
- view.poolKey = poolKeyOf(view.kind, key);
27064
+ view.poolKey = poolKeyOf(view.kind, key, isHollow(view.object));
26248
27065
  if (key !== "") trackKey(state, entity, key);
26249
27066
  }
26250
27067
  /**
26251
- * Resolves a key and says whether the placeholder had to step in.
26252
- *
26253
- * @param sctx - Domain context of the sync module.
26254
- * @param key - The asset key.
26255
- * @returns The texture to draw and whether it is the placeholder.
26256
- */
26257
- function textureFor(sctx, key) {
26258
- const resolved = resolveTexture(sctx, key);
26259
- if (resolved !== void 0) return {
26260
- texture: resolved,
26261
- missing: false
26262
- };
26263
- warnMissing(sctx, key);
26264
- return {
26265
- texture: placeholderTexture(sctx),
26266
- missing: true
26267
- };
26268
- }
26269
- /**
26270
27068
  * Writes the texture and the tint of a sprite, each only when it differs from what Pixi holds.
26271
27069
  *
26272
27070
  * @param sctx - Domain context of the sync module.
@@ -26475,12 +27273,12 @@ function applyOutline(sctx, view, shape, debug, changed) {
26475
27273
  sctx.ctx.state.sync.redraws += 1;
26476
27274
  }
26477
27275
  /**
26478
- * Writes one number of a nine-slice only when it differs from what Pixi holds: Pixi rebuilds the
26479
- * panel on every write of a width, even an equal one.
27276
+ * Writes one number of a nine-slice only when it differs from what the object holds: Pixi rebuilds the
27277
+ * panel on every write of a width, even an equal one, and the rim-only object rewrites its vertices.
26480
27278
  *
26481
27279
  * @param sctx - Domain context of the sync module.
26482
27280
  * @param entity - The entity.
26483
- * @param object - The nine-slice sprite.
27281
+ * @param object - Pixi's nine-slice sprite, or the engine's rim-only one.
26484
27282
  * @param field - Which number.
26485
27283
  * @param value - The new number.
26486
27284
  * @returns True when it was written.
@@ -26494,7 +27292,9 @@ function writeSlice(sctx, entity, object, field, value) {
26494
27292
  /**
26495
27293
  * Writes the `NineSlice` component onto its display object and recomputes the hit box. Pixi reads
26496
27294
  * a texture's borders only in the constructor, so they are copied here on every write: a pooled
26497
- * object, a new key and a reloaded bundle all get the insets of the texture they draw now.
27295
+ * object, a new key and a reloaded bundle all get the insets of the texture they draw now. A `hollow` that flipped
27296
+ * swaps the view's object first (F59). The hit box is the whole panel, hollow or not. A write that changes nothing,
27297
+ * or only the size, allocates nothing: the borders, the outline's shape and the clip's box are written in place.
26498
27298
  *
26499
27299
  * @param sctx - Domain context of the sync module.
26500
27300
  * @param entity - The entity.
@@ -26502,11 +27302,16 @@ function writeSlice(sctx, entity, object, field, value) {
26502
27302
  */
26503
27303
  function applyNineSlice(sctx, entity, view) {
26504
27304
  const value = sctx.ctx.deps.world.ecs.get(entity, NineSlice);
27305
+ if (value === void 0 || !isNineSlice(view.object)) return;
27306
+ if (isHollow(view.object) !== (value.hollow === true)) swapSlice(sctx, entity, view);
26505
27307
  const object = view.object;
26506
- if (value === void 0 || !isNineSlice(object)) return;
27308
+ if (!isNineSlice(object)) return;
26507
27309
  retargetKey(sctx.ctx.state.sync, entity, view, value.texture);
26508
- const { texture, missing } = textureFor(sctx, value.texture);
26509
- const borders = bordersOf(texture);
27310
+ const resolved = resolveTexture(sctx, value.texture);
27311
+ const missing = resolved === void 0;
27312
+ if (missing) warnMissing(sctx, value.texture);
27313
+ const texture = resolved ?? placeholderTexture(sctx);
27314
+ const borders = bordersInto(texture, BORDERS);
26510
27315
  view.placeholder = missing;
26511
27316
  let changed = false;
26512
27317
  if (texture !== void 0 && object.texture !== texture) {
@@ -26522,25 +27327,59 @@ function applyNineSlice(sctx, entity, view) {
26522
27327
  applyAlpha(sctx, entity, view, value);
26523
27328
  changed = writeSlice(sctx, entity, object, "width", value.width) || changed;
26524
27329
  changed = writeSlice(sctx, entity, object, "height", value.height) || changed;
26525
- view.hitBox = {
26526
- x: 0,
26527
- y: 0,
26528
- width: value.width,
26529
- height: value.height
26530
- };
26531
- applyOutline(sctx, view, {
26532
- entity,
26533
- width: value.width,
26534
- height: value.height,
26535
- borders,
26536
- missing
26537
- }, value.debug, changed);
26538
- applyClip(sctx, entity, view, {
26539
- ...Shape.defaults,
26540
- w: value.width,
26541
- h: value.height,
26542
- clip: value.clip
26543
- });
27330
+ writeHitBox(view.hitBox, value.width, value.height);
27331
+ OUTLINE.entity = entity;
27332
+ OUTLINE.width = value.width;
27333
+ OUTLINE.height = value.height;
27334
+ OUTLINE.missing = missing;
27335
+ applyOutline(sctx, view, OUTLINE, value.debug, changed);
27336
+ NINE_CLIP.w = value.width;
27337
+ NINE_CLIP.h = value.height;
27338
+ NINE_CLIP.clip = value.clip;
27339
+ applyClip(sctx, entity, view, NINE_CLIP);
27340
+ }
27341
+ /**
27342
+ * A new nine-slice object of one kind: the engine's rim-only mesh, or Pixi's `NineSliceSprite`; both of the white
27343
+ * texture until the first write.
27344
+ *
27345
+ * @param sctx - Domain context of the sync module.
27346
+ * @param pixi - The Pixi module.
27347
+ * @param hollow - True for the rim-only object.
27348
+ * @returns The object.
27349
+ */
27350
+ function newSlice(sctx, pixi, hollow) {
27351
+ return hollow ? makeHollow(sctx.ctx.state.sync, pixi) : new pixi.NineSliceSprite({ texture: pixi.Texture.WHITE });
27352
+ }
27353
+ /**
27354
+ * Gives a nine-slice view whose `hollow` flipped the other object (F59), on the write that flipped it: the view gets
27355
+ * its wrapper first (it keeps the pose, the alpha, the filters, the clip and the outline), then the other kind takes
27356
+ * the old object's place in it, out of its pool or new, and the old object goes back to its own pool. The entity, its
27357
+ * children and its hit box stay as they were; the caller writes the new object in full.
27358
+ *
27359
+ * @param sctx - Domain context of the sync module.
27360
+ * @param entity - The entity.
27361
+ * @param view - Its view, drawn by a nine-slice object.
27362
+ */
27363
+ function swapSlice(sctx, entity, view) {
27364
+ const pixi = sctx.deps.host.pixi();
27365
+ const wrapper = ensureWrapper(sctx, entity);
27366
+ if (pixi === void 0 || wrapper === void 0) return;
27367
+ const state = sctx.ctx.state.sync;
27368
+ const old = view.object;
27369
+ const hollow = !isHollow(old);
27370
+ const poolKey = poolKeyOf("NineSlice", view.textureKey, hollow);
27371
+ const next = acquire(state, poolKey) ?? newSlice(sctx, pixi, hollow);
27372
+ markView(sctx, entity);
27373
+ next.label = old.label;
27374
+ next.zIndex = old.zIndex;
27375
+ wrapper.addChildAt(next, Math.max(0, wrapper.children.indexOf(old)));
27376
+ state.entityOf.delete(old);
27377
+ state.entityOf.set(next, entity);
27378
+ view.object = next;
27379
+ releaseObject(sctx, old, view.poolKey);
27380
+ view.poolKey = poolKey;
27381
+ writeRenderable(view);
27382
+ applyTransform(sctx, entity, view);
26544
27383
  }
26545
27384
  /**
26546
27385
  * Gives a clipping view the masked container its children hang in, next to its own visual in the
@@ -26558,7 +27397,9 @@ function ensureClipped(pixi, entity, view, wrapper) {
26558
27397
  const clipped = new pixi.Container();
26559
27398
  clipped.label = `children#${entity}`;
26560
27399
  clipped.sortableChildren = true;
26561
- for (const child of wrapper.children) if (child !== view.object && child !== view.outline) clipped.addChild(child);
27400
+ let index = 0;
27401
+ for (let child = wrapper.children[0]; child !== void 0; child = wrapper.children[index]) if (child === view.object || child === view.outline) index += 1;
27402
+ else clipped.addChild(child);
26562
27403
  wrapper.addChild(clipped);
26563
27404
  view.clipped = clipped;
26564
27405
  return clipped;
@@ -26990,6 +27831,7 @@ function attach$1(sctx, entity, view) {
26990
27831
  * @param visual - Which component gives the entity its object, and its texture key.
26991
27832
  * @param visual.kind - Which component gives the entity its object.
26992
27833
  * @param visual.textureKey - The asset key the object draws.
27834
+ * @param visual.hollow - True for the rim-only object of a hollow `NineSlice`.
26993
27835
  * @returns The object, or `undefined` while inert.
26994
27836
  */
26995
27837
  function makeObject(sctx, entity, visual) {
@@ -27003,7 +27845,7 @@ function makeObject(sctx, entity, visual) {
27003
27845
  const pixi = sctx.deps.host.pixi();
27004
27846
  if (pixi === void 0) return void 0;
27005
27847
  if (visual.display !== void 0) return adapterObject(sctx, entity, visual.display);
27006
- const pooled = acquire(sctx.ctx.state.sync, poolKeyOf(visual.kind, visual.textureKey));
27848
+ const pooled = acquire(sctx.ctx.state.sync, poolKeyOf(visual.kind, visual.textureKey, visual.hollow));
27007
27849
  if (pooled !== void 0) return {
27008
27850
  object: pooled,
27009
27851
  value: void 0
@@ -27017,7 +27859,7 @@ function makeObject(sctx, entity, visual) {
27017
27859
  value: void 0
27018
27860
  };
27019
27861
  return {
27020
- object: new pixi.NineSliceSprite({ texture: pixi.Texture.WHITE }),
27862
+ object: newSlice(sctx, pixi, visual.hollow),
27021
27863
  value: void 0
27022
27864
  };
27023
27865
  }
@@ -27060,7 +27902,7 @@ function createView(sctx, entity) {
27060
27902
  const view = {
27061
27903
  object,
27062
27904
  kind: visual.kind,
27063
- poolKey: poolKeyOf(visual.kind, visual.textureKey),
27905
+ poolKey: poolKeyOf(visual.kind, visual.textureKey, visual.hollow),
27064
27906
  layer: "",
27065
27907
  textureKey: visual.textureKey,
27066
27908
  wrapper: void 0,
@@ -27185,8 +28027,9 @@ function dropView(sctx, entity) {
27185
28027
  release$1(sctx, view);
27186
28028
  }
27187
28029
  /**
27188
- * Drops every view, every layer, every pool, every filter slot and filter area, and the warm-up (its waiting promises
27189
- * answer false, its probe tree is destroyed). Works on the state alone,
28030
+ * Drops every view, every layer, every pool, every filter slot and filter area, the warm-up (its waiting promises
28031
+ * answer false, its probe tree is destroyed) and the upload queue (its waiting calls answer false). Works on the state
28032
+ * alone,
27190
28033
  * because `onStop` has no context. A `Display` object belongs to the game: it is detached, never
27191
28034
  * destroyed, and leaves without the filters, the filter area and the hidden reasons the renderer gave it.
27192
28035
  *
@@ -27231,6 +28074,7 @@ function stopSync(state) {
27231
28074
  state.areas.clear();
27232
28075
  state.viewlessEntries = false;
27233
28076
  stopWarm(state);
28077
+ stopUploads(state);
27234
28078
  state.held.clear();
27235
28079
  state.redraws = 0;
27236
28080
  state.overdraw.listeners.length = 0;
@@ -27374,7 +28218,8 @@ function withDeps$8(ctx) {
27374
28218
  * takes the same path: the backing store changes, the frame and the scale do not), then the
27375
28219
  * overlay step (spec 22), then `kept` reads the marks before `demand` decides whether the frame is
27376
28220
  * drawn and clears them; a drawn frame draws the probes of the warm-up onto the canvas first (F4: the
27377
- * stage pass clears them, in the same task, so they never show), then the kept pictures
28221
+ * stage pass clears them, in the same task, so they never show), then uploads the queued texture sources (F60), then
28222
+ * the kept pictures
27378
28223
  * (`kept.flush`, so the canvas stays one pass, E7), then the stage, then the pixel leg of the kept
27379
28224
  * oracle; `demand` settles the
27380
28225
  * frame (the dev check, the pixel oracle), and `monitor` closes it and hands a drawn one to a
@@ -27409,10 +28254,11 @@ function renderFrame(ctx, modules) {
27409
28254
  const drawn = modules.demand.decide(modules.monitor.waiting());
27410
28255
  if (drawn) {
27411
28256
  const probed = modules.sync.warmStep();
28257
+ const uploaded = modules.sync.uploadStep();
27412
28258
  modules.kept.flush();
27413
28259
  if (overdraw ? modules.sync.drawOverdraw(modules.host.render) : modules.host.render()) {
27414
28260
  ctx.deps.time.frameDrawn();
27415
- if (probed) ctx.deps.time.frameProbed();
28261
+ if (probed || uploaded) ctx.deps.time.frameProbed();
27416
28262
  }
27417
28263
  modules.kept.after();
27418
28264
  }
@@ -29616,6 +30462,7 @@ function createSyncApi(ctx, deps) {
29616
30462
  const forgetTree = () => {
29617
30463
  deps.kept.reset();
29618
30464
  forgetOverlays(state);
30465
+ stopUploads(state);
29619
30466
  state.held.clear();
29620
30467
  for (const view of state.views.values()) {
29621
30468
  state.viewOfTop.delete(view.wrapper ?? view.object);
@@ -29658,7 +30505,7 @@ function createSyncApi(ctx, deps) {
29658
30505
  state.removed.clear();
29659
30506
  state.reparented.clear();
29660
30507
  state.outlinesStale = false;
29661
- startWarm$1(sctx);
30508
+ startWarm$2(sctx);
29662
30509
  };
29663
30510
  return {
29664
30511
  hitTest: (x, y, accept) => hitTest(sctx, x, y, accept),
@@ -29685,7 +30532,8 @@ function createSyncApi(ctx, deps) {
29685
30532
  if (state.root === void 0) return;
29686
30533
  for (const key of keys) state.invalidated.add(key);
29687
30534
  dropPooled(state, keys);
29688
- }
30535
+ },
30536
+ upload: (textures) => uploadTextures(sctx, textures)
29689
30537
  },
29690
30538
  displays: { provide: (component, adapter) => provideDisplay(sctx, component, adapter, {
29691
30539
  added: markAdded,
@@ -29733,6 +30581,7 @@ function createSyncApi(ctx, deps) {
29733
30581
  stepFilters: () => applySwitched(state, deps.kept),
29734
30582
  drainOverlayEvents: (monitor) => drainOverlayEvents(sctx, monitor),
29735
30583
  warmStep: () => warmStep(sctx),
30584
+ uploadStep: () => uploadStep(sctx),
29736
30585
  onWarm: (fn) => {
29737
30586
  state.warm.listeners.push(fn);
29738
30587
  },
@@ -30524,8 +31373,22 @@ function createWarmState() {
30524
31373
  };
30525
31374
  }
30526
31375
  /**
31376
+ * Creates the empty upload queue: nothing queued, nobody waiting, nothing uploaded. Its own non-exported function, as
31377
+ * `emptyMap`.
31378
+ *
31379
+ * @returns The upload branch of the sync state.
31380
+ */
31381
+ function createUploadState() {
31382
+ return {
31383
+ queue: [],
31384
+ head: 0,
31385
+ waiting: emptyMap$6(),
31386
+ done: emptyWeakSet()
31387
+ };
31388
+ }
31389
+ /**
30527
31390
  * Creates the initial sync state: no root, no layers, no view, no orphan, no pool, no provider, no filter, no
30528
- * filter area, no slice, no hidden mask, no overlay, no warm-up, no redraw counted and no unseen walk kept.
31391
+ * filter area, no slice, no hidden mask, no overlay, no warm-up, no upload, no redraw counted and no unseen walk kept.
30529
31392
  *
30530
31393
  * @param debug - The debug switches of the config; copied, so the frozen config stays untouched.
30531
31394
  * @returns The sync branch of the plugin state.
@@ -30564,6 +31427,8 @@ function createSyncState(debug) {
30564
31427
  areas: emptyMap$6(),
30565
31428
  viewlessEntries: false,
30566
31429
  warm: createWarmState(),
31430
+ upload: createUploadState(),
31431
+ hollowClass: void 0,
30567
31432
  filterKinds: emptyWeakMap$1(),
30568
31433
  filterOwners: emptyWeakMap$1(),
30569
31434
  objectHidden: emptyWeakMap$1(),
@@ -30631,6 +31496,8 @@ const RING_FRAMES = {
30631
31496
  };
30632
31497
  /** The most probe draws of the warm-up a drawn frame may make. */
30633
31498
  const MAX_WARM = 16;
31499
+ /** The most textures the upload queue may put on the GPU in one drawn frame: about 9 ms each on a Galaxy A26. */
31500
+ const MAX_UPLOADS = 8;
30634
31501
  /**
30635
31502
  * Tells a finite number of at least 0 from anything else a config can hold.
30636
31503
  *
@@ -30677,14 +31544,14 @@ function checkAdaptiveScale(adaptiveScale) {
30677
31544
  }
30678
31545
  /**
30679
31546
  * Checks the numbers of the config a game can get wrong, at `createApp`, in every build: the
30680
- * resolution cap, the pool trim, the adaptive render scale, the performance monitor, the budget of the kept pictures
30681
- * and the probe draws of the warm-up.
31547
+ * resolution cap, the pool trim, the adaptive render scale, the performance monitor, the budget of the kept pictures,
31548
+ * the probe draws of the warm-up and the textures uploaded per drawn frame.
30682
31549
  *
30683
31550
  * @param config - The resolved plugin config.
30684
31551
  * @throws {Error} When `maxResolution` is not a number above 0, `poolTrim` or `adaptiveScale` fails its check,
30685
31552
  * `monitor.budgetMs` is neither `"rate"` nor a number above 0, `monitor.frames` is not a whole
30686
- * number from 64 to 16384, `keptMb` is not a finite number of at least 0, or `warm` is not a
30687
- * whole number from 0 to 16.
31553
+ * number from 64 to 16384, `keptMb` is not a finite number of at least 0, `warm` is not a
31554
+ * whole number from 0 to 16, or `uploads` is not a whole number from 0 to 8.
30688
31555
  */
30689
31556
  function checkConfig(config) {
30690
31557
  const { maxResolution, keptMb } = config;
@@ -30697,6 +31564,8 @@ function checkConfig(config) {
30697
31564
  if (typeof keptMb !== "number" || !Number.isFinite(keptMb) || keptMb < 0) throw new Error(`[game] renderer.keptMb is ${String(keptMb)}.\n Give the MiB kept pictures may hold, or 0 to keep none.`);
30698
31565
  const { warm } = config;
30699
31566
  if (!Number.isInteger(warm) || warm < 0 || warm > MAX_WARM) throw new Error(`[game] renderer.warm is ${String(warm)}.\n Give the probe draws per frame, a whole number from 0 to 16.`);
31567
+ const { uploads } = config;
31568
+ if (!Number.isInteger(uploads) || uploads < 0 || uploads > MAX_UPLOADS) throw new Error(`[game] renderer.uploads is ${String(uploads)}.\n Give the textures uploaded per drawn frame, a whole number from 0 to 8.`);
30700
31569
  }
30701
31570
  /**
30702
31571
  * Creates the initial renderer state: one branch per module, composed from the module state
@@ -30778,6 +31647,7 @@ const rendererPlugin = /*#__PURE__*/ createPlugin$1("renderer", {
30778
31647
  onDemand: false,
30779
31648
  keptMb: 64,
30780
31649
  warm: 1,
31650
+ uploads: 1,
30781
31651
  adaptiveScale: {
30782
31652
  floor: .75,
30783
31653
  devices: "phone"
@@ -34291,8 +35161,9 @@ function reportSettled(ctx, progress, signal) {
34291
35161
  ctx.emit("assets:bundle-progress", { ...progress });
34292
35162
  }
34293
35163
  /**
34294
- * Publishes a loaded bundle: what was loaded moves into the record, `renderer` re-resolves the
34295
- * keys, the event goes out, the frame loop wakes and the budget is enforced.
35164
+ * Publishes a loaded bundle: what was loaded moves into the record, a failure `ready` remembered
35165
+ * is forgotten, `renderer` re-resolves the keys, the event goes out, the frame loop wakes and the
35166
+ * budget is enforced.
34296
35167
  *
34297
35168
  * @param ctx - Domain context of the plugin.
34298
35169
  * @param bundle - Name of the bundle.
@@ -34309,6 +35180,7 @@ function finish$1(ctx, bundle, entry, assets, reason) {
34309
35180
  record.status = "loaded";
34310
35181
  record.inflight = void 0;
34311
35182
  touch(ctx.state, record);
35183
+ ctx.state.readyFailed.delete(bundle);
34312
35184
  ctx.deps.renderer.sync.textures.invalidate(entry.files.map((file) => file.key));
34313
35185
  ctx.emit("assets:bundle-loaded", {
34314
35186
  bundle,
@@ -34646,6 +35518,8 @@ async function bootTiers(ctx) {
34646
35518
  */
34647
35519
  /** How many of the heaviest entries the over-budget warning names. */
34648
35520
  const HEAVIEST = 5;
35521
+ /** No feature holds a bundle: the stack as a leaving scene's non-`feature` bundle sees it. */
35522
+ const NO_FEATURES = Object.freeze(/* @__PURE__ */ new Set());
34649
35523
  /**
34650
35524
  * Adds up the estimated texture memory of every loaded bundle.
34651
35525
  *
@@ -34712,9 +35586,9 @@ function releaseAssets(io, assets) {
34712
35586
  assets.audio.clear();
34713
35587
  }
34714
35588
  /**
34715
- * Frees one loaded bundle: the textures are destroyed, every companion drops what travels with
34716
- * it, `renderer` is told that its keys are gone and the event goes out. A bundle that is not
34717
- * loaded is left alone.
35589
+ * Frees one loaded bundle: the textures are destroyed, its warm is forgotten, every companion drops
35590
+ * what travels with it, `renderer` is told that its keys are gone and the event goes out. A bundle
35591
+ * that is not loaded is left alone.
34718
35592
  *
34719
35593
  * @param ctx - Domain context of the plugin.
34720
35594
  * @param bundle - Name of the bundle.
@@ -34728,6 +35602,7 @@ function unloadBundle(ctx, bundle, reason) {
34728
35602
  releaseAssets(state.io, record);
34729
35603
  record.status = "idle";
34730
35604
  record.lastUsed = 0;
35605
+ state.warmed.delete(bundle);
34731
35606
  unloadCompanions(ctx, state.companions, bundle);
34732
35607
  const keys = entry.files.map((file) => file.key);
34733
35608
  ctx.deps.renderer.sync.textures.invalidate(keys);
@@ -34875,10 +35750,13 @@ function nameSceneBundles(state, named) {
34875
35750
  /**
34876
35751
  * Lets the bundles a leaving scene named go once nothing carries `Exiting` any more: each loaded
34877
35752
  * `"leave"` bundle among them that nothing else holds is unloaded with the reason `"leave"`, at
34878
- * once, not at the next rest. A bundle of another release stays, as it would have. The system
34879
- * `assets.leave` calls it in every phase `sync`: after the reconcile of `layout`, which tags the
34880
- * leaving elements `Exiting` in the frame after the switch, so the hold never ends before the leave
34881
- * begins (F51b). It costs a length read while no scene leaves, and a count read while one does.
35753
+ * once, not at the next rest. Its feature on the stack holds only a `feature`-tier bundle, which
35754
+ * lives with its feature's flow: a scene's bundle goes with the scene, also while another scene of
35755
+ * its feature shows (the splash's 7.5 MiB under the ward). A bundle of another release stays, as
35756
+ * it would have. The system `assets.leave` calls it in every phase `sync`: after the reconcile of
35757
+ * `layout`, which tags the leaving elements `Exiting` in the frame after the switch, so the hold
35758
+ * never ends before the leave begins (F51b). It costs a length read while no scene leaves, and a
35759
+ * count read while one does.
34882
35760
  *
34883
35761
  * @param ctx - Domain context of the plugin.
34884
35762
  */
@@ -34889,7 +35767,10 @@ function releaseSceneLeft(ctx) {
34889
35767
  state.sceneLeaving = [];
34890
35768
  if (state.io === void 0) return;
34891
35769
  const onStack = featuresOnStack(ctx);
34892
- for (const name of names) unloadIfLeft(ctx, name, onStack);
35770
+ for (const name of names) {
35771
+ const tier = state.manifest.bundles[name]?.tier;
35772
+ unloadIfLeft(ctx, name, tier === "feature" ? onStack : NO_FEATURES);
35773
+ }
34893
35774
  }
34894
35775
  /**
34895
35776
  * Unloads one bundle with the reason `"leave"` when it is a loaded `"leave"` bundle nobody holds.
@@ -35378,9 +36259,125 @@ function startPreload(ctx) {
35378
36259
  runQueue(ctx, queue).catch(ignoreFailure$1);
35379
36260
  }
35380
36261
  //#endregion
36262
+ //#region src/plugins/assets/ready.ts
36263
+ /**
36264
+ * @file assets plugin — a bundle ready to show. `warm` loads a bundle and puts its textures on the
36265
+ * GPU before a scene shows them (F60): on a Galaxy A26 the frame that showed 6 cold textures took
36266
+ * 55 ms, 8 ms once they were uploaded before. The `ready` effect lets a node rest on that work and
36267
+ * stay answerable (F52). Nothing here is on the frame path: one promise per bundle, and the inbox
36268
+ * event is posted from a promise.
36269
+ */
36270
+ /**
36271
+ * Runs one warm: loads the bundle when it is not loaded, then hands every texture and atlas page
36272
+ * of it to the renderer's upload queue. The answer of the upload is not read: when the renderer
36273
+ * could not upload, the first draw does. A failure drops the entry, so the next `warm` tries again.
36274
+ *
36275
+ * @param ctx - Domain context of the plugin.
36276
+ * @param bundle - Name of a bundle of the manifest.
36277
+ * @param entry - The entry of this warm in `state.warmed`.
36278
+ * @returns A promise that resolves once the upload answered.
36279
+ * @throws {Error} When the load fails or is aborted.
36280
+ */
36281
+ async function runWarm(ctx, bundle, entry) {
36282
+ try {
36283
+ await loadBundle(ctx, bundle, void 0, "request");
36284
+ const { textures, pages } = ctx.state.records.get(bundle) ?? emptyAssets();
36285
+ await ctx.deps.renderer.sync.textures.upload([...textures.values(), ...pages.values()]);
36286
+ entry.done = true;
36287
+ } catch (error) {
36288
+ if (ctx.state.warmed.get(bundle) === entry) ctx.state.warmed.delete(bundle);
36289
+ throw error;
36290
+ }
36291
+ }
36292
+ /**
36293
+ * Starts the one warm of a bundle and records it.
36294
+ *
36295
+ * @param ctx - Domain context of the plugin.
36296
+ * @param bundle - Name of a bundle of the manifest.
36297
+ * @returns The entry every later caller joins.
36298
+ */
36299
+ function startWarm$1(ctx, bundle) {
36300
+ const entry = {
36301
+ promise: Promise.resolve(),
36302
+ done: false
36303
+ };
36304
+ ctx.state.warmed.set(bundle, entry);
36305
+ entry.promise = runWarm(ctx, bundle, entry);
36306
+ return entry;
36307
+ }
36308
+ /**
36309
+ * Warms one bundle, or joins its running or finished warm. Headless it resolves at once, whatever
36310
+ * the name, as `load` does.
36311
+ *
36312
+ * @param ctx - Domain context of the plugin.
36313
+ * @param bundle - Name of a bundle of the manifest.
36314
+ * @returns A promise that resolves once the bundle is loaded and its textures went to the GPU.
36315
+ * @throws {Error} When the manifest has no such bundle, and when the load fails or is aborted.
36316
+ */
36317
+ async function warmBundle(ctx, bundle) {
36318
+ const state = ctx.state;
36319
+ if (state.io === void 0) return;
36320
+ if (state.manifest.bundles[bundle] === void 0) throw unknownBundle(bundle);
36321
+ await (state.warmed.get(bundle) ?? startWarm$1(ctx, bundle)).promise;
36322
+ }
36323
+ /**
36324
+ * Tells whether the `ready` effect counts a bundle as there: loaded and warmed, or its last warm
36325
+ * for `ready` failed.
36326
+ *
36327
+ * @param state - The plugin state.
36328
+ * @param bundle - Name of a bundle of the manifest.
36329
+ * @returns True when a node need not rest on it.
36330
+ */
36331
+ function isThere(state, bundle) {
36332
+ if (state.readyFailed.has(bundle)) return true;
36333
+ return state.records.get(bundle)?.status === "loaded" && state.warmed.get(bundle)?.done === true;
36334
+ }
36335
+ /**
36336
+ * Warms one bundle for the `ready` effect. It never rejects: a failure that is not an abort marks
36337
+ * the bundle failed and is logged, so the node resting on it moves on; an abort (an unload, a stop)
36338
+ * marks nothing, and the next `ready` starts the bundle again.
36339
+ *
36340
+ * @param ctx - Domain context of the plugin.
36341
+ * @param bundle - Name of a bundle of the manifest.
36342
+ * @returns A promise that resolves once the warm settled.
36343
+ */
36344
+ async function warmForReady(ctx, bundle) {
36345
+ try {
36346
+ await warmBundle(ctx, bundle);
36347
+ } catch (error) {
36348
+ if (isAbortError(error)) return;
36349
+ ctx.state.readyFailed.add(bundle);
36350
+ ctx.log.warn("assets:ready-failed", {
36351
+ bundle,
36352
+ error: String(error)
36353
+ });
36354
+ }
36355
+ }
36356
+ /**
36357
+ * The handler of the `ready` effect (F52). Headless it answers `true` before any name check, as the
36358
+ * `load` effect does. Every bundle there: `true`, and nothing is posted. Otherwise it starts the
36359
+ * warm of each bundle that is not there, answers `false`, and posts `{ type: "ready" }` into
36360
+ * `flow.inbox` once all of them settled. The posting does not wait on the node's signal: the warm
36361
+ * is shared work, and the inbox keeps only the latest `ready`.
36362
+ *
36363
+ * @param ctx - Domain context of the plugin.
36364
+ * @param bundles - The bundle names of the descriptor.
36365
+ * @returns True when the node may go on, false when it should rest until the inbox event `ready`.
36366
+ * @throws {Error} When the manifest has no such bundle.
36367
+ */
36368
+ function answerReady(ctx, bundles) {
36369
+ const state = ctx.state;
36370
+ if (state.io === void 0) return true;
36371
+ for (const name of bundles) if (state.manifest.bundles[name] === void 0) throw unknownBundle(name);
36372
+ const missing = bundles.filter((name) => !isThere(state, name));
36373
+ if (missing.length === 0) return true;
36374
+ Promise.all(missing.map((name) => warmForReady(ctx, name))).then(() => ctx.deps.flow.inbox.post({ type: "ready" }));
36375
+ return false;
36376
+ }
36377
+ //#endregion
35381
36378
  //#region src/plugins/assets/lifecycle.ts
35382
36379
  /**
35383
- * @file assets plugin — lifecycle: the six registrations of `onInit`, the boot of `onStart`
36380
+ * @file assets plugin — lifecycle: the seven registrations of `onInit`, the boot of `onStart`
35384
36381
  * and the teardown that frees every texture and cancels every fetch.
35385
36382
  */
35386
36383
  /**
@@ -35411,7 +36408,7 @@ function withDeps$6(ctx) {
35411
36408
  };
35412
36409
  }
35413
36410
  /**
35414
- * Reads the bundle names out of a `load` descriptor.
36411
+ * Reads the bundle names out of a `load` or a `ready` descriptor.
35415
36412
  *
35416
36413
  * @param descriptor - What the node awaited.
35417
36414
  * @returns The bundle names, in the order the node listed them.
@@ -35494,17 +36491,17 @@ async function enterNode(ctx, node, run) {
35494
36491
  }
35495
36492
  }
35496
36493
  /**
35497
- * Connects the plugin in `onInit`: the `load` stage of `onEnter`, the handler of the `load`
35498
- * effect, the texture provider of `renderer`, the start and the end of an exit in `world`, counted,
35499
- * and the system `assets.leave` of phase `sync`, which lets a leaving scene's bundles go once its
35500
- * elements played their exit (F51). The six removers go to the state, because `onStop` has nothing
35501
- * but the state.
36494
+ * Connects the plugin in `onInit`: the `load` stage of `onEnter`, the handlers of the `load` and
36495
+ * the `ready` effects (F52), both run in fast mode too, the texture provider of `renderer`, the
36496
+ * start and the end of an exit in `world`, counted, and the system `assets.leave` of phase `sync`,
36497
+ * which lets a leaving scene's bundles go once its elements played their exit (F51). The seven
36498
+ * removers go to the state, because `onStop` has nothing but the state.
35502
36499
  *
35503
36500
  * @param ctx - Kernel context of the assets plugin.
35504
36501
  */
35505
36502
  function connectAssets(ctx) {
35506
36503
  const assets = withDeps$6(ctx);
35507
- assets.state.removers.push(assets.deps.flow.onEnter("load", (node, run) => enterNode(assets, node, run)), assets.deps.flow.fx.handle("load", (descriptor, run) => runLoadEffect(assets, descriptor, run), { runInFast: true }), assets.deps.renderer.sync.textures.provide((key) => lookupTexture(assets, key)), assets.deps.world.ecs.onAdded(Exiting, () => {
36504
+ assets.state.removers.push(assets.deps.flow.onEnter("load", (node, run) => enterNode(assets, node, run)), assets.deps.flow.fx.handle("load", (descriptor, run) => runLoadEffect(assets, descriptor, run), { runInFast: true }), assets.deps.flow.fx.handle("ready", (descriptor) => answerReady(assets, bundlesOfDescriptor(descriptor)), { runInFast: true }), assets.deps.renderer.sync.textures.provide((key) => lookupTexture(assets, key)), assets.deps.world.ecs.onAdded(Exiting, () => {
35508
36505
  assets.state.exiting += 1;
35509
36506
  }), assets.deps.world.ecs.onRemoved(Exiting, () => {
35510
36507
  assets.state.exiting -= 1;
@@ -35584,7 +36581,7 @@ async function startAssets(ctx) {
35584
36581
  }
35585
36582
  /**
35586
36583
  * Frees everything in `onStop`: the preload and every running load are aborted, every texture is
35587
- * destroyed, the six registrations are removed and the registries are emptied, the companions
36584
+ * destroyed, the seven registrations are removed and the registries are emptied, the companions
35588
36585
  * too. A teardown context carries the state and nothing else, so no other plugin is called here:
35589
36586
  * no companion's `unload` either, its plugin stopped before this one.
35590
36587
  *
@@ -35607,6 +36604,8 @@ function releaseAll(state) {
35607
36604
  state.featureOfFlow.clear();
35608
36605
  state.pinned.clear();
35609
36606
  state.warned.clear();
36607
+ state.warmed.clear();
36608
+ state.readyFailed.clear();
35610
36609
  state.companions.length = 0;
35611
36610
  state.current = void 0;
35612
36611
  state.sceneId = void 0;
@@ -35753,6 +36752,7 @@ function createAssetsApi(ctx) {
35753
36752
  const assets = withDeps$6(ctx);
35754
36753
  return {
35755
36754
  load: (bundle) => loadBundle(assets, bundle, void 0, "request"),
36755
+ warm: (bundle) => warmBundle(assets, bundle),
35756
36756
  unload: (bundle) => unload(assets, bundle),
35757
36757
  isLoaded: (bundle) => isLoaded(assets, bundle),
35758
36758
  texture: (key) => lookupTexture(assets, key),
@@ -36430,7 +37430,9 @@ function createAssetsState() {
36430
37430
  sceneLeaving: [],
36431
37431
  exiting: 0,
36432
37432
  companions: [],
36433
- restPending: false
37433
+ restPending: false,
37434
+ readyFailed: emptySet$2(),
37435
+ warmed: emptyMap$3()
36434
37436
  };
36435
37437
  }
36436
37438
  /**
@@ -41339,7 +42341,7 @@ function complete(ectx) {
41339
42341
  ectx.log.info("effects:checks", {
41340
42342
  definitions: checks.checked,
41341
42343
  broken: checks.failed,
41342
- ms: tenths(pageNow() - checks.startedAt)
42344
+ ms: tenths(pageNow$1() - checks.startedAt)
41343
42345
  });
41344
42346
  checks.resolve();
41345
42347
  }
@@ -41482,7 +42484,7 @@ function checkGlsl(ectx, run, definition) {
41482
42484
  function startChecks(ectx) {
41483
42485
  const { checks } = ectx.state;
41484
42486
  const backend = ectx.deps.renderer.host.kind();
41485
- checks.startedAt = pageNow();
42487
+ checks.startedAt = pageNow$1();
41486
42488
  for (const definition of checkList(ectx)) {
41487
42489
  if (checks.status.has(definition)) continue;
41488
42490
  checks.status.set(definition, "pending");
@@ -41618,12 +42620,12 @@ async function gateChecks(ectx, node, run) {
41618
42620
  if (!ectx.state.checks.started) startChecks(ectx);
41619
42621
  const { checks } = ectx.state;
41620
42622
  if (checks.waiting === 0) return;
41621
- const began = pageNow();
42623
+ const began = pageNow$1();
41622
42624
  await Promise.race([checks.settled, whenAborted(run.signal)]);
41623
42625
  if (run.signal.aborted) return;
41624
42626
  ectx.log.info("effects:mount-waited", {
41625
42627
  node: node.path,
41626
- ms: tenths(pageNow() - began)
42628
+ ms: tenths(pageNow$1() - began)
41627
42629
  });
41628
42630
  }
41629
42631
  //#endregion
@@ -49781,9 +50783,23 @@ function buildRun(ctx, pixi, run, style, at) {
49781
50783
  return objects;
49782
50784
  }
49783
50785
  /**
50786
+ * Tells whether a label of a `"box"` style is drawn outside a ui layout: it has no box, it is not
50787
+ * empty (ui lays an empty one out 0 wide), and no layout measured its style at a width (a laid-out
50788
+ * label that fits its row gets no box, F65).
50789
+ *
50790
+ * @param ctx - Domain context of the text plugin.
50791
+ * @param style - The style of the label.
50792
+ * @param value - The component value.
50793
+ * @returns True when the label wraps at no box for want of a layout.
50794
+ */
50795
+ function isOutsideLayout(ctx, style, value) {
50796
+ if (style.wrap !== "box") return false;
50797
+ return (value.box ?? 0) <= 0 && value.resolved !== "" && !ctx.state.laidOut.has(value.style);
50798
+ }
50799
+ /**
49784
50800
  * The style and the laid-out block of one component value, under the generation of this moment.
49785
- * A `"box"` style wraps at the box `ui` wrote; with none (a label outside a ui layout) it is one
49786
- * line, and a dev warning says so once per style.
50801
+ * A `"box"` style wraps at the box `ui` wrote; with none it is one line, and a dev warning says
50802
+ * so once per style unless a layout measured that style at a width (a label that fits its row).
49787
50803
  *
49788
50804
  * @param ctx - Domain context of the text plugin.
49789
50805
  * @param value - The component value.
@@ -49791,7 +50807,7 @@ function buildRun(ctx, pixi, run, style, at) {
49791
50807
  */
49792
50808
  function labelOf(ctx, value) {
49793
50809
  const style = styleOf$1(ctx, value.style);
49794
- if (style.wrap === "box" && (value.box ?? 0) <= 0 && value.resolved !== "") warnOnce(ctx, `wrap-box:${value.style}`, "text: wrap \"box\" outside layout", { style: value.style });
50810
+ if (isOutsideLayout(ctx, style, value)) warnOnce(ctx, `wrap-box:${value.style}`, "text: wrap \"box\" outside layout", { style: value.style });
49795
50811
  return {
49796
50812
  style,
49797
50813
  layout: layoutFor(ctx, value.resolved, value.style, value.box),
@@ -50159,6 +51175,7 @@ function stopText(state) {
50159
51175
  state.added.length = 0;
50160
51176
  state.rewrite.length = 0;
50161
51177
  state.warned.clear();
51178
+ state.laidOut.clear();
50162
51179
  }
50163
51180
  //#endregion
50164
51181
  //#region src/plugins/text/api.ts
@@ -50183,6 +51200,7 @@ function createTextApi(ctx) {
50183
51200
  return {
50184
51201
  measure: (content, style, width = 0) => {
50185
51202
  const tctx = text();
51203
+ if (width > 0) tctx.state.laidOut.add(style);
50186
51204
  return sizeOf(layoutFor(tctx, sourceOf(tctx, content), style, width));
50187
51205
  },
50188
51206
  hasGlyph: (char, style) => hasGlyphOf(text(), char, style),
@@ -50311,6 +51329,7 @@ function createTextState$1() {
50311
51329
  rewrite: [],
50312
51330
  generation: 0,
50313
51331
  warned: emptySet$1(),
51332
+ laidOut: emptySet$1(),
50314
51333
  removers: []
50315
51334
  };
50316
51335
  }
@@ -52766,6 +53785,26 @@ function nineSliceClipped(element) {
52766
53785
  };
52767
53786
  }
52768
53787
  /**
53788
+ * Reports an element whose style is `hollow` but names no nine-slice: only a nine-slice has a
53789
+ * centre to leave out, so the field does nothing there. `hollow: false` asks for nothing either way.
53790
+ *
53791
+ * @param element - The element to check.
53792
+ * @returns The finding, or `undefined`.
53793
+ * @example
53794
+ * ```ts
53795
+ * hollowWithoutNineSlice({ key: "memFrost", type: "stack", style: { hollow: true } } as Element);
53796
+ * // { rule: "hollow-without-nine-slice", key: "memFrost", detail: "stack" }
53797
+ * ```
53798
+ */
53799
+ function hollowWithoutNineSlice(element) {
53800
+ if (element.style.hollow !== true || element.style.nineSlice !== void 0) return void 0;
53801
+ return {
53802
+ rule: "hollow-without-nine-slice",
53803
+ key: nameOf(element),
53804
+ detail: element.type
53805
+ };
53806
+ }
53807
+ /**
52769
53808
  * Reports a root element whose style sets a `zIndex`: a root draws at the order of its layer,
52770
53809
  * so the value is ignored.
52771
53810
  *
@@ -52786,7 +53825,7 @@ function zIndexOnRoot(element) {
52786
53825
  };
52787
53826
  }
52788
53827
  /**
52789
- * Runs the five rules over every live element.
53828
+ * Runs the six rules over every live element.
52790
53829
  *
52791
53830
  * @param ctx - Domain context of the ui plugin.
52792
53831
  * @returns One finding per rule and element, in element order.
@@ -52801,6 +53840,7 @@ function runLint(ctx) {
52801
53840
  textOverflow(ctx, element),
52802
53841
  absoluteWithoutReason(element),
52803
53842
  nineSliceClipped(element),
53843
+ hollowWithoutNineSlice(element),
52804
53844
  zIndexOnRoot(element)
52805
53845
  ]) if (finding !== void 0) findings.push(finding);
52806
53846
  }
@@ -55453,33 +56493,40 @@ function createReconciler(ctx, modules, fields) {
55453
56493
  *
55454
56494
  * @param element - The element that left the description.
55455
56495
  * @param cause - Why it leaves: `"change"` for a commit, `"mount"` when it goes with its screen.
56496
+ * @param leads - The element is the top of a root that goes with its screen.
55456
56497
  */
55457
- function exitElement(element, cause) {
56498
+ function exitElement(element, cause, leads = false) {
55458
56499
  forgetSubtree(element);
55459
56500
  forgetInstances(state, element.identity);
55460
56501
  state.exiting.add(element.entity);
55461
- exitSubtree(element, cause);
56502
+ exitSubtree(element, cause, false, leads);
55462
56503
  }
55463
56504
  /**
55464
56505
  * Plays the exit hook of an element and of everything under it, each told why it leaves. A child
55465
- * keeps its rect and its `Exiting` tag until the whole subtree stopped moving.
56506
+ * keeps its rect and its `Exiting` tag until the whole subtree stopped moving. Under the top of a
56507
+ * root that goes with its screen and plays an exit of its own, the elements stand and go with it
56508
+ * (F55b): an exit of theirs would change the subtree in every frame of the top's, so a kept
56509
+ * picture of it would be drawn live through its filters.
55466
56510
  *
55467
56511
  * @param element - The element that leaves, or one of its children.
55468
56512
  * @param cause - Why the subtree leaves.
56513
+ * @param stands - The element stands under an exit that leads the leave: its own hook does not play.
56514
+ * @param leads - The element is the top of a root that goes with its screen.
55469
56515
  */
55470
- function exitSubtree(element, cause) {
56516
+ function exitSubtree(element, cause, stands, leads) {
55471
56517
  dropHost(state, element.entity);
55472
56518
  state.scrolls.delete(element.entity);
55473
- if (overlayOf(element) === void 0) modules.layout.exit(element, cause);
56519
+ if (overlayOf(element) === void 0) modules.layout.exit(element, cause, void 0, stands);
55474
56520
  else {
55475
56521
  const span = createSpan();
55476
- modules.layout.exit(element, cause, span);
56522
+ modules.layout.exit(element, cause, span, stands);
55477
56523
  exitOverlay(ctx, modules.layout, element, span);
55478
56524
  }
55479
56525
  if (element.type === "input") fields.exit(element);
56526
+ const under = stands || leads && !modules.layout.settled(element);
55480
56527
  for (const child of element.children) {
55481
56528
  const childElement = state.elements.get(child);
55482
- if (childElement !== void 0) exitSubtree(childElement, cause);
56529
+ if (childElement !== void 0) exitSubtree(childElement, cause, under, false);
55483
56530
  }
55484
56531
  }
55485
56532
  /**
@@ -56186,7 +57233,7 @@ function createReconciler(ctx, modules, fields) {
56186
57233
  state.removing.add(entity);
56187
57234
  hostsMoved(state);
56188
57235
  if (element !== void 0) {
56189
- exitElement(element, cause);
57236
+ exitElement(element, cause, cause === "mount");
56190
57237
  if (cause === "mount" && !modules.layout.settled(element)) state.leading.add(element.entity);
56191
57238
  }
56192
57239
  ctx.deps.time.wake();
@@ -57174,15 +58221,16 @@ const EXIT_WAITS_FOREVER = "ui:exit-waits-forever";
57174
58221
  * @param element - The element that left the description.
57175
58222
  * @param cause - Why it leaves: a `when: "change"` hook plays nothing for `"mount"`, and the next sweep takes it.
57176
58223
  * @param span - Notes the longest finite track the exit hook starts, for the level of an overlay.
58224
+ * @param stands - The element stands and goes with the exit over it: its own exit hook does not play (F55b).
57177
58225
  */
57178
- function beginExit(ctx, state, element, cause, span) {
58226
+ function beginExit(ctx, state, element, cause, span, stands = false) {
57179
58227
  detachNode(state, element);
57180
58228
  element.dropKey?.();
57181
58229
  element.dropKey = void 0;
57182
58230
  if (!element.live) return;
57183
58231
  ctx.deps.world.ecs.tag(element.entity, Exiting);
57184
58232
  element.handles = keptExtras(ctx, element);
57185
- const hook = element.motion?.exit;
58233
+ const hook = stands ? void 0 : element.motion?.exit;
57186
58234
  play(ctx, element, hook === void 0 ? void 0 : (view) => hook(view, element.node, void 0, cause), span);
57187
58235
  }
57188
58236
  /**
@@ -58611,7 +59659,7 @@ function createLayoutApi(ctx) {
58611
59659
  changeExtra: (element, name, values, hint) => playExtraChange(ctx, element, name, values, hint),
58612
59660
  repose: (element, parent, hooked, hint) => repose(ctx, element, parent, hooked, hint),
58613
59661
  lift: (element, units) => liftRest(ctx, element, units),
58614
- exit: (element, cause, span) => beginExit(ctx, state, element, cause, span),
59662
+ exit: (element, cause, span, stands) => beginExit(ctx, state, element, cause, span, stands),
58615
59663
  playTrack: (element, hook) => {
58616
59664
  play(ctx, element, hook);
58617
59665
  },
@@ -59985,19 +61033,48 @@ function load(bundle) {
59985
61033
  };
59986
61034
  }
59987
61035
  /**
59988
- * Binds `defineBundles` and `load` to one game's bundle keys. Type-only: the same functions.
61036
+ * Creates the effect a node awaits to rest on bundles and stay answerable (F52). The handler of the
61037
+ * kind `"ready"` is registered by this plugin and runs in fast mode too. It answers `true` when every
61038
+ * bundle is loaded and its textures went to the GPU (`assets.warm`), or its last load failed, so a
61039
+ * node never hangs on it. Otherwise it starts what is missing, answers `false`, and posts the world
61040
+ * event `ready` into `flow.inbox` once all of it settled: the node rests on `inbox: ["ready"]`
61041
+ * meanwhile, and Back still answers. A headless `assets` answers `true`; an app without `assets`,
61042
+ * such as the headless app of `game.headless()`, resolves `undefined`, hence `=== false`. A name
61043
+ * the manifest lacks throws, as for `load`.
61044
+ *
61045
+ * @param bundle - One bundle name, or a list of them.
61046
+ * @returns The descriptor a transit node awaits.
61047
+ * @example
61048
+ * ```ts
61049
+ * ready("ep01.memory"); // { kind: "ready", payload: { bundles: ["ep01.memory"] } }
61050
+ * // The transit node in front of the rest node `wait` (inbox: ["elapsed", "ready"]):
61051
+ * if ((await fx(ready(["ep01.memory", "ui"]))) === false) return out.wait(); // false: still loading
61052
+ * return out.open(); // true: loaded and on the GPU
61053
+ * ```
61054
+ */
61055
+ function ready(bundle) {
61056
+ return {
61057
+ kind: "ready",
61058
+ payload: { bundles: typeof bundle === "string" ? [bundle] : [...bundle] }
61059
+ };
61060
+ }
61061
+ /**
61062
+ * Binds `defineBundles`, `load` and `ready` to one game's bundle keys. Type-only: the same
61063
+ * functions.
59989
61064
  *
59990
- * @returns The two helpers whose keys are checked by the compiler.
61065
+ * @returns The three helpers whose keys are checked by the compiler.
59991
61066
  * @example
59992
61067
  * ```ts
59993
- * const { load } = bundlesFor<"board" | "board.chains">();
61068
+ * const { load, ready } = bundlesFor<"board" | "board.chains">();
59994
61069
  * load("board.chains"); // { kind: "load", payload: { bundles: ["board.chains"] } }
61070
+ * ready("board"); // { kind: "ready", payload: { bundles: ["board"] } }
59995
61071
  * ```
59996
61072
  */
59997
61073
  function bundlesFor() {
59998
61074
  return {
59999
61075
  defineBundles,
60000
- load
61076
+ load,
61077
+ ready
60001
61078
  };
60002
61079
  }
60003
61080
  //#endregion
@@ -61203,6 +62280,7 @@ function cubicBezier(x1, y1, x2, y2) {
61203
62280
  * | renderer | monitor | { budgetMs: "rate", frames: 1024, stillFrames: 60, stillMpx: 0.25, churnPerSecond: 4, churnSeconds: 3, churnAfterTrimMs: 1000 }, the performance log of an agent build, the thresholds of its `problem` rules and `sections`, the sections it starts with (`MonitorSections`, every one on when left out); shallow merge: a game that sets `monitor` gives `budgetMs` and `frames`, a threshold it leaves out keeps its default, a section it leaves out is on |
61204
62281
  * | renderer | keptMb | 64, MiB the kept pictures may hold: a filtered view that did not change is drawn from its picture, the least recently drawn goes first; 0 keeps none |
61205
62282
  * | renderer | warm | 1, probe draws a drawn frame that create the GPU pipelines while the game loads; 0 creates each in the first frame that needs it |
62283
+ * | renderer | uploads | 1, texture sources `sync.textures.upload` puts on the GPU a drawn frame (~9 ms each on the A26); 0 turns the queue off |
61206
62284
  * | input | tapSlopPx | 12 |
61207
62285
  * | input | longPressMs | 450 |
61208
62286
  * | input | dragStartPx | 8 |
@@ -61360,4 +62438,4 @@ const screen = [
61360
62438
  uiPlugin
61361
62439
  ];
61362
62440
  //#endregion
61363
- export { spawn as $, exit as $t, types_exports$11 as A, Outline as At, textPlugin as B, rendererPlugin as Bt, types_exports$4 as C, Emitter as Ct, types_exports$8 as D, Displacement as Dt, types_exports$7 as E, ColorMatrix as Et, types_exports$16 as F, assetsPlugin as Ft, frames as G, PERF_GAP_BUCKETS as Gt, HAPTIC_KINDS as H, frameAt as Ht, types_exports$17 as I, animPlugin as It, parallel as J, SHADER_WHATS as Jt, haptic as K, PERF_LOG_VERSION as Kt, uiPlugin as L, AnimPlayer as Lt, types_exports$13 as M, debugPlugin as Mt, types_exports$14 as N, audioPlugin as Nt, types_exports$9 as O, Glow as Ot, types_exports$15 as P, scenesPlugin as Pt, sfx as Q, refuseStart as Qt, resolve as R, Animation as Rt, types_exports$3 as S, effectsPlugin as St, types_exports$6 as T, Blur as Tt, defineAnimation as U, MONITOR_SECTIONS as Ut, platformPlugin as V, PASS_KINDS as Vt, external as W, PERF_CAUSES as Wt, sequence as X, worldPlugin as Xt, play$1 as Y, DEMAND_REASONS as Yt, set as Z, flowPlugin as Zt, defineBundles as _, Traceable as _t, cubicBezier as a, lifecyclePlugin as an, inputPlugin as at, types_exports$1 as b, messageArgument as bt, hint as c, Held as ct, defineScene as d, PointerOver as dt, slot as en, spawned as et, tr as f, Pressable as ft, music as g, Touchable as gt, defineEmitter as h, Tappable as ht, screen as i, SaveUnreadableError as in, wait as it, types_exports$12 as j, defineFilter as jt, types_exports$10 as k, Noise as kt, schedule as l, Hovered as lt, flattenFeatures as m, Swipeable as mt, createPlugin as n, type as nn, tween as nt, defineEffect as o, timePlugin as on, Draggable as ot, defineFeature as p, Pressed as pt, mark as q, PROBLEM_RULES as qt, defineGame as r, modelPlugin as rn, use as rt, guide as s, clockPlugin as sn, DropTarget as st, createApp as t, to as tn, stagger as tt, projection as u, Pointer as ut, load as v, Traced as vt, types_exports$5 as w, Alpha as wt, types_exports$2 as x, messageDuration as xt, types_exports as y, i18nPlugin as yt, defineMotion as z, Frames as zt };
62441
+ export { sfx as $, refuseStart as $t, types_exports$10 as A, Noise as At, defineMotion as B, Frames as Bt, types_exports$3 as C, effectsPlugin as Ct, types_exports$7 as D, ColorMatrix as Dt, types_exports$6 as E, Blur as Et, types_exports$15 as F, scenesPlugin as Ft, external as G, PERF_CAUSES as Gt, platformPlugin as H, PASS_KINDS as Ht, types_exports$16 as I, assetsPlugin as It, mark as J, PROBLEM_RULES as Jt, frames as K, PERF_GAP_BUCKETS as Kt, types_exports$17 as L, animPlugin as Lt, types_exports$12 as M, defineFilter as Mt, types_exports$13 as N, debugPlugin as Nt, types_exports$8 as O, Displacement as Ot, types_exports$14 as P, audioPlugin as Pt, set as Q, flowPlugin as Qt, uiPlugin as R, AnimPlayer as Rt, types_exports$2 as S, messageDuration as St, types_exports$5 as T, Alpha as Tt, HAPTIC_KINDS as U, frameAt as Ut, textPlugin as V, rendererPlugin as Vt, defineAnimation as W, MONITOR_SECTIONS as Wt, play$1 as X, DEMAND_REASONS as Xt, parallel as Y, SHADER_WHATS as Yt, sequence as Z, worldPlugin as Zt, defineBundles as _, Touchable as _t, cubicBezier as a, SaveUnreadableError as an, wait as at, types_exports as b, i18nPlugin as bt, hint as c, clockPlugin as cn, DropTarget as ct, defineScene as d, Pointer as dt, exit as en, spawn as et, tr as f, PointerOver as ft, music as g, Tappable as gt, defineEmitter as h, Swipeable as ht, screen as i, modelPlugin as in, use as it, types_exports$11 as j, Outline as jt, types_exports$9 as k, Glow as kt, schedule as l, Held as lt, flattenFeatures as m, Pressed as mt, createPlugin as n, to as nn, stagger as nt, defineEffect as o, lifecyclePlugin as on, inputPlugin as ot, defineFeature as p, Pressable as pt, haptic as q, PERF_LOG_VERSION as qt, defineGame as r, type as rn, tween as rt, guide as s, timePlugin as sn, Draggable as st, createApp as t, slot as tn, spawned as tt, projection as u, Hovered as ut, load as v, Traceable as vt, types_exports$4 as w, Emitter as wt, types_exports$1 as x, messageArgument as xt, ready as y, Traced as yt, resolve as z, Animation as zt };