@moku-labs/game 0.15.13 → 0.15.16

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-aLTGwuyk.d.mts → commands-Ay-OfSPt.d.mts} +5 -5
  8. package/dist/control.d.mts +4 -4
  9. package/dist/{define-DbxBzO-G.d.mts → define-BfWdvNHC.d.mts} +2 -2
  10. package/dist/{headless-DHtavbqk.d.mts → headless-ByFLyHxx.d.mts} +1 -1
  11. package/dist/{index-YUceiUFm.d.mts → index-rLpq23uS.d.mts} +7 -6
  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-B5OD9JTT.d.mts → runtime-DkhhsFss.d.mts} +1 -1
  19. package/dist/{sources-Fbq_PFVH.mjs → sources-24TY22DK.mjs} +1 -1
  20. package/dist/{src-3_HjYegY.mjs → src-AvZjWoPX.mjs} +481 -92
  21. package/dist/testing.d.mts +4 -4
  22. package/dist/testing.mjs +1 -1
  23. package/dist/{types-CuEK0Vam.d.mts → types-DOldmhIw.d.mts} +21 -5
  24. package/dist/{types-3bEK86Z1.d.mts → types-De1AZYym.d.mts} +2 -53
  25. package/dist/{types-xmH1X8qd.d.mts → types-DklpXQQk.d.mts} +132 -15
  26. package/dist/{types-Dm4tM5nH.d.mts → types-WqPbnC6B.d.mts} +4 -4
  27. package/dist/{types-_a6-ALzz.d.mts → types-qsE-I59e.d.mts} +2 -2
  28. package/dist/{visual-DExHHx-a.d.mts → visual-B1mq5n-9.d.mts} +6 -6
  29. package/dist/{visual-CbGKzlj0.mjs → visual-BStMZRVc.mjs} +28 -10
  30. package/dist/visual.d.mts +1 -1
  31. package/dist/visual.mjs +1 -1
  32. package/llms.txt +1 -1
  33. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
- import { Qn as AnyFlow, Xn as FeaturePlugin, cr as Descriptor, fr as Hint, gr as ClockSource, ir as NodeTable, pr as NodeFx, vr as FakeClock, wr as Json } from "./types-CuEK0Vam.mjs";
2
- import { t as createApp } from "./index-YUceiUFm.mjs";
3
- import { b as saveOf, v as WithGameState, y as memory } from "./types-Dm4tM5nH.mjs";
4
- import { a as createHeadless, i as ReproResult, n as HeadlessGame, o as runRepro, r as Repro, s as stepFrames, t as HeadlessApp } from "./headless-DHtavbqk.mjs";
1
+ import { Qn as AnyFlow, Xn as FeaturePlugin, cr as Descriptor, fr as Hint, gr as ClockSource, ir as NodeTable, pr as NodeFx, vr as FakeClock, wr as Json } from "./types-DOldmhIw.mjs";
2
+ import { t as createApp } from "./index-rLpq23uS.mjs";
3
+ import { b as saveOf, v as WithGameState, y as memory } from "./types-WqPbnC6B.mjs";
4
+ import { a as createHeadless, i as ReproResult, n as HeadlessGame, o as runRepro, r as Repro, s as stepFrames, t as HeadlessApp } from "./headless-ByFLyHxx.mjs";
5
5
  import { AnyPluginInstance } from "@moku-labs/core";
6
6
 
7
7
  //#region src/plugins/clock/fake.d.ts
package/dist/testing.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $t as exit, Qt as refuseStart, m as flattenFeatures, nn as type, t as createApp } from "./src-3_HjYegY.mjs";
1
+ import { $t as exit, Qt as refuseStart, m as flattenFeatures, nn as type, t as createApp } from "./src-AvZjWoPX.mjs";
2
2
  import { n as saveOf, t as memory } from "./memory-CWlqhS7l.mjs";
3
3
  import { t as fakeClock } from "./fake-6mazmHK3.mjs";
4
4
  import { i as stepFrames, r as runRepro, t as createHeadless } from "./headless-BA6ffzqD.mjs";
