@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.
- 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-BaCx2B9j.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-Ce8SVEN-.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-CMHNsKjW.d.mts → headless-cFveciuY.d.mts} +1 -1
- package/dist/hot.d.mts +4 -2
- package/dist/hot.mjs +7 -4
- package/dist/{index-CLQ8NOTw.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-BCnKn0xf.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-CtC8w3_z.mjs → src-BEbJ_sSx.mjs} +35536 -17789
- package/dist/testing.d.mts +23 -13
- package/dist/testing.mjs +10 -6
- package/dist/{types-DOlpeuGx.d.mts → types-7GXcXxxv.d.mts} +174 -242
- package/dist/{types-BjTtGSFb.d.mts → types-BGcda5Le.d.mts} +4459 -732
- package/dist/{types-gisvSHoC.d.mts → types-C73hbmxi.d.mts} +129 -17
- package/dist/{types-CAPv9l29.d.mts → types-DngFQJro.d.mts} +43 -5
- package/dist/{types-Y_rCuXtj.d.mts → types-GHE4J5Uf.d.mts} +1025 -191
- 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 +99 -48
- package/package.json +8 -6
- package/dist/dev-Cc50Dk7H.mjs +0 -47
- package/dist/memory-pUQ7YZWC.d.mts +0 -50
- package/dist/types-BX4Ge_j5.d.mts +0 -656
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ bun add @moku-labs/game pixi.js
|
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
> [!IMPORTANT]
|
|
56
|
-
> Bun only. ESM only. There is no CJS build. `pixi.js` `^8.
|
|
56
|
+
> Bun only. ESM only. There is no CJS build. `pixi.js` `^8.22.0` is a peer dependency. Screens are `.tsx`, so a game sets `"jsx": "react-jsx"` and `"jsxImportSource": "@moku-labs/game"`.
|
|
57
57
|
|
|
58
58
|
A game in a native shell adds the optional peers: `bun add @moku-labs/system` for the system plugins and the store save, `bun add -d @moku-labs/native` for the native build. A web-only game installs neither.
|
|
59
59
|
|
|
@@ -144,7 +144,7 @@ The engine bin `moku-game` serves, builds and packs a game folder. A game writes
|
|
|
144
144
|
|---|---|
|
|
145
145
|
| `index.ts` | `export default defineGameApp({ ... })` |
|
|
146
146
|
| `config.ts` | `page`, `native`, `system`, `save`, `assets`: plain data that `satisfies GameConfig` |
|
|
147
|
-
| `core/`, `shared/`, `features/`, `plugins/` | The kit, the shared layer, the features, the game's plugins |
|
|
147
|
+
| `core/`, `shared/`, `features/`, `plugins/` | The kit, the shared layer, the features (a feature may hold sub-features, `features/episodes/ep01/`), the game's plugins |
|
|
148
148
|
| `tests/` | The tests. `tests/scenarios/<name>.ts` are the saves of `?player=<name>` on the dev page |
|
|
149
149
|
| `.moku/` | What `moku-game` writes. Add `.moku/` to `.gitignore` |
|
|
150
150
|
|
|
@@ -160,9 +160,9 @@ The engine bin `moku-game` serves, builds and packs a game folder. A game writes
|
|
|
160
160
|
|
|
161
161
|
| Command | Does |
|
|
162
162
|
|---|---|
|
|
163
|
-
| `moku-game dev [--port 3000] [--packed]` | Serves the game with hot reload. Prints the bound URL, also for `--port 0`. On the raw files it runs the scan of `keys` at start and on a save of an asset or a strings file |
|
|
164
|
-
| `moku-game build [--out dist/web]` | Packs the assets and builds the
|
|
165
|
-
| `moku-game native build <target> [--simulator]` | Builds the native app: `ios`, `macos`, `android` |
|
|
163
|
+
| `moku-game dev [--port 3000] [--packed]` | Serves the game with hot reload. Prints the bound URL, also for `--port 0`. On the raw files it runs the scan of `keys` at start and on a save of an asset or a strings file. Restarts its server when the installed engine changes |
|
|
164
|
+
| `moku-game build [--out dist/web] [--debug[=full] [--editor host:port]]` | Packs the assets and builds the page; `--debug` adds the editor's page agent (read and cosmetic commands), `--debug=full` every command; `--editor` names where the agent finds the editor (default `127.0.0.1:47600`). A debug page is never shipped: a release page that carries the agent is refused |
|
|
165
|
+
| `moku-game native build <target> [--simulator] [--debug[=full] [--editor host:port]]` | Builds the native app: `ios`, `macos`, `android`; `--debug` builds an Android debug app beside the store app |
|
|
166
166
|
| `moku-game native dev <target>` | Runs the native shell on the dev server |
|
|
167
167
|
| `moku-game native doctor`, `native clean` | Checks or removes the native project |
|
|
168
168
|
| `moku-game keys [--check]` | Writes `generated/assets.ts`, the strings and `generated/manifest.json` |
|
|
@@ -201,7 +201,7 @@ A rest node waits. A player answer comes through the gate. A world event comes t
|
|
|
201
201
|
| Flow | `defineFlow(id, { nodes, start, edges })`. A flow with `outcomes` is a node of another flow. |
|
|
202
202
|
| Edge target | A node name, `exit("outcome")` to leave a sub-flow, or `to("node", mapper)`. |
|
|
203
203
|
| Slot | `slot("name")`. Features contribute sub-flows to it. |
|
|
204
|
-
| Feature | `defineFeature(name, { nodes?, flows?, contribute? })`. A plugin that adds to the graph. |
|
|
204
|
+
| Feature | `defineFeature(name, { nodes?, flows?, contribute?, features? })`. A plugin that adds to the graph. `features` declares the sub-features of the feature; `flattenFeatures` turns the tree into the plugin list. |
|
|
205
205
|
| Effect | `await fx(descriptor)` for awaited effects, `fx.emit(hint(...))` for cosmetic ones. |
|
|
206
206
|
| Transition | Rest node to rest node, as one transaction. An error rolls back and retries. |
|
|
207
207
|
|
|
@@ -225,10 +225,10 @@ createApp({ plugins: [...screen, effectsPlugin, audioPlugin, platformPlugin] });
|
|
|
225
225
|
| [`world`](./src/plugins/world/README.md) | screen | A small ECS and projections from committed state |
|
|
226
226
|
| [`renderer`](./src/plugins/renderer/README.md) | screen | Pixi v8, loaded lazily, synced from the ECS |
|
|
227
227
|
| [`input`](./src/plugins/input/README.md) | screen | Gestures as data: tap, press, drag, swipe |
|
|
228
|
-
| [`assets`](./src/plugins/assets/README.md) | screen | Manifest, load tiers, typed keys, the production pack |
|
|
229
|
-
| [`scenes`](./src/plugins/scenes/README.md) | screen | A scene as a declaration: bundle, layers, music |
|
|
228
|
+
| [`assets`](./src/plugins/assets/README.md) | screen | Manifest, load tiers, typed keys, the production pack; bundles per feature and sub-feature, `release: "leave"`, `mipmaps` |
|
|
229
|
+
| [`scenes`](./src/plugins/scenes/README.md) | screen | A scene as a declaration: bundle, `bundles(state)`, layers, music |
|
|
230
230
|
| [`anim`](./src/plugins/anim/README.md) | screen | Choreography as data: tweens, timelines, motions |
|
|
231
|
-
| [`i18n`](./src/plugins/i18n/README.md) | screen | Strings as data, ICU messages, compiled per locale |
|
|
231
|
+
| [`i18n`](./src/plugins/i18n/README.md) | screen | Strings as data, ICU messages, compiled per locale; a sub-feature's strings travel with its bundle |
|
|
232
232
|
| [`text`](./src/plugins/text/README.md) | screen | MSDF text, styles, inline tags and icons |
|
|
233
233
|
| [`ui`](./src/plugins/ui/README.md) | screen | Screens in JSX, Yoga layout, popups, styles |
|
|
234
234
|
| [`effects`](./src/plugins/effects/README.md) | opt-in | Particles and filters as components, WGSL and GLSL |
|
|
@@ -258,7 +258,7 @@ createApp({ plugins: [...screen, effectsPlugin, audioPlugin, platformPlugin] });
|
|
|
258
258
|
|
|
259
259
|
### Lint for games
|
|
260
260
|
|
|
261
|
-
`@moku-labs/game/lint` is an oxlint JS plugin, `moku-game`. It checks the engine rules in a game: lazy Pixi and Yoga, no native package in the logic, the editor only in dev files, no module-scope state, determinism, pure rules and JSX keys the project index can follow.
|
|
261
|
+
`@moku-labs/game/lint` is an oxlint JS plugin, `moku-game`. It checks the engine rules in a game: lazy Pixi and Yoga, no native package in the logic, the editor only in dev files, no module-scope state, determinism, pure rules and JSX keys the project index can follow. Four layout rules check a game on the layered layout: the order of the layers, the doors of the features at any depth, the sub-features a feature declares and the suffix of each test file. Eleven rules in all; a flat game lints exactly as in 0.14.12.
|
|
262
262
|
|
|
263
263
|
```json
|
|
264
264
|
// .oxlintrc.json
|
|
@@ -274,12 +274,13 @@ createApp({ plugins: [...screen, effectsPlugin, audioPlugin, platformPlugin] });
|
|
|
274
274
|
"moku-game/static-keys": "error",
|
|
275
275
|
"moku-game/layer-imports": "error",
|
|
276
276
|
"moku-game/feature-door": "error",
|
|
277
|
+
"moku-game/sub-features": "error",
|
|
277
278
|
"moku-game/test-suffix": "error"
|
|
278
279
|
}
|
|
279
280
|
}
|
|
280
281
|
```
|
|
281
282
|
|
|
282
|
-
Each rule takes `{ "files": [...], "ignores": [...] }`. `layer-imports`, `feature-door` and `rules-siblings` also take `root` and `tsconfig`; `test-suffix` takes `root` and `suffixes`. The defaults follow the layout of a game. See [Lint for games](./docs/lint.md).
|
|
283
|
+
Each rule takes `{ "files": [...], "ignores": [...] }`. `layer-imports`, `feature-door`, `sub-features` and `rules-siblings` also take `root` and `tsconfig`; `test-suffix` takes `root` and `suffixes`. The defaults follow the layout of a game. See [Lint for games](./docs/lint.md).
|
|
283
284
|
|
|
284
285
|
## Docs
|
|
285
286
|
|
|
@@ -332,7 +333,7 @@ A save of a logic file reloads the page. What swaps and what reloads: [docs/hot-
|
|
|
332
333
|
|
|
333
334
|
- **Node `>= 24`** and **Bun `>= 1.4.2`**. Use `bun` only.
|
|
334
335
|
- **TypeScript** in strict mode, with `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess`.
|
|
335
|
-
- **`pixi.js` `^8.
|
|
336
|
+
- **`pixi.js` `^8.22.0`** as a peer dependency. `playwright-core` for visual tests, `sharp` for the asset pack, `typescript` for the project index, `@moku-labs/system` for the system shell and `@moku-labs/native` for the native build are optional peers. `yoga-layout` is loaded lazily by `ui`. The string compiler uses `@formatjs/icu-messageformat-parser` at build time only.
|
|
336
337
|
- **[`@moku-labs/core`](https://github.com/moku-labs/core)** is the kernel. **[`@moku-labs/common`](https://github.com/moku-labs/common)** brings `log` and `env`.
|
|
337
338
|
|
|
338
339
|
## License
|
package/dist/app/page.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { m as StartedPage, r as GameConfig, t as AnyGameApp, u as PageOptions } from "../types-
|
|
1
|
+
import { m as StartedPage, r as GameConfig, t as AnyGameApp, u as PageOptions } from "../types-C73hbmxi.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/app/page.d.ts
|
|
4
4
|
/**
|
|
@@ -6,8 +6,11 @@ import { m as StartedPage, r as GameConfig, t as AnyGameApp, u as PageOptions }
|
|
|
6
6
|
* fresh memory save, never on the stored one), builds the system shell when the page has one,
|
|
7
7
|
* picks the save, makes the screen app on `#game` over the device clock and the manifest next to
|
|
8
8
|
* the page, sets `globalThis.game`, `system` and `doors`, follows reduced motion, starts the shell,
|
|
9
|
-
* then the app, and runs the graph. A graph that stops is logged, never thrown. In dev
|
|
10
|
-
*
|
|
9
|
+
* then the app, and runs the graph. A graph that stops is logged, never thrown. In dev and in a
|
|
10
|
+
* debug build it then logs `page:build` (the build id, the engine id, the control profile),
|
|
11
|
+
* starts the agents and shows the overlay as `config.ts` `debug.overlay` asks. Every problem goes
|
|
12
|
+
* to the app's log. On `pagehide` it flushes the save, and stops the app when the page is really
|
|
13
|
+
* left (`persisted` false); a page kept in the back/forward cache keeps its app running.
|
|
11
14
|
*
|
|
12
15
|
* @param game - The default export of the game's `index.ts`.
|
|
13
16
|
* @param config - The default export of the game's `config.ts`.
|
|
@@ -18,6 +21,7 @@ import { m as StartedPage, r as GameConfig, t as AnyGameApp, u as PageOptions }
|
|
|
18
21
|
* @example
|
|
19
22
|
* ```ts
|
|
20
23
|
* // Written by moku-game dev. Do not edit.
|
|
24
|
+
* import "./script.ts";
|
|
21
25
|
* import "./dev.ts";
|
|
22
26
|
* import "./assets-stamp.ts";
|
|
23
27
|
* import { startPage } from "@moku-labs/game/app/page";
|
|
@@ -29,6 +33,7 @@ import { m as StartedPage, r as GameConfig, t as AnyGameApp, u as PageOptions }
|
|
|
29
33
|
* import * as devModule0 from "../features/board/board.dev.ts";
|
|
30
34
|
* import agent0 from "@moku-labs/editor/agent/page";
|
|
31
35
|
*
|
|
36
|
+
* performance.mark("moku:main");
|
|
32
37
|
* await startPage(game, config, {
|
|
33
38
|
* scenarios: { "empty": scenario0, "full": scenario1 },
|
|
34
39
|
* system: systemShellOf({
|
package/dist/app/page.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { r as systemSource, t as memory } from "../memory-CWlqhS7l.mjs";
|
|
2
2
|
import { t as applyTo } from "../drafts-BawpSaVp.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
3
|
+
import { i as isControl } from "../dev-DbHgx-R-.mjs";
|
|
4
|
+
import { n as read, r as watch, t as sources } from "../sources-Cdvb0rOS.mjs";
|
|
5
|
+
import { t as resolveConfig } from "../config-B87aFTW-.mjs";
|
|
5
6
|
//#region src/app/save.ts
|
|
6
7
|
/**
|
|
7
8
|
* @file The saves of the page: `localSave` keeps the player in `localStorage`, `pageSave` picks
|
|
@@ -170,9 +171,9 @@ function pageSave(kind, where) {
|
|
|
170
171
|
* @file The `@moku-labs/game/app/page` entry: `startPage`, the page of a game. The generated
|
|
171
172
|
* `.moku/main.ts` calls it with the game, its config and what the CLI found: scenarios, the system
|
|
172
173
|
* shell, the dev agents and the `.dev` modules. It reaches the shell only through
|
|
173
|
-
* `options.system`, never by an import, so a web-only page bundles no system code.
|
|
174
|
-
*
|
|
175
|
-
* production build drops them.
|
|
174
|
+
* `options.system`, never by an import, so a web-only page bundles no system code. The write door
|
|
175
|
+
* `/control` and the agents sit behind the inline dev or agent guard, the audio journal behind
|
|
176
|
+
* the dev guard, so a production build drops them; a debug build keeps the door and the agents.
|
|
176
177
|
*/
|
|
177
178
|
/** How many started sounds the audio journal of the dev page keeps. */
|
|
178
179
|
const DEV_AUDIO_JOURNAL = 200;
|
|
@@ -252,7 +253,7 @@ function screenOf(game, runtime) {
|
|
|
252
253
|
}
|
|
253
254
|
/**
|
|
254
255
|
* Sets the three handles of the page on `globalThis`: `game` for e2e, `system` for the shell,
|
|
255
|
-
* `doors` for the editor.
|
|
256
|
+
* `doors` for the editor. `/control` is loaded on the dev page and on a debug page.
|
|
256
257
|
*
|
|
257
258
|
* @param app - The screen app.
|
|
258
259
|
* @param shell - The system shell, or `undefined`.
|
|
@@ -261,7 +262,7 @@ function screenOf(game, runtime) {
|
|
|
261
262
|
async function setHandles(app, shell) {
|
|
262
263
|
Reflect.set(globalThis, "game", app);
|
|
263
264
|
Reflect.set(globalThis, "system", shell?.handle);
|
|
264
|
-
const control = typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ ? await import("../control.mjs") : void 0;
|
|
265
|
+
const control = typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? await import("../control.mjs") : void 0;
|
|
265
266
|
Reflect.set(globalThis, "doors", {
|
|
266
267
|
read,
|
|
267
268
|
watch,
|
|
@@ -284,8 +285,27 @@ function followReducedMotion(app) {
|
|
|
284
285
|
reducedMotion.addEventListener("change", (event) => app.anim.setReducedMotion(event.matches));
|
|
285
286
|
}
|
|
286
287
|
/**
|
|
287
|
-
*
|
|
288
|
-
*
|
|
288
|
+
* Follows the page out. On `pagehide` the save is flushed at once: the browser waits for no
|
|
289
|
+
* promise there, so the write starts now, and the failure, if any, is already in the log. A page
|
|
290
|
+
* going into the back/forward cache (`persisted`) may come back with its app running, so only a
|
|
291
|
+
* page that is really left stops its app, once.
|
|
292
|
+
*
|
|
293
|
+
* @param app - The started screen app.
|
|
294
|
+
*/
|
|
295
|
+
function followPageHide(app) {
|
|
296
|
+
const listener = (event) => {
|
|
297
|
+
app.model.store.flush().catch(() => {});
|
|
298
|
+
if (event.persisted) return;
|
|
299
|
+
removeEventListener("pagehide", listener);
|
|
300
|
+
app.stop().catch((error) => {
|
|
301
|
+
app.log.error("[game] The page did not stop cleanly.", void 0, toError(error));
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
addEventListener("pagehide", listener);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Starts the agents, in order, after the app, in dev and in a debug build: each gets the app, the
|
|
308
|
+
* page title and the game's `.dev` modules. A throwing agent is logged and the page keeps running.
|
|
289
309
|
*
|
|
290
310
|
* @param app - The running app.
|
|
291
311
|
* @param settings - The resolved config.
|
|
@@ -304,12 +324,26 @@ async function startAgents(app, settings, options) {
|
|
|
304
324
|
}
|
|
305
325
|
}
|
|
306
326
|
/**
|
|
327
|
+
* Shows the editor's overlay the way `config.ts` `debug.overlay` asks, once the agents started: the agent's channel
|
|
328
|
+
* exists only then.
|
|
329
|
+
*
|
|
330
|
+
* @param app - The running app.
|
|
331
|
+
* @param overlay - `"closed"` makes no call, `"pill"` opens the pill, `"open"` the card.
|
|
332
|
+
*/
|
|
333
|
+
function openStartOverlay(app, overlay) {
|
|
334
|
+
if (overlay === "closed") return;
|
|
335
|
+
app.debug.overlay.open({ mode: overlay === "pill" ? "pill" : "card" });
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
307
338
|
* Runs a game on its page: resolves `config.ts`, picks the start (a `?player=` scenario runs on a
|
|
308
339
|
* fresh memory save, never on the stored one), builds the system shell when the page has one,
|
|
309
340
|
* picks the save, makes the screen app on `#game` over the device clock and the manifest next to
|
|
310
341
|
* the page, sets `globalThis.game`, `system` and `doors`, follows reduced motion, starts the shell,
|
|
311
|
-
* then the app, and runs the graph. A graph that stops is logged, never thrown. In dev
|
|
312
|
-
*
|
|
342
|
+
* then the app, and runs the graph. A graph that stops is logged, never thrown. In dev and in a
|
|
343
|
+
* debug build it then logs `page:build` (the build id, the engine id, the control profile),
|
|
344
|
+
* starts the agents and shows the overlay as `config.ts` `debug.overlay` asks. Every problem goes
|
|
345
|
+
* to the app's log. On `pagehide` it flushes the save, and stops the app when the page is really
|
|
346
|
+
* left (`persisted` false); a page kept in the back/forward cache keeps its app running.
|
|
313
347
|
*
|
|
314
348
|
* @param game - The default export of the game's `index.ts`.
|
|
315
349
|
* @param config - The default export of the game's `config.ts`.
|
|
@@ -320,6 +354,7 @@ async function startAgents(app, settings, options) {
|
|
|
320
354
|
* @example
|
|
321
355
|
* ```ts
|
|
322
356
|
* // Written by moku-game dev. Do not edit.
|
|
357
|
+
* import "./script.ts";
|
|
323
358
|
* import "./dev.ts";
|
|
324
359
|
* import "./assets-stamp.ts";
|
|
325
360
|
* import { startPage } from "@moku-labs/game/app/page";
|
|
@@ -331,6 +366,7 @@ async function startAgents(app, settings, options) {
|
|
|
331
366
|
* import * as devModule0 from "../features/board/board.dev.ts";
|
|
332
367
|
* import agent0 from "@moku-labs/editor/agent/page";
|
|
333
368
|
*
|
|
369
|
+
* performance.mark("moku:main");
|
|
334
370
|
* await startPage(game, config, {
|
|
335
371
|
* scenarios: { "empty": scenario0, "full": scenario1 },
|
|
336
372
|
* system: systemShellOf({
|
|
@@ -370,7 +406,16 @@ async function startPage(game, config, options = {}) {
|
|
|
370
406
|
app.flow.run().catch((error) => {
|
|
371
407
|
app.log.error("[game] The graph stopped.", void 0, toError(error));
|
|
372
408
|
});
|
|
373
|
-
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__
|
|
409
|
+
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) {
|
|
410
|
+
app.log.info("page:build", {
|
|
411
|
+
build: globalThis.__MOKU_GAME_BUILD__ ?? "dev",
|
|
412
|
+
engine: globalThis.__MOKU_GAME_ENGINE__ ?? null,
|
|
413
|
+
control: isControl() ? "full" : "read"
|
|
414
|
+
});
|
|
415
|
+
await startAgents(app, settings, options);
|
|
416
|
+
openStartOverlay(app, settings.debug.overlay);
|
|
417
|
+
}
|
|
418
|
+
followPageHide(app);
|
|
374
419
|
return {
|
|
375
420
|
app,
|
|
376
421
|
system: shell?.handle
|
package/dist/app/system.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { _ as SystemShellFactory, g as SystemName, h as SystemModules } from "../types-
|
|
1
|
+
import { hr as PlayerStateProvider } from "../types-BGcda5Le.mjs";
|
|
2
|
+
import { ft as PlatformProvider } from "../index-DedbED3T.mjs";
|
|
3
|
+
import { _ as SystemShellFactory, g as SystemName, h as SystemModules } from "../types-C73hbmxi.mjs";
|
|
4
4
|
import { Back, Haptics, KeepAwake, Lifecycle, Store } from "@moku-labs/system";
|
|
5
5
|
|
|
6
6
|
//#region src/app/system.d.ts
|
|
@@ -126,11 +126,13 @@ declare function storeSave(store: StoreSlice, key: string, report: (problem: str
|
|
|
126
126
|
* @example
|
|
127
127
|
* ```ts
|
|
128
128
|
* // The in-memory .moku/build/main.ts of moku-game build, for a game whose config.ts names back.
|
|
129
|
+
* import "./script.ts";
|
|
129
130
|
* import { startPage } from "@moku-labs/game/app/page";
|
|
130
131
|
* import { systemShellOf } from "@moku-labs/game/app/system";
|
|
131
132
|
* import game from "../../index.ts";
|
|
132
133
|
* import config from "../../config.ts";
|
|
133
134
|
*
|
|
135
|
+
* performance.mark("moku:main");
|
|
134
136
|
* await startPage(game, config, {
|
|
135
137
|
* system: systemShellOf({
|
|
136
138
|
* system: () => import("@moku-labs/system"),
|
package/dist/app/system.mjs
CHANGED
|
@@ -300,11 +300,13 @@ function storeSave(store, key, report) {
|
|
|
300
300
|
* @example
|
|
301
301
|
* ```ts
|
|
302
302
|
* // The in-memory .moku/build/main.ts of moku-game build, for a game whose config.ts names back.
|
|
303
|
+
* import "./script.ts";
|
|
303
304
|
* import { startPage } from "@moku-labs/game/app/page";
|
|
304
305
|
* import { systemShellOf } from "@moku-labs/game/app/system";
|
|
305
306
|
* import game from "../../index.ts";
|
|
306
307
|
* import config from "../../config.ts";
|
|
307
308
|
*
|
|
309
|
+
* performance.mark("moku:main");
|
|
308
310
|
* await startPage(game, config, {
|
|
309
311
|
* system: systemShellOf({
|
|
310
312
|
* system: () => import("@moku-labs/system"),
|
package/dist/app.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ur as Json } from "./types-BGcda5Le.mjs";
|
|
2
|
+
import { V as StringTable } from "./types-7GXcXxxv.mjs";
|
|
3
|
+
import { a as GameHandle, c as MemoryProvider, d as SaveKind, f as Scenario, g as SystemName, i as GameDefinition, l as PageAgent, n as GameApp, o as GamePluginConfigs, p as ScreenSeams, r as GameConfig, s as HeadlessSeams, v as WithGameState } from "./types-C73hbmxi.mjs";
|
|
3
4
|
import { AnyPluginInstance } from "@moku-labs/core";
|
|
4
5
|
|
|
5
6
|
//#region src/app/define.d.ts
|
|
@@ -15,23 +16,30 @@ declare const startMoment = 1000000;
|
|
|
15
16
|
/**
|
|
16
17
|
* Turns a game's one data object into the game: `game.headless()` for logic tests and
|
|
17
18
|
* `game.screen()` for the page and the screen tests. Every type comes from the object: the plugin
|
|
18
|
-
* tuples, the player and the
|
|
19
|
+
* tuples, the player, the session and, through `strings: type<Strings>()`, the string keys. Checks the definition at once, so an error points at
|
|
19
20
|
* `index.ts`.
|
|
20
21
|
*
|
|
21
22
|
* @param definition - The game: its flow, starting state, features, plugins and plugin configs.
|
|
22
23
|
* @returns The game, with `headless()` and `screen()`; no app is created yet.
|
|
23
|
-
* @throws {Error} When `flow` is missing, or an entry of `shared`, `features` or `headless.features`
|
|
24
|
+
* @throws {Error} When `flow` is missing, or an entry of `shared`, `features` or `headless.features`
|
|
25
|
+
* is not a feature, at any depth of the tree.
|
|
26
|
+
* @throws {Error} When a feature name is listed twice in `features` or in `headless.features`: the
|
|
27
|
+
* game and a parent both list it, or two parents declare it.
|
|
28
|
+
* @throws {Error} When `shared` declares sub-features.
|
|
29
|
+
* @throws {Error} When `referenceSide` is not a finite number above 0, or `orientation` is neither
|
|
30
|
+
* `"portrait"` nor `"landscape"`.
|
|
24
31
|
* @example
|
|
25
32
|
* ```ts
|
|
26
33
|
* // index.ts of a game: the whole game, no createApp call. Its tests import it as `game`.
|
|
27
34
|
* export default defineGameApp({
|
|
28
|
-
* flow: mainFlow, safeNode: "home", player: startingPlayer, session: startingSession,
|
|
35
|
+
* flow: mainFlow, safeNode: "home", player: startingPlayer, session: startingSession, strings: type<Strings>(),
|
|
29
36
|
* shared: sharedFeature, features: [homeFeature, boardFeature, rewardFeature], plugins: [loadingPlugin],
|
|
30
37
|
* headless: { features: [rewardFeature] }, pluginConfigs: { ui: { tapTargetPt: 48 } }
|
|
31
38
|
* });
|
|
32
39
|
* game.headless({ seed: 7 }).app.flow.state().running; // false: the test starts it
|
|
40
|
+
* game.screen().app.i18n.isKey("home.play"); // a guard to the game's keys: true for a key of Strings
|
|
33
41
|
* ```
|
|
34
42
|
*/
|
|
35
|
-
declare function defineGameApp<const Plugins extends readonly AnyPluginInstance[] = readonly [], const HeadlessPlugins extends readonly AnyPluginInstance[] = readonly [], Player extends Json = Json, Session extends Json = Json>(definition: GameDefinition<Plugins, HeadlessPlugins, Player, Session>): GameApp<Plugins, HeadlessPlugins, Player, Session>;
|
|
43
|
+
declare function defineGameApp<const Plugins extends readonly AnyPluginInstance[] = readonly [], const HeadlessPlugins extends readonly AnyPluginInstance[] = readonly [], Player extends Json = Json, Session extends Json = Json, Strings extends StringTable = StringTable>(definition: GameDefinition<Plugins, HeadlessPlugins, Player, Session, Strings>): GameApp<Plugins, HeadlessPlugins, Player, Session, Strings>;
|
|
36
44
|
//#endregion
|
|
37
|
-
export { type GameApp, type GameConfig, type GameDefinition, type GameHandle, type GamePluginConfigs, type HeadlessSeams, type MemoryProvider, type PageAgent, type SaveKind, type Scenario, type ScreenSeams, type SystemName, defineGameApp, startMoment };
|
|
45
|
+
export { type GameApp, type GameConfig, type GameDefinition, type GameHandle, type GamePluginConfigs, type HeadlessSeams, type MemoryProvider, type PageAgent, type SaveKind, type Scenario, type ScreenSeams, type SystemName, type WithGameState, defineGameApp, startMoment };
|
package/dist/app.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Dt as effectsPlugin, K as platformPlugin, Rt as audioPlugin, i as screen, p as flattenFeatures, t as createApp } from "./src-BEbJ_sSx.mjs";
|
|
2
2
|
import { t as memory } from "./memory-CWlqhS7l.mjs";
|
|
3
|
-
import { t as fakeClock } from "./fake-
|
|
3
|
+
import { n as isFakeClock, t as fakeClock } from "./fake-6mazmHK3.mjs";
|
|
4
4
|
//#region src/app/define.ts
|
|
5
5
|
/**
|
|
6
6
|
* The moment the default fake clock starts at, in epoch milliseconds. A test stamps times from it.
|
|
@@ -13,12 +13,13 @@ import { t as fakeClock } from "./fake-MpzwZhIZ.mjs";
|
|
|
13
13
|
const startMoment = 1e6;
|
|
14
14
|
/** The rng seed of a new save when neither the definition nor the seam names one. */
|
|
15
15
|
const defaultSeed = 42;
|
|
16
|
-
/** The plugins every app has: the engine's core
|
|
16
|
+
/** The plugins every app has: the engine's core six and the core plugins `log` and `env`. */
|
|
17
17
|
const corePluginNames = [
|
|
18
18
|
"time",
|
|
19
19
|
"lifecycle",
|
|
20
20
|
"model",
|
|
21
21
|
"clock",
|
|
22
|
+
"debug",
|
|
22
23
|
"flow",
|
|
23
24
|
"log",
|
|
24
25
|
"env"
|
|
@@ -37,27 +38,108 @@ function notAFeature(path) {
|
|
|
37
38
|
return /* @__PURE__ */ new Error(`[game] defineGameApp: ${path} is not a feature.\n Make it with defineFeature(name, description).`);
|
|
38
39
|
}
|
|
39
40
|
/**
|
|
40
|
-
*
|
|
41
|
-
* says so; a game in JavaScript, or one that casts, is caught here, at the definition.
|
|
41
|
+
* Builds the error of a feature name met twice in one list.
|
|
42
42
|
*
|
|
43
|
-
* @param
|
|
44
|
-
* @param
|
|
45
|
-
* @
|
|
43
|
+
* @param name - The feature name.
|
|
44
|
+
* @param first - The path met first in the walk.
|
|
45
|
+
* @param second - The path met second.
|
|
46
|
+
* @returns The error.
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* listedTwice("ep01", "features[1].features[1]", "features[2]").message; // "[game] defineGameApp: the feature \"ep01\" is listed twice: features[1].features[1] and features[2].\n List a sub-feature only in the features of its parent."
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
function listedTwice(name, first, second) {
|
|
53
|
+
return /* @__PURE__ */ new Error(`[game] defineGameApp: the feature "${name}" is listed twice: ${first} and ${second}.\n List a sub-feature only in the features of its parent.`);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Lists every entry of a feature list and of the sub-features under it, depth first, a parent
|
|
57
|
+
* before its children, each with its path. An entry without a `features` list has no children.
|
|
58
|
+
*
|
|
59
|
+
* @param list - A feature list, or the `features` of one entry.
|
|
60
|
+
* @param path - Where the list is: `features`, `features[1].features`.
|
|
61
|
+
* @returns The entries with their paths.
|
|
46
62
|
*/
|
|
47
|
-
function
|
|
48
|
-
|
|
63
|
+
function walkTree(list, path) {
|
|
64
|
+
return list.flatMap((entry, index) => {
|
|
65
|
+
const at = `${path}[${index}]`;
|
|
66
|
+
return [{
|
|
67
|
+
path: at,
|
|
68
|
+
entry
|
|
69
|
+
}, ...walkTree(entry?.features ?? [], `${at}.features`)];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Throws for the first entry of a feature tree, at any depth, that `defineFeature` did not make,
|
|
74
|
+
* and for a feature name met twice in the tree. The type already rules out the first; a game in
|
|
75
|
+
* JavaScript, or one that casts, is caught here, at the definition.
|
|
76
|
+
*
|
|
77
|
+
* @param list - `features` or `headless.features`.
|
|
78
|
+
* @param root - The name of the list in the paths.
|
|
79
|
+
* @throws {Error} When an entry has no `logicOnly`, or a name is listed twice.
|
|
80
|
+
*/
|
|
81
|
+
function checkTree(list, root) {
|
|
82
|
+
const firstPaths = /* @__PURE__ */ new Map();
|
|
83
|
+
for (const { path, entry } of walkTree(list, root)) {
|
|
84
|
+
if (entry?.logicOnly === void 0) throw notAFeature(path);
|
|
85
|
+
const first = firstPaths.get(entry.name);
|
|
86
|
+
if (first !== void 0) throw listedTwice(entry.name, first, path);
|
|
87
|
+
firstPaths.set(entry.name, path);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Throws for a `shared` that is not a feature, or that declares sub-features: the shared layer is
|
|
92
|
+
* composed alone, so its children would never be.
|
|
93
|
+
*
|
|
94
|
+
* @param shared - The shared layer of the definition.
|
|
95
|
+
* @throws {Error} When `shared` has no `logicOnly`, or its `features` is not empty.
|
|
96
|
+
*/
|
|
97
|
+
function checkShared(shared) {
|
|
98
|
+
if (shared.logicOnly === void 0) throw notAFeature("shared");
|
|
99
|
+
if ((shared.features ?? []).length > 0) throw new Error("[game] defineGameApp: shared declares sub-features.\n The shared layer has none: declare them in a feature of features.");
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Writes a value after `got` in an error: a string in quotes, anything else as it prints.
|
|
103
|
+
*
|
|
104
|
+
* @param value - The value the definition holds.
|
|
105
|
+
* @returns The value as the error shows it.
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* shown("390"); // "\"390\""
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
function shown(value) {
|
|
112
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Throws for a design frame the engine would draw wrong: a `referenceSide` that is not a finite
|
|
116
|
+
* number above 0 (the fit answers scale 1, CSS-pixel size), and an `orientation` other than
|
|
117
|
+
* `"portrait"` or `"landscape"` (most often `page.orientation`'s `"any"`). `referenceLong: 0` is
|
|
118
|
+
* a documented value and is not checked.
|
|
119
|
+
*
|
|
120
|
+
* @param definition - The game's definition.
|
|
121
|
+
* @throws {Error} When `referenceSide` or `orientation` is set to a value the engine cannot lay out.
|
|
122
|
+
*/
|
|
123
|
+
function checkFrame(definition) {
|
|
124
|
+
const side = definition.referenceSide;
|
|
125
|
+
const orientation = definition.orientation;
|
|
126
|
+
if (side !== void 0 && !(typeof side === "number" && Number.isFinite(side) && side > 0)) throw new Error(`[game] defineGameApp: referenceSide must be a finite number above 0, got ${shown(side)}.\n Pass the short side of the design frame in reference units: defineGameApp({ referenceSide: 390, ... }).`);
|
|
127
|
+
if (orientation !== void 0 && orientation !== "portrait" && orientation !== "landscape") throw new Error(`[game] defineGameApp: orientation must be "portrait" or "landscape", got ${shown(orientation)}.\n The layout is designed for one orientation. A native build that turns with the device sets page.orientation: "any" in config.ts.`);
|
|
49
128
|
}
|
|
50
129
|
/**
|
|
51
130
|
* Checks what TypeScript cannot stop: a definition from JavaScript, or one that casts.
|
|
52
131
|
*
|
|
53
132
|
* @param definition - The game's definition.
|
|
54
|
-
* @throws {Error} When `flow` is missing,
|
|
133
|
+
* @throws {Error} When `flow` is missing, a feature entry at any depth is not a feature, a feature
|
|
134
|
+
* name is listed twice in one list, `shared` declares sub-features, or the design frame is one
|
|
135
|
+
* the engine cannot lay out.
|
|
55
136
|
*/
|
|
56
137
|
function checkDefinition(definition) {
|
|
57
138
|
if (definition.flow === void 0) throw new Error("[game] defineGameApp needs flow.\n Pass the main flow: defineGameApp({ flow: mainFlow, ... }).");
|
|
58
|
-
if (definition.shared !== void 0)
|
|
59
|
-
|
|
60
|
-
|
|
139
|
+
if (definition.shared !== void 0) checkShared(definition.shared);
|
|
140
|
+
checkTree(definition.features ?? [], "features");
|
|
141
|
+
checkTree(definition.headless?.features ?? [], "headless.features");
|
|
142
|
+
checkFrame(definition);
|
|
61
143
|
}
|
|
62
144
|
/**
|
|
63
145
|
* Copies the keys of a seam whose value is set, so a seam left out never erases a game value.
|
|
@@ -75,7 +157,9 @@ function definedKeys(seam) {
|
|
|
75
157
|
}
|
|
76
158
|
/**
|
|
77
159
|
* Merges the seam-owned keys over the game's plugin configs, one plugin at a time: a key the game
|
|
78
|
-
* owns stays, a key the shell owns is written. With `screen` the screen keys are written too.
|
|
160
|
+
* owns stays, a key the shell owns is written. With `screen` the screen keys are written too. The
|
|
161
|
+
* game's adaptive frame rate stands only in a screen app on a device clock (the page): a headless
|
|
162
|
+
* app and a screen app on a fake clock never adapt.
|
|
79
163
|
*
|
|
80
164
|
* @param definition - The game's definition.
|
|
81
165
|
* @param seams - The seams of this call.
|
|
@@ -99,6 +183,10 @@ function mergeConfigs(definition, seams, runtime, withScreen) {
|
|
|
99
183
|
...game.flow,
|
|
100
184
|
mainFlow: definition.flow,
|
|
101
185
|
safeNode: definition.safeNode
|
|
186
|
+
},
|
|
187
|
+
time: {
|
|
188
|
+
...game.time,
|
|
189
|
+
...withScreen && !isFakeClock(runtime.clock) ? {} : { adaptive: false }
|
|
102
190
|
}
|
|
103
191
|
};
|
|
104
192
|
if (!withScreen) return logic;
|
|
@@ -149,8 +237,10 @@ function runtimeOf(seams) {
|
|
|
149
237
|
};
|
|
150
238
|
}
|
|
151
239
|
/**
|
|
152
|
-
* Creates one app over the engine: the plugins in order, the merged configs, and
|
|
153
|
-
* only when the definition sets
|
|
240
|
+
* Creates one app over the engine: the plugins in order, the merged configs, and the frame keys
|
|
241
|
+
* `orientation`, `referenceSide` and `referenceLong` only when the definition sets them. A
|
|
242
|
+
* definition that sets none passes no `config` key, so the core defaults stay the one place of
|
|
243
|
+
* truth.
|
|
154
244
|
*
|
|
155
245
|
* @param definition - The game's definition.
|
|
156
246
|
* @param plugins - The plugins beyond the core, in order.
|
|
@@ -158,16 +248,22 @@ function runtimeOf(seams) {
|
|
|
158
248
|
* @returns The app, not started.
|
|
159
249
|
*/
|
|
160
250
|
function appOf(definition, plugins, pluginConfigs) {
|
|
251
|
+
const frame = definedKeys({
|
|
252
|
+
orientation: definition.orientation,
|
|
253
|
+
referenceSide: definition.referenceSide,
|
|
254
|
+
referenceLong: definition.referenceLong
|
|
255
|
+
});
|
|
161
256
|
return createApp({
|
|
162
257
|
plugins: [...plugins],
|
|
163
258
|
pluginConfigs,
|
|
164
|
-
...
|
|
259
|
+
...Object.keys(frame).length === 0 ? {} : { config: frame }
|
|
165
260
|
});
|
|
166
261
|
}
|
|
167
262
|
/**
|
|
168
|
-
* Makes the headless app of a definition: the logic of `headless.features
|
|
169
|
-
* `headless.plugins`. Without a `headless.features` list it takes `features`;
|
|
170
|
-
* none. `shared` is not composed.
|
|
263
|
+
* Makes the headless app of a definition: the logic of `headless.features` and of their
|
|
264
|
+
* sub-features, then `headless.plugins`. Without a `headless.features` list it takes `features`;
|
|
265
|
+
* an empty list means none. `shared` is not composed. The `logicOnly` is taken before flattening:
|
|
266
|
+
* a parent's `logicOnly` carries its children's.
|
|
171
267
|
*
|
|
172
268
|
* @param definition - The game's definition.
|
|
173
269
|
* @param seams - The seams of this call.
|
|
@@ -175,7 +271,7 @@ function appOf(definition, plugins, pluginConfigs) {
|
|
|
175
271
|
*/
|
|
176
272
|
function headlessOf(definition, seams) {
|
|
177
273
|
const runtime = runtimeOf(seams);
|
|
178
|
-
const plugins = [...(definition.headless?.features ?? definition.features ?? []).map((feature) => feature.logicOnly), ...definition.headless?.plugins ?? []];
|
|
274
|
+
const plugins = [...flattenFeatures((definition.headless?.features ?? definition.features ?? []).map((feature) => feature.logicOnly)), ...definition.headless?.plugins ?? []];
|
|
179
275
|
return {
|
|
180
276
|
app: appOf(definition, plugins, composedOnly(mergeConfigs(definition, seams, runtime, false), plugins)),
|
|
181
277
|
...runtime
|
|
@@ -183,7 +279,8 @@ function headlessOf(definition, seams) {
|
|
|
183
279
|
}
|
|
184
280
|
/**
|
|
185
281
|
* Makes the screen app of a definition: the screen set, `audio`, `effects`, `platform`, then
|
|
186
|
-
* `shared`, the features
|
|
282
|
+
* `shared`, the features with their sub-features (depth first, a parent before its children) and
|
|
283
|
+
* the game's plugins.
|
|
187
284
|
*
|
|
188
285
|
* @param definition - The game's definition.
|
|
189
286
|
* @param seams - The seams of this call.
|
|
@@ -198,7 +295,7 @@ function screenOf(definition, seams) {
|
|
|
198
295
|
effectsPlugin,
|
|
199
296
|
platformPlugin,
|
|
200
297
|
...definition.shared === void 0 ? [] : [definition.shared],
|
|
201
|
-
...definition.features ?? [],
|
|
298
|
+
...flattenFeatures(definition.features ?? []),
|
|
202
299
|
...definition.plugins ?? []
|
|
203
300
|
], mergeConfigs(definition, seams, runtime, true)),
|
|
204
301
|
...runtime
|
|
@@ -207,21 +304,28 @@ function screenOf(definition, seams) {
|
|
|
207
304
|
/**
|
|
208
305
|
* Turns a game's one data object into the game: `game.headless()` for logic tests and
|
|
209
306
|
* `game.screen()` for the page and the screen tests. Every type comes from the object: the plugin
|
|
210
|
-
* tuples, the player and the
|
|
307
|
+
* tuples, the player, the session and, through `strings: type<Strings>()`, the string keys. Checks the definition at once, so an error points at
|
|
211
308
|
* `index.ts`.
|
|
212
309
|
*
|
|
213
310
|
* @param definition - The game: its flow, starting state, features, plugins and plugin configs.
|
|
214
311
|
* @returns The game, with `headless()` and `screen()`; no app is created yet.
|
|
215
|
-
* @throws {Error} When `flow` is missing, or an entry of `shared`, `features` or `headless.features`
|
|
312
|
+
* @throws {Error} When `flow` is missing, or an entry of `shared`, `features` or `headless.features`
|
|
313
|
+
* is not a feature, at any depth of the tree.
|
|
314
|
+
* @throws {Error} When a feature name is listed twice in `features` or in `headless.features`: the
|
|
315
|
+
* game and a parent both list it, or two parents declare it.
|
|
316
|
+
* @throws {Error} When `shared` declares sub-features.
|
|
317
|
+
* @throws {Error} When `referenceSide` is not a finite number above 0, or `orientation` is neither
|
|
318
|
+
* `"portrait"` nor `"landscape"`.
|
|
216
319
|
* @example
|
|
217
320
|
* ```ts
|
|
218
321
|
* // index.ts of a game: the whole game, no createApp call. Its tests import it as `game`.
|
|
219
322
|
* export default defineGameApp({
|
|
220
|
-
* flow: mainFlow, safeNode: "home", player: startingPlayer, session: startingSession,
|
|
323
|
+
* flow: mainFlow, safeNode: "home", player: startingPlayer, session: startingSession, strings: type<Strings>(),
|
|
221
324
|
* shared: sharedFeature, features: [homeFeature, boardFeature, rewardFeature], plugins: [loadingPlugin],
|
|
222
325
|
* headless: { features: [rewardFeature] }, pluginConfigs: { ui: { tapTargetPt: 48 } }
|
|
223
326
|
* });
|
|
224
327
|
* game.headless({ seed: 7 }).app.flow.state().running; // false: the test starts it
|
|
328
|
+
* game.screen().app.i18n.isKey("home.play"); // a guard to the game's keys: true for a key of Strings
|
|
225
329
|
* ```
|
|
226
330
|
*/
|
|
227
331
|
function defineGameApp(definition) {
|