@moku-labs/game 0.15.8 → 0.15.10

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 (33) 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/{commands-DWyII1jn.d.mts → commands-DoMH0hnR.d.mts} +5 -5
  8. package/dist/control.d.mts +4 -4
  9. package/dist/{define-Bebctfvs.d.mts → define-B8A5IoeS.d.mts} +2 -2
  10. package/dist/{headless-DobgwTKK.d.mts → headless-CrxofjLt.d.mts} +1 -1
  11. package/dist/{index-B3dfnsUZ.d.mts → index-PbX7OQIC.d.mts} +3 -3
  12. package/dist/index.d.mts +4 -4
  13. package/dist/index.mjs +2 -2
  14. package/dist/inspect.d.mts +6 -6
  15. package/dist/inspect.mjs +1 -1
  16. package/dist/jsx-dev-runtime.d.mts +1 -1
  17. package/dist/jsx-runtime.d.mts +1 -1
  18. package/dist/{runtime-gnMApclv.d.mts → runtime-Bk_rHR3K.d.mts} +1 -1
  19. package/dist/{sources-OvHvyeJS.mjs → sources-QCowxSpC.mjs} +1 -1
  20. package/dist/{src-DNVAJHwd.mjs → src-zXAFOqEc.mjs} +359 -60
  21. package/dist/testing.d.mts +4 -4
  22. package/dist/testing.mjs +1 -1
  23. package/dist/{types-CPxx3NSG.d.mts → types-B73p1XBn.d.mts} +2 -2
  24. package/dist/{types-BcEtn9XV.d.mts → types-Dr5uC2fS.d.mts} +4 -4
  25. package/dist/{types-CYoGvBkh.d.mts → types-DwktxRrt.d.mts} +18 -6
  26. package/dist/{types-Mj75CDO6.d.mts → types-YXykgTOp.d.mts} +2 -2
  27. package/dist/{types-C4D9UVgb.d.mts → types-i77Nrrv-.d.mts} +29 -16
  28. package/dist/{visual-2m4wJL_z.mjs → visual-CIUx49C0.mjs} +26 -6
  29. package/dist/{visual-CxQ4Iqd3.d.mts → visual-CQnurNOj.d.mts} +6 -6
  30. package/dist/visual.d.mts +1 -1
  31. package/dist/visual.mjs +1 -1
  32. package/llms.txt +4 -3
  33. package/package.json +1 -1
@@ -2,7 +2,7 @@ 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
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 { A as Order, C as Sprite, D as Exiting, F as system, I as systemsFor, L as tag, M as component, O as Layer, P as resource, S as Shape, T as componentsFor, _ as HudPart, b as OverlayLevel, c as Countdown, d as builtInStyles, g as Display, h as textFor, i as samePose, j as Tree, k as Now, l as Text, m as readStyle, n as drawnRect, o as visualOf$1, r as fitScaleOf, s as visualRectOf, t as CONTENT$1, v as NineSlice, w as Transform, x as Parent, y as Overlay } from "./visual-2m4wJL_z.mjs";
5
+ import { A as Now, C as Sprite, E as componentsFor, F as resource, I as system, L as systemsFor, M as Tree, N as component, O as Exiting, R as tag, S as Shape, T as arcDefaults, _ as HudPart, b as OverlayLevel, c as Countdown, d as builtInStyles, g as Display, h as textFor, i as samePose, j as Order, k as Layer, l as Text, m as readStyle, n as drawnRect, o as visualOf$1, r as fitScaleOf, s as visualRectOf, t as CONTENT$1, v as NineSlice, w as Transform, x as Parent, y as Overlay } from "./visual-CIUx49C0.mjs";
6
6
  import { a as isDev } from "./dev-BNndV-hU.mjs";
7
7
  import { i as isComponentDefinition, n as flatten, r as defineComponent } from "./flatten-BkXuscAV.mjs";
8
8
  import { envPlugin, logPlugin } from "@moku-labs/common/browser";
@@ -16484,8 +16484,9 @@ function writeTakeKeys(record, now, box) {
16484
16484
  }
