@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,13 +1,14 @@
1
- import { A as Api$2, E as DebugSwitches, Ot as Bookmark, h as Api$1, jt as FlowState, un as Api, w as Captured, yn as Time } from "./types-BjTtGSFb.mjs";
2
- import { a as AudioApi, r as UiApi, v as ScenesApi, x as AnimApi } from "./types-Y_rCuXtj.mjs";
3
- import { A as InputApi } from "./types-DOlpeuGx.mjs";
4
- import { t as HeadlessApp } from "./headless-CMHNsKjW.mjs";
5
- import { n as Command, r as ControlApp } from "./types-CAPv9l29.mjs";
1
+ import { Hn as FlowState, I as Captured, Ir as Time, Mr as FrameRate, S as ScenesApi, T as Api$1, ht as DebugSwitches, wr as Api, xr as StoreApi, yr as Snapshot, zn as Bookmark, zt as Api$2 } from "./types-BGcda5Le.mjs";
2
+ import { P as AnimApi, a as AudioApi, h as EffectsApi, r as UiApi } from "./types-GHE4J5Uf.mjs";
3
+ import { j as InputApi } from "./types-7GXcXxxv.mjs";
4
+ import { t as HeadlessApp } from "./headless-cFveciuY.mjs";
5
+ import { n as Command, r as ControlApp } from "./types-DngFQJro.mjs";
6
6
 
7
7
  //#region src/plugins/flow/doors/commands.d.ts
8
8
  /**
9
9
  * Every command of the engine, keyed by its short name: what an editor lists as buttons and MCP
10
- * tools. Frozen. Each runs in dev builds only.
10
+ * tools. Frozen. Each runs on the dev page and on a debug page, by the profile `run` checks: a
11
+ * `--debug` page runs the read and cosmetic ones.
11
12
  *
12
13
  * @example
13
14
  * ```ts
@@ -62,6 +63,28 @@ declare const commands: Readonly<{
62
63
  } & {
63
64
  readonly scenes?: ScenesApi;
64
65
  }>;
66
+ saveExport: Command<{}, Snapshot, HeadlessApp & {
67
+ readonly log: import("@moku-labs/common/browser").LogApi;
68
+ } & {
69
+ readonly scenes?: ScenesApi;
70
+ } & {
71
+ readonly model: {
72
+ readonly store: Pick<StoreApi, "snapshot" | "flush">;
73
+ };
74
+ }>;
75
+ saveImport: Command<{
76
+ save: "json";
77
+ }, {
78
+ restored: boolean;
79
+ }, HeadlessApp & {
80
+ readonly log: import("@moku-labs/common/browser").LogApi;
81
+ } & {
82
+ readonly scenes?: ScenesApi;
83
+ } & {
84
+ readonly model: {
85
+ readonly store: Pick<StoreApi, "snapshot" | "flush">;
86
+ };
87
+ }>;
65
88
  step: Command<{
66
89
  frames: "number";
67
90
  deltaMs: "number?";
@@ -89,7 +112,9 @@ declare const commands: Readonly<{
89
112
  readonly world: Api$2;
90
113
  }>;
91
114
  debug: Command<{
92
- nineSlice: "boolean";
115
+ nineSlice: "boolean?";
116
+ verifyFrames: "boolean?";
117
+ kept: "boolean?";
93
118
  }, DebugSwitches, HeadlessApp & {
94
119
  readonly log: import("@moku-labs/common/browser").LogApi;
95
120
  } & {
@@ -113,6 +138,9 @@ declare const commands: Readonly<{
113
138
  timeScale: Command<{
114
139
  scale: "number";
115
140
  }, Readonly<Time>, ControlApp>;
141
+ frameRate: Command<{
142
+ fps: "number?";
143
+ }, Readonly<FrameRate>, ControlApp>;
116
144
  mute: Command<{
117
145
  muted: "boolean";
118
146
  }, boolean, HeadlessApp & {
@@ -127,6 +155,14 @@ declare const commands: Readonly<{
127
155
  } & {
128
156
  readonly input: InputApi;
129
157
  }>;
158
+ filter: Command<{
159
+ kind: "string";
160
+ on: "boolean";
161
+ }, readonly string[], HeadlessApp & {
162
+ readonly log: import("@moku-labs/common/browser").LogApi;
163
+ } & {
164
+ readonly effects?: EffectsApi;
165
+ }>;
130
166
  }>;
131
167
  //#endregion
132
168
  export { commands as t };
@@ -35,10 +35,27 @@ function systemNames() {
35
35
  ]);
36
36
  }
37
37
  /**
38
+ * The modes a debug page may start the overlay in.
39
+ *
40
+ * @returns The set of `debug.overlay` values.
41
+ * @example
42
+ * ```ts
43
+ * overlayStarts().has("pill"); // true
44
+ * ```
45
+ */
46
+ function overlayStarts() {
47
+ return /* @__PURE__ */ new Set([
48
+ "closed",
49
+ "pill",
50
+ "open"
51
+ ]);
52
+ }
53
+ /**
38
54
  * Throws for the first value of a config that TypeScript would have refused.
39
55
  *
40
56
  * @param config - The default export of `config.ts`.
41
- * @throws {Error} When `save` is not a save kind, a `system` name is not wired, or the title is empty.
57
+ * @throws {Error} When `save` is not a save kind, a `system` name is not wired, the title is empty, or
58
+ * `debug.overlay` is not a start mode.
42
59
  */