@@ -11712,7 +11712,11 @@ type ManifestBundle = {
11712
11712
  * manifest.
11713
11713
  */
11714
11714
  dir?: string;
11715
- tier: Tier; /** `"leave"`: unloaded at the next rest once its feature left. Absent: `"budget"`. */
11715
+ tier: Tier;
11716
+ /**
11717
+ * `"leave"`: unloaded at the next rest once its feature left, or once the exit of a scene that named it is over.
11718
+ * Absent: `"budget"`.
11719
+ */
11716
11720
  release?: "leave";
11717
11721
  mb: number;
11718
11722
  pages?: readonly AtlasPage[];
@@ -11962,11 +11966,17 @@ type State = {
11962
11966
  queue: PreloadQueue | undefined;
11963
11967
  current: NodeInfo | undefined;
11964
11968
  useCounter: number; /** Keys already reported missing, so one key warns and starts its background load once. */
11965
- warned: Set<string>; /** `onEnter`, the `load` handler and the texture provider. */
11969
+ warned: Set<string>; /** `onEnter`, the `load` handler, the texture provider, and the start and the end of an exit in `world`. */
11966
11970
  removers: Array<() => void>; /** The `files` map of the stamp the dev hot swap applied last. `undefined` until the first swap. */
11967
11971
  stamps: Readonly<Record<string, string>> | undefined; /** The running dev hot swap. The next one waits for it, so two swaps never overlap. */
11968
11972
  swapping: Promise<void> | undefined; /** The list the mounted scene names, from the last `sceneBundles` call. */
11969
- sceneNamed: readonly string[]; /** Registered companions, in registration order. */
11973
+ sceneNamed: readonly string[];
11974
+ /**
11975
+ * The bundles a scene named before and the list names no more: held while anything carries `Exiting`, so a
11976
+ * leaving scene's pictures stay until its elements played their exit (F51).
11977
+ */
11978
+ sceneLeaving: string[]; /** How many entities carry `Exiting` now, counted from the world's `onAdded` and `onRemoved` of the tag. */
11979
+ exiting: number; /** Registered companions, in registration order. */
11970
11980
  companions: BundleCompanion[]; /** A live rest was announced; the arriving node's `load` stage runs the release. */
11971
11981
  restPending: boolean;
11972
11982
  };
@@ -12188,7 +12198,9 @@ type Api = {
12188
12198
  /**
12189
12199
  * Tells assets which bundles the mounted scene names: its `bundle`, then what its `bundles(state)` returned.
12190
12200
  * Each call replaces the list before it. A bundle in the list is never a victim of the budget and is not
12191
- * released by `release: "leave"` while it stays in the list. Called by `scenes`; a game never calls it.
12201
+ * released by `release: "leave"` while it stays in the list. A bundle the list named before and names no more
12202
+ * stays held while anything carries `Exiting`, so a leaving scene's pictures stay until its elements played their
12203
+ * exit; then a `"leave"` one goes at once (F51). Called by `scenes`; a game never calls it.
12192
12204
  *
12193
12205
  * @param named - The bundle names of the mounted scene, the scene bundle first.
12194
12206
  * @example
@@ -12196,6 +12208,8 @@ type Api = {
12196
12208
  * // scenes, right after it mounted the ward for episode 1 (and after a re-read that changed the list).
12197
12209
  * ctx.require(assetsPlugin).sceneBundles(["ui", "ep01"]);
12198
12210
  * // ep01 (release "leave") now stays while the ward shows it, even after the ep01 flow left the stack
12211
+ * ctx.require(assetsPlugin).sceneBundles(["board"]);
12212
+ * // the board is mounted: ep01 stays until the ward's elements played their exit, then goes
12199
12213
  * ```
12200
12214
  */
12201
12215
  sceneBundles(named: readonly string[]): void;
@@ -12248,6 +12262,7 @@ type Deps = {
12248
12262
  renderer: Api$1;
12249
12263
  time: Api$7;
12250
12264
  model: Api$5;
12265
+ world: Api$2;
12251
12266
  };
12252
12267
  /**
12253
12268
  * What the kernel context offers before the deps are attached.
@@ -12282,7 +12297,8 @@ type HotSwap = Events$8["ui:hot-swap"];
12282
12297
  /**
12283
12298
  * When a bundle leaves besides the budget. `"budget"` (default): only the LRU unloads it when memory
12284
12299
  * runs out. `"leave"`: also unloaded at the next rest once no node of its feature is on the stack
12285
- * and no mounted scene names it.
12300
+ * and no mounted scene names it; a scene that named it and left holds it until its elements played
12301
+ * their exit, and then it goes at once.
12286
12302
  *
12287
12303
  * @example
12288
12304
  * ```ts
@@ -1,8 +1,7 @@
1
- import { $r as Events$2, $t as Api$3, An as ReducedMotion, Br as Events$5, Cn as Ease, Cr as Events$3, D as FilterSlotInputs, Dn as NumericFields, E as FilterSlot, En as MotionHandle, K as Api$4, Mn as TrackSegment, O as SyncApi, Pn as Api$2, Q as PixiTexture, Wr as Api$1, X as PixiModule, Y as PixiFilter, Z as PixiSprite, cr as Descriptor, dn as Entity, ei as Require, in as AnyComponent, jn as TrackOptions, ln as ComponentType, lt as FilterCache, on as AnyComponentValue, s as Events$1, sn as ComponentHandle, sr as TypeTag, st as Point, t as Api$5, v as Events$4, wr as Json, xr as Api$6 } from "./types-CuEK0Vam.mjs";
2
- import { C as TextApi, L as I18nApi, M as InputApi, Y as StylesState, c as UiNode, i as Finding, n as ElementChange, o as JsxState, r as ElementMotion } from "./types-xmH1X8qd.mjs";
1
+ import { $r as Events$2, $t as Api$3, An as ReducedMotion, Br as Events$5, Cn as Ease, Cr as Events$3, D as FilterSlotInputs, Dn as NumericFields, E as FilterSlot, En as MotionHandle, K as Api$4, Mn as TrackSegment, O as SyncApi, Pn as Api$2, Q as PixiTexture, Wr as Api$1, X as PixiModule, Y as PixiFilter, Z as PixiSprite, cr as Descriptor, dn as Entity, ei as Require, in as AnyComponent, jn as TrackOptions, ln as ComponentType, lt as FilterCache, on as AnyComponentValue, s as Events$1, sn as ComponentHandle, sr as TypeTag, st as Point, t as Api$5, v as Events$4, wr as Json, xr as Api$6 } from "./types-DOldmhIw.mjs";
2
+ import { N as InputApi, R as I18nApi, X as StylesState, c as UiNode, i as Finding, l as LayoutState, n as ElementChange, o as JsxState, r as ElementMotion, w as TextApi } from "./types-DklpXQQk.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
  import { PluginCtx } from "@moku-labs/core";
5
- import { Node, Yoga } from "yoga-layout/load";
6
5
 
7
6
  //#region src/plugins/debug/types.d.ts
8
7
  declare namespace types_d_exports$4 {
@@ -2807,56 +2806,6 @@ type MusicRequest = {
2807
2806
  key: string | null;
2808
2807
  fadeMs: number;
2809
2808
  };
2810
- //#endregion
2811
- //#region src/plugins/ui/layout/types.d.ts
2812
- /**
2813
- * The patches the scroll step writes with, one object each, filled in place: a frame builds none.
2814
- * Every number in them is a whole reference unit.
2815
- */
2816
- type ScrollPatches = {
2817
- scroll: {
2818
- offset: number;
2819
- min: number;
2820
- };
2821
- state: {
2822
- offset: number;
2823
- max: number;
2824
- moving: boolean;
2825
- };
2826
- scrollable: {
2827
- moving: boolean;
2828
- };
2829
- x: {
2830
- x: number;
2831
- };
2832
- y: {
2833
- y: number;
2834
- };
2835
- };
2836
- /**
2837
- * layout module state. `nodes` is attach minus detach: Yoga 3.2.1 has no instance counter.
2838
- * `scrolling` is the scroll the finger holds; `scrollClock` the ms the scroll step has run, the
2839
- * clock of the samples; `scrollRing` the last pointer samples of the press on its axis, a
2840
- * position and a time each, `scrollHead` the slot the next one takes and `scrollSamples` how
2841
- * many it holds; `scrollMoved` when the finger last moved.
2842
- */
2843
- type LayoutState = {
2844
- yoga: Yoga | undefined;
2845
- byEntity: Map<Entity, Node>;
2846
- nodes: number;
2847
- measured: number;
2848
- solves: number;
2849
- scrolling: Entity | undefined;
2850
- scrollClock: number;
2851
- scrollRing: Float64Array;
2852
- scrollHead: number;
2853
- scrollSamples: number;
2854
- scrollMoved: number;
2855
- scrollPatches: ScrollPatches; /** One line of the `body` style as the icons were last measured with it; none before that. */
2856
- iconLine: number | undefined;
2857
- order: number;
2858
- cleanups: Array<() => void>;
2859
- };
2860
2809
  declare namespace types_d_exports {
2861
2810
  export { Config, Deps, ElementChange, ElementMotion, Finding, FocusRing, KernelSlice, State, TextInputLook, UiApi, UiCtx, UiNode };
2862
2811
  }
@@ -1,6 +1,7 @@
1
- import { $t as Api$2, Dn as NumericFields, En as MotionHandle, K as Api$3, Nn as ViewHandle, On as ProjectionMotion, Pn as Api$1, Tn as MotionCause, U as TransformValue, Wr as Api, an as AnyComponentType, bn as TagType, cr as Descriptor, ct as ViewportSize, dn as Entity, ei as Require, fr as Hint, hr as Api$5, in as AnyComponent, ln as ComponentType, mr as Answer, on as AnyComponentValue, s as Events$1, t as Api$4, wn as Motion, wr as Json, xn as ChangeHook } from "./types-CuEK0Vam.mjs";
1
+ import { $t as Api$2, An as ReducedMotion, Cn as Ease, Dn as NumericFields, En as MotionHandle, K as Api$3, Nn as ViewHandle, On as ProjectionMotion, Pn as Api$1, Tn as MotionCause, U as TransformValue, Wr as Api, an as AnyComponentType, bn as TagType, cr as Descriptor, ct as ViewportSize, dn as Entity, ei as Require, fr as Hint, hr as Api$5, in as AnyComponent, ln as ComponentType, mr as Answer, on as AnyComponentValue, s as Events$1, t as Api$4, wn as Motion, wr as Json, xn as ChangeHook } from "./types-DOldmhIw.mjs";
2
2
  import { Log } from "@moku-labs/common/browser";
3
3
  import { PluginCtx } from "@moku-labs/core";
4
+ import { Node, Yoga } from "yoga-layout/load";
4
5
 
5
6
  //#region src/plugins/ui/styles/types.d.ts
6
7
  /**
@@ -1421,16 +1422,23 @@ type CountdownValue = {
1421
1422
  /**
1422
1423
  * What the `Text` component holds. `resolved` is engine-owned: a game writes `content`, `style`,
1423
1424
  * `bind`, `anchor` and `alpha`, and reads `resolved`. `alpha` fades every run, shadow and icon of
1424
- * the block together, 1 by default; a change is written in place and a tween can drive it.
1425
+ * the block together, 1 by default; a change is written in place and a tween can drive it. `box`
1426
+ * is the width a style with `wrap: "box"` wraps at: `ui` writes the laid-out width of the text
1427
+ * element there, 0 means no box and one line. A style that wraps otherwise ignores it.
1425
1428
  *
1426
1429
  * @example
1427
1430
  * ```ts
1428
1431
  * const value: TextValue = {
1429
1432
  * content: "+5", style: "body", bind: undefined, anchor: { x: 0.5, y: 0.5 }, alpha: 1,
1430
- * resolved: "+5"
1433
+ * box: 0, resolved: "+5"
1431
1434
  * };
1432
1435
  * // The hint of an empty name field, at half alpha.
1433
1436
  * const hint: TextValue = { ...value, content: "Your name", alpha: 0.5, resolved: "Your name" };
1437
+ * // A player name in a "card.name" style with wrap "box", laid out 200 wide by ui: two lines.
1438
+ * const name: TextValue = {
1439
+ * ...value, content: "Aleksandra Kucherenko", style: "card.name", box: 200,
1440
+ * resolved: "Aleksandra Kucherenko"
1441
+ * };
1434
1442
  * ```
1435
1443
  */
1436
1444
  type TextValue = {
@@ -1439,6 +1447,7 @@ type TextValue = {
1439
1447
  bind: TextBind | undefined;
1440
1448
  anchor: Point;
1441
1449
  alpha: number;
1450
+ box: number;
1442
1451
  resolved: string;
1443
1452
  };
1444
1453
  /**
@@ -1451,14 +1460,17 @@ type TextValue = {
1451
1460
  */
1452
1461
  type TextAlign = "left" | "center" | "right";
1453
1462
  /**
1454
- * The wrap width of a style in reference pixels, or `"none"` for one line per `\n` segment.
1463
+ * The wrap width of a style in reference pixels; `"box"` for the width the `ui` layout gave the
1464
+ * text element, one line outside a layout; or `"none"` for one line per `\n` segment.
1455
1465
  *
1456
1466
  * @example
1457
1467
  * ```ts
1458
1468
  * const wrap: TextWrap = 480;
1469
+ * // A name that wraps at the room its row leaves it: the boxes on the way need `shrink: 1`.
1470
+ * const name: TextWrap = "box";
1459
1471
  * ```
1460
1472
  */
1461
- type TextWrap = number | "none";
1473
+ type TextWrap = number | "box" | "none";
1462
1474
  /**
1463
1475
  * The drop shadow of a style as the plugin stores it: a copy of every glyph run in `color`, at
1464
1476
  * `alpha`, moved by `dx` and `dy` reference pixels.
@@ -1662,13 +1674,15 @@ type LayoutOptions = {
1662
1674
  * What was resolved for one entity last, so the frame step knows whether anything moved. `unit`
1663
1675
  * is what the shown string of a bound label was built from: the rounded value for `"int"`, whole
1664
1676
  * seconds for a time format. `bindRef` is the bind object `bind` was keyed from, so the key is
1665
- * built once per attached bind and a frame compares one reference.
1677
+ * built once per attached bind and a frame compares one reference. `box` is the `Text.box` the
1678
+ * label was laid out at: a label of a `"box"` style is laid out again when it moves.
1666
1679
  *
1667
1680
  * @example
1668
1681
  * ```ts
1669
1682
  * const left = bind(Countdown, "left", { format: "mm:ss" });
1670
1683
  * const seen: SeenText = {
1671
- * content: "", style: "digits", bindRef: left, bind: "Countdown.left:mm:ss", locale: "ru", unit: 95
1684
+ * content: "", style: "digits", bindRef: left, bind: "Countdown.left:mm:ss", locale: "ru",
1685
+ * unit: 95, box: 0
1672
1686
  * };
1673
1687
  * ```
1674
1688
  */
@@ -1679,6 +1693,7 @@ type SeenText = {
1679
1693
  bind: string | undefined;
1680
1694
  locale: string;
1681
1695
  unit: number | undefined;
1696
+ box: number;
1682
1697
  };
1683
1698
  /**
1684
1699
  * text plugin config.
@@ -1705,7 +1720,7 @@ type State = {
1705
1720
  installed: Set<string>; /** What was resolved for an entity last. */
1706
1721
  seen: Map<Entity, SeenText>; /** `bind.component` to its world type; `undefined` after the one warning. */
1707
1722
  bindTypes: Map<string, AnyComponent | undefined>; /** The measured size of every live label, for `ui`. */
1708
- measured: Map<Entity, Size>; /** Layout per `style + resolved`, oldest dropped first. */
1723
+ measured: Map<Entity, Size>; /** Layout per `style + resolved`, and the box width for a `"box"` style; oldest dropped first. */
1709
1724
  cache: Map<string, TextLayout>; /** What every live label container was last filled from, by container. */
1710
1725
  drawn: WeakMap<object, DrawnLabel>; /** Entities to re-resolve in the next layout phase. */
1711
1726
  dirty: Set<Entity>;
@@ -1743,10 +1758,13 @@ type TextApi = {
1743
1758
  /**
1744
1759
  * The size of a piece of text in reference pixels, from the advance table of the style's font.
1745
1760
  * A `Message` is formatted in the current locale first. Never touches a canvas, so the answer
1746
- * is the same in a browser and in plain Bun. Cached per style and resolved string.
1761
+ * is the same in a browser and in plain Bun. Cached per style and resolved string, and per
1762
+ * width for a `"box"` style.
1747
1763
  *
1748
1764
  * @param content - A plain string, or what `tr` returned.
1749
1765
  * @param style - A registered style name. An unknown one warns once and measures as `body`.
1766
+ * @param width - The width a style with `wrap: "box"` wraps at, in reference pixels. Left out
1767
+ * (or 0) the block is one line. A style that wraps otherwise ignores it.
1750
1768
  * @returns The width and height of the block.
1751
1769
  * @example
1752
1770
  * ```ts
@@ -1754,9 +1772,12 @@ type TextApi = {
1754
1772
  * const text = ctx.require(textPlugin);
1755
1773
  *
1756
1774
  * text.measure("120", "digits"); // { width: 57.6, height: 38.4 } on the 0.6 em fallback
1775
+ * // "card.name" is a 32 px style with wrap "box": the width Yoga offers wraps it.
1776
+ * text.measure("Aleksandra Kucherenko", "card.name", 200); // { width: 192, height: 76.8 }
1777
+ * text.measure("Aleksandra Kucherenko", "card.name"); // { width: 403.2, height: 38.4 }
1757
1778
  * ```
1758
1779
  */
1759
- measure(content: string | Message, style: string): Size;
1780
+ measure(content: string | Message, style: string, width?: number): Size;
1760
1781
  /**
1761
1782
  * Whether the font of a style has a glyph for one character, the lookup Pixi draws by. A
1762
1783
  * character its font lacks is drawn as nothing and `measure` counts it 0 wide. While the font is
@@ -2339,6 +2360,74 @@ declare function popup<Properties extends object, Local extends object, Outcomes
2339
2360
  over?: string;
2340
2361
  }): Descriptor<Answer<keyof Outcomes & string>>;
2341
2362
  //#endregion
2363
+ //#region src/plugins/ui/layout/types.d.ts
2364
+ /**
2365
+ * The patches the scroll step writes with, one object each, filled in place: a frame builds none.
2366
+ * Every number in them is a whole reference unit.
2367
+ */
2368
+ type ScrollPatches = {
2369
+ scroll: {
2370
+ offset: number;
2371
+ min: number;
2372
+ };
2373
+ state: {
2374
+ offset: number;
2375
+ max: number;
2376
+ moving: boolean;
2377
+ };
2378
+ scrollable: {
2379
+ moving: boolean;
2380
+ };
2381
+ x: {
2382
+ x: number;
2383
+ };
2384
+ y: {
2385
+ y: number;
2386
+ };
2387
+ };
2388
+ /**
2389
+ * layout module state. `nodes` is attach minus detach: Yoga 3.2.1 has no instance counter.
2390
+ * `scrolling` is the scroll the finger holds; `scrollClock` the ms the scroll step has run, the
2391
+ * clock of the samples; `scrollRing` the last pointer samples of the press on its axis, a
2392
+ * position and a time each, `scrollHead` the slot the next one takes and `scrollSamples` how
2393
+ * many it holds; `scrollMoved` when the finger last moved.
2394
+ */
2395
+ type LayoutState = {
2396
+ yoga: Yoga | undefined;
2397
+ byEntity: Map<Entity, Node>;
2398
+ nodes: number;
2399
+ measured: number;
2400
+ solves: number;
2401
+ scrolling: Entity | undefined;
2402
+ scrollClock: number;
2403
+ scrollRing: Float64Array;
2404
+ scrollHead: number;
2405
+ scrollSamples: number;
2406
+ scrollMoved: number;
2407
+ scrollPatches: ScrollPatches; /** One line of the `body` style as the icons were last measured with it; none before that. */
2408
+ iconLine: number | undefined;
2409
+ order: number;
2410
+ cleanups: Array<() => void>;
2411
+ };
2412
+ /**
2413
+ * The longest finite track a motion hook started through its view handle: where it ends (delay included), its length,
2414
+ * delay, ease and reduced-motion rule. The level of an overlay follows the root's own enter and exit motion by it
2415
+ * (spec 22, LEAD-FIXES-4 item 5a); `end` 0 means the hook started no timed track.
2416
+ *
2417
+ * @example
2418
+ * ```ts
2419
+ * // A sheet that fades in over 250 ms after 40 ms.
2420
+ * const span: MotionSpan = { end: 290, ms: 250, delayMs: 40, ease: "out", reduced: undefined };
2421
+ * ```
2422
+ */
2423
+ type MotionSpan = {
2424
+ end: number;
2425
+ ms: number;
2426
+ delayMs: number;
2427
+ ease: Ease | undefined;
2428
+ reduced: ReducedMotion | undefined;
2429
+ };
2430
+ //#endregion
2342
2431
  //#region src/plugins/ui/jsx/types.d.ts
2343
2432
  /**
2344
2433
  * One node of an element description, as the JSX runtime builds it. `key` is the third argument
@@ -2534,7 +2623,9 @@ type StyleBasis = {
2534
2623
  * warnings of its props in `warned`; `scrolledIn` marks an element a range change of a windowed
2535
2624
  * scroll created: it plays no `enter` hook. A scroll carries its `scroll` motion from its enter. A text or an icon that sizes itself keeps in
2536
2625
  * `measuredSize` what its measure function last answered, before Yoga's clamp: a bound text whose
2537
- * new string measures the same asks for no solve. `basis` is what `style` was resolved on, set
2626
+ * new string measures the same asks for no solve. `measuredWidth` is the width Yoga offered that
2627
+ * call, `undefined` when it bounded none: a text of a `"box"` style is measured again at it, so the
2628
+ * lines it wraps into are compared, not one line. `basis` is what `style` was resolved on, set
2538
2629
  * once the style is on the layout node and the components are written; an element without one is
2539
2630
  * patched in full at its next patch. `index` is its place among the element children of its parent
2540
2631
  * at the last diff (0 for a root element): with its `zIndex` it gives the child its `Order`.
@@ -2592,8 +2683,34 @@ type Element = {
2592
2683
  width: number;
2593
2684
  height: number;
2594
2685
  };
2686
+ measuredWidth?: number | undefined;
2595
2687
  basis?: StyleBasis; /** The motions of its hooks that started a `repeat: "forever"` track; made with the first one. */
2596
- endless?: Set<MotionHandle>;
2688
+ endless?: Set<MotionHandle>; /** What its enter hook did at the `Box`, until `ui.enter` keeps the play or plays it again (F49). */
2689
+ posed?: PosedEnter | undefined;
2690
+ };
2691
+ /**
2692
+ * One `set` of an enter hook: the fields it wrote, and the values they had before. A hook may write any component of
2693
+ * the game, so the fields are kept by name, as records.
2694
+ */
2695
+ type PoseWrite = {
2696
+ component: ComponentType<Record<string, unknown>>;
2697
+ wrote: Record<string, unknown>;
2698
+ before: Record<string, unknown>;
2699
+ };
2700
+ /**
2701
+ * What the play of an enter hook at the `Box` kept: the motion it returned, the components it read
2702
+ * (with their fields as they stood after the play), the names of the components it wrote, its
2703
+ * `set` writes, and the span noted for an overlay.
2704
+ */
2705
+ type PosedEnter = {
2706
+ motion: MotionHandle | undefined;
2707
+ reads: Array<{
2708
+ component: ComponentType<Record<string, unknown>>;
2709
+ seen: Record<string, unknown>;
2710
+ }>;
2711
+ written: string[];
2712
+ writes: PoseWrite[];
2713
+ span: MotionSpan | undefined;
2597
2714
  };
2598
2715
  /**
2599
2716
  * The popup side of a root: the handler that shows it now and the root it was opened over.
@@ -2792,8 +2909,8 @@ type JsxState = {
2792
2909
  text: TextState;
2793
2910
  hints: Hint[];
2794
2911
  /**
2795
- * The elements whose `Box` came since the last `sync`: their enter hooks play there, after the game's systems
2796
- * (F48). The first `count` slots are the queue; the array keeps its length, so a frame allocates nothing.
2912
+ * The elements whose `Box` came since the last `sync`: their enter hooks played at the `Box` (F49) and are settled
2913
+ * there, after the game's systems, played again when a system changed what a hook read (F48). The first `count` slots are the queue; the array keeps its length, so a frame allocates nothing.
2797
2914
  */
2798
2915
  entering: {
2799
2916
  list: Entity[];
@@ -2922,4 +3039,4 @@ type CommonProps = {
2922
3039
  components?: ElementComponents;
2923
3040
  };
2924
3041
  //#endregion
2925
- export { defineComponent as A, Part as B, TextApi as C, TextStyles as D, TextStyleInput as E, Argument as F, types_d_exports$2 as G, StringTable as H, Config$2 as I, Style as J, IsFlags as K, I18nApi as L, InputApi as M, State$1 as N, TextValue as O, types_d_exports$1 as P, IntlKit as R, State as S, TextFormat as T, TextKey as U, State$2 as V, TypedTr as W, WhenFlags as X, StylesState as Y, BindField as _, JsxChild as a, CountdownValue as b, UiNode as c, LocalWrite as d, ScrollState as f, UiIntrinsicElements as g, IntrinsicElementsFor as h, Finding as i, Config$1 as j, types_d_exports as k, Box as l, defineTokens as m, ElementChange as n, JsxState as o, popup as p, ResolvedStyle as q, ElementMotion as r, PopupComponent as s, DescriptionNode as t, DebugTrigger as u, BindOptions as v, TextBind as w, Point as x, Config as y, Message as z };
3042
+ export { types_d_exports as A, Message as B, State as C, TextStyleInput as D, TextFormat as E, types_d_exports$1 as F, TypedTr as G, State$2 as H, Argument as I, ResolvedStyle as J, types_d_exports$2 as K, Config$2 as L, Config$1 as M, InputApi as N, TextStyles as O, State$1 as P, I18nApi as R, Point as S, TextBind as T, StringTable as U, Part as V, TextKey as W, StylesState as X, Style as Y, WhenFlags as Z, UiIntrinsicElements as _, JsxChild as a, Config as b, UiNode as c, DebugTrigger as d, LocalWrite as f, IntrinsicElementsFor as g, defineTokens as h, Finding as i, defineComponent as j, TextValue as k, LayoutState as l, popup as m, ElementChange as n, JsxState as o, ScrollState as p, IsFlags as q, ElementMotion as r, PopupComponent as s, DescriptionNode as t, Box as u, BindField as v, TextApi as w, CountdownValue as x, BindOptions as y, IntlKit as z };
@@ -1,7 +1,7 @@
1
- import { Ar as ProviderCall, In as Config$1, Pr as SnapshotOf, Qn as AnyFlow, Sr as Config, X as PixiModule, Xn as FeaturePlugin, a as Config$3, d as Manifest, gr as ClockSource, jr as SaveDoc, kr as PlayerStateProvider, n as AssetsIo, ot as Orientation, q as Config$2, sr as TypeTag, vr as FakeClock, wr as Json } from "./types-CuEK0Vam.mjs";
2
- import { o as AudioContextLike, s as Config$4 } from "./types-3bEK86Z1.mjs";
3
- import { H as StringTable, L as I18nApi, U as TextKey } from "./types-xmH1X8qd.mjs";
4
- import { ft as platformPlugin, i as screen, mt as PlatformProvider, t as createApp, wt as audioPlugin, xt as effectsPlugin } from "./index-YUceiUFm.mjs";
1
+ import { Ar as ProviderCall, In as Config$1, Pr as SnapshotOf, Qn as AnyFlow, Sr as Config, X as PixiModule, Xn as FeaturePlugin, a as Config$3, d as Manifest, gr as ClockSource, jr as SaveDoc, kr as PlayerStateProvider, n as AssetsIo, ot as Orientation, q as Config$2, sr as TypeTag, vr as FakeClock, wr as Json } from "./types-DOldmhIw.mjs";
2
+ import { o as AudioContextLike, s as Config$4 } from "./types-De1AZYym.mjs";
3
+ import { R as I18nApi, U as StringTable, W as TextKey } from "./types-DklpXQQk.mjs";
4
+ import { ft as platformPlugin, i as screen, mt as PlatformProvider, t as createApp, wt as audioPlugin, xt as effectsPlugin } from "./index-rLpq23uS.mjs";
5
5
  import { AnyPluginInstance } from "@moku-labs/core";
6
6
 
7
7
  //#region src/plugins/model/store/providers/memory.d.ts
@@ -1,5 +1,5 @@
1
- import { Nr as Snapshot, wr as Json } from "./types-CuEK0Vam.mjs";
2
- import { t as HeadlessApp } from "./headless-DHtavbqk.mjs";
1
+ import { Nr as Snapshot, wr as Json } from "./types-DOldmhIw.mjs";
2
+ import { t as HeadlessApp } from "./headless-ByFLyHxx.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
 
5
5
  //#region src/plugins/flow/doors/types.d.ts
@@ -1,9 +1,9 @@
1
- import { $t as Api$1, K as Api, Rr as Api$3, xr as Api$2 } from "./types-CuEK0Vam.mjs";
2
- import { P as AnimApi, r as UiApi } from "./types-3bEK86Z1.mjs";
3
- import { M as InputApi } from "./types-xmH1X8qd.mjs";
4
- import { r as Repro } from "./headless-DHtavbqk.mjs";
5
- import { a as InputOf, r as ControlApp } from "./types-_a6-ALzz.mjs";
6
- import { t as commands } from "./commands-aLTGwuyk.mjs";
1
+ import { $t as Api$1, K as Api, Rr as Api$3, xr as Api$2 } from "./types-DOldmhIw.mjs";
2
+ import { P as AnimApi, r as UiApi } from "./types-De1AZYym.mjs";
3
+ import { N as InputApi } from "./types-DklpXQQk.mjs";
4
+ import { r as Repro } from "./headless-ByFLyHxx.mjs";
5
+ import { a as InputOf, r as ControlApp } from "./types-qsE-I59e.mjs";
6
+ import { t as commands } from "./commands-Ay-OfSPt.mjs";
7
7
 
8
8
  //#region src/visual/types.d.ts
9
9
  /** The base commands of the `/control` door, keyed by their short names. */
@@ -618,7 +618,8 @@ function rootPointOf(ecs, entity, point) {
618
618
  * Words on the screen: a string or a message, the style it is drawn in, and the string `text`
619
619
  * resolved out of it. A game writes `content`, `style`, `bind`, `anchor` and `alpha`; `resolved`
620
620
  * is engine-owned and `ui` and the tests read it. `alpha` (1 by default) fades the whole block and
621
- * is patched in place, so a tween on it rebuilds nothing.
621
+ * is patched in place, so a tween on it rebuilds nothing. `box` (0 by default) is the width a
622
+ * style with `wrap: "box"` wraps at; `ui` writes the laid-out width of a text element there.
622
623
  */
623
624
  const Text = /*#__PURE__*/ component("Text", {
624
625
  content: "",
@@ -629,6 +630,7 @@ const Text = /*#__PURE__*/ component("Text", {
629
630
  y: .5
630
631
  },
631
632
  alpha: 1,
633
+ box: 0,
632
634
  resolved: ""
633
635
  }, { owned: ["resolved"] });
634
636
  /** What a `Countdown` starts as. Its own const, because a component takes a typed defaults object. */
@@ -691,7 +693,7 @@ const Countdown = /*#__PURE__*/ component("Countdown", countdownDefaults, { owne
691
693
  * ```ts
692
694
  * label({ text: "+5", style: "board.float", at: { x: 90, y: 180 } });
693
695
  * // [Text({ content: "+5", style: "board.float", bind: undefined,
694
- * // anchor: { x: 0.5, y: 0.5 }, alpha: 1, resolved: "" }),
696
+ * // anchor: { x: 0.5, y: 0.5 }, alpha: 1, box: 0, resolved: "" }),
695
697
  * // Transform({ x: 90, y: 180, rotation: 0, scale: 1 })]
696
698
  * ```
697
699
  */
@@ -726,16 +728,30 @@ function shadowOf(input) {
726
728
  };
727
729
  }
728
730
  /**
729
- * Fills the defaults of one style and refuses a wrap that is neither a width nor `"none"`.
731
+ * Tells whether a wrap is one a style can carry: a width above zero, `"box"` or `"none"`. A style
732
+ * read back from a feature may carry anything, so a word is checked as well as a number.
733
+ *
734
+ * @param wrap - The wrap a style names, as it was stored.
735
+ * @returns False for a number that is not above zero, and for anything else.
736
+ * @example
737
+ * ```ts
738
+ * isWrap("box"); // true
739
+ * ```
740
+ */
741
+ function isWrap(wrap) {
742
+ return typeof wrap === "number" ? wrap > 0 : wrap === "box" || wrap === "none";
743
+ }
744
+ /**
745
+ * Fills the defaults of one style and refuses a wrap that is not a width, `"box"` or `"none"`.
730
746
  *
731
747
  * @param name - The style name, for the error.
732
748
  * @param input - What the game wrote.
733
749
  * @returns The style with every field filled.
734
- * @throws {Error} When `wrap` is a number that is not greater than zero.
750
+ * @throws {Error} When `wrap` is a number that is not greater than zero, or another word.
735
751
  */
736
752
  function normalizeStyle(name, input) {
737
753
  const wrap = input.wrap ?? "none";
738
- if (wrap !== "none" && wrap <= 0) throw new Error(`[game] Text style "${name}" has wrap ${wrap}.\n Use a width in reference px or "none".`);
754
+ if (!isWrap(wrap)) throw new Error(`[game] Text style "${name}" has wrap ${wrap}.\n Use a width in reference px, "box" or "none".`);
739
755
  return {
740
756
  font: input.font,
741
757
  bold: input.bold,
@@ -757,7 +773,7 @@ function normalizeStyle(name, input) {
757
773
  *
758
774
  * @param map - Style name to what that style looks like.
759
775
  * @returns The registration a feature carries.
760
- * @throws {Error} When a style names a wrap that is not a width and not `"none"`.
776
+ * @throws {Error} When a style names a wrap that is not a width, `"box"` or `"none"`.
761
777
  * @example
762
778
  * ```ts
763
779
  * defineTextStyles({ "hud.digits": { font: "ui.font-digits", size: 40, fill: 0xffe082 } });
@@ -1182,9 +1198,10 @@ function contentOf(props) {
1182
1198
  return props.content ?? "";
1183
1199
  }
1184
1200
  /**
1185
- * The visual component of an element: a sprite for an image or an icon, the text for a text, the
1186
- * nine-slice of its style (outlined when the style sets `debug`), or a rounded rectangle, a
1187
- * triangle with `shape: "triangle"`, its stroke dashed with `dash`, or an arc with `shape: "arc"`
1201
+ * The visual component of an element: a sprite for an image or an icon, the text for a text (with
1202
+ * its laid-out width as the box a `"box"` style wraps at), the nine-slice of its style (outlined
1203
+ * when the style sets `debug`), or a rounded rectangle, a triangle with `shape: "triangle"`, its
1204
+ * stroke dashed with `dash`, or an arc with `shape: "arc"`
1188
1205
  * over the `sweep` of its style, ended by its `cap`. A clipping element (`scroll`,
1189
1206
  * `overflow: "hidden"`) keeps the rectangle, which carries the clip; a text field's rectangle
1190
1207
  * clips too, and a nine-slice field draws its slices and clips to them. A style with a stroke and
@@ -1221,7 +1238,8 @@ function visualOf(element) {
1221
1238
  anchor: {
1222
1239
  x: 0,
1223
1240
  y: 0
1224
- }
1241
+ },
1242
+ box: rect.w
1225
1243
  })];
1226
1244
  }
1227
1245
  const clip = type === "scroll" || style.overflow === "hidden";
package/dist/visual.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as VisualApp, c as VisualRenderer, d as VisualStart, f as VisualStep, h as VisualTolerance, i as CheckpointResult, l as VisualReport, m as VisualTestResult, n as runVisualTests, o as VisualOptions, p as VisualTest, r as defineVisualTest, s as VisualPage, t as parseVisualArgv, u as VisualSetup } from "./visual-DExHHx-a.mjs";
1
+ import { a as VisualApp, c as VisualRenderer, d as VisualStart, f as VisualStep, h as VisualTolerance, i as CheckpointResult, l as VisualReport, m as VisualTestResult, n as runVisualTests, o as VisualOptions, p as VisualTest, r as defineVisualTest, s as VisualPage, t as parseVisualArgv, u as VisualSetup } from "./visual-B1mq5n-9.mjs";
2
2
  export { type CheckpointResult, type VisualApp, type VisualOptions, type VisualPage, type VisualRenderer, type VisualReport, type VisualSetup, type VisualStart, type VisualStep, type VisualTest, type VisualTestResult, type VisualTolerance, defineVisualTest, parseVisualArgv, runVisualTests };
package/dist/visual.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as read, t as sources } from "./sources-Fbq_PFVH.mjs";
1
+ import { n as read, t as sources } from "./sources-24TY22DK.mjs";
2
2
  import { n as commands, t as run } from "./run-BqYmcNo7.mjs";
3
3
  import path from "node:path";
4
4
  import { createBrandConsole } from "@moku-labs/common/cli";
package/llms.txt CHANGED
@@ -554,7 +554,7 @@ A screen is a projection whose `view` returns JSX. The game's `tsconfig.json` se
554
554
  - `defineComponent(name, { local?, outcomes?, view(props, local) })`. A component with `outcomes` is a popup: `await fx(popup(Component, props))` opens the gate for its outcomes and resolves with `{ intent, payload }` of the pressed button. A button with `local` writes into the nearest component instance; it never reaches the graph.
555
555
  - `tr(key, params?)` returns a frozen `Message` `{ key, params }`; no locale is read at the call site. Strings live in `strings/<locale>.json` of any feature or sub-feature, as ICU MessageFormat 1 (`{n, plural, one {# order} other {# orders}}`, `select`, `selectordinal`, `number`, `date`, `time`; no skeletons, no `currency`). `bun run assets:keys` compiles them. The keys of the top-level features and the layers go into `generated/strings.<locale>.ts`, which one feature registers; a sub-feature's keys start with `<name>.` (`ep01.title`) and go into `generated/strings.<name>.<locale>.ts`, which the sub-feature registers lazily, `strings: { en: () => import("@generated/strings.ep01.en"), ru: () => import("@generated/strings.ep01.ru") }`. With `assets` composed those modules load and unload with the bundle named like the sub-feature: every way the bundle comes (a node entry, a scene's `bundles`, `load`, a restore, the `safeNode`) brings its strings, a locale switch brings them in the new locale, and while the bundle is not loaded a key of it gives `⟦key⟧` and `has(key)` is false. A compiled module calls the root helpers `messageArgument(value, intl)` (an element stays an element part, a list goes through `Intl.ListFormat`, a number through `Intl.NumberFormat`, else text) and `messageDuration(ms)` (`95_000` gives `{ minutes: 1, seconds: 35 }`). `app.i18n`: `locale()`, `setLocale(locale)` (async, emits `i18n:locale-changed`), `format(message, locale?)` returns `Part[]`, `plain(message)` returns a string, `has(key)`, `isKey(value)`, `locales()`, `replace(locale, messages)` (dev hot swap of a generated strings file). Config: `locale: "en"`, `fallback: "en"`, `locales: {}`.
556
556
  - Keys that come from data: `I18n.TextKey<Strings>` is the keys of the generated `Strings` without parameters (nothing new is generated). A data table types its keys `TextKey` or builds them with the kit's `key("board.hint.red")`; the kit's `tr` never takes a plain `string`. A key read back from a save is checked where the app is (an effect handler, app or page code, loading a save) with one `isKey(value)`: `defineGameApp({ ..., strings: type<Strings>() })` types the game's `app.i18n`, so a true `app.i18n.isKey(value)` narrows the value to `TextKey`; `ctx.require(i18nPlugin).isKey(value)` in a plugin narrows to `string`. `app.i18n.isKey(value)` answers true for a key of the loaded tables of the current locale or the fallback, and for the `<feature>.` prefix of a sub-feature whose strings are not loaded yet; a key that takes parameters answers true too.
557
- - Text: `Text` component, `label({ text, style, at, anchor? })` for board text in a projection view, `defineTextStyles({ "hud.digits": { font, size, fill, bold?, italic?, stroke?, strokeWidth?, letterSpacing?, align?, wrap?, digits? } })` under the feature's `textStyles` key. Tags inside a string: `<b>`, `<i>`, `<color=#rrggbb>`, `<icon=key>`, `\<`, `\n`. Built-in styles `body` and `digits` from config `fonts: { body: "ui.font-body", digits: "ui.font-digits" }`. `app.text.measure(content, style)` gives the same numbers in a browser and in Bun. `app.text.replaceStyles(styles)` is the dev hot swap of a saved `defineTextStyles` value.
557
+ - Text: `Text` component, `label({ text, style, at, anchor? })` for board text in a projection view, `defineTextStyles({ "hud.digits": { font, size, fill, bold?, italic?, stroke?, strokeWidth?, letterSpacing?, align?, wrap? (a width, "none" or "box": wrap at the laid-out width of its ui element), digits? } })` under the feature's `textStyles` key. Tags inside a string: `<b>`, `<i>`, `<color=#rrggbb>`, `<icon=key>`, `\<`, `\n`. Built-in styles `body` and `digits` from config `fonts: { body: "ui.font-body", digits: "ui.font-digits" }`. `app.text.measure(content, style, width?)` gives the same numbers in a browser and in Bun. `app.text.replaceStyles(styles)` is the dev hot swap of a saved `defineTextStyles` value.
558
558
  - Animation is data: `defineAnimation(id, { slots: { name: type<Anim.Target>() }, build: (slots, { at }) => Step })`. Steps: `sequence(...)`, `parallel(...)`, `stagger(items, ms, item => Step)`, `tween(target, Component, to, { ms, ease?, delayMs?, additive?, space?, reduced? })`, `set(target, Component, patch)`, `wait(ms)`, `mark(name)`, `frames(target, { keys, fps, loop? })`, `sfx(key, { bus? })`, `haptic(kind)`, `use(animation, slots, tools?)`, `spawn` (a temporary entity, flying coins, that the timeline despawns when it ends; see the anim README). `Frames({ keys, fps, loop, playing })` is a component that walks a sprite through its textures for the entity's whole life. A target is `{ projection, key }` or an entity. A timeline resolves each `{ projection, key }` target once, when it starts: every step writes the view that held the key then, also after a commit removed it and while its exit plays, until it despawns; a view spawned again under the same key is not followed, and a key nothing held at the start is looked up at its step. `reduced: "keep"` (on a `tween` step, `transition` and `loop` of `defineMotion`, `TweenOptions`, `RestOptions`) plays a track as written under reduced motion, a fade for one; the default `"cut"` lands it at once or holds a loop on its first key. A node plays it with `await fx(play(animation, slots))`; in fast mode it finishes at once. `defineMotion({ states, transition?, on: { enter?, exit?, change? }, when? })` builds the enter, exit and change hooks for a projection or the `motion` prop of a tag; `when: "change"` plays enter and exit only for a commit, so an element that comes or goes with its screen stands at rest and leaves without a motion (default `"always"`). Events `anim:mark` `{ animation, mark }` and `anim:finished` `{ animation }`. `app.anim.replace(definition)` is the dev hot swap: the next `play` builds the new tree. A system cannot `require` a plugin, so it plays through the resource `AnimPlayer`: `res(AnimPlayer).play(animation, slots)` returns the same handle as `app.anim.play`, and the registered animation of `animation.id` wins, so a hot swap reaches the system. Play on a change, never every frame; from phase `input` the tree moves in `animate` of the same frame. Before `anim` starts it throws `[game] AnimPlayer is not ready: anim has not started.` Config `maxTracks: 2000`.
559
559
  - Audio is opt-in: `plugins: [...screen, audioPlugin]`. `fx(sfx("orders.complete"))` resolves when the sound started. `await fx(music("board.theme"))` cross-fades; `music(null)` fades out; a scene's `music` key switches the track on `scenes:changed`. Buses `master`, `music`, `sfx`. Volumes come from the committed player: config `volumes: player => (player as Player).settings.audio`, applied on every `model:committed`. `app.audio`: `setVolume(bus, value)`, `volume(bus)`, `mute(bus, on)`, `muted(bus)`, `unlocked()`, `journal()` (the last started sounds, empty unless config `journal` is above 0). Config: `buses: { master: 1, music: 0.6, sfx: 1 }`, `musicFadeMs: 600`, `volumes`, `context`, `journal: 0`, `music: "decode" | "stream"` default `"decode"` (gapless buffer; `"stream"` is an `<audio>` element, less memory, a gap at every loop), `session: "ambient" | "playback" | "auto"` default `"ambient"` (iOS only: ambient mixes with other apps and obeys the silent switch). The context is asked to run at start: where the shell lets audio play (a native webview with the gesture requirement off) `unlocked()` is true with no touch; a browser that wants a gesture unlocks on the first `pointerdown` or `touchend`. Without `audio` every `sfx` and `music` effect resolves `undefined` at once, so the same node runs in a game with no sound.
560
560
  - `app.ui`: `tree()` the live screen as plain data (a text node carries `content`, the words it draws after `tr()` and `bind`; a text field carries `value`), `find(key)` the entity of a keyed element, `fill(key, value)` types into the text field with that `key`, `scroll(key, to)` puts the `scroll` with that `key` at `to` from its start at once, inside `0..max`, its fling or glide stopped (`false` and `ui:scroll-without-scroll` for no live `scroll`, `false` and `ui:scroll-before-solve` for a `scroll` its first solve has not placed yet), `lint()` tap targets under `tapTargetPt` (default `44`), text that overflows in some locale, absolute elements without `reason`. Config: `tapTargetPt: 44`, `breakpoints: { tall: 2, wide: 1.5 }`. `ui` emits only the dev global event `ui:hot-swap`, see "Hot swap".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/game",
3
- "version": "0.15.13",
3
+ "version": "0.15.16",
4
4
  "description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
5
5
  "type": "module",
6
6
  "sideEffects": [