16485
16485
  /**
16486
16486
  * Takes or retakes the picture of a record: stages 1, 2 (when its first filter has inputs) and 3, at the phase of this
16487
- * frame. Refused, and the view drawn live, for a `Display` in the subtree without `onDemand`, empty bounds, a picture
16488
- * over 4096 device pixels, or a budget that has no room (P6.3, warned once).
16487
+ * frame. The bounds are cut to the filter area of the top when it holds one (F43). Refused, and the view drawn live,
16488
+ * for a `Display` in the subtree without `onDemand`, empty bounds, a picture over 4096 device pixels, or a budget that
16489
+ * has no room (P6.3, warned once).
16489
16490
  *
16490
16491
  * @param kctx - The kept context.
16491
16492
  * @param pixi - The module that draws.
@@ -16498,7 +16499,7 @@ function writeTakeKeys(record, now, box) {
16498
16499
  function takePicture(kctx, pixi, matrices, record, view, now) {
16499
16500
  const { top } = record;
16500
16501
  if (!kctx.ctx.config.onDemand && holdsDisplay(kctx, top)) return false;
16501
- const local = top.getLocalBounds();
16502
+ const local = clippedToArea(top.getLocalBounds(), top.filterArea);
16502
16503
  if (isEmptyBounds(local)) return false;
16503
16504
  const box = linearBounds(now.a, now.b, now.c, now.d, local);
16504
16505
  const x = gridSpan(now.tx + box.x0, now.tx + box.x1, now.rs);
@@ -16530,6 +16531,27 @@ function takePicture(kctx, pixi, matrices, record, view, now) {
16530
16531
  return ok;
16531
16532
  }
16532
16533
  /**
16534
+ * The local bounds of a top cut to its filter area, both in the top's own units: its filters draw nothing outside
16535
+ * the area. With no area the bounds are returned as they are. Pure.
16536
+ *
16537
+ * @param bounds - The top's local bounds.
16538
+ * @param area - The filter area on the top, or none.
16539
+ * @returns The bounds the picture takes.
16540
+ * @example
16541
+ * ```ts
16542
+ * clippedToArea({ minX: 0, minY: 0, maxX: 400, maxY: 400 }, { x: 100, y: 100, width: 40, height: 40 }); // { minX: 100, minY: 100, maxX: 140, maxY: 140 }
16543
+ * ```
16544
+ */
16545
+ function clippedToArea(bounds, area) {
16546
+ if (area === null || area === void 0) return bounds;
16547
+ return {
16548
+ minX: Math.max(bounds.minX, area.x),
16549
+ minY: Math.max(bounds.minY, area.y),
16550
+ maxX: Math.min(bounds.maxX, area.x + area.width),
16551
+ maxY: Math.min(bounds.maxY, area.y + area.height)
16552
+ };
16553
+ }
16554
+ /**
16533
16555
  * Tells whether local bounds hold nothing to draw: no width or no height. Pure.
16534
16556
  *
16535
16557
  * @param bounds - The bounds of a subtree in its own space.
@@ -25527,10 +25549,22 @@ function stopOverlays(state) {
25527
25549
  }
25528
25550
  //#endregion
25529
25551
  //#region src/plugins/renderer/sync/shape-path.ts
25552
+ /**
25553
+ * @file renderer/sync — the path of a `Shape`: a rectangle, a rounded rectangle or a triangle,
25554
+ * its fill, and its stroke, solid or dashed, or an arc, a stroke alone. A dashed stroke walks the
25555
+ * outline (straight edges, and rounded corners sampled as arcs) and strokes one open line per dash.
25556
+ * An arc runs on the circle inscribed in its box, from 12 o'clock clockwise.
25557
+ */
25530
25558
  /** Chords per rounded corner of a dashed outline: under 0.3 u off the arc at a 48 u radius. */
25531
25559
  const CORNER_SAMPLES = 8;
25532
25560
  /** The gap between two dashes, as a fraction of the dash length. */
25533
25561
  const GAP_RATIO = .5;
