@moku-labs/game 0.11.0 → 0.12.0

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.
@@ -1,7 +1,7 @@
1
1
  import { $ as DescriptionNode, A as Api$6, B as ComponentType, Et as FeatureDescription, Ft as SceneIdOf, G as Mut, Gt as State$2, J as QueryTerm, Jt as Config$5, K as Narrowed, Lt as Descriptor, N as State$8, Qt as State$5, R as ComponentHandle, Rt as GuideOptions, Sn as Require, Ut as Config$2, V as ComponentValue, Vt as Api$1, X as SystemDefinition, Xt as Json, Y as ResourceType, Z as TagType, Zt as Root, _n as State$7, a as Config$1, at as NumericFields, bn as Config$9, c as LoadBundles, d as State$1, dn as Events, dt as Api$2, en as Patch, et as Ease, f as Tier, fn as PauseReason, ft as BundlesOf, g as Config$6, gn as Config$7, gt as GameTypes, h as Api$4, hn as Api$5, ht as FeaturePlugin, i as BundleSpec, j as Config$8, k as ViewportSize, kt as DefineNode, l as LoadReason, ln as Api$3, mt as EmitterIdOf, o as DefineBundles, on as StoreApi, ot as ProjectionMotion, pn as State$4, pt as Config$3, r as BundleMap, sn as RngApi, st as ProjectionSpec, t as Api, un as Config$4, vt as State$3, x as State$6, xn as Events$1, xt as defineFlow, yt as TextStylesOf, zt as Hint } from "./types-ZQLYXo5X.mjs";
2
- import { A as SfxDescriptor, C as Config$10, D as Target, E as State$9, N as defineAnimation, S as AnimPlayerValue, T as MotionKeyframe, _ as SceneSpec, a as AudioApi, c as MusicDescriptor, d as State$10, g as SceneDefinition, h as LayerMap, j as Step, k as HapticKind, l as MusicOptions, m as DefineScene, n as State$12, p as AnySceneProjection, r as UiApi, s as Config$11, t as Config$12, v as ScenesApi, w as FramesValue, x as AnimApi, y as State$11, z as play } from "./types-BM4shh8K.mjs";
3
- import { _ as NoiseValue, c as EmitterValue, d as ColorMatrixValue, f as DisplacementValue, g as GlowValue, h as FilterValueOf, i as State$13, l as AlphaValue, m as FilterSpec, n as EffectsApi, o as EmitterConfig, p as FilterComponent, s as EmitterDefinition, t as Config$13, u as BlurValue, v as OutlineValue, y as UniformDeclaration } from "./types-CefgYzFt.mjs";
4
- import { $ as ResolvedStyle, A as InputApi, B as SpriteOptions, E as TextValue, F as NineSliceValue, G as Argument, J as Message, K as Config$14, O as defineComponent, Q as IsFlags, R as ShapeValue, S as TextBind, T as TextStyles, U as TransformValue, V as SpriteValue, W as sprite, X as TypedTr, Y as State$14, _ as Config$16, b as State$16, et as Style, f as defineTokens, g as BindOptions, h as BindField, j as State$15, k as Config$15, nt as WhenFlags, p as IntrinsicElementsFor, q as I18nApi, s as PopupComponent, v as CountdownValue, w as TextStyleInput, x as TextApi, y as Point } from "./types-BUwDHKXm.mjs";
2
+ import { A as SfxDescriptor, C as Config$10, D as Target, E as State$9, N as defineAnimation, S as AnimPlayerValue, T as MotionKeyframe, _ as SceneSpec, a as AudioApi, c as MusicDescriptor, d as State$10, g as SceneDefinition, h as LayerMap, j as Step, k as HapticKind, l as MusicOptions, m as DefineScene, n as State$12, p as AnySceneProjection, r as UiApi, s as Config$11, t as Config$12, v as ScenesApi, w as FramesValue, x as AnimApi, y as State$11, z as play } from "./types-D_2G6Q68.mjs";
3
+ import { _ as NoiseValue, c as EmitterValue, d as ColorMatrixValue, f as DisplacementValue, g as GlowValue, h as FilterValueOf, i as State$13, l as AlphaValue, m as FilterSpec, n as EffectsApi, o as EmitterConfig, p as FilterComponent, s as EmitterDefinition, t as Config$13, u as BlurValue, v as OutlineValue, y as UniformDeclaration } from "./types-Bcdo5-2w.mjs";
4
+ import { A as InputApi, B as SpriteOptions, E as TextValue, F as NineSliceValue, G as Argument, J as IntlKit, K as Config$14, O as defineComponent, Q as TypedTr, R as ShapeValue, S as TextBind, T as TextStyles, U as TransformValue, V as SpriteValue, W as sprite, X as Part, Y as Message, Z as State$14, _ as Config$16, b as State$16, et as IsFlags, f as defineTokens, g as BindOptions, h as BindField, it as WhenFlags, j as State$15, k as Config$15, nt as Style, p as IntrinsicElementsFor, q as I18nApi, s as PopupComponent, tt as ResolvedStyle, v as CountdownValue, w as TextStyleInput, x as TextApi, y as Point } from "./types-BrTpz9oL.mjs";
5
5
  import { Log } from "@moku-labs/common/browser";
6
6
  import { PluginCtx } from "@moku-labs/core";
7
7
 