43
60
  function checkConfig(config) {
44
61
  if (config.save !== void 0 && !saveKinds().has(config.save)) throw new Error(`[game] config.save is "${config.save}".\n Use "memory", "local" or "store".`);
@@ -46,15 +63,18 @@ function checkConfig(config) {
46
63
  const unwired = config.system?.find((name) => !wired.has(name));
47
64
  if (unwired !== void 0) throw new Error(`[game] config.system names "${unwired}", which the game shell does not wire.\n Use lifecycle, back, haptics, keepAwake or store.`);
48
65
  if (config.page.title.trim() === "") throw new Error("[game] config.page.title is empty.\n Give the page a title in config.ts.");
66
+ const overlay = config.debug?.overlay;
67
+ if (overlay !== void 0 && !overlayStarts().has(overlay)) throw new Error(`[game] config.ts: debug.overlay must be "closed", "pill" or "open", got "${String(overlay)}".`);
49
68
  }
50
69
  /**
51
70
  * Fills every default of a game's `config.ts`: `lang` `"en"`, `background` `"#000000"`,
52
71
  * `orientation` `"portrait"`, no icons, no head tags, no system plugin, the memory save, no asset
53
- * layers.
72
+ * layers, the overlay of a debug page closed at its start.
54
73
  *
55
74
  * @param config - The default export of `config.ts`.
56
75
  * @returns The config with every field set.
57
- * @throws {Error} When `save` is not a save kind, a `system` name is not wired, or the title is empty.
76
+ * @throws {Error} When `save` is not a save kind, a `system` name is not wired, the title is empty, or
77
+ * `debug.overlay` is not a start mode.
58
78
  * @example
59
79
  * ```ts
60
80
  * resolveConfig({ page: { title: "T" } }).save; // "memory"
@@ -74,7 +94,8 @@ function resolveConfig(config) {
74
94
  native: config.native,
75
95
  system: config.system ?? [],
76
96
  save: config.save ?? "memory",
77
- assets: { layers: config.assets?.layers ?? {} }
97
+ assets: { layers: config.assets?.layers ?? {} },
98
+ debug: { overlay: config.debug?.overlay ?? "closed" }
78
99
  };
79
100
  }
80
101
  //#endregion
@@ -1,12 +1,12 @@
1
- import { t as HeadlessApp } from "./headless-CMHNsKjW.mjs";
2
- import { i as InputArguments, n as Command, o as InputSchema, s as Ran } from "./types-CAPv9l29.mjs";
3
- import { t as commands } from "./commands-BaCx2B9j.mjs";
4
- import { t as defineCommand } from "./define-Ce8SVEN-.mjs";
1
+ import { t as HeadlessApp } from "./headless-cFveciuY.mjs";
2
+ import { i as InputArguments, n as Command, o as InputSchema, s as Ran } from "./types-DngFQJro.mjs";
3
+ import { t as commands } from "./commands-DAETmK86.mjs";
4
+ import { t as defineCommand } from "./define-BASd9ZUh.mjs";
5
5
 
6
6
  //#region src/plugins/flow/doors/dev.d.ts
7
7
  /**
8
- * @file flow/doors — the dev flag and the errors a door throws. `__MOKU_GAME_DEV__` is a global
9
- * the engine never replaces: `moku-game dev` defines it `true` (bunfig `define`) and sets
8
+ * @file flow/doors — the flags of a page and the errors a door throws. `__MOKU_GAME_DEV__` is a
9
+ * global the engine never replaces: `moku-game dev` defines it `true` (bunfig `define`) and sets
10
10
  * `globalThis.__MOKU_GAME_DEV__` in `.moku/dev.ts` before the engine runs; `moku-game build`
11
11
  * defines it `false`; undefined means a production build.
12
12
  *
@@ -14,6 +14,8 @@ import { t as defineCommand } from "./define-Ce8SVEN-.mjs";
14
14
  * vanish from a production bundle writes the guard inline:
15
15
  * `if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();`.
16
16
  * A `define` of `false` folds that condition, and the minifier drops the code behind it.
17
+ * `__MOKU_GAME_AGENT__` and `__MOKU_GAME_CONTROL__` follow the same rules: a define of `false`
18
+ * folds their guard.
17
19
  */
18
20
  declare global {
19
21
  /**
@@ -27,40 +29,150 @@ declare global {
27
29
  * ```
28
30
  */
29
31
  var __MOKU_GAME_DEV__: boolean | undefined;
32
+ /**
33
+ * The agent flag: the editor's page agent is on the page, and measurement code runs. `moku-game dev` and
34
+ * `moku-game build --debug` set it, `moku-game build` defines it `false`. A game never re-declares it.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * // The in-memory agent.ts of `moku-game build --debug`, the first import of its main.ts.
39
+ * globalThis.__MOKU_GAME_AGENT__ = true;
40
+ * ```
41
+ */
42
+ var __MOKU_GAME_AGENT__: boolean | undefined;
43
+ /**
44
+ * The control flag: every /control command runs, the state-changing ones too (`route`, `cheat`, `raw`).
45
+ * `moku-game dev` and `moku-game build --debug=full` set it; `--debug` and `moku-game build` define it `false`.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * // The in-memory agent.ts of `moku-game build --debug=full`.
50
+ * globalThis.__MOKU_GAME_CONTROL__ = true;
51
+ * ```
52
+ */
53
+ var __MOKU_GAME_CONTROL__: boolean | undefined;
54
+ /**
55
+ * The id of the build the page came from: `"dev"` on the dev page, `"debug-20261014T153012Z"` or
56
+ * `"debug-full-20261014T153012Z"` on a debug build, undefined on a release build.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * // The overlay shows which build runs on the phone.
61
+ * globalThis.__MOKU_GAME_BUILD__; // "debug-20261014T153012Z"
62
+ * ```
63
+ */
64
+ var __MOKU_GAME_BUILD__: string | undefined;
65
+ /**
66
+ * The engine the page runs: the id of `engineFingerprint` (E), its version plus a hash of its bundled `dist` and of
67
+ * any patch of that version. Set on the dev page and on a debug build; undefined on a release build. The editor's
68
+ * overlay and the header of the performance log (spec 11) read it; the engine's plugins never do.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * // The overlay names the engine the phone runs.
73
+ * globalThis.__MOKU_GAME_ENGINE__; // "0.14.12+9c41e0.patch.3f2a7b"
74
+ * ```
75
+ */
76
+ var __MOKU_GAME_ENGINE__: string | undefined;
77
+ /**
78
+ * Where the page agent of a debug build finds the editor: `host:port`, written by `--editor` (default
79
+ * `"127.0.0.1:47600"`). Undefined on the dev page, whose agent talks to the server that served it, and on a release
80
+ * build.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * // moku-game build --debug --editor 192.168.1.20:47600
85
+ * globalThis.__MOKU_GAME_EDITOR__; // "192.168.1.20:47600"
86
+ * ```
87
+ */
88
+ var __MOKU_GAME_EDITOR__: string | undefined;
89
+ /**
90
+ * The overlay channel of the page agent: the editor's page agent sets it when it starts and deletes it when it stops.
91
+ * `open()` shows the card; `open({ mode: "pill" })` the small pill; `toggle()` switches between hidden and the last
92
+ * visible mode; `isOpen()` is true for the pill or the card. A game never sets it: it calls `app.debug.overlay`.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * // The editor's page agent, at start:
97
+ * globalThis.__MOKU_GAME_OVERLAY__ = { open: options => overlay.show(options?.mode ?? "card"), close, toggle, isOpen };
98
+ * ```
99
+ */
100
+ var __MOKU_GAME_OVERLAY__: {
101
+ open(options?: {
102
+ mode?: "pill" | "card";
103
+ }): void;
104
+ close(): void;
105
+ toggle(): void;
106
+ isOpen(): boolean;
107
+ } | undefined;
30
108
  }
31
109
  /**
32
- * Reads the dev flag at call time. Undefined means off: production is the default.
110
+ * The error `run` throws on a `--debug` page for a command that changes the game: its `reason`
111
+ * tells it apart from any other failure.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * // The editor greys the command out instead of reporting a failure.
116
+ * const refused: ControlReadOnlyError = controlReadOnly("game.tap");
117
+ * refused.reason; // "read-only"
118
+ * ```
119
+ */
120
+ type ControlReadOnlyError = Error & {
121
+ readonly reason: "read-only";
122
+ };
123
+ /**
124
+ * The error a control command throws when its body runs outside the builds its guard allows. It
125
+ * carries `reason: "dev-only"`, so an editor tells a refusal by the body's guard apart from a
126
+ * failure, as `reason: "read-only"` tells the refusal of `run` on a `--debug` page.
33
127
  *
34
- * @returns True only when `__MOKU_GAME_DEV__` is exactly `true`.
35
128
  * @example
36
129
  * ```ts
37
- * // The dev page set the flag in .moku/dev.ts before the engine started.
38
- * globalThis.__MOKU_GAME_DEV__ = true;
39
- * isDev(); // true
130
+ * const refused: ControlRefusedError = controlRefused();
131
+ * refused.reason; // "dev-only"
40
132
  * ```
41
133
  */
134
+ type ControlRefusedError = Error & {
135
+ readonly reason: "dev-only";
136
+ };
42
137
  /**
43
- * Builds the error a control command throws outside a dev build.
138
+ * Builds the error a control command throws outside a dev build. It carries `reason: "dev-only"`.
44
139
  *
45
140
  * @returns The error, ready to throw.
46
141
  * @example
47
142
  * ```ts
48
143
  * // A production build called a /control command.
49
144
  * controlRefused().message; // "[game] Control commands run in dev builds only.\n Define __MOKU_GAME_DEV__ as true in the dev build."
145
+ * controlRefused().reason; // "dev-only"
146
+ * ```
147
+ */
148
+ declare function controlRefused(): ControlRefusedError;
149
+ /**
150
+ * Builds the error `run` throws on a `--debug` page for a command whose effect is `route`,
151
+ * `cheat` or `raw`. It carries `reason: "read-only"`, so an editor tells it apart from a failure.
152
+ *
153
+ * @param id - The id of the refused command.
154
+ * @returns The error, ready to throw.
155
+ * @example
156
+ * ```ts
157
+ * // The editor on a --debug page.
158
+ * try { await run(app, commands.tap, { key: "play" }); } catch (error) { Reflect.get(Object(error), "reason"); // "read-only" }
50
159
  * ```
51
160
  */
52
- declare function controlRefused(): Error;
161
+ declare function controlReadOnly(id: string): ControlReadOnlyError;
53
162
  //#endregion
54
163
  //#region src/plugins/flow/doors/run.d.ts
55
164
  /**
56
- * Runs a command in a dev build. A `cheat` or `raw` command taints the session and is journaled
57
- * before it runs, so a failing one still counts.
165
+ * Runs a command in a dev build or a debug build. A debug build of `--debug` runs the read and
166
+ * cosmetic commands only; `--debug=full` and the dev page run every command. A `cheat` or `raw`
167
+ * command taints the session and is journaled before it runs, so a failing one still counts.
58
168
  *
59
169
  * @param app - The app the command acts on.
60
170
  * @param command - The command.
61
171
  * @param input - Its input; left out when every field of its schema is optional.
62
172
  * @returns The command's value and the envelope read after it.
63
- * @throws {Error} Outside a dev build, and whatever the command throws.
173
+ * @throws {Error} Outside dev builds and debug builds (read and cosmetic commands; every command
174
+ * with `--debug=full`), the read-only refusal of `controlReadOnly` for a `route`, `cheat` or
175
+ * `raw` command on a `--debug` page, and whatever the command throws.
64
176
  * @example
65
177
  * ```ts
66
178
  * // An e2e script: load the bug report, then tap Play.
@@ -68,8 +180,9 @@ declare function controlRefused(): Error;
68
180
  * const ran = await run(app, commands.answer, { intent: "play" });
69
181
  * ran.value; // true
70
182
  * ran.state; // { path: "board/awaitIntent", frame: 14, tainted: true }
183
+ * // On a --debug page: await run(app, commands.tap, { key: "play" }); // throws "[game] The command game.tap changes the game: …"
71
184
  * ```
72
185
  */
73
186
  declare function run<S extends InputSchema, O, App extends HeadlessApp>(app: App, command: Command<S, O, App>, ...input: InputArguments<S>): Promise<Ran<O>>;
74
187
  //#endregion
75
- export { type Command, type Ran, commands, controlRefused, defineCommand, run };
188
+ export { type Command, type ControlReadOnlyError, type ControlRefusedError, type Ran, commands, controlReadOnly, controlRefused, defineCommand, run };
package/dist/control.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as controlRefused } from "./dev-Cc50Dk7H.mjs";
2
- import { s as defineCommand } from "./session-DgukhR_7.mjs";
3
- import { n as commands, t as run } from "./run-Bf2hKyjZ.mjs";
4
- export { commands, controlRefused, defineCommand, run };
1
+ import { n as controlRefused, t as controlReadOnly } from "./dev-DbHgx-R-.mjs";
2
+ import { s as defineCommand } from "./session-CeKgO2hy.mjs";
3
+ import { n as commands, t as run } from "./run-BbipFRc6.mjs";
4
+ export { commands, controlReadOnly, controlRefused, defineCommand, run };
@@ -1,5 +1,5 @@
1
- import { t as HeadlessApp } from "./headless-CMHNsKjW.mjs";
2
- import { c as Source, n as Command, o as InputSchema } from "./types-CAPv9l29.mjs";
1
+ import { t as HeadlessApp } from "./headless-cFveciuY.mjs";
2
+ import { c as Source, n as Command, o as InputSchema } from "./types-DngFQJro.mjs";
3
3
 
