@moku-labs/game 0.14.12 → 0.14.13

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 (56) hide show
  1. package/README.md +13 -12
  2. package/dist/app/page.d.mts +8 -3
  3. package/dist/app/page.mjs +57 -12
  4. package/dist/app/system.d.mts +5 -3
  5. package/dist/app/system.mjs +2 -0
  6. package/dist/app.d.mts +15 -7
  7. package/dist/app.mjs +131 -27
  8. package/dist/{assets-BrKSg1O6.mjs → assets-CEMfBogh.mjs} +471 -120
  9. package/dist/assets.d.mts +30 -13
  10. package/dist/assets.mjs +1 -1
  11. package/dist/cli.d.mts +105 -8
  12. package/dist/cli.mjs +1036 -128
  13. package/dist/{commands-BaCx2B9j.d.mts → commands-DAETmK86.d.mts} +43 -7
  14. package/dist/{config-CdARrrhU.mjs → config-B87aFTW-.mjs} +25 -4
  15. package/dist/control.d.mts +130 -17
  16. package/dist/control.mjs +4 -4
  17. package/dist/{define-Ce8SVEN-.d.mts → define-BASd9ZUh.d.mts} +32 -4
  18. package/dist/dev-DbHgx-R-.mjs +89 -0
  19. package/dist/{fake-MpzwZhIZ.mjs → fake-6mazmHK3.mjs} +20 -2
  20. package/dist/features-DowEilZm.mjs +266 -0
  21. package/dist/{headless-CMHNsKjW.d.mts → headless-cFveciuY.d.mts} +1 -1
  22. package/dist/hot.d.mts +4 -2
  23. package/dist/hot.mjs +7 -4
  24. package/dist/{index-CLQ8NOTw.d.mts → index-DedbED3T.d.mts} +317 -69
  25. package/dist/index.d.mts +5 -6
  26. package/dist/index.mjs +3 -3
  27. package/dist/inspect.d.mts +12 -7
  28. package/dist/inspect.mjs +2 -2
  29. package/dist/jsx-dev-runtime.d.mts +1 -1
  30. package/dist/jsx-runtime.d.mts +1 -1
  31. package/dist/lint.d.mts +8 -3
  32. package/dist/lint.mjs +432 -20
  33. package/dist/project.d.mts +15 -1
  34. package/dist/project.mjs +1 -1
  35. package/dist/{run-Bf2hKyjZ.mjs → run-BbipFRc6.mjs} +250 -51
  36. package/dist/{runtime-BCnKn0xf.d.mts → runtime-DoSlRx8X.d.mts} +1 -1
  37. package/dist/{session-DgukhR_7.mjs → session-CeKgO2hy.mjs} +30 -2
  38. package/dist/{sources-Duzj6lhV.mjs → sources-Cdvb0rOS.mjs} +33 -6
  39. package/dist/{src-CtC8w3_z.mjs → src-BEbJ_sSx.mjs} +35536 -17789
  40. package/dist/testing.d.mts +23 -13
  41. package/dist/testing.mjs +10 -6
  42. package/dist/{types-DOlpeuGx.d.mts → types-7GXcXxxv.d.mts} +174 -242
  43. package/dist/{types-BjTtGSFb.d.mts → types-BGcda5Le.d.mts} +4459 -732
  44. package/dist/{types-gisvSHoC.d.mts → types-C73hbmxi.d.mts} +129 -17
  45. package/dist/{types-CAPv9l29.d.mts → types-DngFQJro.d.mts} +43 -5
  46. package/dist/{types-Y_rCuXtj.d.mts → types-GHE4J5Uf.d.mts} +1025 -191
  47. package/dist/{visual-dMW8iqAE.mjs → visual-2m4wJL_z.mjs} +111 -7
  48. package/dist/visual-_XhoGeIC.d.mts +345 -0
  49. package/dist/visual.d.mts +1 -332
  50. package/dist/visual.mjs +130 -20
  51. package/dist/{watch-CWI_on4T.mjs → watch-BEdrp80i.mjs} +149 -30
  52. package/llms.txt +99 -48
  53. package/package.json +8 -6
  54. package/dist/dev-Cc50Dk7H.mjs +0 -47
  55. package/dist/memory-pUQ7YZWC.d.mts +0 -50
  56. package/dist/types-BX4Ge_j5.d.mts +0 -656