25562
+ /** Segments of a full ring: a ring of 28 u across strays under 0.03 u off its circle. */
25563
+ const RING_SEGMENTS = 48;
25564
+ /** The fewest segments of an arc that draws: a sliver still bends. */
25565
+ const ARC_MIN_SEGMENTS = 2;
25566
+ /** 12 o'clock, where an arc starts: Pixi's angles run clockwise from 3 o'clock. */
25567
+ const TWELVE = -Math.PI / 2;
25534
25568
  /**
25535
25569
  * The corner radius the rectangle is drawn with: Pixi clamps it to half the shorter side.
25536
25570
  *
@@ -25715,8 +25749,9 @@ function dashesOf(outline, dash) {
25715
25749
  }
25716
25750
  /**
25717
25751
  * Draws the path of a shape and fills it in a colour at the alpha given. At alpha 0 the path stays unfilled, so only
25718
- * a stroke shows. A clip mask is drawn here too, never dashed. A fill-only shape is drawn white and coloured by the
25719
- * object's tint, so a colour change draws no path (spec 26 R2.1).
25752
+ * a stroke shows. A clip mask is drawn here too, never dashed; the path of an arc here is its box, which is what it
25753
+ * clips its children to. A fill-only shape is drawn white and coloured by the object's tint, so a colour change draws
25754
+ * no path (spec 26 R2.1).
25720
25755
  *
25721
25756
  * @param object - The graphics to draw on.
25722
25757
  * @param value - The shape value.
@@ -25726,7 +25761,7 @@ function dashesOf(outline, dash) {
25726
25761
  function drawShapePath(object, value, fillAlpha, color = value.fill) {
25727
25762
  object.clear();
25728
25763
  if (value.kind === "triangle") object.moveTo(0, 0).lineTo(value.w, value.h / 2).lineTo(0, value.h).closePath();
25729
- else if (value.radius > 0) object.roundRect(0, 0, value.w, value.h, value.radius);
25764
+ else if (value.kind === "rect" && value.radius > 0) object.roundRect(0, 0, value.w, value.h, value.radius);
25730
25765
  else object.rect(0, 0, value.w, value.h);
25731
25766
  if (fillAlpha <= 0) return;
25732
25767
  object.fill(fillAlpha < 1 ? {
@@ -25759,6 +25794,102 @@ function strokeShape(object, value) {
25759
25794
  }
25760
25795
  object.stroke(style);
25761
25796
  }
25797
+ /**
25798
+ * The share of the circle an arc sweeps, clamped to 0..1: anything not above 0, `NaN` too, is 0; a whole ring when
25799
+ * the value leaves it out.
25800
+ *
25801
+ * @param value - The shape value.
25802
+ * @returns The share.
25803
+ * @example
25804
+ * ```ts
25805
+ * sweepOf({ ...Shape.defaults, kind: "arc", sweep: 1.4 }); // 1
25806
+ * ```
25807
+ */
25808
+ function sweepOf(value) {
25809
+ const sweep = value.sweep ?? arcDefaults.sweep;
25810
+ if (Number.isNaN(sweep) || sweep <= 0) return 0;
25811
+ return Math.min(sweep, 1);
25812
+ }
25813
+ /**
25814
+ * The radius of the circle an arc runs on: inscribed in the shorter side of its box, inside its stroke.
25815
+ *
25816
+ * @param value - The shape value.
25817
+ * @returns The radius in reference units; 0 or below when the stroke leaves no circle.
25818
+ * @example
25819
+ * ```ts
25820
+ * arcRadiusOf({ ...Shape.defaults, kind: "arc", w: 28, h: 40, strokeWidth: 2 }); // 13
25821
+ * ```
25822
+ */
25823
+ function arcRadiusOf(value) {
25824
+ return (Math.min(value.w, value.h) - value.strokeWidth) / 2;
25825
+ }
25826
+ /**
25827
+ * Tells whether an arc draws nothing: no stroke width, no sweep, or no circle inside its stroke.
25828
+ *
25829
+ * @param value - The shape value, an arc.
25830
+ * @returns True for an arc whose `Graphics` would hold no instruction.
25831
+ * @example
25832
+ * ```ts
25833
+ * isEmptyArc({ ...Shape.defaults, kind: "arc", w: 28, h: 28, strokeWidth: 2, sweep: 0 }); // true
25834
+ * ```
25835
+ */
25836
+ function isEmptyArc(value) {
25837
+ return value.strokeWidth <= 0 || sweepOf(value) <= 0 || arcRadiusOf(value) <= 0;
25838
+ }
25839
+ /**
25840
+ * Strokes an arc: from 12 o'clock clockwise over its share of the circle, in segments as many as its angle needs (48
25841
+ * for a ring, at least 2), ended by its `cap`. A full ring is closed and has no ends. Nothing for an empty arc. Drawn
25842
+ * only when the shape changed: never rebuilt per frame.
25843
+ *
25844
+ * @param object - The graphics, cleared.
25845
+ * @param value - The shape value, an arc.
25846
+ */
25847
+ function strokeArc(object, value) {
25848
+ if (isEmptyArc(value)) return;
25849
+ const sweep = sweepOf(value);
25850
+ const radius = arcRadiusOf(value);
25851
+ const ring = sweep >= 1;
25852
+ const segments = Math.max(ARC_MIN_SEGMENTS, Math.ceil(sweep * RING_SEGMENTS));
25853
+ const step = sweep * 2 * Math.PI / segments;
25854
+ const centreX = value.w / 2;
25855
+ const centreY = value.h / 2;
25856
+ const last = ring ? segments - 1 : segments;
25857
+ object.moveTo(centreX, centreY - radius);
25858
+ for (let index = 1; index <= last; index += 1) {
25859
+ const angle = TWELVE + index * step;
25860
+ object.lineTo(centreX + radius * Math.cos(angle), centreY + radius * Math.sin(angle));
25861
+ }
25862
+ if (ring) {
25863
+ object.closePath();
25864
+ object.stroke({
25865
+ color: value.stroke,
25866
+ width: value.strokeWidth
25867
+ });
25868
+ return;
25869
+ }
25870
+ object.stroke({
25871
+ color: value.stroke,
25872
+ width: value.strokeWidth,
25873
+ cap: value.cap ?? arcDefaults.cap
25874
+ });
25875
+ }
25876
+ /**
25877
+ * Paints a shape on its graphics: an arc as its stroke alone, every other kind as its path, its fill in a colour at its
25878
+ * own fill alpha, and its stroke. The one call that draws a shape's visual.
25879
+ *
25880
+ * @param object - The graphics.
25881
+ * @param value - The shape value.
25882
+ * @param color - The colour of the fill: white for a tinted shape, the value's own otherwise.
25883
+ */
25884
+ function paintShape(object, value, color) {
25885
+ if (value.kind === "arc") {
25886
+ object.clear();
25887
+ strokeArc(object, value);
25888
+ return;
25889
+ }
25890
+ drawShapePath(object, value, value.fillAlpha, color);
25891
+ strokeShape(object, value);
25892
+ }
25762
25893
  //#endregion
25763
25894
  //#region src/plugins/renderer/sync/sizing.ts
25764
25895
  /**
@@ -26738,8 +26869,20 @@ function copyClip(drawn, value) {
26738
26869
  return drawn;
26739
26870
  }
26740
26871
  /**
26872
+ * Tells whether an arc was drawn with this value already: only what its stroke is built from, its
26873
+ * box, its stroke, its share and its ends. A new fill, fill alpha, radius or dash builds nothing.
26874
+ *
26875
+ * @param drawn - What the graphics was last drawn with, an arc too.
26876
+ * @param value - The arc now.
26877
+ * @returns True when nothing has to be drawn.
26878
+ */
26879
+ function sameArc(drawn, value) {
26880
+ return drawn.w === value.w && drawn.h === value.h && drawn.stroke === value.stroke && drawn.strokeWidth === value.strokeWidth && drawn.sweep === value.sweep && drawn.cap === value.cap;
26881
+ }
26882
+ /**
26741
26883
  * Tells whether a shape's graphics was drawn with this value already: the path, the fill and the
26742
- * stroke. Its alpha, its clip and the fill of a tinted shape (its tint) are written apart.
26884
+ * stroke; for an arc only its stroke. Its alpha, its clip and the fill of a tinted shape (its tint)
26885
+ * are written apart.
26743
26886
  *
26744
26887
  * @param drawn - What the graphics was last drawn with.
26745
26888
  * @param value - The shape value now.
@@ -26747,7 +26890,9 @@ function copyClip(drawn, value) {
26747
26890
  * @returns True when nothing has to be drawn.
26748
26891
  */