4
4
  //#region src/plugins/flow/doors/define.d.ts
5
5
  /**
@@ -25,8 +25,13 @@ import { c as Source, n as Command, o as InputSchema } from "./types-CAPv9l29.mj
25
25
  */
26
26
  declare function defineSource<S extends InputSchema, O, App = HeadlessApp>(source: Source<S, O, App>): Source<S, O, App>;
27
27
  /**
28
- * Declares a command: a dev-only action the editor lists and runs. Its body starts with the
29
- * inline dev guard, so a production build drops it.
28
+ * Declares a command: an action the editor lists and runs on the dev page and on a debug page.
29
+ * Its body starts with the guard of its effect: the agent form for `read` and `cosmetic`, the
30
+ * control form for the others (see docs/doors.md), so a production build drops it. A game's
31
+ * `.dev` command with only the `__MOKU_GAME_DEV__` guard runs in dev and is refused on a debug
32
+ * page. `overlay` shows it in the editor's overlay: `"toggle"` with the input `{ on: "boolean" }`
33
+ * and a `value(app)` the switch is drawn from, `"button"` with no required input and no value;
34
+ * any other combination is a type error.
30
35
  *
31
36
  * @param command - The descriptor.
32
37
  * @returns The same descriptor, frozen.
@@ -46,6 +51,29 @@ declare function defineSource<S extends InputSchema, O, App = HeadlessApp>(sourc
46
51
  * });
47
52
  * (await run(app, openShop)).state.path; // "shop"
48
53
  * ```
54
+ * @example
55
+ * ```ts
56
+ * // features/ward/glow.dev.ts — the overlay's "Glow" switch of this game.
57
+ * export const glowSwitch = defineCommand({
58
+ * id: "ward.glow",
59
+ * title: "Glow",
60
+ * input: { on: "boolean" },
61
+ * effect: "cosmetic",
62
+ * overlay: "toggle",
63
+ * value: (app: { readonly effects: Effects.EffectsApi }) =>
64
+ * !app.effects.diagnostics.disabled().includes("effects.glow"),
65
+ * run: (app, { on }) => {
66
+ * if (
67
+ * (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) &&
68
+ * (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)
69
+ * ) {
70
+ * throw controlRefused();
71
+ * }
72
+ * return on ? app.effects.diagnostics.enable("effects.glow") : app.effects.diagnostics.disable("effects.glow");
73
+ * }
74
+ * });
75
+ * // The overlay draws a switch "Glow", on; a tap runs it with { on: false } and the next frame draws no glow.
76
+ * ```
49
77
  */