@@ -1,9 +1,56 @@
1
- import { Dt as AnyFlow, Gt as FakeClock, Ut as ClockSource, Yt as Config, Zt as Json, a as Config$3, g as Config$2, ht as FeaturePlugin, n as AssetsIo, nn as PlayerStateProvider, pt as Config$1, u as Manifest, v as PixiModule } from "./types-BjTtGSFb.mjs";
2
- import { o as AudioContextLike, s as Config$4 } from "./types-Y_rCuXtj.mjs";
3
- import { _t as audioPlugin, ct as PlatformProvider, ht as effectsPlugin, i as screen, ot as platformPlugin, t as createApp } from "./index-CLQ8NOTw.mjs";
4
- import { t as memory } from "./memory-pUQ7YZWC.mjs";
1
+ import { E as Config$2, In as FeaturePlugin, Rn as AnyFlow, Sn as Config$1, _r as SaveDoc, a as Config$3, br as SnapshotOf, cr as Config, d as Manifest, gr as ProviderCall, hr as PlayerStateProvider, ir as FakeClock, k as PixiModule, n as AssetsIo, nr as ClockSource, qn as TypeTag, ur as Json, vt as Orientation } from "./types-BGcda5Le.mjs";
2
+ import { o as AudioContextLike, s as Config$4 } from "./types-GHE4J5Uf.mjs";
3
+ import { H as TextKey, I as I18nApi, V as StringTable } from "./types-7GXcXxxv.mjs";
4
+ import { St as audioPlugin, ft as PlatformProvider, i as screen, t as createApp, ut as platformPlugin, yt as effectsPlugin } from "./index-DedbED3T.mjs";
5
5
  import { AnyPluginInstance } from "@moku-labs/core";
6
6
 
7
+ //#region src/plugins/model/store/providers/memory.d.ts
8
+ /**
9
+ * Creates an in-memory provider that keeps what it was committed and records every call.
10
+ * It is the default when a game configures no `playerProvider`, and the double every test uses:
11
+ * `provider.calls` is the whole persistence protocol of one run, in order. The document lives as
12
+ * long as the provider does, so a second app over the same instance reads what the first one
13
+ * wrote, and nothing survives the process.
14
+ *
15
+ * `load()` reads the fixture, or everything committed since; without either, the player is new.
16
+ * `commitDurable()` resolves at once: there is no disk behind it.
17
+ *
18
+ * @param fixture - The save `load()` starts from. Omitted: a new player.
19
+ * @param fixture.state - The saved document.
20
+ * @param fixture.version - Schema version of the saved document.
21
+ * @returns A provider that keeps its document and records its calls.
22
+ * @example
23
+ * ```ts
24
+ * // A new player is saved at once. One roll reaches the provider at the next rest node.
25
+ * const provider = memory();
26
+ * const game = await createHeadless(createGame(provider));
27
+ *
28
+ * await game.walk([{ at: "home", intent: "roll" }]);
29
+ * provider.calls.map(call => call.method); // ["load", "commit", "commit"]
30
+ * ```
31
+ */
32
+ declare function memory(fixture?: {
33
+ state: SaveDoc;
34
+ version: number;
35
+ }): PlayerStateProvider & {
36
+ calls: ProviderCall[];
37
+ };
38
+ /**
39
+ * Builds a save document from a player tree, with no rng streams drawn yet.
40
+ * Short fixtures in tests read as `saveOf({ coins: 5 })` instead of spelling out the rng branch.
41
+ *
42
+ * @param player - Player tree of the save.
43
+ * @param seed - Rng seed of the save.
44
+ * @returns The save document.
45
+ * @example
46
+ * ```ts
47
+ * // A returning player with 5 coins. The test starts from this save, not from `initialPlayer`.
48
+ * saveOf({ coins: 5 }, 42); // { player: { coins: 5 }, rng: { seed: 42, streams: {} } }
49
+ * const provider = memory({ state: saveOf({ coins: 5 }, 42), version: 1 });
50
+ * ```
51
+ */
52
+ declare function saveOf(player: Json, seed?: number): SaveDoc;
53
+ //#endregion
7
54
  //#region src/app/types.d.ts