@@ -1140,6 +1140,46 @@ type MotionSpec = {
1140
1140
  */
1141
1141
  declare function defineMotion(spec: MotionSpec): ProjectionMotion<unknown>;
1142
1142
  //#endregion
1143
+ //#region src/plugins/i18n/intl.d.ts
1144
+ /**
1145
+ * Splits milliseconds into the record `Intl.DurationFormat` reads. The value is rounded up to
1146
+ * whole seconds, so a countdown never reads zero while time is left; a negative or non-finite
1147
+ * value is zero. Hours and minutes appear when above zero, seconds always, because `format({})`
1148
+ * throws. A generated `strings.<locale>.ts` imports it as `duration` for a `{left, duration}`
1149
+ * message, and `app.i18n.duration` splits the same way.
1150
+ *
1151
+ * @param ms - The duration in milliseconds.
1152
+ * @returns The hours, minutes and seconds of the duration.
1153
+ * @example
1154
+ * ```ts
1155
+ * // A chest that opens in 95 s, then in an hour and 5 s; a timer that ran out.
1156
+ * messageDuration(95_000); // { minutes: 1, seconds: 35 }
1157
+ * messageDuration(3_605_000); // { hours: 1, seconds: 5 }
1158
+ * messageDuration(-5); // { seconds: 0 }
1159
+ * ```
1160
+ */
1161
+ declare function messageDuration(ms: number): Partial<Record<Intl.DurationFormatUnit, number>>;
1162
+ /**
1163
+ * One plain argument of a compiled message as a part: an element node keeps its place in the
1164
+ * sentence, a list goes through `Intl.ListFormat` and a number through `Intl.NumberFormat` of the
1165
+ * kit's locale, anything else is read as text. A generated `strings.<locale>.ts` imports it as
1166
+ * `argument` for a message with `{name}`.
1167
+ *
1168
+ * @param value - The parameter of the message.
1169
+ * @param intl - The formatter kit of the locale the message is formatted in.
1170
+ * @returns The part.
1171
+ * @example
1172
+ * ```ts
1173
+ * // A compiled module written by hand, the shape `moku-game keys` writes for "Host: {name}".
1174
+ * const lobby: I18n.CompiledMessages = {
1175
+ * "lobby.host": (p, intl) => [{ kind: "text", text: "Host: " }, messageArgument(p.name, intl)]
1176
+ * };
1177
+ * app.i18n.plain(tr("lobby.host", { name: ["Ann", "Bob", "Cy"] })); // "Host: Ann, Bob, and Cy"
1178
+ * app.i18n.format(tr("lobby.host", { name: coin })); // [{ kind: "text", text: "Host: " }, { kind: "element", node: coin }]
1179
+ * ```
1180
+ */
1181
+ declare function messageArgument(value: unknown, intl: IntlKit): Part;
1182
+ //#endregion
1143
1183
  //#region src/plugins/i18n/tr.d.ts
1144
1184
  /**
1145
1185
  * Builds a message. The result is frozen and the parameters are copied, so the caller's object
@@ -1621,4 +1661,4 @@ declare const screen: readonly [import("@moku-labs/core").PluginInstance<"world"
1621
1661
  };
1622
1662
  }> & Record<never, never>, import("@moku-labs/core").PluginInstance<"text", Config$16, State$16, TextApi, {}> & Record<never, never>, import("@moku-labs/core").PluginInstance<"ui", Config$12, State$12, UiApi, {}> & Record<never, never>];
1623
1663
  //#endregion
1624
- export { uiPlugin as $, Pointer as A, Layer as B, defineScene as C, label as Ct, DropTarget as D, Draggable as E, Tappable as F, resource as G, Tree as H, Touchable as I, guide as J, system as K, Traceable as L, Pressable as M, Pressed as N, Held as O, Swipeable as P, worldPlugin as Q, Traced as R, Frames as S, defineTextStyles as St, load as T, component as U, Order as V, mut as W, schedule as X, hint as Y, defineFeature as Z, defineStyle as _, animPlugin as _t, defineEmitter as a, BackResult as at, AnimPlayer as b, Text as bt, Alpha as c, modelPlugin as ct, Displacement as d, i18nPlugin as dt, timePlugin as et, Glow as f, flowPlugin as ft, resolve as g, assetsPlugin as gt, music as h, audioPlugin as ht, screen as i, platformPlugin as it, PointerOver as j, Hovered as k, Blur as l, lifecyclePlugin as lt, Outline as m, clockPlugin as mt, createPlugin as n, scenesPlugin as nt, Emitter as o, PlatformProvider as ot, Noise as p, effectsPlugin as pt, tag as q, defineGame as r, rendererPlugin as rt, defineFilter as s, types_d_exports as st, createApp as t, textPlugin as tt, ColorMatrix as u, inputPlugin as ut, tr as v, projection as vt, defineBundles as w, Animation as x, bind as xt, defineMotion as y, Countdown as yt, Exiting as z };
1664
+ export { defineFeature as $, Held as A, Traced as B, Animation as C, bind as Ct, load as D, defineBundles as E, Pressed as F, component as G, Layer as H, Swipeable as I, system as J, mut as K, Tappable as L, Pointer as M, PointerOver as N, Draggable as O, Pressable as P, schedule as Q, Touchable as R, AnimPlayer as S, Text as St, defineScene as T, label as Tt, Order as U, Exiting as V, Tree as W, guide as X, tag as Y, hint as Z, defineStyle as _, audioPlugin as _t, defineEmitter as a, rendererPlugin as at, messageDuration as b, projection as bt, Alpha as c, PlatformProvider as ct, Displacement as d, lifecyclePlugin as dt, worldPlugin as et, Glow as f, inputPlugin as ft, resolve as g, clockPlugin as gt, music as h, effectsPlugin as ht, screen as i, scenesPlugin as it, Hovered as j, DropTarget as k, Blur as l, types_d_exports as lt, Outline as m, flowPlugin as mt, createPlugin as n, timePlugin as nt, Emitter as o, platformPlugin as ot, Noise as p, i18nPlugin as pt, resource as q, defineGame as r, textPlugin as rt, defineFilter as s, BackResult as st, createApp as t, uiPlugin as tt, ColorMatrix as u, modelPlugin as ut, tr as v, assetsPlugin as vt, Frames as w, defineTextStyles as wt, defineMotion as x, Countdown as xt, messageArgument as y, animPlugin as yt, Traceable as z };
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { $ as DescriptionNode, $t as types_d_exports$9, C as types_d_exports$11, Ct as slot, Kt as types_d_exports$3, P as types_d_exports$16, St as exit, Tt as type, bt as types_d_exports$5, in as SaveUnreadableError, m as types_d_exports$1, mn as types_d_exports$8, wt as to, yn as types_d_exports$14 } from "./types-ZQLYXo5X.mjs";
2
- import { B as sequence, F as frames, G as stagger, H as sfx, I as haptic, J as wait, K as tween, L as mark, M as HAPTIC_KINDS, N as defineAnimation, O as types_d_exports, P as external, R as parallel, U as spawn, V as set, W as spawned, b as types_d_exports$12, f as types_d_exports$2, i as types_d_exports$15, k as HapticKind, q as use, z as play } from "./types-BM4shh8K.mjs";
3
- import { a as types_d_exports$4 } from "./types-CefgYzFt.mjs";
4
- import { C as TextFormat, D as types_d_exports$13, H as Transform, I as Parent, L as Shape, M as types_d_exports$7, N as Display, O as defineComponent, P as NineSlice, W as sprite, Z as types_d_exports$6, d as popup, f as defineTokens, g as BindOptions, l as Box, u as LocalWrite, v as CountdownValue, z as Sprite } from "./types-BUwDHKXm.mjs";
5
- import { $ as uiPlugin, A as Pointer, B as Layer, C as defineScene, Ct as label, D as DropTarget, E as Draggable, F as Tappable, G as resource, H as Tree, I as Touchable, J as guide, K as system, L as Traceable, M as Pressable, N as Pressed, O as Held, P as Swipeable, Q as worldPlugin, R as Traced, S as Frames, St as defineTextStyles, T as load, U as component, V as Order, W as mut, X as schedule, Y as hint, Z as defineFeature, _ as defineStyle, _t as animPlugin, a as defineEmitter, at as BackResult, b as AnimPlayer, bt as Text, c as Alpha, ct as modelPlugin, d as Displacement, dt as i18nPlugin, et as timePlugin, f as Glow, ft as flowPlugin, g as resolve, gt as assetsPlugin, h as music, ht as audioPlugin, i as screen, it as platformPlugin, j as PointerOver, k as Hovered, l as Blur, lt as lifecyclePlugin, m as Outline, mt as clockPlugin, n as createPlugin, nt as scenesPlugin, o as Emitter, ot as PlatformProvider, p as Noise, pt as effectsPlugin, q as tag, r as defineGame, rt as rendererPlugin, s as defineFilter, st as types_d_exports$10, t as createApp, tt as textPlugin, u as ColorMatrix, ut as inputPlugin, v as tr, vt as projection, w as defineBundles, x as Animation, xt as bind, y as defineMotion, yt as Countdown, z as Exiting } from "./index-8TiJQjQA.mjs";
6
- export { Alpha, types_d_exports as Anim, AnimPlayer, Animation, types_d_exports$1 as Assets, types_d_exports$2 as Audio, type BackResult, type BindOptions, Blur, Box, types_d_exports$3 as Clock, ColorMatrix, Countdown, type CountdownValue, type DescriptionNode, Displacement, Display, Draggable, DropTarget, types_d_exports$4 as Effects, Emitter, Exiting, types_d_exports$5 as Flow, Frames, Glow, HAPTIC_KINDS, type HapticKind, Held, Hovered, types_d_exports$6 as I18n, types_d_exports$7 as Input, Layer, types_d_exports$8 as Lifecycle, LocalWrite, types_d_exports$9 as Model, NineSlice, Noise, Order, Outline, Parent, types_d_exports$10 as Platform, type PlatformProvider, Pointer, PointerOver, Pressable, Pressed, types_d_exports$11 as Renderer, SaveUnreadableError, types_d_exports$12 as Scenes, Shape, Sprite, Swipeable, Tappable, Text, type TextFormat, types_d_exports$13 as TextTypes, types_d_exports$14 as Time, Touchable, Traceable, Traced, Transform, Tree, types_d_exports$15 as Ui, types_d_exports$16 as World, animPlugin, assetsPlugin, audioPlugin, bind, clockPlugin, component, createApp, createPlugin, defineAnimation, defineBundles, defineComponent, defineEmitter, defineFeature, defineFilter, defineGame, defineMotion, defineScene, defineStyle, defineTextStyles, defineTokens, effectsPlugin, exit, external, flowPlugin, frames, guide, haptic, hint, i18nPlugin, inputPlugin, label, lifecyclePlugin, load, mark, modelPlugin, music, mut, parallel, platformPlugin, play, popup, projection, rendererPlugin, resolve, resource, scenesPlugin, schedule, screen, sequence, set, sfx, slot, spawn, spawned, sprite, stagger, system, tag, textPlugin, timePlugin, to, tr, tween, type, uiPlugin, use, wait, worldPlugin };
2
+ import { B as sequence, F as frames, G as stagger, H as sfx, I as haptic, J as wait, K as tween, L as mark, M as HAPTIC_KINDS, N as defineAnimation, O as types_d_exports, P as external, R as parallel, U as spawn, V as set, W as spawned, b as types_d_exports$12, f as types_d_exports$2, i as types_d_exports$15, k as HapticKind, q as use, z as play } from "./types-D_2G6Q68.mjs";
3
+ import { a as types_d_exports$4 } from "./types-Bcdo5-2w.mjs";
4
+ import { $ as types_d_exports$6, C as TextFormat, D as types_d_exports$13, H as Transform, I as Parent, L as Shape, M as types_d_exports$7, N as Display, O as defineComponent, P as NineSlice, W as sprite, d as popup, f as defineTokens, g as BindOptions, l as Box, u as LocalWrite, v as CountdownValue, z as Sprite } from "./types-BrTpz9oL.mjs";
5
+ import { $ as defineFeature, A as Held, B as Traced, C as Animation, Ct as bind, D as load, E as defineBundles, F as Pressed, G as component, H as Layer, I as Swipeable, J as system, K as mut, L as Tappable, M as Pointer, N as PointerOver, O as Draggable, P as Pressable, Q as schedule, R as Touchable, S as AnimPlayer, St as Text, T as defineScene, Tt as label, U as Order, V as Exiting, W as Tree, X as guide, Y as tag, Z as hint, _ as defineStyle, _t as audioPlugin, a as defineEmitter, at as rendererPlugin, b as messageDuration, bt as projection, c as Alpha, ct as PlatformProvider, d as Displacement, dt as lifecyclePlugin, et as worldPlugin, f as Glow, ft as inputPlugin, g as resolve, gt as clockPlugin, h as music, ht as effectsPlugin, i as screen, it as scenesPlugin, j as Hovered, k as DropTarget, l as Blur, lt as types_d_exports$10, m as Outline, mt as flowPlugin, n as createPlugin, nt as timePlugin, o as Emitter, ot as platformPlugin, p as Noise, pt as i18nPlugin, q as resource, r as defineGame, rt as textPlugin, s as defineFilter, st as BackResult, t as createApp, tt as uiPlugin, u as ColorMatrix, ut as modelPlugin, v as tr, vt as assetsPlugin, w as Frames, wt as defineTextStyles, x as defineMotion, xt as Countdown, y as messageArgument, yt as animPlugin, z as Traceable } from "./index-rnOYAriI.mjs";
6
+ export { Alpha, types_d_exports as Anim, AnimPlayer, Animation, types_d_exports$1 as Assets, types_d_exports$2 as Audio, type BackResult, type BindOptions, Blur, Box, types_d_exports$3 as Clock, ColorMatrix, Countdown, type CountdownValue, type DescriptionNode, Displacement, Display, Draggable, DropTarget, types_d_exports$4 as Effects, Emitter, Exiting, types_d_exports$5 as Flow, Frames, Glow, HAPTIC_KINDS, type HapticKind, Held, Hovered, types_d_exports$6 as I18n, types_d_exports$7 as Input, Layer, types_d_exports$8 as Lifecycle, LocalWrite, types_d_exports$9 as Model, NineSlice, Noise, Order, Outline, Parent, types_d_exports$10 as Platform, type PlatformProvider, Pointer, PointerOver, Pressable, Pressed, types_d_exports$11 as Renderer, SaveUnreadableError, types_d_exports$12 as Scenes, Shape, Sprite, Swipeable, Tappable, Text, type TextFormat, types_d_exports$13 as TextTypes, types_d_exports$14 as Time, Touchable, Traceable, Traced, Transform, Tree, types_d_exports$15 as Ui, types_d_exports$16 as World, animPlugin, assetsPlugin, audioPlugin, bind, clockPlugin, component, createApp, createPlugin, defineAnimation, defineBundles, defineComponent, defineEmitter, defineFeature, defineFilter, defineGame, defineMotion, defineScene, defineStyle, defineTextStyles, defineTokens, effectsPlugin, exit, external, flowPlugin, frames, guide, haptic, hint, i18nPlugin, inputPlugin, label, lifecyclePlugin, load, mark, messageArgument, messageDuration, modelPlugin, music, mut, parallel, platformPlugin, play, popup, projection, rendererPlugin, resolve, resource, scenesPlugin, schedule, screen, sequence, set, sfx, slot, spawn, spawned, sprite, stagger, system, tag, textPlugin, timePlugin, to, tr, tween, type, uiPlugin, use, wait, worldPlugin };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as sfx, A as types_exports$3, At as defineFilter, B as defineStyle, Bt as flowPlugin, C as types_exports$10, Ct as Alpha, D as types_exports$6, Dt as Glow, E as types_exports$7, Et as Displacement, F as resolve, Ft as AnimPlayer, G as external, Gt as modelPlugin, H as platformPlugin, Ht as slot, I as Box, It as Animation, J as mark, Jt as timePlugin, K as frames, Kt as SaveUnreadableError, L as LocalWrite, Lt as Frames, M as types_exports$1, Mt as scenesPlugin, N as types_exports, Nt as assetsPlugin, O as types_exports$5, Ot as Noise, P as uiPlugin, Pt as animPlugin, Q as set, R as popup, Rt as rendererPlugin, S as types_exports$11, St as Emitter, T as types_exports$8, Tt as ColorMatrix, U as HAPTIC_KINDS, Ut as to, V as textPlugin, Vt as exit, W as defineAnimation, Wt as type, X as play, Y as parallel, Yt as clockPlugin, Z as sequence, _ as types_exports$16, _t as Touchable, a as defineMotion, at as wait, b as types_exports$13, bt as i18nPlugin, c as schedule, ct as DropTarget, d as tr, dt as Pointer, et as spawn, f as defineFeature, ft as PointerOver, g as load, gt as Tappable, h as defineBundles, ht as Swipeable, i as screen, it as use, j as types_exports$2, jt as audioPlugin, k as types_exports$4, kt as Outline, l as projection, lt as Held, m as music, mt as Pressed, n as createPlugin, nt as stagger, o as guide, ot as inputPlugin, p as defineEmitter, pt as Pressable, q as haptic, qt as lifecyclePlugin, r as defineGame, rt as tween, s as hint, st as Draggable, t as createApp, tt as spawned, u as defineScene, ut as Hovered, v as types_exports$15, vt as Traceable, w as types_exports$9, wt as Blur, x as types_exports$12, xt as effectsPlugin, y as types_exports$14, yt as Traced, z as defineTokens, zt as worldPlugin } from "./src-BSbtHZWk.mjs";
1
+ import { $ as sfx, A as types_exports$3, At as Noise, B as defineStyle, Bt as rendererPlugin, C as types_exports$10, Ct as effectsPlugin, D as types_exports$6, Dt as ColorMatrix, E as types_exports$7, Et as Blur, F as resolve, Ft as assetsPlugin, G as external, Gt as to, H as platformPlugin, Ht as flowPlugin, I as Box, It as animPlugin, J as mark, Jt as SaveUnreadableError, K as frames, Kt as type, L as LocalWrite, Lt as AnimPlayer, M as types_exports$1, Mt as defineFilter, N as types_exports, Nt as audioPlugin, O as types_exports$5, Ot as Displacement, P as uiPlugin, Pt as scenesPlugin, Q as set, R as popup, Rt as Animation, S as types_exports$11, St as messageDuration, T as types_exports$8, Tt as Alpha, U as HAPTIC_KINDS, Ut as exit, V as textPlugin, Vt as worldPlugin, W as defineAnimation, Wt as slot, X as play, Xt as timePlugin, Y as parallel, Yt as lifecyclePlugin, Z as sequence, Zt as clockPlugin, _ as types_exports$16, _t as Touchable, a as defineMotion, at as wait, b as types_exports$13, bt as i18nPlugin, c as schedule, ct as DropTarget, d as tr, dt as Pointer, et as spawn, f as defineFeature, ft as PointerOver, g as load, gt as Tappable, h as defineBundles, ht as Swipeable, i as screen, it as use, j as types_exports$2, jt as Outline, k as types_exports$4, kt as Glow, l as projection, lt as Held, m as music, mt as Pressed, n as createPlugin, nt as stagger, o as guide, ot as inputPlugin, p as defineEmitter, pt as Pressable, q as haptic, qt as modelPlugin, r as defineGame, rt as tween, s as hint, st as Draggable, t as createApp, tt as spawned, u as defineScene, ut as Hovered, v as types_exports$15, vt as Traceable, w as types_exports$9, wt as Emitter, x as types_exports$12, xt as messageArgument, y as types_exports$14, yt as Traced, z as defineTokens, zt as Frames } from "./src-SmT-XxVf.mjs";
2
2
  import { A as system, C as Exiting, D as component, E as Tree, O as mut, S as sprite, T as Order, _ as Parent, b as Transform, c as Text, d as defineTextStyles, f as label, g as NineSlice, h as Display, j as tag, k as resource, l as bind, s as Countdown, v as Shape, w as Layer, y as Sprite } from "./visual-dMW8iqAE.mjs";
3
3
  import { r as defineComponent } from "./flatten-BkXuscAV.mjs";
4
- export { Alpha, types_exports as Anim, AnimPlayer, Animation, types_exports$1 as Assets, types_exports$2 as Audio, Blur, Box, types_exports$3 as Clock, ColorMatrix, Countdown, Displacement, Display, Draggable, DropTarget, types_exports$4 as Effects, Emitter, Exiting, types_exports$5 as Flow, Frames, Glow, HAPTIC_KINDS, Held, Hovered, types_exports$6 as I18n, types_exports$7 as Input, Layer, types_exports$8 as Lifecycle, LocalWrite, types_exports$9 as Model, NineSlice, Noise, Order, Outline, Parent, types_exports$10 as Platform, Pointer, PointerOver, Pressable, Pressed, types_exports$11 as Renderer, SaveUnreadableError, types_exports$12 as Scenes, Shape, Sprite, Swipeable, Tappable, Text, types_exports$13 as TextTypes, types_exports$14 as Time, Touchable, Traceable, Traced, Transform, Tree, types_exports$15 as Ui, types_exports$16 as World, animPlugin, assetsPlugin, audioPlugin, bind, clockPlugin, component, createApp, createPlugin, defineAnimation, defineBundles, defineComponent, defineEmitter, defineFeature, defineFilter, defineGame, defineMotion, defineScene, defineStyle, defineTextStyles, defineTokens, effectsPlugin, exit, external, flowPlugin, frames, guide, haptic, hint, i18nPlugin, inputPlugin, label, lifecyclePlugin, load, mark, modelPlugin, music, mut, parallel, platformPlugin, play, popup, projection, rendererPlugin, resolve, resource, scenesPlugin, schedule, screen, sequence, set, sfx, slot, spawn, spawned, sprite, stagger, system, tag, textPlugin, timePlugin, to, tr, tween, type, uiPlugin, use, wait, worldPlugin };
4
+ export { Alpha, types_exports as Anim, AnimPlayer, Animation, types_exports$1 as Assets, types_exports$2 as Audio, Blur, Box, types_exports$3 as Clock, ColorMatrix, Countdown, Displacement, Display, Draggable, DropTarget, types_exports$4 as Effects, Emitter, Exiting, types_exports$5 as Flow, Frames, Glow, HAPTIC_KINDS, Held, Hovered, types_exports$6 as I18n, types_exports$7 as Input, Layer, types_exports$8 as Lifecycle, LocalWrite, types_exports$9 as Model, NineSlice, Noise, Order, Outline, Parent, types_exports$10 as Platform, Pointer, PointerOver, Pressable, Pressed, types_exports$11 as Renderer, SaveUnreadableError, types_exports$12 as Scenes, Shape, Sprite, Swipeable, Tappable, Text, types_exports$13 as TextTypes, types_exports$14 as Time, Touchable, Traceable, Traced, Transform, Tree, types_exports$15 as Ui, types_exports$16 as World, animPlugin, assetsPlugin, audioPlugin, bind, clockPlugin, component, createApp, createPlugin, defineAnimation, defineBundles, defineComponent, defineEmitter, defineFeature, defineFilter, defineGame, defineMotion, defineScene, defineStyle, defineTextStyles, defineTokens, effectsPlugin, exit, external, flowPlugin, frames, guide, haptic, hint, i18nPlugin, inputPlugin, label, lifecyclePlugin, load, mark, messageArgument, messageDuration, modelPlugin, music, mut, parallel, platformPlugin, play, popup, projection, rendererPlugin, resolve, resource, scenesPlugin, schedule, screen, sequence, set, sfx, slot, spawn, spawned, sprite, stagger, system, tag, textPlugin, timePlugin, to, tr, tween, type, uiPlugin, use, wait, worldPlugin };
@@ -1,7 +1,7 @@
1
1
  import { A as Api$1, At as FlowGraph, M as Explained, Mt as JournalEntry, S as Under, T as RenderStats, U as EntitySnapshot, W as FrameDiff, _t as Position, an as Snapshot, h as Api$2, p as Usage, qt as Api, t as Api$3, z as ComponentSchema } from "./types-ZQLYXo5X.mjs";
2
- import { a as AudioApi, r as UiApi, u as SoundEntry } from "./types-BM4shh8K.mjs";
3
- import { n as EffectsApi, r as EffectsStats } from "./types-CefgYzFt.mjs";
4
- import { c as UiNode } from "./types-BUwDHKXm.mjs";
2
+ import { a as AudioApi, r as UiApi, u as SoundEntry } from "./types-D_2G6Q68.mjs";
3
+ import { n as EffectsApi, r as EffectsStats } from "./types-Bcdo5-2w.mjs";
4
+ import { c as UiNode } from "./types-BrTpz9oL.mjs";
5
5
  import { t as HeadlessApp } from "./headless-G4nuZ-xz.mjs";
6
6
  import { a as InputOf, c as Source, i as InputArguments, l as WatchApp, o as InputSchema, t as CheatEntry, u as WatchInput } from "./types-DAdwEJMp.mjs";
7
7
  import { n as defineSource } from "./define-CCc0ozKe.mjs";
@@ -1,2 +1,2 @@
1
- import { a as jsxs, i as jsxDEV, n as JSX, r as jsx, t as Fragment } from "./runtime-DOBYM6g4.mjs";
1
+ import { a as jsxs, i as jsxDEV, n as JSX, r as jsx, t as Fragment } from "./runtime-B276Gg0s.mjs";
2
2
  export { Fragment, type JSX, jsx, jsxDEV, jsxs };
@@ -1,2 +1,2 @@
1
- import { a as jsxs, n as JSX, r as jsx, t as Fragment } from "./runtime-DOBYM6g4.mjs";
1
+ import { a as jsxs, n as JSX, r as jsx, t as Fragment } from "./runtime-B276Gg0s.mjs";
2
2
  export { Fragment, type JSX, jsx, jsxs };
package/dist/lint.mjs CHANGED
@@ -1370,6 +1370,8 @@ function isHelper(folders) {
1370
1370
  }
1371
1371
  /**
1372
1372
  * What test-suffix says about a file: a file in a test kind folder ends with that folder's suffix.
1373
+ * The `index` of a kind folder passes: it is the folder's door, such as the module of
1374
+ * `moku-game visual`, `tests/visual/index.ts`.
1373
1375
  *
1374
1376
  * @param file - The file, relative to the game root.
1375
1377
  * @param table - Kind folder to suffix.
@@ -1387,7 +1389,7 @@ function suffixMessage(file, table) {
1387
1389
  const kind = isHelper(folder.split("/")) ? void 0 : kindOf(`/${folder}/`, table);
1388
1390
  if (extension === void 0 || kind === void 0) return void 0;
1389
1391
  const suffix = extension === TSX_EXTENSION && kind.suffix.endsWith(TS_EXTENSION) ? `${kind.suffix}x` : kind.suffix;
1390
- if (name.endsWith(suffix)) return void 0;
1392
+ if (name.endsWith(suffix) || INDEX_FILE.test(name)) return void 0;
1391
1393
  const renamed = `${name.slice(0, -extension.length).replace(TEST_WORD, "")}${suffix}`;
1392
1394
  const helpers = kind.folder.includes(COLOCATED_TESTS) ? COLOCATED_HELPERS : ROOT_HELPERS;
1393
1395
  return `Tests: a file in ${kind.folder} ends with ${suffix}. Rename ${name} to ${renamed}, or move a helper to ${helpers}.`;
@@ -1,4 +1,4 @@
1
- import { a as JsxChild, m as UiIntrinsicElements, t as DescriptionNode } from "./types-BUwDHKXm.mjs";
1
+ import { a as JsxChild, m as UiIntrinsicElements, t as DescriptionNode } from "./types-BrTpz9oL.mjs";
2
2
 
3
3
  //#region src/plugins/ui/jsx/runtime.d.ts
4
4
  /** What the compiler passes as props: the children plus whatever the tag declared. */
@@ -20908,16 +20908,20 @@ function buildDurationFormat(locale, options) {
20908
20908
  * Splits milliseconds into the record `Intl.DurationFormat` reads. The value is rounded up to
20909
20909
  * whole seconds, so a countdown never reads zero while time is left; a negative or non-finite
20910
20910
  * value is zero. Hours and minutes appear when above zero, seconds always, because `format({})`
20911
- * throws. The generated `duration(ms)` helper of a locale module does the same split.
20911
+ * throws. A generated `strings.<locale>.ts` imports it as `duration` for a `{left, duration}`
20912
+ * message, and `app.i18n.duration` splits the same way.
20912
20913
  *
20913
20914
  * @param ms - The duration in milliseconds.
20914
20915
  * @returns The hours, minutes and seconds of the duration.
20915
20916
  * @example
20916
20917
  * ```ts
20917
- * durationInput(95_000); // { minutes: 1, seconds: 35 }
20918
+ * // A chest that opens in 95 s, then in an hour and 5 s; a timer that ran out.
20919
+ * messageDuration(95_000); // { minutes: 1, seconds: 35 }
20920
+ * messageDuration(3_605_000); // { hours: 1, seconds: 5 }
20921
+ * messageDuration(-5); // { seconds: 0 }
20918
20922
  * ```
20919
20923
  */
20920
- function durationInput(ms) {
20924
+ function messageDuration(ms) {
20921
20925
  const total = Number.isFinite(ms) ? Math.max(0, Math.ceil(ms / SECOND)) : 0;
20922
20926
  const hours = Math.floor(total / HOUR);
20923
20927
  const minutes = Math.floor(total % HOUR / MINUTE);
@@ -20928,6 +20932,52 @@ function durationInput(ms) {
20928
20932
  return input;
20929
20933
  }
20930
20934
  /**
20935
+ * Tells whether a parameter is an element node: an object with `type`, `props` and `children`.
20936
+ *
20937
+ * @param value - The parameter of the message.
20938
+ * @returns True for an element node.
20939
+ */
20940
+ function isElementNode(value) {
20941
+ return typeof value === "object" && value !== null && "type" in value && "props" in value && "children" in value;
20942
+ }
20943
+ /**
20944
+ * One plain argument of a compiled message as a part: an element node keeps its place in the
20945
+ * sentence, a list goes through `Intl.ListFormat` and a number through `Intl.NumberFormat` of the
20946
+ * kit's locale, anything else is read as text. A generated `strings.<locale>.ts` imports it as
20947
+ * `argument` for a message with `{name}`.
20948
+ *
20949
+ * @param value - The parameter of the message.
20950
+ * @param intl - The formatter kit of the locale the message is formatted in.
20951
+ * @returns The part.
20952
+ * @example
20953
+ * ```ts
20954
+ * // A compiled module written by hand, the shape `moku-game keys` writes for "Host: {name}".
20955
+ * const lobby: I18n.CompiledMessages = {
20956
+ * "lobby.host": (p, intl) => [{ kind: "text", text: "Host: " }, messageArgument(p.name, intl)]
20957
+ * };
20958
+ * app.i18n.plain(tr("lobby.host", { name: ["Ann", "Bob", "Cy"] })); // "Host: Ann, Bob, and Cy"
20959
+ * app.i18n.format(tr("lobby.host", { name: coin })); // [{ kind: "text", text: "Host: " }, { kind: "element", node: coin }]
20960
+ * ```
20961
+ */
20962
+ function messageArgument(value, intl) {
20963
+ if (Array.isArray(value)) return {
20964
+ kind: "text",
20965
+ text: intl.list().format(value)
20966
+ };
20967
+ if (typeof value === "number") return {
20968
+ kind: "text",
20969
+ text: intl.number().format(value)
20970
+ };
20971
+ if (isElementNode(value)) return {
20972
+ kind: "element",
20973
+ node: value
20974
+ };
20975
+ return {
20976
+ kind: "text",
20977
+ text: String(value)
20978
+ };
20979
+ }
20980
+ /**
20931
20981
  * Creates the formatter kit of one locale. Every kit is built once, on first use of its locale,
20932
20982
  * and kept in `state.intl`.
20933
20983
  *
@@ -21381,7 +21431,7 @@ function createI18nApi(ctx) {
21381
21431
  duration: (ms, style = "short") => kitOf(ctx.state, ctx.state.locale).duration({
21382
21432
  style,
21383
21433
  secondsDisplay: "always"
21384
- }).format(durationInput(ms)),
21434
+ }).format(messageDuration(ms)),
21385
21435
  locales: () => [...ctx.state.registered.keys()].toSorted(),
21386
21436
  replace: (locale, messages) => replaceModule(ctx, locale, messages)
21387
21437
  };
@@ -34279,7 +34329,7 @@ function defineMotion(spec) {
34279
34329
  * | `@moku-labs/game/project` | the project index of a game's sources, node and bun only |
34280
34330
  * | `@moku-labs/game/jsx-runtime`, `/jsx-dev-runtime` | the JSX runtimes `jsxImportSource: "@moku-labs/game"` names |
34281
34331
  * | `@moku-labs/game/fonts/*` | the MSDF body font `font-body.fnt` + `.png` and its OFL licence; a game copies them into `features/ui/assets/` |
34282
- * | bin `moku-game` | `dev`, `build`, `native <verb>`, `keys`, `pack`: serves, builds, packages and packs a game folder, bun only |
34332
+ * | bin `moku-game` | `dev`, `build`, `native <verb>`, `keys`, `pack`, `visual`: serves, builds, packages, packs and visually tests a game folder, bun only |
34283
34333
  * | bin `moku-game-assets` | the asset scanner CLI, bun only |
34284
34334
  * | bin `moku-game-index` | the project index CLI, bun only |
34285
34335
  *
@@ -34376,4 +34426,4 @@ const screen = [
34376
34426
  uiPlugin
34377
34427
  ];
34378
34428
  //#endregion
34379
- export { sfx as $, types_exports$13 as A, defineFilter as At, defineStyle as B, flowPlugin as Bt, types_exports$6 as C, Alpha as Ct, types_exports$10 as D, Glow as Dt, types_exports$9 as E, Displacement as Et, resolve as F, AnimPlayer as Ft, external as G, modelPlugin as Gt, platformPlugin as H, slot as Ht, Box as I, Animation as It, mark as J, timePlugin as Jt, frames as K, SaveUnreadableError as Kt, LocalWrite as L, Frames as Lt, types_exports$15 as M, scenesPlugin as Mt, types_exports$16 as N, assetsPlugin as Nt, types_exports$11 as O, Noise as Ot, uiPlugin as P, animPlugin as Pt, set as Q, popup as R, rendererPlugin as Rt, types_exports$5 as S, Emitter as St, types_exports$8 as T, ColorMatrix as Tt, HAPTIC_KINDS as U, to as Ut, textPlugin as V, exit as Vt, defineAnimation as W, type as Wt, play$1 as X, parallel as Y, clockPlugin as Yt, sequence as Z, types_exports as _, Touchable as _t, defineMotion as a, wait as at, types_exports$3 as b, i18nPlugin as bt, schedule as c, DropTarget as ct, tr as d, Pointer as dt, spawn as et, defineFeature as f, PointerOver as ft, load as g, Tappable as gt, defineBundles as h, Swipeable as ht, screen as i, use as it, types_exports$14 as j, audioPlugin as jt, types_exports$12 as k, Outline as kt, projection as l, Held as lt, music as m, Pressed as mt, createPlugin as n, stagger as nt, guide as o, inputPlugin as ot, defineEmitter as p, Pressable as pt, haptic as q, lifecyclePlugin as qt, defineGame as r, tween as rt, hint as s, Draggable as st, createApp as t, spawned as tt, defineScene as u, Hovered as ut, types_exports$1 as v, Traceable as vt, types_exports$7 as w, Blur as wt, types_exports$4 as x, effectsPlugin as xt, types_exports$2 as y, Traced as yt, defineTokens as z, worldPlugin as zt };
34429
+ export { sfx as $, types_exports$13 as A, Noise as At, defineStyle as B, rendererPlugin as Bt, types_exports$6 as C, effectsPlugin as Ct, types_exports$10 as D, ColorMatrix as Dt, types_exports$9 as E, Blur as Et, resolve as F, assetsPlugin as Ft, external as G, to as Gt, platformPlugin as H, flowPlugin as Ht, Box as I, animPlugin as It, mark as J, SaveUnreadableError as Jt, frames as K, type as Kt, LocalWrite as L, AnimPlayer as Lt, types_exports$15 as M, defineFilter as Mt, types_exports$16 as N, audioPlugin as Nt, types_exports$11 as O, Displacement as Ot, uiPlugin as P, scenesPlugin as Pt, set as Q, popup as R, Animation as Rt, types_exports$5 as S, messageDuration as St, types_exports$8 as T, Alpha as Tt, HAPTIC_KINDS as U, exit as Ut, textPlugin as V, worldPlugin as Vt, defineAnimation as W, slot as Wt, play$1 as X, timePlugin as Xt, parallel as Y, lifecyclePlugin as Yt, sequence as Z, clockPlugin as Zt, types_exports as _, Touchable as _t, defineMotion as a, wait as at, types_exports$3 as b, i18nPlugin as bt, schedule as c, DropTarget as ct, tr as d, Pointer as dt, spawn as et, defineFeature as f, PointerOver as ft, load as g, Tappable as gt, defineBundles as h, Swipeable as ht, screen as i, use as it, types_exports$14 as j, Outline as jt, types_exports$12 as k, Glow as kt, projection as l, Held as lt, music as m, Pressed as mt, createPlugin as n, stagger as nt, guide as o, inputPlugin as ot, defineEmitter as p, Pressable as pt, haptic as q, modelPlugin as qt, defineGame as r, tween as rt, hint as s, Draggable as st, createApp as t, spawned as tt, defineScene as u, Hovered as ut, types_exports$1 as v, Traceable as vt, types_exports$7 as w, Emitter as wt, types_exports$4 as x, messageArgument as xt, types_exports$2 as y, Traced as yt, defineTokens as z, Frames as zt };
@@ -1,5 +1,5 @@
1
1
  import { Dt as AnyFlow, Ht as ClockSource, Nt as NodeTable, Wt as FakeClock, Xt as Json, ht as FeaturePlugin } from "./types-ZQLYXo5X.mjs";
2
- import { t as createApp } from "./index-8TiJQjQA.mjs";
2
+ import { t as createApp } from "./index-rnOYAriI.mjs";
3
3
  import { n as saveOf, t as memory } from "./memory-DwDhfFWK.mjs";
4
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-G4nuZ-xz.mjs";
5
5
  import { AnyPluginInstance } from "@moku-labs/core";
package/dist/testing.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { Vt as exit, Wt as type, t as createApp } from "./src-BSbtHZWk.mjs";
1
+ import { Kt as type, Ut as exit, t as createApp } from "./src-SmT-XxVf.mjs";
2
2
  import { n as saveOf, t as memory } from "./memory-CWlqhS7l.mjs";
3
3
  import { t as fakeClock } from "./fake-MpzwZhIZ.mjs";
4
4
  import { i as stepFrames, r as runRepro, t as createHeadless } from "./headless-BsNdJs9W.mjs";
@@ -1,5 +1,5 @@
1
1
  import { A as Api$1, B as ComponentType, D as FilterSlot, H as Entity, O as Point, R as ComponentHandle, Sn as Require, _ as PixiFilter, b as PixiTexture, dt as Api, h as Api$2, s as Events, t as Api$3, v as PixiModule, xn as Events$1, y as PixiSprite } from "./types-ZQLYXo5X.mjs";
2
- import { x as AnimApi } from "./types-BM4shh8K.mjs";
2
+ import { x as AnimApi } from "./types-D_2G6Q68.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
  import { PluginCtx } from "@moku-labs/core";
5
5
 
@@ -2777,4 +2777,4 @@ type CommonProps = {
2777
2777
  components?: ElementComponents;
2778
2778
  };
2779
2779
  //#endregion
2780
- export { ResolvedStyle as $, InputApi as A, SpriteOptions as B, TextFormat as C, types_d_exports as D, TextValue as E, NineSliceValue as F, Argument as G, Transform as H, Parent as I, Message as J, Config$2 as K, Shape as L, types_d_exports$1 as M, Display as N, defineComponent as O, NineSlice as P, IsFlags as Q, ShapeValue as R, TextBind as S, TextStyles as T, TransformValue as U, SpriteValue as V, sprite as W, TypedTr as X, State$2 as Y, types_d_exports$2 as Z, Config as _, JsxChild as a, State as b, UiNode as c, popup as d, Style as et, defineTokens as f, BindOptions as g, BindField as h, Finding as i, State$1 as j, Config$1 as k, Box as l, UiIntrinsicElements as m, ElementChange as n, WhenFlags as nt, JsxState as o, IntrinsicElementsFor as p, I18nApi as q, ElementMotion as r, PopupComponent as s, DescriptionNode as t, StylesState as tt, LocalWrite as u, CountdownValue as v, TextStyleInput as w, TextApi as x, Point as y, Sprite as z };
2780
+ export { types_d_exports$2 as $, InputApi as A, SpriteOptions as B, TextFormat as C, types_d_exports as D, TextValue as E, NineSliceValue as F, Argument as G, Transform as H, Parent as I, IntlKit as J, Config$2 as K, Shape as L, types_d_exports$1 as M, Display as N, defineComponent as O, NineSlice as P, TypedTr as Q, ShapeValue as R, TextBind as S, TextStyles as T, TransformValue as U, SpriteValue as V, sprite as W, Part as X, Message as Y, State$2 as Z, Config as _, JsxChild as a, State as b, UiNode as c, popup as d, IsFlags as et, defineTokens as f, BindOptions as g, BindField as h, Finding as i, WhenFlags as it, State$1 as j, Config$1 as k, Box as l, UiIntrinsicElements as m, ElementChange as n, Style as nt, JsxState as o, IntrinsicElementsFor as p, I18nApi as q, ElementMotion as r, StylesState as rt, PopupComponent as s, DescriptionNode as t, ResolvedStyle as tt, LocalWrite as u, CountdownValue as v, TextStyleInput as w, TextApi as x, Point as y, Sprite as z };
@@ -1,6 +1,6 @@
1
1
  import { Dt as AnyFlow, Ht as ClockSource, Jt as Config, Wt as FakeClock, Xt as Json, a as Config$3, g as Config$2, ht as FeaturePlugin, n as AssetsIo, pt as Config$1, tn as PlayerStateProvider, u as Manifest, v as PixiModule } from "./types-ZQLYXo5X.mjs";
2
- import { o as AudioContextLike, s as Config$4 } from "./types-BM4shh8K.mjs";
3
- import { ht as audioPlugin, i as screen, it as platformPlugin, ot as PlatformProvider, pt as effectsPlugin, t as createApp } from "./index-8TiJQjQA.mjs";
2
+ import { o as AudioContextLike, s as Config$4 } from "./types-D_2G6Q68.mjs";
3
+ import { _t as audioPlugin, ct as PlatformProvider, ht as effectsPlugin, i as screen, ot as platformPlugin, t as createApp } from "./index-rnOYAriI.mjs";
4
4
  import { t as memory } from "./memory-DwDhfFWK.mjs";
5
5
  import { AnyPluginInstance } from "@moku-labs/core";
6
6
 
@@ -1,5 +1,5 @@
1
1
  import { A as Api$2, B as ComponentType, F as AnyComponent, H as Entity, It as TypeTag, L as AnyComponentValue, Lt as Descriptor, R as ComponentHandle, Sn as Require, Xt as Json, Yt as Events$2, at as NumericFields, ct as TrackOptions, dn as Events$3, dt as Api$1, et as Ease, h as Api$5, hn as Api, it as MotionHandle, lt as TrackSegment, nt as LayerSpec, q as Owner, qt as Api$4, s as Events$4, t as Api$3, tt as LayerSort, xn as Events$5 } from "./types-ZQLYXo5X.mjs";
2
- import { A as InputApi, c as UiNode, i as Finding, n as ElementChange, o as JsxState, q as I18nApi, r as ElementMotion, tt as StylesState, x as TextApi } from "./types-BUwDHKXm.mjs";
2
+ import { A as InputApi, c as UiNode, i as Finding, n as ElementChange, o as JsxState, q as I18nApi, r as ElementMotion, rt as StylesState, x as TextApi } from "./types-BrTpz9oL.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
  import { PluginCtx } from "@moku-labs/core";
5
5
  import { Node, Yoga } from "yoga-layout/load";
package/dist/visual.d.mts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { A as Api$1, h as Api, ln as Api$3, qt as Api$2 } from "./types-ZQLYXo5X.mjs";
2
- import { r as UiApi, x as AnimApi } from "./types-BM4shh8K.mjs";
3
- import { A as InputApi } from "./types-BUwDHKXm.mjs";
2
+ import { r as UiApi, x as AnimApi } from "./types-D_2G6Q68.mjs";
3
+ import { A as InputApi } from "./types-BrTpz9oL.mjs";
4
4
  import { r as Repro } from "./headless-G4nuZ-xz.mjs";
5
5
  import { a as InputOf, r as ControlApp } from "./types-DAdwEJMp.mjs";
6
- import { t as commands } from "./commands-C4QlqijJ.mjs";
6
+ import { t as commands } from "./commands-DvFr8Uwp.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. */
@@ -273,9 +273,11 @@ declare function defineVisualTest(name: string, test: {
273
273
  //#endregion
274
274
  //#region src/visual/run.d.ts
275
275
  /**
276
- * Reads the flags of the visual tests from a command line: `--update`, `--no-pixels`, `--webgl`,
277
- * `--only <name>` (repeatable) and `--dir <path>`. An argument it does not know is left to the
278
- * script that got it, so a test runner's own arguments pass through.
276
+ * Reads the flags of the visual tests from a command line: `--update`, `--no-pixels`, `--pixels`,
277
+ * `--webgl`, `--only <name>` (repeatable) and `--dir <path>`. `--pixels` forces the pixel leg on
278
+ * off a Mac; the last of `--pixels` and `--no-pixels` wins. An argument it does not know is left
279
+ * to the script that got it, so a test runner's own arguments pass through. `moku-game visual`
280
+ * reads its flags with it.
279
281
  *
280
282
  * @param argv - The arguments after the script name.
281
283
  * @returns The flags that were given, and nothing else.
@@ -286,6 +288,7 @@ declare function defineVisualTest(name: string, test: {
286
288
  * parseVisualArgv(["--update", "--only", "reward-popup"]); // { update: true, only: ["reward-popup"] }
287
289
  * parseVisualArgv(["--no-pixels", "--dir", "shots"]); // { pixels: false, dir: "shots" }
288
290
  * parseVisualArgv(["--webgl"]); // { renderer: "webgl" }
291
+ * parseVisualArgv(["--pixels"]); // { pixels: true }
289
292
  * ```
290
293
  */
291
294
  declare function parseVisualArgv(argv: readonly string[]): VisualFlags;
package/dist/visual.mjs CHANGED
@@ -1906,9 +1906,11 @@ function valueAfter(argv, index, placeholder) {
1906
1906
  throw new Error(`[game] The flag ${flag} needs a value.\n Write it as ${flag} ${placeholder}.`);
1907
1907
  }
1908
1908
  /**
1909
- * Reads the flags of the visual tests from a command line: `--update`, `--no-pixels`, `--webgl`,
1910
- * `--only <name>` (repeatable) and `--dir <path>`. An argument it does not know is left to the
1911
- * script that got it, so a test runner's own arguments pass through.
1909
+ * Reads the flags of the visual tests from a command line: `--update`, `--no-pixels`, `--pixels`,
1910
+ * `--webgl`, `--only <name>` (repeatable) and `--dir <path>`. `--pixels` forces the pixel leg on
1911
+ * off a Mac; the last of `--pixels` and `--no-pixels` wins. An argument it does not know is left
1912
+ * to the script that got it, so a test runner's own arguments pass through. `moku-game visual`
1913
+ * reads its flags with it.
1912
1914
  *
1913
1915
  * @param argv - The arguments after the script name.
1914
1916
  * @returns The flags that were given, and nothing else.
@@ -1919,6 +1921,7 @@ function valueAfter(argv, index, placeholder) {
1919
1921
  * parseVisualArgv(["--update", "--only", "reward-popup"]); // { update: true, only: ["reward-popup"] }
1920
1922
  * parseVisualArgv(["--no-pixels", "--dir", "shots"]); // { pixels: false, dir: "shots" }
1921
1923
  * parseVisualArgv(["--webgl"]); // { renderer: "webgl" }
1924
+ * parseVisualArgv(["--pixels"]); // { pixels: true }
1922
1925
  * ```
1923
1926
  */
1924
1927
  function parseVisualArgv(argv) {
@@ -1927,6 +1930,7 @@ function parseVisualArgv(argv) {
1927
1930
  for (const [index, argument] of argv.entries()) {
1928
1931
  if (argument === "--update") flags.update = true;
1929
1932
  if (argument === "--no-pixels") flags.pixels = false;
1933
+ if (argument === "--pixels") flags.pixels = true;
1930
1934
  if (argument === "--webgl") flags.renderer = "webgl";
1931
1935
  if (argument === "--only") only.push(valueAfter(argv, index, "<name>"));
1932
1936
  if (argument === "--dir") flags.dir = valueAfter(argv, index, "<path>");
package/llms.txt CHANGED
@@ -12,7 +12,7 @@ Facts: Bun only. ESM only. `"sideEffects": false`. `pixi.js` `^8.0.0` is a peer
12
12
  - `@moku-labs/game/app/system` (the browser, the native shell): `systemShellOf`, `fromSystem`, `storeSave`, `createSystemApp`, the type `SystemModules`, over the optional peer `@moku-labs/system`. The one entry that names it, in types only: the page hands it the loaders.
13
13
  - `@moku-labs/game/cli` (node and bun only): `runCli`, the bin `moku-game`, and `preparePage`, the editor's dev page.
14
14
  - `@moku-labs/game/testing`: headless play, repro runs, isolated feature tests (`isolate`, `stub`), fake clock, in-memory save provider. No `node:` import.
15
- - `@moku-labs/game/visual` (node and bun only): the visual tests `defineVisualTest`, `runVisualTests`, `parseVisualArgv`.
15
+ - `@moku-labs/game/visual` (node and bun only): the visual tests `defineVisualTest`, `runVisualTests`, `parseVisualArgv`. The bin runs them: `moku-game visual`.
16
16
  - `@moku-labs/game/assets` (node and bun only): the asset key scanner, the production packer and the string compiler. The package bin `moku-game-assets` runs it.
17
17
  - `@moku-labs/game/inspect`: the read door of the editor, safe in a production build. See "Doors for the editor".
18
18
  - `@moku-labs/game/control`: the write door of the editor, dev builds only. See "Doors for the editor".
@@ -27,11 +27,11 @@ Root exports:
27
27
  - Class: `SaveUnreadableError`.
28
28
  - Plugin instances: `timePlugin`, `lifecyclePlugin`, `modelPlugin`, `clockPlugin`, `flowPlugin`, `worldPlugin`, `rendererPlugin`, `inputPlugin`, `assetsPlugin`, `scenesPlugin`, `animPlugin`, `i18nPlugin`, `textPlugin`, `uiPlugin`, `audioPlugin`, `effectsPlugin`, `platformPlugin`; the list `screen` = `world`, `renderer`, `input`, `assets`, `scenes`, `anim`, `i18n`, `text`, `ui`. `effects`, `audio` and `platform` are opt-in: `plugins: [...screen, effectsPlugin, audioPlugin, platformPlugin]`, `platform` last.
29
29
  - Screen helpers from the root: `component`, `tag`, `resource`, `mut`, `system`, `projection`, `Layer`, `Order`, `Exiting`, `Tree`, `Transform`, `Sprite`, `NineSlice`, `Shape`, `Parent`, `Display`, `sprite`, `Tappable`, `Pressable`, `Draggable`, `DropTarget`, `Swipeable`, `Traceable`, `Touchable`, `Held`, `Hovered`, `PointerOver`, `Pressed`, `Pointer`, `Traced`, `defineBundles`, `load`, `defineScene`.
30
- - Interface helpers from the root (V3): `defineAnimation`, `sequence`, `parallel`, `stagger`, `tween`, `set`, `wait`, `mark`, `frames`, `sfx`, `haptic`, `use`, `spawn`, `spawned`, `play`, `external`, `defineMotion`, `Animation`, `Frames`, the resource `AnimPlayer`; `tr`; `Text`, `label`, `defineTextStyles`, `bind`, `Countdown` and the types `BindOptions`, `CountdownValue`, `TextFormat`; `defineComponent`, `popup`, `defineStyle`, `defineTokens`, `resolve`, `Box`, `LocalWrite`; `music`.
30
+ - Interface helpers from the root (V3): `defineAnimation`, `sequence`, `parallel`, `stagger`, `tween`, `set`, `wait`, `mark`, `frames`, `sfx`, `haptic`, `use`, `spawn`, `spawned`, `play`, `external`, `defineMotion`, `Animation`, `Frames`, the resource `AnimPlayer`; `tr`, `messageArgument`, `messageDuration`; `Text`, `label`, `defineTextStyles`, `bind`, `Countdown` and the types `BindOptions`, `CountdownValue`, `TextFormat`; `defineComponent`, `popup`, `defineStyle`, `defineTokens`, `resolve`, `Box`, `LocalWrite`; `music`.
31
31
  - Effects helpers from the root (V5): `defineEmitter`, `Emitter`, `defineFilter`, `Glow`, `Outline`, `Blur`, `ColorMatrix`, `Noise`, `Displacement`, `Alpha`.
32
32
  - Platform from the root (V6): the types `PlatformProvider`, `BackResult`, `HapticKind` and the frozen list `HAPTIC_KINDS`.
33
33
  - `defineGame<Types>()` returns, typed by the game's `player`, `session`, `assets`, `bundles`, `scenes`, `strings` and `textStyles`: `defineNode`, `defineFlow`, `defineFeature`, `projection`, `sprite`, `Sprite`, `NineSlice`, `defineBundles`, `load`, `defineScene`, `defineAnimation`, `frames`, `sfx`, `play`, `tr`, `label`, `defineTextStyles`, `defineComponent`, `defineStyle`, `defineTokens`, `popup`, `music`, `Frames`, `defineEmitter`, `Emitter`, `Displacement`. `Types` is `{ player; session; assets: string; bundles?: string; scenes?: string; strings: Record<string, unknown>; textStyles?: string; emitters?: string }`.
34
- - `@moku-labs/game/assets`: `scanAssets(options)`, `emitKeys`, `emitManifest`, `compileStrings(root, out, options?)`, `checkStrings`, `packAssets(options)`, `exportStrings`, `importStrings`, `runCli(argv, strings)` with `strings: StringsTools` = `{ compile: compileStrings, exportStrings, importStrings }`. Audio files are `.mp3` or `.m4a`. Asset key = feature (or layer name) + "." + path inside `assets/`. `runCli` (a game's `bun run assets:keys`) scans the assets and compiles `features/*/strings/<locale>.json`, and the `strings/` of every layer, into `generated/strings.ts` (the `Strings` type for `defineGame`) and one `generated/strings.<locale>.ts` per locale; `--check` fails when any output is out of date; `--pack <dir>` writes the production build. `--layer <folder>[=<name>]`, repeatable and good with every mode, scans `<root>/<folder>/` like one more feature under `<name>` (`<folder>` alone maps to itself): its `assets.ts`, `assets/` and `strings/<locale>.json`. A game on the layered layout passes `--layer shared=ui`, so `shared/assets/*` keeps the `ui.*` keys. A missing layer folder is a warning. `scanAssets`, `compileStrings`, `exportStrings` and `importStrings` take the same map as `layers: { shared: "ui" }`. `--pseudo` also writes the pseudo-locale `en-XA`. `--export <dir>` and `--import <dir>` exchange strings with translators; `--source <locale>` names the locale they read from, `"en"` by default. The package bin `moku-game-assets` is this CLI: a script is `"assets:keys": "moku-game-assets --root src --manifest public/assets/manifest.json --keys src/generated/assets.ts"`. A game with `config.ts` runs `moku-game keys` and `moku-game pack` instead: they pass its paths and its layers.
34
+ - `@moku-labs/game/assets`: `scanAssets(options)`, `emitKeys`, `emitManifest`, `compileStrings(root, out, options?)`, `checkStrings`, `packAssets(options)`, `exportStrings`, `importStrings`, `runCli(argv, strings)` with `strings: StringsTools` = `{ compile: compileStrings, exportStrings, importStrings }`. Audio files are `.mp3` or `.m4a`. Asset key = feature (or layer name) + "." + path inside `assets/`. `runCli` (a game's `bun run assets:keys`) scans the assets and compiles `features/*/strings/<locale>.json`, and the `strings/` of every layer, into `generated/strings.ts` (the `Strings` type for `defineGame`) and one `generated/strings.<locale>.ts` per locale (it imports `messageArgument` and `messageDuration` from `@moku-labs/game` when a message needs them, no helper is copied in); `--check` fails when any output is out of date; `--pack <dir>` writes the production build. `--layer <folder>[=<name>]`, repeatable and good with every mode, scans `<root>/<folder>/` like one more feature under `<name>` (`<folder>` alone maps to itself): its `assets.ts`, `assets/` and `strings/<locale>.json`. A game on the layered layout passes `--layer shared=ui`, so `shared/assets/*` keeps the `ui.*` keys. A missing layer folder is a warning. `scanAssets`, `compileStrings`, `exportStrings` and `importStrings` take the same map as `layers: { shared: "ui" }`. `--pseudo` also writes the pseudo-locale `en-XA`. `--export <dir>` and `--import <dir>` exchange strings with translators; `--source <locale>` names the locale they read from, `"en"` by default. The package bin `moku-game-assets` is this CLI: a script is `"assets:keys": "moku-game-assets --root src --manifest public/assets/manifest.json --keys src/generated/assets.ts"`. A game with `config.ts` runs `moku-game keys` and `moku-game pack` instead: they pass its paths and its layers.
35
35
  - Type namespaces: `Time`, `Lifecycle`, `Model`, `Clock`, `Flow`, `World`, `Renderer`, `Input`, `Assets`, `Scenes`, `Anim`, `I18n`, `TextTypes`, `Ui`, `Audio`, `Effects`, `Platform`. Use `import type { Flow } from "@moku-labs/game"` and then `Flow.RouteStep`, `Flow.NodeContext`, `Model.Json`, `Model.PlayerStateProvider`, `Clock.ClockSource`, `Time.Phase`, `Lifecycle.PauseReason`, `Anim.Target`, `I18n.Message`. `Text` is the component, so the text types are `TextTypes`.
36
36
  - `defineNode` and `defineFlow` are NOT root exports. They come from `defineGame<Types>()`.
37
37
 
@@ -169,7 +169,7 @@ type NodeContext = {
169
169
  - `moku-game/static-keys`: a JSX `key` is a literal, a template of such parts, `props.id` or `props.<name>Key`, a call, or a `const` of one. `tabKeys[props.tab]`, `a ?? b`, `a || b`, `a ? b : c` and `props.tab` are reported: pass the key in as a prop, `<TabButton id="tabSound" />`. Default files `**/*.tsx`.
170
170
  - `moku-game/layer-imports`: the order is core ← shared ← features ← `game.ts`; plugins import core and shared; every layer may import `generated/`. An import, export-from or `import()` that reaches a layer above its own is reported, `import type` too: `@features/home` from `shared/`. Only `game.ts` imports `game.ts`.
171
171
  - `moku-game/feature-door`: another feature is imported only through its `index.ts`, `@features/orders`, never `@features/orders/flow/merge`. Only `game.ts` imports the barrels `@features` and `@plugins`. A feature does not import its own `index.ts`. A relative import that leaves the feature is written as an alias: `@features/<f>`, `@shared`, `@core/<file>`. Inside one feature any relative import passes.
172
- - `moku-game/test-suffix`: a file in a test kind folder ends with its suffix. `tests/e2e/` `.e2e.ts`, `tests/visual/` `.visual.ts`, `tests/editor/` `.editor.ts`, `__tests__/`, `__tests__/unit/` and `__tests__/integration/` `.test.ts`, `__tests__/isolated/` `.isolated.ts`, `__tests__/visual/` `.visual.ts`; a `.tsx` file adds an `x`. Helpers go to `tests/helpers/` or `__tests__/fixtures/`.
172
+ - `moku-game/test-suffix`: a file in a test kind folder ends with its suffix. `tests/e2e/` `.e2e.ts`, `tests/visual/` `.visual.ts`, `tests/editor/` `.editor.ts`, `__tests__/`, `__tests__/unit/` and `__tests__/integration/` `.test.ts`, `__tests__/isolated/` `.isolated.ts`, `__tests__/visual/` `.visual.ts`; a `.tsx` file adds an `x`. An `index.ts` of a kind folder passes, such as `tests/visual/index.ts` of `moku-game visual`. Helpers go to `tests/helpers/` or `__tests__/fixtures/`.
173
173
  - Layers under the game root: `core/**`, `shared/**`, `features/<f>/**`, `plugins/<p>/**`, `generated/**`, `game.ts`, `tests/**`. A file outside them (`kit.ts`, `nodes/`, `web/`) keeps the layout rules quiet, so a game on the template layout lints as before.
174
174
  - Aliases: `layer-imports`, `feature-door` and `rules-siblings` read the `compilerOptions.paths` of `tsconfig.json` (comments, trailing commas and relative `extends` included), the way the project index does. Without `paths` they read the v15 table: `@core/*` → `core/*`, `@shared` → `shared/index.ts`, `@shared/rules` → `shared/rules/index.ts`, `@features` → `features/index.ts`, `@features/*` → `features/*/index.ts`, `@plugins` → `plugins/index.ts`, `@generated/*` → `generated/*`, `@tests/*` → `tests/*`. `aliasTargetsOf(tsconfig, root, specifier)` from `@moku-labs/game/lint` answers what they read: for a game without a tsconfig, `aliasTargetsOf("/game/tsconfig.json", "/game", "@features/orders")` is `["/game/features/orders/index.ts"]`.
175
175
  - Logic = the root `index.ts`, `**/state.ts`, `**/tables.ts`, `**/game.ts`, `**/{core,nodes,flows,rules,features,shared}/**`. Tests (`tests/**`, `**/__tests__/**`, `*.test.ts`) are skipped by every rule but `test-suffix`.
@@ -321,6 +321,7 @@ Commands of the bin `moku-game` (bun). Every command takes `--root <dir>` (defau
321
321
  - `moku-game build [--out dist/web]`: packs the assets, bundles the page minified with `__MOKU_GAME_DEV__` defined `false` (no scenario, no agent, no `.dev` module, no `/control`), every link `./`, and copies the pack beside it with `manifest.json` next to `index.html`. Runs from any http sub-path and from Tauri, not from `file://`.
322
322
  - `moku-game native build <target> [--simulator]`, `native dev <target>`, `native doctor [<target>]`, `native clean [<target>]`: one verb of `@moku-labs/native` (a dev dependency, `^0.3.2`) over `config.ts`. App name, identifier and icon from `native`, orientation and background colour from `page`; the web build is `moku-game build`, the dev server `moku-game dev --port 5173`; system rows for `back`, `haptics`, `store`; the Tauri project in `.moku/tauri`, the apps in `dist-native`. `[game] config.ts has no native section.` without `native`.
323
323
  - `moku-game keys [--check]`: `generated/assets.ts`, the compiled strings and `manifest.json`. `moku-game pack [--no-cache]`: `dist/assets`. Both pass the layers of `config.ts`.
324
+ - `moku-game visual [--update] [--only <name>] [--no-pixels | --pixels] [--webgl] [--dir <path>] [--tests <file>] [--url <url>]`: runs `runVisualTests` with the default export of `tests/visual/index.ts`, `{ app, tests }` (`app` is a `VisualSetup` `{ app, page? }`: `export default { app: { app: fixtureApp }, tests }`), against `tests/visual/baselines/`. The flags go through `parseVisualArgv`. The pixel leg runs on a Mac only unless `--pixels`/`--no-pixels`; without `--url` the command writes its own page into `.moku/visual/` (the page of `dev` and of the editor in `.moku/` stays), runs Bun again under `.moku/visual/bunfig.toml` as `dev` does (hot plugin, `--serve-plugin`, `--preload`), and the child serves the page on a free port, then stops it. No `manifest.json`: `[game] visual: no manifest.json in "<game>".` Exit 0 when the report is ok, else 1. `[game] visual: <file> must export default { app, tests }.` for a missing module or another shape, a bare app factory too.
324
325
  - `preparePage(root, { agents?, preload?, servePlugins? })` of `@moku-labs/game/cli` writes the dev page of `moku-game dev` and resolves `{ html, bunfig }`, both absolute, for a server the caller starts: `bun --config=<bunfig>`. With agents the page also imports the game's `**/*.dev.ts` and hands them to the agents. The editor calls it: `await preparePage("games/timber", { agents: ["@moku-labs/editor/agent/page"] })`.
325
326
 
326
327
  ## More patterns
@@ -489,7 +490,7 @@ A screen is a projection whose `view` returns JSX. The game's `tsconfig.json` se
489
490
  - Tags: `screen`, `layer`, `row`, `column`, `stack`, `spacer`, `panel` (swallows taps), `image` (`texture`, `fit?: "contain" | "cover" | "fill"`), `icon` (`name`, `fit?`), `text` (`content: string | Message`, or `bind={bind(Counter, "value")}` with `components={[Counter({ value })]}`; formats `int`, `mm:ss`, `h:mm:ss`, `duration`; `Countdown({ until })` with `bind(Countdown, "left", { format: "mm:ss" })` counts down to a `clock` moment; `style` is a text style key), `button` (`intent` + `payload`, or `local`, never both), `scroll` (`axis: "y"`; children for a short list, or the windowed form `rows`, `rowHeight`, `overscan?` (5), `row: index => node`; row state belongs in the model, a row that leaves the window loses its local state), `input` (a text field; its text lives in the component's `local`, see the ui README). Common props: `key`, `style`, `state: { active?, disabled?, selected? }`, `motion`, `components` (extra component values for the element's entity, such as a filter: `components={[Glow({ strength: 2 })]}`); containers take `hosts?: string[]` (projections drawn inside the element). Style adds `nineSlice`, `tint`, `offsetX`, `offsetY`, `scale`, `origin`, `fit: "contain"`; state variants `is: { hover, pressed, disabled, active, selected, covered }`. `popup(Component, props, { over })` stacks a popup over a kept one.
490
491
  - Style vocabulary (`defineStyle`, plain data): flow `direction`, `wrap`, `justify`, `align`, `alignSelf`, `gap`, `grow`, `shrink`; box `padding`, `margin`, `width`, `height`, `minWidth`, `minHeight`, `maxWidth`, `maxHeight`, `aspect`, `overflow`; position `position`, `left`, `top`, `right`, `bottom`, `reason`; visual `fill`, `stroke`, `strokeWidth`, `radius`, `alpha`; variants `is: { pressed?, disabled?, active?, selected? }` and `when: { portrait?, landscape?, tall?, wide? }`, each a partial style merged in when the flag holds. Lengths are reference units or `"safeArea.top" | "safeArea.right" | "safeArea.bottom" | "safeArea.left"`. No font size here: it comes from the text style.
491
492
  - `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.
492
- - `tr(key, params?)` returns a frozen `Message` `{ key, params }`; no locale is read at the call site. Strings live in `features/<feature>/strings/<locale>.json` 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. `app.i18n`: `locale()`, `setLocale(locale)` (async, emits `i18n:locale-changed`), `format(message, locale?)` returns `Part[]`, `plain(message)` returns a string, `has(key)`, `locales()`, `replace(locale, messages)` (dev hot swap of a generated strings file). Config: `locale: "en"`, `fallback: "en"`, `locales: {}`.
493
+ - `tr(key, params?)` returns a frozen `Message` `{ key, params }`; no locale is read at the call site. Strings live in `features/<feature>/strings/<locale>.json` 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. 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)`, `locales()`, `replace(locale, messages)` (dev hot swap of a generated strings file). Config: `locale: "en"`, `fallback: "en"`, `locales: {}`.
493
494
  - 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.
494
495
  - 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? })`, `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 node plays it with `await fx(play(animation, slots))`; in fast mode it finishes at once. `defineMotion({ states, transition?, on: { enter?, exit?, change? } })` builds the enter, exit and change hooks for a projection or the `motion` prop of a tag. 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`.
495
496
  - 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). Without `audio` every `sfx` and `music` effect resolves `undefined` at once, so the same node runs in a game with no sound.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/game",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -108,7 +108,7 @@
108
108
  "mini:pack": "bun tests/fixtures/moku-game.ts pack --root tests/fixtures/mini-game",
109
109
  "mini:dev": "bun tests/fixtures/moku-game.ts dev --root tests/fixtures/mini-game",
110
110
  "mini:visual": "bun tests/visual/run.ts",
111
- "validate": "publint && attw --pack . --profile esm-only",
111
+ "validate": "publint && (attw --pack . --profile esm-only --quiet || attw --pack . --profile esm-only)",
112
112
  "release:setup": "moku-release setup",
113
113
  "release:doctor": "moku-release doctor",
114
114
  "release": "moku-release"
@@ -153,7 +153,7 @@
153
153
  "@arethetypeswrong/core": "0.18.3",
154
154
  "@biomejs/biome": "2.4.16",
155
155
  "@formatjs/icu-messageformat-parser": "3.5.20",
156
- "@moku-labs/ci": "1.2.6",
156
+ "@moku-labs/ci": "1.3.0",
157
157
  "@moku-labs/common": "0.3.4",
158
158
  "@moku-labs/core": "1.7.1",
159
159
  "@moku-labs/native": "0.3.2",