26749
26892
  function sameShape$1(drawn, value, tinted) {
26750
- return drawn !== void 0 && drawn.tinted === tinted && (tinted || drawn.fill === value.fill) && drawn.kind === value.kind && drawn.w === value.w && drawn.h === value.h && drawn.fillAlpha === value.fillAlpha && drawn.radius === value.radius && drawn.stroke === value.stroke && drawn.strokeWidth === value.strokeWidth && drawn.dash === value.dash;
26893
+ if (drawn === void 0 || drawn.tinted !== tinted || drawn.kind !== value.kind) return false;
26894
+ if (value.kind === "arc") return sameArc(drawn, value);
26895
+ return (tinted || drawn.fill === value.fill) && drawn.w === value.w && drawn.h === value.h && drawn.fillAlpha === value.fillAlpha && drawn.radius === value.radius && drawn.stroke === value.stroke && drawn.strokeWidth === value.strokeWidth && drawn.dash === value.dash && drawn.sweep === value.sweep && drawn.cap === value.cap;
26751
26896
  }
26752
26897
  /**
26753
26898
  * Keeps what a shape's graphics was drawn with, in the object it had, or in a new one the first
@@ -26760,7 +26905,7 @@ function sameShape$1(drawn, value, tinted) {
26760
26905
  */