8
55
  /**
9
56
  * The name of a `@moku-labs/system` plugin the game shell wires, as the system package names it.
@@ -64,8 +111,25 @@ type GameConfig = {
64
111
  save?: SaveKind; /** The asset build. Default: no layers. */
65
112
  assets?: {
66
113
  /** Asset layers by name: `{ shared: "ui" }` packs the shared layer from `features/ui`. */layers?: Readonly<Record<string, string>>;
114
+ }; /** The debug page. Read only on a page with the agent. Default: `{ overlay: "closed" }`. */
115
+ debug?: {
116
+ /**
117
+ * How the editor's overlay shows when a debug page starts: `"closed"` (default), the `"pill"`, or `"open"` (the
118
+ * card). Only a page with the agent reads it.
119
+ */
120
+ overlay?: OverlayStart;
67
121
  };
68
122
  };
123
+ /**
124
+ * How the editor's overlay shows when a debug page starts, `config.ts` `debug.overlay`.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * // QA builds of the game start with the small pill shown.
129
+ * const start: OverlayStart = "pill";
130
+ * ```
131
+ */
132
+ type OverlayStart = "closed" | "pill" | "open";
69
133
  /**
70
134
  * `GameConfig` with every default filled, as `resolveConfig` returns it. The page, the CLI and the
71
135
  * shell read this one, never the raw config.
@@ -74,7 +138,7 @@ type GameConfig = {
74
138
  * ```ts
75
139
  * const settings: ResolvedGameConfig = {
76
140
  * page: { title: "mini-game", lang: "en", background: "#000000", orientation: "portrait", icons: {}, head: [] },
77
- * native: undefined, system: [], save: "memory", assets: { layers: {} }
141
+ * native: undefined, system: [], save: "memory", assets: { layers: {} }, debug: { overlay: "closed" }
78
142
  * };
79
143
  * ```
80
144
  */
@@ -85,6 +149,9 @@ type ResolvedGameConfig = {
85
149
  save: SaveKind; /** The asset build. */
86
150
  assets: {
87
151
  layers: Readonly<Record<string, string>>;
152
+ }; /** The debug page: how the overlay shows at its start. */
153
+ debug: {
154
+ overlay: OverlayStart;
88
155
  };
89
156
  };
90
157
  /**
@@ -147,24 +214,35 @@ type GamePluginConfigs<Plugins extends readonly AnyPluginInstance[] = readonly [
147
214
  * };
148
215
  * ```
149
216
  */