50
78
  declare function defineCommand<S extends InputSchema, O, App = HeadlessApp>(command: Command<S, O, App>): Command<S, O, App>;
51
79
  //#endregion
@@ -0,0 +1,89 @@
1
+ //#region src/plugins/flow/doors/dev.ts
2
+ /**
3
+ * Reads the dev flag at call time. Undefined means off: production is the default.
4
+ *
5
+ * @returns True only when `__MOKU_GAME_DEV__` is exactly `true`.
6
+ * @example
7
+ * ```ts
8
+ * // The dev page set the flag in .moku/dev.ts before the engine started.
9
+ * globalThis.__MOKU_GAME_DEV__ = true;
10
+ * isDev(); // true
11
+ * ```
12
+ */
13
+ function isDev() {
14
+ return typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ === true;
15
+ }
16
+ /**
17
+ * Reads the agent flag at call time, for a runtime check; a branch that must leave a bundle
18
+ * writes the inline guard instead.
19
+ *
20
+ * @returns True only when `__MOKU_GAME_AGENT__` is exactly `true`.
21
+ * @example
22
+ * ```ts
23
+ * isAgent(); // true on the page of moku-game build --debug, false on a release page
24
+ * ```
25
+ */
26
+ function isAgent() {
27
+ return typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ === true;
28
+ }
29
+ /**
30
+ * Reads the control flag at call time, for a runtime check; a branch that must leave a bundle
31
+ * writes the inline guard instead.
32
+ *
33
+ * @returns True only when `__MOKU_GAME_CONTROL__` is exactly `true`.
34
+ * @example
35
+ * ```ts
36
+ * isControl(); // true on the page of moku-game build --debug=full, false on a --debug page
37
+ * ```
38
+ */
39
+ function isControl() {
40
+ return typeof __MOKU_GAME_CONTROL__ !== "undefined" && __MOKU_GAME_CONTROL__ === true;
41
+ }
42
+ /**
43
+ * Builds the error a control command throws outside a dev build. It carries `reason: "dev-only"`.
44
+ *
45
+ * @returns The error, ready to throw.
46
+ * @example
47
+ * ```ts
48
+ * // A production build called a /control command.
49
+ * controlRefused().message; // "[game] Control commands run in dev builds only.\n Define __MOKU_GAME_DEV__ as true in the dev build."
50
+ * controlRefused().reason; // "dev-only"
51
+ * ```
52
+ */
53
+ function controlRefused() {
54
+ return Object.assign(/* @__PURE__ */ new Error("[game] Control commands run in dev builds only.\n Define __MOKU_GAME_DEV__ as true in the dev build."), { reason: "dev-only" });
55
+ }
56
+ /**
57
+ * Builds the error `run` throws on a `--debug` page for a command whose effect is `route`,
58
+ * `cheat` or `raw`. It carries `reason: "read-only"`, so an editor tells it apart from a failure.
59
+ *
60
+ * @param id - The id of the refused command.
61
+ * @returns The error, ready to throw.
62
+ * @example
63
+ * ```ts
64
+ * // The editor on a --debug page.
65
+ * try { await run(app, commands.tap, { key: "play" }); } catch (error) { Reflect.get(Object(error), "reason"); // "read-only" }
66
+ * ```
67
+ */
68
+ function controlReadOnly(id) {
69
+ return Object.assign(/* @__PURE__ */ new Error(`[game] The command ${id} changes the game: this debug build runs read and cosmetic commands only.\n Build with moku-game build --debug=full to run it.`), { reason: "read-only" });
70
+ }
71
+ /**
72
+ * Builds the error a door source or command throws when the opt-in plugin it uses is not
73
+ * installed.
74
+ *
75
+ * @param id - The id of the source or the command.
76
+ * @param plugin - The export name of the plugin it uses.
77
+ * @param kind - What the door descriptor is. A source by default.
78
+ * @returns The error, ready to throw.
79
+ * @example
80
+ * ```ts
81
+ * notInstalled("game.sounds", "audioPlugin").message; // "[game] The source game.sounds needs audioPlugin.\n Add audioPlugin to createApp({ plugins })."
82
+ * notInstalled("game.mute", "audioPlugin", "command").message; // "[game] The command game.mute needs audioPlugin.\n Add audioPlugin to createApp({ plugins })."
83
+ * ```
84
+ */
85
+ function notInstalled(id, plugin, kind = "source") {
86
+ return /* @__PURE__ */ new Error(`[game] The ${kind} ${id} needs ${plugin}.\n Add ${plugin} to createApp({ plugins }).`);
87
+ }
88
+ //#endregion
89
+ export { isDev as a, isControl as i, controlRefused as n, notInstalled as o, isAgent as r, controlReadOnly as t };
@@ -1,4 +1,6 @@
1
1
  //#region src/plugins/clock/fake.ts
