@moku-labs/game 0.14.11 → 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.
- package/README.md +13 -12
- package/dist/app/page.d.mts +8 -3
- package/dist/app/page.mjs +57 -12
- package/dist/app/system.d.mts +5 -3
- package/dist/app/system.mjs +2 -0
- package/dist/app.d.mts +15 -7
- package/dist/app.mjs +131 -27
- package/dist/{assets-BrKSg1O6.mjs → assets-CEMfBogh.mjs} +471 -120
- package/dist/assets.d.mts +30 -13
- package/dist/assets.mjs +1 -1
- package/dist/cli.d.mts +105 -8
- package/dist/cli.mjs +1036 -128
- package/dist/{commands-d5Zlh9hf.d.mts → commands-DAETmK86.d.mts} +43 -7
- package/dist/{config-CdARrrhU.mjs → config-B87aFTW-.mjs} +25 -4
- package/dist/control.d.mts +130 -17
- package/dist/control.mjs +4 -4
- package/dist/{define-BN15lAWb.d.mts → define-BASd9ZUh.d.mts} +32 -4
- package/dist/dev-DbHgx-R-.mjs +89 -0
- package/dist/{fake-MpzwZhIZ.mjs → fake-6mazmHK3.mjs} +20 -2
- package/dist/features-DowEilZm.mjs +266 -0
- package/dist/{headless-CrhefcLu.d.mts → headless-cFveciuY.d.mts} +1 -1
- package/dist/hot.d.mts +4 -2
- package/dist/hot.mjs +7 -4
- package/dist/{index-0GIP3tgm.d.mts → index-DedbED3T.d.mts} +317 -69
- package/dist/index.d.mts +5 -6
- package/dist/index.mjs +3 -3
- package/dist/inspect.d.mts +12 -7
- package/dist/inspect.mjs +2 -2
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/lint.d.mts +8 -3
- package/dist/lint.mjs +432 -20
- package/dist/project.d.mts +15 -1
- package/dist/project.mjs +1 -1
- package/dist/{run-Bf2hKyjZ.mjs → run-BbipFRc6.mjs} +250 -51
- package/dist/{runtime-aLkoeoGj.d.mts → runtime-DoSlRx8X.d.mts} +1 -1
- package/dist/{session-DgukhR_7.mjs → session-CeKgO2hy.mjs} +30 -2
- package/dist/{sources-Duzj6lhV.mjs → sources-Cdvb0rOS.mjs} +33 -6
- package/dist/{src-Do697seR.mjs → src-BEbJ_sSx.mjs} +29154 -11121
- package/dist/testing.d.mts +119 -14
- package/dist/testing.mjs +97 -7
- package/dist/{types-BZa187kz.d.mts → types-7GXcXxxv.d.mts} +196 -242
- package/dist/{types-BT9S_Lo2.d.mts → types-BGcda5Le.d.mts} +4464 -735
- package/dist/{types-yiHtzg1Y.d.mts → types-C73hbmxi.d.mts} +129 -17
- package/dist/{types-Bc9AvZVO.d.mts → types-DngFQJro.d.mts} +43 -5
- package/dist/{types-kNiV7iFj.d.mts → types-GHE4J5Uf.d.mts} +1027 -192
- package/dist/{visual-dMW8iqAE.mjs → visual-2m4wJL_z.mjs} +111 -7
- package/dist/visual-_XhoGeIC.d.mts +345 -0
- package/dist/visual.d.mts +1 -332
- package/dist/visual.mjs +130 -20
- package/dist/{watch-CWI_on4T.mjs → watch-BEdrp80i.mjs} +149 -30
- package/llms.txt +103 -51
- package/package.json +8 -6
- package/dist/dev-Cc50Dk7H.mjs +0 -47
- package/dist/memory-DR1ibF_s.d.mts +0 -50
- package/dist/types-DMoi447V.d.mts +0 -656
|
@@ -1,9 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { o as AudioContextLike, s as Config$4 } from "./types-
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
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;
|
|
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
|
|
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
|
|
475
|
-
system?: SystemShellFactory; /** Agents started after the app
|
|
476
|
-
agents?: readonly PageAgent[]; /** The game's `.dev` modules, handed to the agents
|
|
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 {
|
|
2
|
-
import { t as HeadlessApp } from "./headless-
|
|
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
|
-
*
|
|
120
|
-
*
|
|
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.
|