26761
26906
  function copyShape(drawn, value, tinted) {
26762
26907
  if (drawn === void 0) {
26763
- const { kind, w, h, fill, fillAlpha, radius, stroke, strokeWidth, dash } = value;
26908
+ const { kind, w, h, fill, fillAlpha, radius, stroke, strokeWidth, dash, sweep, cap } = value;
26764
26909
  return {
26765
26910
  tinted,
26766
26911
  kind,
@@ -26771,7 +26916,9 @@ function copyShape(drawn, value, tinted) {
26771
26916
  radius,
26772
26917
  stroke,
26773
26918
  strokeWidth,
26774
- dash
26919
+ dash,
26920
+ sweep,
26921
+ cap
26775
26922
  };
26776
26923
  }
26777
26924
  const copy = drawn;
@@ -26785,10 +26932,13 @@ function copyShape(drawn, value, tinted) {
26785
26932
  copy.stroke = value.stroke;
26786
26933
  copy.strokeWidth = value.strokeWidth;
26787
26934
  copy.dash = value.dash;
26935
+ copy.sweep = value.sweep;
26936
+ copy.cap = value.cap;
26788
26937
  return copy;
26789
26938
  }
26790
26939
  /**
26791
- * Tells whether a shape draws nothing: no stroke, and no fill alpha or no area. Pure.
26940
+ * Tells whether a shape draws nothing: no stroke, and no fill alpha or no area; an arc with no stroke, no sweep or no
26941
+ * circle inside its stroke. Pure.
26792
26942
  *
26793
26943
  * @param value - The shape value.
26794
26944
  * @returns True for a shape whose `Graphics` would hold no instruction.
@@ -26798,6 +26948,7 @@ function copyShape(drawn, value, tinted) {
26798
26948
  * ```
26799
26949
  */
26800
26950
  function isEmptyShape(value) {
26951
+ if (value.kind === "arc") return isEmptyArc(value);
26801
26952
  return value.strokeWidth <= 0 && (value.fillAlpha <= 0 || value.w <= 0 || value.h <= 0);
26802
26953
  }
26803
26954
  /**
@@ -26815,8 +26966,7 @@ function drawShape(sctx, entity, view, object, value) {
26815
26966
  const tinted = value.strokeWidth <= 0;
26816
26967
  if (!sameShape$1(view.drawn, value, tinted)) {
26817
26968
  markView(sctx, entity);
26818
- drawShapePath(object, value, value.fillAlpha, tinted ? WHITE$1 : value.fill);
26819
- strokeShape(object, value);
26969
+ paintShape(object, value, tinted ? WHITE$1 : value.fill);
26820
26970
  sctx.ctx.state.sync.redraws += 1;
26821
26971
  view.drawn = copyShape(view.drawn, value, tinted);
26822
26972
  }
@@ -27201,7 +27351,9 @@ function displayObjectOf(sctx, entity) {
27201
27351
  }
27202
27352
  /**
27203
27353
  * Detaches the children of a parent that left, and names the ones that stay. A child that leaves in the same teardown
27204
- * is no orphan: its view goes in this very pass (it waits in `removed`), or it plays its exit (`Exiting`).
27354
+ * is no orphan: its view goes in this very pass (it waits in `removed`), or it plays its exit (`Exiting`). Nor is a
27355
+ * child whose `Parent` names another entity, or none, by now: it moved in the same frame (a hosted view a restore
27356
+ * hung under the rebuilt host, F44), and the later steps of this pass hang it where it belongs.
27205
27357
  *
27206
27358
  * @param sctx - Domain context of the sync module.
27207
27359
  * @param entity - The parent entity.
@@ -27209,7 +27361,6 @@ function displayObjectOf(sctx, entity) {
27209
27361
  */
27210
27362
  function detachChildren(sctx, entity, host) {
27211
27363
  const state = sctx.ctx.state.sync;
27212
- const ecs = sctx.ctx.deps.world.ecs;
27213
27364
  const orphans = [];
27214
27365
  for (const [child, childView] of state.views) {
27215
27366
  if (child === entity) continue;
@@ -27218,7 +27369,7 @@ function detachChildren(sctx, entity, host) {
27218
27369
  markView(sctx, child);
27219
27370
  detach$1(object);
27220
27371
  state.orphans.add(child);
27221
- if (!state.removed.has(child) && !isLeaving(ecs, child)) orphans.push(child);
27372
+ if (isLostOrphan(sctx, child, entity)) orphans.push(child);
27222
27373
  }
27223
27374
  if (orphans.length > 0) sctx.ctx.log.warn("renderer: parent left before its children", {
27224
27375
  parent: entity,
@@ -27226,6 +27377,19 @@ function detachChildren(sctx, entity, host) {
27226
27377
  });
27227
27378
  }
27228
27379
  /**
27380
+ * Tells whether a child of a parent that left is an orphan worth a warning: it does not go in this pass, it plays no
27381
+ * exit, and its `Parent` still names the parent that left.
27382
+ *
27383
+ * @param sctx - Domain context of the sync module.
27384
+ * @param child - The child entity.
27385
+ * @param parent - The parent that left.
27386
+ * @returns True when the child stays behind with no parent.
27387
+ */
27388
+ function isLostOrphan(sctx, child, parent) {
27389
+ const ecs = sctx.ctx.deps.world.ecs;
27390
+ return !sctx.ctx.state.sync.removed.has(child) && !isLeaving(ecs, child) && parentOf$1(ecs, child) === parent;
27391
+ }
27392
+ /**
27229
27393
  * Takes a view out of the tree and gives its object back to the pool.
27230
27394
  *
27231
27395
  * @param sctx - Domain context of the sync module.
@@ -32811,9 +32975,23 @@ function startTrack$1(actx, entity, component, to, options, muted, driven = fals
32811
32975
  countPlaying(actx.deps.world.ecs, entity, 1);
32812
32976
  actx.deps.time.wake();
32813
32977
  warnIfCrowded(actx);
32978
+ if (landsAtStart(track, options)) writeFields(actx, track, true);
32814
32979
  return track;
32815
32980
  }
32816
32981
  /**
32982
+ * Tells whether a track writes its target as it starts: an absolute one of 0 ms with no delay,
32983
+ * no repeat and no keys, started so by its caller (not cut by reduced motion). A rest pose a
32984
+ * caller sets in phase `layout` so lands with the fields written beside it in that frame (F47);
32985
+ * the next frame step ends it, where its `Animation` count lands too.
32986
+ *
32987
+ * @param track - The track that starts.
32988
+ * @param options - What its caller asked for.
32989
+ * @returns True when it lands now.
32990
+ */
32991
+ function landsAtStart(track, options) {
32992
+ return options.ms === 0 && track.delayMs === 0 && !track.additive && !track.driven && track.repeatsLeft === 0 && track.segments === void 0;
32993
+ }
32994
+ /**
32817
32995
  * Tells whether a loop stands on its first key instead of walking: with reduced motion on, unless
32818
32996
  * the author kept it, or when it has no length to walk. It stays in the table either way, so it
32819
32997
  * never ends by itself.
@@ -42224,16 +42402,20 @@ function boxCovers(ectx, entity, width, height, size) {
42224
42402
  return width * scale >= size.width && height * scale >= size.height;
42225
42403
  }
42226
42404
  /**
42227
- * Tells whether a filtered view covers the whole viewport: the box its sprite, its nine-slice or
42228
- * its shape declares, times its scale, covers the reference size on both axes. A sprite without a
42229
- * size declares none. Read from components, never from Pixi bounds.
42405
+ * Tells whether a filtered view covers the whole viewport. A view whose enabled filters declare an
42406
+ * area draws only there: that area, times the view's scale, covers the reference size on both axes
42407
+ * (F43, a lens over a full-screen sprite is not full screen). Otherwise the box its sprite, its
42408
+ * nine-slice or its shape declares, times its scale, does. A sprite without a size declares none.
42409
+ * Read from components and the area handed over last, never from Pixi bounds.
42230
42410
  *
42231
42411
  * @param ectx - Domain context of the effects plugin.
42232
- * @param entity - The entity.
42412
+ * @param view - The view's record.
42233
42413
  * @param size - The viewport size.
42234
42414
  * @returns True for a full-screen view.
42235
42415
  */
42236
- function coversScreen(ectx, entity, size) {
42416
+ function coversScreen(ectx, view, size) {
42417
+ const { entity, areaSent } = view;
42418
+ if (areaSent.some) return boxCovers(ectx, entity, areaSent.width, areaSent.height, size);
42237
42419
  const ecs = ectx.deps.world.ecs;
42238
42420
  const sprite = ecs.get(entity, Sprite);
42239
42421
  if (sprite !== void 0) {
@@ -42308,7 +42490,7 @@ function countFullScreen(ectx, size) {
42308
42490
  let count = 0;
42309
42491
  for (const view of ectx.state.viewList) {
42310
42492
  if (view.gone) continue;
42311
- if (hasEnabledFilter(ectx, view) && coversScreen(ectx, view.entity, size)) count += 1;
42493
+ if (hasEnabledFilter(ectx, view) && coversScreen(ectx, view, size)) count += 1;
42312
42494
  }
42313
42495
  return count;
42314
42496
  }
@@ -49225,24 +49407,37 @@ function createTextSystem(ctx, frame) {
49225
49407
  });
49226
49408
  }
49227
49409
  /**
49228
- * Resolves the labels whose `Text` came after the layout system ran. `ui` adds a label in its
49229
- * solve, and the flush at the end of phase `layout` attaches it with the rest of its batch, a bound
49230
- * component included; this runs right after that flush and before phase `sync`, so the renderer
49231
- * builds the label with its string, and lists it, so the next system run writes its `Text` once
49232
- * more for the remeasure of `ui`. A label whose content changed is not among them: it resolves in
49233
- * the next layout phase, as before. While the world is not live its layout phase did not run: the
49234
- * list is dropped, and the labels, marked, wait for the next live run, as every label does.
49410
+ * Resolves the labels whose `Text` came or changed after the layout system ran. `ui` adds a label in
49411
+ * its solve, and the flush at the end of phase `layout` attaches it with the rest of its batch, a
49412
+ * bound component included; this runs right after that flush and before phase `sync`, so the
49413
+ * renderer builds the label with its string, and lists it, so the next system run writes its `Text`
49414
+ * once more for the remeasure of `ui`. A label whose content or style `ui` changed in its reconcile
49415
+ * is resolved here too, in the frame of the change (F45): `ui` measured its content itself, so it is
49416
+ * not listed. While the world is not live its layout phase did not run: the list is dropped, and the
49417
+ * labels, marked, wait for the next live run, as every label does.
49235
49418
  *
49236
49419
  * @param ctx - Domain context of the text plugin.
49237
49420
  * @param frame - What the system's runs share.
49238
49421
  */
49239
49422
  function resolveAdded(ctx, frame) {
49240
49423
  const added = ctx.state.added;
49241
- if (added.length === 0) return;
49242
- if (ctx.deps.world.ecs.mode() !== "live") {
49243
- added.length = 0;
49244
- return;
49424
+ if (added.length > 0) {
49425
+ if (ctx.deps.world.ecs.mode() !== "live") {
49426
+ added.length = 0;
49427
+ return;
49428
+ }
49429
+ resolveCame(ctx, frame, added);
49245
49430
  }
49431
+ resolveChanged(ctx, frame);
49432
+ }
49433
+ /**
49434
+ * Resolves the labels whose `Text` came with the layout flush, and lists them for the remeasure.
49435
+ *
49436
+ * @param ctx - Domain context of the text plugin.
49437
+ * @param frame - What the system's runs share.
49438
+ * @param added - The labels that came.
49439
+ */
49440
+ function resolveCame(ctx, frame, added) {
49246
49441
  frame.now = NaN;
49247
49442
  frame.locale = ctx.deps.i18n.locale();
49248
49443
  for (const entity of added) {
@@ -49254,6 +49449,25 @@ function resolveAdded(ctx, frame) {
49254
49449
  forgetAdded(added);
49255
49450
  }
49256
49451
  /**
49452
+ * Resolves the labels of this frame's `changed(Text)` whose content or style moved since they were
49453
+ * resolved: `ui` writes `Text` after the layout system. The labels this frame resolved already
49454
+ * stand on what they show; a bound label shows its value, as in the system.
49455
+ *
49456
+ * @param ctx - Domain context of the text plugin.
49457
+ * @param frame - What the system's runs share; the locale of this frame is already in it.
49458
+ */
49459
+ function resolveChanged(ctx, frame) {
49460
+ const ecs = ctx.deps.world.ecs;
49461
+ let live = false;
49462
+ for (const entity of ecs.changed(Text)) {
49463
+ const text = ecs.get(entity, Text);
49464
+ if (text === void 0 || !isStale(ctx, entity, text, frame)) continue;
49465
+ if (!live && ecs.mode() !== "live") return;
49466
+ live = true;
49467
+ stepText(ctx, entity, text, frame);
49468
+ }
49469
+ }
49470
+ /**
49257
49471
  * Marks labels for the next layout phase: every one of them, or only the ones `keep` names. A
49258
49472
  * locale change names the labels whose content is a message; a dev hot swap names the labels
49259
49473
  * drawn with a replaced font or icon.
@@ -53845,7 +54059,10 @@ function recordRest(ctx, element, value) {
53845
54059
  * @param name - The component name of the extra.
53846
54060
  */
53847
54061
  function cancelExtra(element, name) {
53848
- element.extraHandles.get(name)?.cancel();
54062
+ const last = element.extraHandles.get(name);
54063
+ if (last === void 0) return;
54064
+ last.cancel();
54065
+ element.endless?.delete(last);
53849
54066
  element.extraHandles.delete(name);
53850
54067
  }
53851
54068
  //#endregion
@@ -55559,10 +55776,11 @@ function createReconciler(ctx, modules, fields) {
55559
55776
  */
55560
55777
  function subtreeSettled(element) {
55561
55778
  if (!modules.layout.settled(element)) return false;
55562
- return element.children.every((child) => {
55779
+ for (const child of element.children) {
55563
55780
  const childElement = state.elements.get(child);
55564
- return childElement === void 0 || subtreeSettled(childElement);
55565
- });
55781
+ if (childElement !== void 0 && !subtreeSettled(childElement)) return false;
55782
+ }
55783
+ return true;
55566
55784
  }
55567
55785
  /**
55568
55786
  * Drops the identity, the key and the key registration of an element and of everything under
@@ -55617,6 +55835,24 @@ function createReconciler(ctx, modules, fields) {
55617
55835
  if (!subtreeSettled(element)) continue;
55618
55836
  despawnTree(element);
55619
55837
  }
55838
+ dropRemovedRoots();
55839
+ }
55840
+ /**
55841
+ * Despawns every exiting element at once, finished or not, and drops the roots that were leaving:
55842
+ * what a restore or a load does, as a page reload would (F42).
55843
+ */
55844
+ function dropLeaving() {
55845
+ for (const entity of state.exiting) {
55846
+ const element = state.elements.get(entity);
55847
+ if (element === void 0) state.exiting.delete(entity);
55848
+ else despawnTree(element);
55849
+ }
55850
+ dropRemovedRoots();
55851
+ }
55852
+ /**
55853
+ * Drops every removed root whose element tree is gone, and closes its popup.
55854
+ */
55855
+ function dropRemovedRoots() {
55620
55856
  for (const entity of state.removing) {
55621
55857
  const root = state.roots.get(entity);
55622
55858
  if (root === void 0) continue;
@@ -55951,11 +56187,12 @@ function createReconciler(ctx, modules, fields) {
55951
56187
  * element stands exactly as if its root had just been mounted with the restored state: a new
55952
56188
  * entity with the components of the description, then its `enter` hook, then its loop. What a
55953
56189
  * mount does not bring is gone with the old tree: the local state of a component, a scroll
55954
- * offset, the keyboard focus, a text being edited, a hover. A root that is leaving keeps
55955
- * leaving.
56190
+ * offset, the keyboard focus, a text being edited, a hover. An element or a root that is
56191
+ * leaving is dropped at once, as a page reload drops it.
55956
56192
  */
55957
56193
  function remountRoots() {
55958
56194
  state.remount = false;
56195
+ dropLeaving();
55959
56196
  for (const root of state.roots.values()) {
55960
56197
  if (state.removing.has(root.entity)) continue;
55961
56198
  const element = root.element === void 0 ? void 0 : lookup(root.element);
@@ -56119,8 +56356,8 @@ function createReconciler(ctx, modules, fields) {
56119
56356
  /**
56120
56357
  * The second system of phase `layout`: every root takes its slot, then one solve per marked
56121
56358
  * root, a root in a slot after the root of its slot and in its content box, then the rects, the
56122
- * rest poses and the hooks, the scrolls settled on what the solve and the render gave them, and
56123
- * the hosting of the hosts that came. The hints released since the last frame step are dropped
56359
+ * rest poses and the hooks, the scrolls settled on what the solve and the render gave them, the
56360
+ * hosting of the hosts that came, and a sweep of what left with nothing to wait for. The hints released since the last frame step are dropped
56124
56361
  * at its end, so the `change.Box` and `change.Transform` hooks the solve plays get the hint the
56125
56362
  * reconcile routed, and the next frame starts with none.
56126
56363
  *
@@ -56140,6 +56377,7 @@ function createReconciler(ctx, modules, fields) {
56140
56377
  hostViews(ctx, state, state.risen);
56141
56378
  state.risen.clear();
56142
56379
  }
56380
+ if (state.exiting.size > 0 || state.removing.size > 0) sweep();
56143
56381
  state.hints.length = 0;
56144
56382
  writeCounters();
56145
56383
  }
@@ -56722,14 +56960,15 @@ function noteTrack(span, options) {
56722
56960
  span.reduced = options.reduced;
56723
56961
  }
56724
56962
  /**
56725
- * The view handle of a hook whose tracks are measured: every call goes to the real handle, `tween` and `toRest` note
56726
- * their track first.
56963
+ * The view handle of a watched hook: every call goes to the real handle; `tween` notes whether its track repeats
56964
+ * forever, and with a span `tween` and `toRest` note their track for the level of an overlay.
56727
56965
  *
56728
56966
  * @param handle - The real handle.
56729
- * @param span - The span being measured.
56730
- * @returns The measuring handle.
56967
+ * @param span - The span being measured, or nothing.
56968
+ * @param watch - Where a track that repeats forever is noted.
56969
+ * @returns The watching handle.
56731
56970
  */
56732
- function spanned(handle, span) {
56971
+ function watched(handle, span, watch) {
56733
56972
  return {
56734
56973
  entity: handle.entity,
56735
56974
  key: handle.key,
@@ -56737,11 +56976,12 @@ function spanned(handle, span) {
56737
56976
  rest: (component) => handle.rest(component),
56738
56977
  set: (component, patch) => handle.set(component, patch),
56739
56978
  tween: (component, to, options) => {
56740
- noteTrack(span, options);
56979
+ if (options.repeat === "forever") watch.forever = true;
56980
+ if (span !== void 0) noteTrack(span, options);
56741
56981
  return handle.tween(component, to, options);
56742
56982
  },
56743
56983
  toRest: (component, options) => {
56744
- noteTrack(span, options);
56984
+ if (span !== void 0) noteTrack(span, options);
56745
56985
  return handle.toRest(component, options);
56746
56986
  },
56747
56987
  all: (handles) => handle.all(handles),
@@ -56750,8 +56990,9 @@ function spanned(handle, span) {
56750
56990
  }
56751
56991
  /**
56752
56992
  * Runs one motion hook and keeps the handle it returned, so the exit sweep knows when the
56753
- * element is still playing. Without a hook the value the solve wrote stands; a throwing hook is
56754
- * logged and the same happens.
56993
+ * element is still playing. A motion whose hook started a track that repeats forever is also
56994
+ * noted as endless: the sweep never waits for it. Without a hook the value the solve wrote stands;
56995
+ * a throwing hook is logged and the same happens.
56755
56996
  *
56756
56997
  * @param ctx - Domain context of the ui plugin.
56757
56998
  * @param element - The element the hook belongs to.
@@ -56764,9 +57005,11 @@ function play(ctx, element, hook, span) {
56764
57005
  const handle = handleOf(ctx, element);
56765
57006
  if (handle === void 0) return void 0;
56766
57007
  try {
56767
- const motion = hook(span === void 0 ? handle : spanned(handle, span));
57008
+ const watch = { forever: false };
57009
+ const motion = hook(watched(handle, span, watch));
56768
57010
  if (motion === void 0) return void 0;
56769
57011
  element.handles.push(motion);
57012
+ if (watch.forever) noteEndless(element, motion);
56770
57013
  return motion;
56771
57014
  } catch (error) {
56772
57015
  ctx.log.error("ui:motion-failed", {
@@ -56777,6 +57020,16 @@ function play(ctx, element, hook, span) {
56777
57020
  }
56778
57021
  }
56779
57022
  /**
57023
+ * Notes a motion that never ends on its element, the set made with the first one.
57024
+ *
57025
+ * @param element - The element the motion plays on.
57026
+ * @param motion - The endless motion.
57027
+ */
57028
+ function noteEndless(element, motion) {
57029
+ element.endless ??= /* @__PURE__ */ new Set();
57030
+ element.endless.add(motion);
57031
+ }
57032
+ /**
56780
57033
  * Plays the `change` hook of one extra component whose fields changed. The motion the last change
56781
57034
  * of that name started is cancelled first, so its value stays where it is and the new motion
56782
57035
  * starts from there; the new motion is kept with the element's handles, so an exit waits for it.
@@ -56791,8 +57044,12 @@ function play(ctx, element, hook, span) {
56791
57044
  * @returns True when the hook returned a motion: the caller writes nothing directly.
56792
57045
  */
56793
57046
  function playExtraChange(ctx, element, name, values, hint) {
56794
- element.extraHandles.get(name)?.cancel();
56795
- element.extraHandles.delete(name);
57047
+ const last = element.extraHandles.get(name);
57048
+ if (last !== void 0) {
57049
+ last.cancel();
57050
+ element.endless?.delete(last);
57051
+ element.extraHandles.delete(name);
57052
+ }
56796
57053
  const hook = element.motion?.change?.[name];
56797
57054
  if (hook === void 0) return false;
56798
57055
  const motion = play(ctx, element, (view) => hook(view, values.previous, values.next, hint));
@@ -56828,19 +57085,23 @@ function startLoop(ctx, element) {
56828
57085
  }
56829
57086
  /**
56830
57087
  * Tells whether every motion of an element has finished, which is when an exiting element may
56831
- * be despawned.
57088
+ * be despawned. A motion over a track that repeats forever never finishes, so it does not count:
57089
+ * waiting for it would keep the element for the life of the page (F42).
56832
57090
  *
56833
57091
  * @param element - The element to ask about.
56834
- * @returns True when nothing is still playing.
57092
+ * @returns True when nothing that can end is still playing.
56835
57093
  */
56836
57094
  function still(element) {
56837
- return element.handles.every((handle) => !handle.active());
57095
+ const { handles, endless } = element;
57096
+ for (const handle of handles) if (handle.active() && endless?.has(handle) !== true) return false;
57097
+ return true;
56838
57098
  }
56839
57099
  /**
56840
57100
  * Moves the rest `Transform` of an element to where its rect, style and fit put it now. A live
56841
57101
  * element plays its `change.Transform` motion when it has one, else takes the pose through a
56842
- * 0 ms rest track, which lands on the next frame step and lets an additive loop re-base on the
56843
- * new rest; an element that has not spawned yet only records it, and so does one whose
57102
+ * 0 ms rest track. That track writes the pose as it starts, so the pivot and the position land in
57103
+ * the same frame, and ends at the next frame step, where an additive loop re-bases on the new
57104
+ * rest. An element that has not spawned yet only records it, and so does one whose
56844
57105
  * `change.Box` motion already played. The content of a scroll keeps its `Transform` and records
56845
57106
  * no rest with the world: the scroll step owns it.
56846
57107
  *
@@ -56953,10 +57214,14 @@ function placeChildren(state, parent, children) {
56953
57214
  * @file ui/layout — exiting elements, policy "leave": the element leaves the flow in the same
56954
57215
  * reconcile, so its siblings reflow this frame while it plays its exit over them.
56955
57216
  */
57217
+ /** The dev warning of an element that left while a motion of it repeated forever. */
57218
+ const EXIT_WAITS_FOREVER = "ui:exit-waits-forever";
56956
57219
  /**
56957
57220
  * Starts the exit of one element: out of the Yoga tree at once, tagged `Exiting` so `input`
56958
57221
  * never hits it, then the hook plays over the siblings that already moved. A `change` motion of
56959
57222
  * an extra component that still runs, a counter's roll, is kept, so the sweep waits for it too.
57223
+ * One that repeats forever, a hot pulse, is cancelled where it stands: it would never end, and a
57224
+ * dev build names the element once (F42).
56960
57225
  *
56961
57226
  * @param ctx - Domain context of the ui plugin.
56962
57227
  * @param state - The layout state.
@@ -56970,11 +57235,45 @@ function beginExit(ctx, state, element, cause, span) {
56970
57235
  element.dropKey = void 0;
56971
57236
  if (!element.live) return;
56972
57237
  ctx.deps.world.ecs.tag(element.entity, Exiting);
56973
- element.handles = [...element.extraHandles.values()].filter((handle) => handle.active());
57238
+ element.handles = keptExtras(ctx, element);
56974
57239
  const hook = element.motion?.exit;
56975
57240
  play(ctx, element, hook === void 0 ? void 0 : (view) => hook(view, element.node, void 0, cause), span);
56976
57241
  }
56977
57242
  /**
57243
+ * The `change` motions of the extras an exit waits for: the ones still running that can end. One
57244
+ * that repeats forever is cancelled where it stands, and a dev build warns once per element.
57245
+ *
57246
+ * @param ctx - Domain context of the ui plugin.
57247
+ * @param element - The element that starts its exit.
57248
+ * @returns The motions the sweep waits for.
57249
+ */
57250
+ function keptExtras(ctx, element) {
57251
+ const kept = [];
57252
+ for (const handle of element.extraHandles.values()) {
57253
+ if (!handle.active()) continue;
57254
+ if (element.endless?.has(handle) !== true) {
57255
+ kept.push(handle);
57256
+ continue;
57257
+ }
57258
+ handle.cancel();
57259
+ warnEndless(ctx, element);
57260
+ }
57261
+ return kept;
57262
+ }
57263
+ /**
57264
+ * Names an element that left while a motion of it repeated forever, once per element, in a dev
57265
+ * build: the motion is cut where it stands, which the author may want to end in the exit instead.
57266
+ *
57267
+ * @param ctx - Domain context of the ui plugin.
57268
+ * @param element - The leaving element.
57269
+ */
57270
+ function warnEndless(ctx, element) {
57271
+ if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) return;
57272
+ if (element.warned.has(EXIT_WAITS_FOREVER)) return;
57273
+ element.warned.add(EXIT_WAITS_FOREVER);
57274
+ ctx.log.warn(EXIT_WAITS_FOREVER, { key: element.key ?? element.identity });
57275
+ }
57276
+ /**
56978
57277
  * Tells whether an exiting element may be despawned: no hook, or every hook finished.
56979
57278
  *
56980
57279
  * @param element - The exiting element.