2
+ /** Every source `fakeClock` made: the app shell keeps a fake clock's app from adapting its frame rate. */
3
+ const fakeClocks = /*#__PURE__*/ new WeakSet();
2
4
  /**
3
5
  * Removes and returns the timer that is due first, up to and including `until`.
4
6
  * Timers due at the same moment come out in the order they were scheduled.
@@ -59,7 +61,7 @@ function fakeClock(start = 0) {
59
61
  nextId: 1,
60
62
  timers: []
61
63
  };
62
- return {
64
+ const clock = {
63
65
  now: () => state.now,
64
66
  setTimer: (callback, delayMs) => {
65
67
  const id = state.nextId;
@@ -82,6 +84,22 @@ function fakeClock(start = 0) {
82
84
  state.now = moment;
83
85
  }
84
86
  };
87
+ fakeClocks.add(clock);
88
+ return clock;
89
+ }
90
+ /**
91
+ * Tells a source `fakeClock` made from any other: the app shell's test seams run on it, and their
92
+ * apps never adapt their frame rate. Not in the root.
93
+ *
94
+ * @param source - A clock source.
95
+ * @returns True for a fake source.
96
+ * @example
97
+ * ```ts
98
+ * isFakeClock(fakeClock()); // true
99
+ * ```
100
+ */
101
+ function isFakeClock(source) {
102
+ return fakeClocks.has(source);
85
103
  }
86
104
  //#endregion
87
- export { fakeClock as t };
105
+ export { isFakeClock as n, fakeClock as t };