150
- type GameDefinition<Plugins extends readonly AnyPluginInstance[] = readonly [], HeadlessPlugins extends readonly AnyPluginInstance[] = readonly [], Player extends Json = Json, Session extends Json = Json> = {
151
- /** The main flow. */flow: AnyFlow; /** The checkpoint entered after a failed retry. Default: the start of the main flow. */
217
+ type GameDefinition<Plugins extends readonly AnyPluginInstance[] = readonly [], HeadlessPlugins extends readonly AnyPluginInstance[] = readonly [], Player extends Json = Json, Session extends Json = Json, Strings extends StringTable = StringTable> = {
218
+ /** The main flow. */flow: AnyFlow;
219
+ /**
220
+ * The generated `Strings` type of the game, as a type tag: `strings: type<Strings>()`. Only the
221
+ * compiler reads it: `app.i18n.isKey(value)` of the game's app then narrows to a key of the table.
222
+ * Left out, `isKey` narrows to `string`.
223
+ */
224
+ strings?: TypeTag<Strings>; /** The checkpoint entered after a failed retry. Default: the start of the main flow. */
152
225
  safeNode?: string; /** The player a new save starts from. */
153
226
  player: Player; /** The session at every start. */
154
- session: Session; /** The long side the layout needs, in reference units. Default 1920. */
227
+ session: Session; /** The orientation the layout is designed for. Default `"portrait"`. Not `page.orientation` of `config.ts`, which locks the native build. */
228
+ orientation?: Orientation; /** The short side of the design frame, in reference units. Default 1080. */
229
+ referenceSide?: number; /** The long side the layout needs, in reference units. Default 1920. */
155
230
  referenceLong?: number; /** The rng seed of a new save. Default 42. */
156
231
  seed?: number; /** The shared layer: composed first among the features. */
157
232
  shared?: FeaturePlugin;
158
233
  /**
159
234
  * The features of the screen app, in order, after `shared`. The headless app composes their
160
- * logic too, unless `headless.features` names its own list.
235
+ * logic too, unless `headless.features` names its own list. A feature that declares sub-features
236
+ * (`defineFeature(name, { features })`) brings them: list the parent only; the shell composes the
237
+ * tree depth first, a parent before its children.
161
238
  */
162
239
  features?: readonly FeaturePlugin[]; /** The game's own plugins, after the features. Their APIs land on `screen().app`. */
163
240
  plugins?: Plugins;
164
241
  /**
165
242
  * What the headless app composes: the logic of these features and these plugins, nothing else.
166
243
  * `headless.features` left out is the definition's `features`; `headless: { features: [] }`
167
- * composes no feature. `shared` and `plugins` are never taken by default.
244
+ * composes no feature. `shared` and `plugins` are never taken by default. A parent brings the
245
+ * logic of its sub-features, depth first; `headless.features` may name a sub-feature alone.
168
246
  */
169
247
  headless?: {
170
248
  features?: readonly FeaturePlugin[];
@@ -183,6 +261,40 @@ type GameDefinition<Plugins extends readonly AnyPluginInstance[] = readonly [],
183
261
  * ```
184
262
  */
185
263
  type MemoryProvider = ReturnType<typeof memory>;
264
+ /** The app with `model.store.snapshot()` typed by the game's player and session. */
265
+ type WithGameSnapshot<App, Player, Session> = App extends {
266
+ model: {
267
+ store: {
268
+ snapshot(): unknown;
269
+ };
270
+ };
271
+ } ? Omit<App, "model"> & {
272
+ model: Omit<App["model"], "store"> & {
273
+ store: Omit<App["model"]["store"], "snapshot"> & {
274
+ snapshot(): SnapshotOf<Player, Session>;
275
+ };
276
+ };
277
+ } : App;
278
+ /** The app with `i18n` typed by the game's strings table; an app without i18n, or the wide table, is left as it is. */
279
+ type WithGameStrings<App, Strings extends StringTable> = App extends {
280
+ i18n: I18nApi;
281
+ } ? StringTable extends Strings ? App : Omit<App, "i18n"> & {
282
+ i18n: I18nApi<TextKey<Strings>>;
283
+ } : App;
284
+ /**
285
+ * The app of one game with `model.store.snapshot()` typed by the game's player and session, and
286
+ * `i18n.isKey` by the game's strings table. Only the type: the store and the i18n API are the
287
+ * engine's, and every other member is the app's own.
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * const { app } = game.headless();
292
+ * app.model.store.snapshot().player.count; // 0, typed number
293
+ * // A game whose index.ts passes strings: type<Strings>(): the screen app narrows a saved key.
294
+ * game.screen().app.i18n.isKey("ui.toast.saved"); // true once started, typed I18n.TextKey<Strings>
295
+ * ```
296
+ */
297
+ type WithGameState<App, Player, Session, Strings extends StringTable = StringTable> = WithGameStrings<WithGameSnapshot<App, Player, Session>, Strings>;
186
298
  /**
187
299
  * What one call of `headless()` or `screen()` gives: the app, not started, and the clock and save
188
300
  * provider it runs on, so a test can move the time and read the save.
@@ -366,7 +478,7 @@ type ScreenSeams<Player, Session, Source, Provider> = HeadlessSeams<Player, Sess
366
478
  * const { app, clock, provider } = game.headless({ seed: 7 });
367
479
  * ```
368
480
  */
369
- type GameApp<Plugins extends readonly AnyPluginInstance[] = readonly [], HeadlessPlugins extends readonly AnyPluginInstance[] = readonly [], Player extends Json = Json, Session extends Json = Json> = {
481
+ type GameApp<Plugins extends readonly AnyPluginInstance[] = readonly [], HeadlessPlugins extends readonly AnyPluginInstance[] = readonly [], Player extends Json = Json, Session extends Json = Json, Strings extends StringTable = StringTable> = {
370
482
  /**
371
483
  * Makes the logic app: the core plugins, the logic of `headless.features` and the
372
484
  * `headless.plugins`. A definition without `headless.features` gives the logic of its
@@ -383,7 +495,7 @@ type GameApp<Plugins extends readonly AnyPluginInstance[] = readonly [], Headles
383
495
  * run.state().path; // "home"
384
496
  * ```
385
497
  */
386
- headless<Source extends ClockSource = FakeClock, Provider extends PlayerStateProvider = MemoryProvider>(seams?: HeadlessSeams<Player, Session, Source, Provider>): GameHandle<HeadlessAppOf<HeadlessPlugins>, Source, Provider>;
498
+ headless<Source extends ClockSource = FakeClock, Provider extends PlayerStateProvider = MemoryProvider>(seams?: HeadlessSeams<Player, Session, Source, Provider>): GameHandle<WithGameState<HeadlessAppOf<HeadlessPlugins>, Player, Session, Strings>, Source, Provider>;
387
499
  /**
388
500
  * Makes the screen app: the screen set, `audio`, `effects`, `platform`, then `shared`, the
389
501
  * features and the game's plugins, in that order. Without a mount the renderer is inert, so the
@@ -399,7 +511,7 @@ type GameApp<Plugins extends readonly AnyPluginInstance[] = readonly [], Headles
399
511
  * app.flow.state().path; // "home" once the runner rests
400
512
  * ```
401
513
  */
402
- screen<Source extends ClockSource = FakeClock, Provider extends PlayerStateProvider = MemoryProvider>(seams?: ScreenSeams<Player, Session, Source, Provider>): GameHandle<ScreenAppOf<Plugins>, Source, Provider>;
514
+ screen<Source extends ClockSource = FakeClock, Provider extends PlayerStateProvider = MemoryProvider>(seams?: ScreenSeams<Player, Session, Source, Provider>): GameHandle<WithGameState<ScreenAppOf<Plugins>, Player, Session, Strings>, Source, Provider>;
403
515
  };
404
516
  /**
405
517
  * Any game, whatever its plugins and state: what the page takes.
@@ -471,9 +583,9 @@ type SystemShellFactory = (config: ResolvedGameConfig, report: (problem: string)
471
583
  * ```
472
584
  */
473
585
  type PageOptions = {
474
- /** Prepared saves by name, for `?player=`. Dev only; the build passes none. */scenarios?: Readonly<Record<string, Scenario>>; /** The system shell factory, when `config.ts` names a system plugin or the store save. */
475
- system?: SystemShellFactory; /** Agents started after the app. Dev only. */
476
- agents?: readonly PageAgent[]; /** The game's `.dev` modules, handed to the agents. Dev only. */
586
+ /** Prepared saves by name, for `?player=`. Dev only; the builds pass none. */scenarios?: Readonly<Record<string, Scenario>>; /** The system shell factory, when `config.ts` names a system plugin or the store save. */
587
+ system?: SystemShellFactory; /** Agents started after the app: on the dev page and on a debug page. */
588
+ agents?: readonly PageAgent[]; /** The game's `.dev` modules, handed to the agents: dev and debug pages. */
477
589
  devModules?: readonly object[];
478
590
  };
479
591
  /**
@@ -593,4 +705,4 @@ type SystemShell = {
593
705
  stop(): Promise<void>;
594
706
  };
595
707
  //#endregion
596
- export { SystemShellFactory as _, GameHandle as a, MemoryProvider as c, SaveKind as d, Scenario as f, SystemName as g, SystemModules as h, GameDefinition as i, PageAgent as l, StartedPage as m, GameApp as n, GamePluginConfigs as o, ScreenSeams as p, GameConfig as r, HeadlessSeams as s, AnyGameApp as t, PageOptions as u };
708
+ export { SystemShellFactory as _, GameHandle as a, saveOf as b, MemoryProvider as c, SaveKind as d, Scenario as f, SystemName as g, SystemModules as h, GameDefinition as i, PageAgent as l, StartedPage as m, GameApp as n, GamePluginConfigs as o, ScreenSeams as p, GameConfig as r, HeadlessSeams as s, AnyGameApp as t, PageOptions as u, WithGameState as v, memory as y };
@@ -1,5 +1,5 @@
1
- import { Zt as Json, on as Snapshot } from "./types-BjTtGSFb.mjs";
2
- import { t as HeadlessApp } from "./headless-CMHNsKjW.mjs";
1
+ import { ur as Json, yr as Snapshot } from "./types-BGcda5Le.mjs";
2
+ import { t as HeadlessApp } from "./headless-cFveciuY.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
 
5
5
  //#region src/plugins/flow/doors/types.d.ts
@@ -116,8 +116,46 @@ type Source<S extends InputSchema, O, App = HeadlessApp> = {
116
116
  readonly read: (app: App, input: InputOf<S>) => O;
117
117
  };
118
118
  /**
119
- * A dev-only action on a running game: an id, a title, the input it takes, its effect and how to
120
- * run it. The body starts with the inline dev guard, so a production build drops it.
119
+ * The input schema of an overlay toggle: the state the switch asks for, and nothing else.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * // The overlay runs a toggle with { on: !value(app) }.
124
+ * const input: ToggleInput = { on: "boolean" };
125
+ * ```
126
+ */
127
+ type ToggleInput = {
128
+ readonly on: "boolean";
129
+ };
130
+ /** The schema a toggle accepts: exactly `{ on: "boolean" }`; any schema while `S` is the wide one. */
131
+ type ToggleSchema<S extends InputSchema> = string extends keyof S ? S : [Exclude<keyof S, "on">] extends [never] ? S extends ToggleInput ? S : never : never;
132
+ /** The schema a button accepts: no required field; any schema while `S` is the wide one. */
133
+ type ButtonSchema<S extends InputSchema> = string extends keyof S ? S : [RequiredKeys<S>] extends [never] ? S : never;
134
+ /**
135
+ * The overlay part of a command, one of three shapes told apart by `overlay`: none, a toggle
136
+ * with its input `{ on: "boolean" }` and its `value`, or a button with no required input and no
137
+ * `value`. Any other combination does not compile.
138
+ */
139
+ type CommandOverlay<S extends InputSchema, App> = {
140
+ /** Not shown in the overlay. */readonly overlay?: never; /** Only a toggle has a value. */
141
+ readonly value?: never;
142
+ } | {
143
+ /** A switch of the overlay, run with `{ on: !value(app) }`. */readonly overlay: "toggle";
144
+ readonly input: ToggleSchema<S>; /** The current value of the switch, read by the overlay to draw it. */
145
+ readonly value: (app: App) => boolean;
146
+ } | {
147
+ /** A button of the overlay, run with `{}`. */readonly overlay: "button";
148
+ readonly input: ButtonSchema<S>; /** Only a toggle has a value. */
149
+ readonly value?: never;
150
+ };
151
+ /**
152
+ * An action on a running game, on the dev page and on a debug page: an id, a title, the input it
153
+ * takes, its effect and how to run it. Its body starts with the guard of its effect: the agent
154
+ * form for `read` and `cosmetic`, the control form for the others (see docs/doors.md), so a
155
+ * production build drops it. A game's `.dev` command with only the `__MOKU_GAME_DEV__` guard runs
156
+ * in dev and is refused on a debug page. With `overlay` the editor's overlay shows it: a toggle
157
+ * takes the input `{ on: "boolean" }` and a `value(app)`, a button no required input and no
158
+ * value; the types refuse every other shape.
121
159
  *
122
160
  * @example
123
161
  * ```ts
@@ -141,7 +179,7 @@ type Command<S extends InputSchema, O, App = HeadlessApp> = {
141
179
  readonly input: S;
142
180
  readonly effect: Effect;
143
181
  readonly run: (app: App, input: InputOf<S>) => O | Promise<O>;
144
- };
182
+ } & CommandOverlay<S, App>;
145
183
  /**
146
184
  * Where the game stands after a command: the graph path, the frame and whether the session
147
185
  * was tainted by a cheat or a raw write.