@moku-labs/game 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -22
- package/bin/moku-game.mjs +4 -0
- package/dist/app/page.d.mts +44 -0
- package/dist/app/page.mjs +379 -0
- package/dist/app/system.d.mts +144 -0
- package/dist/app/system.mjs +329 -0
- package/dist/app.d.mts +37 -0
- package/dist/app.mjs +235 -0
- package/dist/assets-BHwTLVta.mjs +7391 -0
- package/dist/assets.d.mts +1 -1
- package/dist/assets.mjs +1 -7390
- package/dist/cli.d.mts +70 -0
- package/dist/cli.mjs +1685 -0
- package/dist/{commands-DuwCxK5t.d.mts → commands-C4QlqijJ.d.mts} +5 -5
- package/dist/config-CdARrrhU.mjs +81 -0
- package/dist/control.d.mts +11 -10
- package/dist/control.mjs +3 -3
- package/dist/{define-B6pHpdBi.d.mts → define-CCc0ozKe.d.mts} +2 -2
- package/dist/{dev-wKLgOw7c.mjs → dev-Cc50Dk7H.mjs} +1 -1
- package/dist/drafts-BawpSaVp.mjs +156 -0
- package/dist/fake-MpzwZhIZ.mjs +87 -0
- package/dist/{headless-SCQXJxQf.d.mts → headless-G4nuZ-xz.d.mts} +1 -1
- package/dist/hot.d.mts +4 -6
- package/dist/hot.mjs +8 -9
- package/dist/{index-BO9kZzY3.d.mts → index-8TiJQjQA.d.mts} +30 -17
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +1 -1
- package/dist/inspect-la_KkjCS.mjs +1 -0
- package/dist/inspect.d.mts +7 -7
- package/dist/inspect.mjs +3 -2
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/lint.mjs +10 -6
- package/dist/memory-CWlqhS7l.mjs +136 -0
- package/dist/memory-DwDhfFWK.d.mts +50 -0
- package/dist/project.mjs +3 -3
- package/dist/{run-CofJEdYx.mjs → run-wYty_Lwo.mjs} +2 -2
- package/dist/{runtime-CdNJ0UBm.d.mts → runtime-DOBYM6g4.d.mts} +1 -1
- package/dist/{input-z_35Jwfn.mjs → session-DgukhR_7.mjs} +20 -20
- package/dist/{sources-DNnzyCYf.mjs → sources-Duzj6lhV.mjs} +2 -2
- package/dist/{src-536arXjV.mjs → src-BSbtHZWk.mjs} +35 -300
- package/dist/testing.d.mts +4 -50
- package/dist/testing.mjs +3 -87
- package/dist/{types-VZVWwqCg.d.mts → types-BM4shh8K.d.mts} +259 -259
- package/dist/{types-xXfyk56Q.d.mts → types-BUwDHKXm.d.mts} +1377 -1377
- package/dist/types-Ca_z_4tQ.d.mts +586 -0
- package/dist/{types-CQueODzo.d.mts → types-CefgYzFt.d.mts} +2 -2
- package/dist/{types-DUMoYPYe.d.mts → types-DAdwEJMp.d.mts} +2 -2
- package/dist/{types-B0v5VVa3.d.mts → types-ZQLYXo5X.d.mts} +304 -304
- package/dist/visual.d.mts +6 -6
- package/dist/visual.mjs +5 -5
- package/llms.txt +120 -43
- package/package.json +30 -2
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import { Dt as AnyFlow, Ht as ClockSource, Jt as Config, Wt as FakeClock, Xt as Json, a as Config$3, g as Config$2, ht as FeaturePlugin, n as AssetsIo, pt as Config$1, tn as PlayerStateProvider, u as Manifest, v as PixiModule } from "./types-ZQLYXo5X.mjs";
|
|
2
|
+
import { o as AudioContextLike, s as Config$4 } from "./types-BM4shh8K.mjs";
|
|
3
|
+
import { ht as audioPlugin, i as screen, it as platformPlugin, ot as PlatformProvider, pt as effectsPlugin, t as createApp } from "./index-8TiJQjQA.mjs";
|
|
4
|
+
import { t as memory } from "./memory-DwDhfFWK.mjs";
|
|
5
|
+
import { AnyPluginInstance } from "@moku-labs/core";
|
|
6
|
+
|
|
7
|
+
//#region src/app/types.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* The name of a `@moku-labs/system` plugin the game shell wires, as the system package names it.
|
|
10
|
+
* Only the five a game shell uses.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const wanted: SystemName[] = ["lifecycle", "back", "haptics", "keepAwake"];
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
type SystemName = "lifecycle" | "back" | "haptics" | "keepAwake" | "store";
|
|
18
|
+
/**
|
|
19
|
+
* Where the player's save lives: in memory for this page only, in `localStorage`, or in the
|
|
20
|
+
* system store (idb on the web, the Tauri store in the native shell).
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const save: SaveKind = "local"; // progress survives a reload of the page
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
type SaveKind = "memory" | "local" | "store";
|
|
28
|
+
/**
|
|
29
|
+
* What `config.ts` of a game holds: plain data, no call. The page, the native build and the shell
|
|
30
|
+
* read it; the game logic never does.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* // config.ts of a game
|
|
35
|
+
* import type { GameConfig } from "@moku-labs/game/app";
|
|
36
|
+
* export default {
|
|
37
|
+
* page: { title: "Лесной городок", lang: "ru", background: "#10161d", orientation: "portrait", icons: { favicon: "assets/icon.png" } },
|
|
38
|
+
* native: { name: "Лесной городок", identifier: "com.mokulabs.timber", icon: "assets/icon.png" },
|
|
39
|
+
* system: ["lifecycle", "back", "haptics", "keepAwake"],
|
|
40
|
+
* save: "memory",
|
|
41
|
+
* assets: { layers: { shared: "ui" } }
|
|
42
|
+
* } satisfies GameConfig;
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
type GameConfig = {
|
|
46
|
+
/** The HTML page the game runs in. */page: {
|
|
47
|
+
/** The page title. Required, never empty. */title: string; /** The `lang` of the page. Default `"en"`. */
|
|
48
|
+
lang?: string; /** CSS colour of the page behind the canvas, also the native window. Default `"#000000"`. */
|
|
49
|
+
background?: string; /** The orientation the native build locks. Default `"portrait"`. */
|
|
50
|
+
orientation?: "portrait" | "landscape" | "any"; /** Icon files, relative to the game folder. None by default. */
|
|
51
|
+
icons?: {
|
|
52
|
+
favicon?: string;
|
|
53
|
+
appleTouch?: string;
|
|
54
|
+
}; /** Raw tags written into `<head>`, verbatim, in order. None by default. */
|
|
55
|
+
head?: readonly string[];
|
|
56
|
+
}; /** The native app. Left out, the game has no native build. */
|
|
57
|
+
native?: {
|
|
58
|
+
/** The app name under the icon. */name: string; /** The bundle identifier, reverse DNS. */
|
|
59
|
+
identifier: string; /** A 1024 px PNG, relative to the game folder. A placeholder by default. */
|
|
60
|
+
icon?: string; /** The native targets a build makes. The target of the command by default. */
|
|
61
|
+
targets?: readonly ("ios" | "android" | "macos")[];
|
|
62
|
+
}; /** The system plugins the shell wires. Default `[]`, a web page with no shell. */
|
|
63
|
+
system?: readonly SystemName[]; /** Where the save lives. Default `"memory"`. */
|
|
64
|
+
save?: SaveKind; /** The asset build. Default: no layers. */
|
|
65
|
+
assets?: {
|
|
66
|
+
/** Asset layers by name: `{ shared: "ui" }` packs the shared layer from `features/ui`. */layers?: Readonly<Record<string, string>>;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* `GameConfig` with every default filled, as `resolveConfig` returns it. The page, the CLI and the
|
|
71
|
+
* shell read this one, never the raw config.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const settings: ResolvedGameConfig = {
|
|
76
|
+
* page: { title: "mini-game", lang: "en", background: "#000000", orientation: "portrait", icons: {}, head: [] },
|
|
77
|
+
* native: undefined, system: [], save: "memory", assets: { layers: {} }
|
|
78
|
+
* };
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
type ResolvedGameConfig = {
|
|
82
|
+
/** The page, every field set. */page: Required<GameConfig["page"]>; /** The native app, or `undefined` for a game with no native build. */
|
|
83
|
+
native: GameConfig["native"]; /** The system plugins the shell wires. */
|
|
84
|
+
system: readonly SystemName[]; /** Where the save lives. */
|
|
85
|
+
save: SaveKind; /** The asset build. */
|
|
86
|
+
assets: {
|
|
87
|
+
layers: Readonly<Record<string, string>>;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The engine part of a screen app, in dependency order: the screen set, `audio`, `effects`,
|
|
92
|
+
* `platform`. The game's features and plugins come after it.
|
|
93
|
+
*/
|
|
94
|
+
type ScreenSet = readonly [...typeof screen, typeof audioPlugin, typeof effectsPlugin, typeof platformPlugin];
|
|
95
|
+
/**
|
|
96
|
+
* The screen app of a game whose own plugins are `Plugins`, as `createApp` types it.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* type App = ScreenAppOf<readonly [typeof scorePlugin]>; // app.ui, app.platform, app.score
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
type ScreenAppOf<Plugins extends readonly AnyPluginInstance[]> = ReturnType<typeof createApp<[...ScreenSet, ...Plugins]>>;
|
|
104
|
+
/**
|
|
105
|
+
* The headless app of a game whose headless plugins are `Plugins`, as `createApp` types it.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* type App = HeadlessAppOf<readonly []>; // app.time, .lifecycle, .model, .clock, .flow, .log, .env
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
type HeadlessAppOf<Plugins extends readonly AnyPluginInstance[]> = ReturnType<typeof createApp<[...Plugins]>>;
|
|
113
|
+
/** Every `pluginConfigs` key of the screen app of a game, as `createApp` accepts them. */
|
|
114
|
+
type AllConfigs<Plugins extends readonly AnyPluginInstance[]> = NonNullable<NonNullable<Parameters<typeof createApp<[...ScreenSet, ...Plugins]>>[0]>["pluginConfigs"]>;
|
|
115
|
+
/** The plugin configs the shell writes as a whole, or in part: they leave the game's type. */
|
|
116
|
+
type SeamOwned = "model" | "clock" | "platform" | "flow" | "renderer" | "assets" | "audio";
|
|
117
|
+
/**
|
|
118
|
+
* The `pluginConfigs` a game writes in `defineGameApp`: every plugin of its screen app, without
|
|
119
|
+
* the keys the shell writes from the seams. `clock` and `platform` are gone; `model` keeps
|
|
120
|
+
* `schemaVersion` and `migrations`; `flow` loses `mainFlow` and `safeNode`; `renderer` loses
|
|
121
|
+
* `mount`; `assets` loses `manifest` and `io`; `audio` loses `context`. A seam-owned key is a
|
|
122
|
+
* compile error, never a value the shallow merge would drop.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* defineGameApp({ flow: mainFlow, player, session, pluginConfigs: { ui: { tapTargetPt: 48 } } }); // passes
|
|
127
|
+
* defineGameApp({ flow: mainFlow, player, session, pluginConfigs: { clock: {} } }); // compile error: the shell owns clock
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
type GamePluginConfigs<Plugins extends readonly AnyPluginInstance[] = readonly []> = Omit<AllConfigs<Plugins>, SeamOwned> & {
|
|
131
|
+
/** The save schema of the game. The shell writes the provider, the seed and the starting state. */model?: Pick<Partial<Config>, "schemaVersion" | "migrations">; /** The runner limits. The shell writes `mainFlow` and `safeNode` from the definition. */
|
|
132
|
+
flow?: Omit<Partial<Config$1>, "mainFlow" | "safeNode">; /** The canvas. The shell writes `mount`; a seam's `loadPixi` and `preference` win. */
|
|
133
|
+
renderer?: Omit<Partial<Config$2>, "mount">; /** The texture budget and the CDN prefix. The shell writes `manifest` and `io`. */
|
|
134
|
+
assets?: Omit<Partial<Config$3>, "manifest" | "io">; /** Buses, volumes, music. The shell writes `context`; a seam's `journal` wins. */
|
|
135
|
+
audio?: Omit<Partial<Config$4>, "context">;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* What `defineGameApp` takes: the whole game as one data object. Nothing here is a seam: the
|
|
139
|
+
* clock, the save provider, the manifest and the platform come from `headless()` and `screen()`.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* // tests/fixtures/mini-game/index.ts
|
|
144
|
+
* const definition: GameDefinition = {
|
|
145
|
+
* flow: mainFlow, safeNode: "home", player: { count: 0 }, session: { opened: 0 },
|
|
146
|
+
* features: [homeFeature, infoFeature], headless: { features: [infoFeature] }
|
|
147
|
+
* };
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
type GameDefinition<Plugins extends readonly AnyPluginInstance[] = readonly [], HeadlessPlugins extends readonly AnyPluginInstance[] = readonly [], Player extends Json = Json, Session extends Json = Json> = {
|
|
151
|
+
/** The main flow. */flow: AnyFlow; /** The checkpoint entered after a failed retry. Default: the start of the main flow. */
|
|
152
|
+
safeNode?: string; /** The player a new save starts from. */
|
|
153
|
+
player: Player; /** The session at every start. */
|
|
154
|
+
session: Session; /** The long side the layout needs, in reference units. Default 1920. */
|
|
155
|
+
referenceLong?: number; /** The rng seed of a new save. Default 42. */
|
|
156
|
+
seed?: number; /** The shared layer: composed first among the features. */
|
|
157
|
+
shared?: FeaturePlugin; /** The features of the screen app, in order, after `shared`. */
|
|
158
|
+
features?: readonly FeaturePlugin[]; /** The game's own plugins, after the features. Their APIs land on `screen().app`. */
|
|
159
|
+
plugins?: Plugins; /** What the headless app composes: the logic of these features and these plugins, nothing else. */
|
|
160
|
+
headless?: {
|
|
161
|
+
features?: readonly FeaturePlugin[];
|
|
162
|
+
plugins?: HeadlessPlugins;
|
|
163
|
+
}; /** Plugin configs of the game. Seam-owned keys are not accepted. */
|
|
164
|
+
pluginConfigs?: NoInfer<GamePluginConfigs<Plugins>>;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* The in-memory save provider a seam gets when it passes none: it records every call.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* const { app, provider } = game.headless();
|
|
172
|
+
* await createHeadless(app); // flow.run() loads the save and commits a new player at once
|
|
173
|
+
* provider.calls.map(call => call.method); // ["load", "commit"]
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
type MemoryProvider = ReturnType<typeof memory>;
|
|
177
|
+
/**
|
|
178
|
+
* What one call of `headless()` or `screen()` gives: the app, not started, and the clock and save
|
|
179
|
+
* provider it runs on, so a test can move the time and read the save.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* const { app, clock, provider } = game.headless({ seed: 7 });
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
type GameHandle<App, Source, Provider> = {
|
|
187
|
+
/**
|
|
188
|
+
* The app, not started.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```ts
|
|
192
|
+
* const { app } = game.headless();
|
|
193
|
+
* await app.start(); // the caller starts it, and runs the graph
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
app: App;
|
|
197
|
+
/**
|
|
198
|
+
* The clock source the app runs on: the fake clock at `startMoment` unless a seam passed one.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* const { clock } = game.headless();
|
|
203
|
+
* clock.advance(60_000); // clock.now() is 1060000
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
clock: Source;
|
|
207
|
+
/**
|
|
208
|
+
* The save provider the app runs on: a fresh memory provider unless a seam passed one.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* const { provider } = game.headless();
|
|
213
|
+
* provider.calls; // []: flow.run() loads the save, start() does not
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
provider: Provider;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* What a test or the page may pin on a headless app. Every field is optional; the definition and
|
|
220
|
+
* the engine defaults fill the rest.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* game.headless({ seed: 7, player: { count: 3 } });
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
type HeadlessSeams<Player, Session, Source, Provider> = {
|
|
228
|
+
/**
|
|
229
|
+
* The rng seed of a new save. Default: the definition's seed, else 42.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```ts
|
|
233
|
+
* game.headless({ seed: 7 }); // a new save draws from seed 7
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
seed?: number;
|
|
237
|
+
/**
|
|
238
|
+
* The clock source. Default: `fakeClock(startMoment)`; the page passes the device clock.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```ts
|
|
242
|
+
* game.headless({ clock: fakeClock(5000) }).clock.now(); // 5000
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
clock?: Source;
|
|
246
|
+
/**
|
|
247
|
+
* The save provider. Default: a fresh `memory()`.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```ts
|
|
251
|
+
* game.headless({ provider: memory({ state: saveOf({ count: 3 }, 42), version: 1 }) }); // a returning player
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
provider?: Provider;
|
|
255
|
+
/**
|
|
256
|
+
* The player a new save starts from. Default: the definition's player.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* game.headless({ player: { count: 3 } }); // a new save counts 3
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
player?: Player;
|
|
264
|
+
/**
|
|
265
|
+
* The session at start. Default: the definition's session.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* game.headless({ session: { opened: 2 } });
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
session?: Session;
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* What a test or the page may pin on a screen app: the headless seams, and the manifest, the
|
|
276
|
+
* file seam, the platform and the screen-on switch, the audio and the renderer seams.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```ts
|
|
280
|
+
* game.screen({ manifest: "/manifest.json", renderer: { mount: "#game" } });
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
type ScreenSeams<Player, Session, Source, Provider> = HeadlessSeams<Player, Session, Source, Provider> & {
|
|
284
|
+
/**
|
|
285
|
+
* The manifest the assets plugin reads: a URL on the page, the parsed file in a test.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* game.screen({ manifest: await readManifest() }); // tests/integration/mini-helpers.ts
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
manifest?: string | Manifest;
|
|
293
|
+
/**
|
|
294
|
+
* The file seam of the assets plugin. Left out, the browser fetch, or headless in a test.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* game.screen({ manifest, io: folderIo().io }); // the fixture's files, read from disk
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
io?: AssetsIo;
|
|
302
|
+
/**
|
|
303
|
+
* The platform provider. Left out, the platform plugin is inert and `back()` answers `"none"`.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* const { app } = game.screen({ platform: provider });
|
|
308
|
+
* await app.start();
|
|
309
|
+
* void app.flow.run();
|
|
310
|
+
* app.platform.back(); // "exit" once the graph rests on Home: Home does not list "back"
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
platform?: PlatformProvider;
|
|
314
|
+
/**
|
|
315
|
+
* Keeps the screen on while the game runs. Default `false`.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```ts
|
|
319
|
+
* await game.screen({ platform: provider, keepAwake: true }).app.start(); // provider.keepAwake(true)
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
keepAwake?: boolean;
|
|
323
|
+
/**
|
|
324
|
+
* The audio seams: the context factory, and how many started sounds the journal keeps.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```ts
|
|
328
|
+
* game.screen({ audio: { context: () => fakeContext, journal: 200 } }); // the dev page keeps 200
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
audio?: {
|
|
332
|
+
context?: () => AudioContextLike;
|
|
333
|
+
journal?: number;
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* The renderer seams: where the canvas goes, how Pixi loads, which backend it asks for.
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* ```ts
|
|
340
|
+
* game.screen({ renderer: { mount: "#game", preference: "webgl" } }); // the page under ?renderer=webgl
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
renderer?: {
|
|
344
|
+
mount?: string | HTMLElement;
|
|
345
|
+
loadPixi?: () => Promise<PixiModule>;
|
|
346
|
+
preference?: "webgpu" | "webgl";
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
/**
|
|
350
|
+
* The game, as `defineGameApp` returns it: two ways to make its app. `headless()` composes the
|
|
351
|
+
* logic only, for tests; `screen()` composes the engine's screen set, the features and the game's
|
|
352
|
+
* plugins, for the page and the screen tests. Neither starts the app.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```ts
|
|
356
|
+
* import game from "../index";
|
|
357
|
+
* const { app, clock, provider } = game.headless({ seed: 7 });
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
type GameApp<Plugins extends readonly AnyPluginInstance[] = readonly [], HeadlessPlugins extends readonly AnyPluginInstance[] = readonly [], Player extends Json = Json, Session extends Json = Json> = {
|
|
361
|
+
/**
|
|
362
|
+
* Makes the logic app: the core plugins, the logic of `headless.features` and the
|
|
363
|
+
* `headless.plugins`. No screen plugin, no game plugin unless listed. Each call gives a fresh
|
|
364
|
+
* app, clock and provider.
|
|
365
|
+
*
|
|
366
|
+
* @param seams - What the test pins: seed, clock, provider, player, session.
|
|
367
|
+
* @returns The app, not started, and its clock and provider.
|
|
368
|
+
* @example
|
|
369
|
+
* ```ts
|
|
370
|
+
* const { app, clock } = game.headless({ seed: 7 });
|
|
371
|
+
* clock.now(); // 1000000, the fake clock at startMoment
|
|
372
|
+
* const run = await createHeadless(app);
|
|
373
|
+
* run.state().path; // "home"
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
headless<Source extends ClockSource = FakeClock, Provider extends PlayerStateProvider = MemoryProvider>(seams?: HeadlessSeams<Player, Session, Source, Provider>): GameHandle<HeadlessAppOf<HeadlessPlugins>, Source, Provider>;
|
|
377
|
+
/**
|
|
378
|
+
* Makes the screen app: the screen set, `audio`, `effects`, `platform`, then `shared`, the
|
|
379
|
+
* features and the game's plugins, in that order. Without a mount the renderer is inert, so the
|
|
380
|
+
* same app runs in a test. Each call gives a fresh app, clock and provider.
|
|
381
|
+
*
|
|
382
|
+
* @param seams - What the test or the page pins, the headless seams and the screen seams.
|
|
383
|
+
* @returns The app, not started, and its clock and provider.
|
|
384
|
+
* @example
|
|
385
|
+
* ```ts
|
|
386
|
+
* const { app } = game.screen({ manifest: await readManifest(), io: folderIo().io });
|
|
387
|
+
* await app.start();
|
|
388
|
+
* void app.flow.run(); // the page runs the graph; the mini game starts on its Home checkpoint
|
|
389
|
+
* app.flow.state().path; // "home" once the runner rests
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
392
|
+
screen<Source extends ClockSource = FakeClock, Provider extends PlayerStateProvider = MemoryProvider>(seams?: ScreenSeams<Player, Session, Source, Provider>): GameHandle<ScreenAppOf<Plugins>, Source, Provider>;
|
|
393
|
+
};
|
|
394
|
+
/**
|
|
395
|
+
* Any game, whatever its plugins and state: what the page takes.
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```ts
|
|
399
|
+
* const page: AnyGameApp = game; // the default export of a game's index.ts
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
type AnyGameApp = GameApp<readonly AnyPluginInstance[], readonly AnyPluginInstance[], Json, Json>;
|
|
403
|
+
/**
|
|
404
|
+
* The screen app of a game, as `game.screen().app` types it.
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```ts
|
|
408
|
+
* type MiniApp = ScreenAppOfGame<typeof game>; // has app.flow, app.ui, app.platform
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
type ScreenAppOfGame<Game extends AnyGameApp> = ReturnType<Game["screen"]>["app"];
|
|
412
|
+
/**
|
|
413
|
+
* One prepared save of the dev page, picked with `?player=<name>`: the default export of
|
|
414
|
+
* `tests/scenarios/<name>.ts`. It gets the device time, so a timer can be due already.
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```ts
|
|
418
|
+
* // tests/scenarios/full.ts of a game: `?player=full` opens on a full board, a refill due in 60 s.
|
|
419
|
+
* const full: Scenario<Player> = now => ({ player: { ...startingPlayer, board: fullBoard, refillAt: now + 60_000 } });
|
|
420
|
+
* export default full;
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
type Scenario<Player = Json, Session = Json> = (now: number) => {
|
|
424
|
+
player: Player;
|
|
425
|
+
session?: Session;
|
|
426
|
+
};
|
|
427
|
+
/**
|
|
428
|
+
* A dev agent the page starts after the app: the default export of an agent module the CLI names,
|
|
429
|
+
* the editor's among them. It gets the running app, the page title and the game's `.dev` modules.
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```ts
|
|
433
|
+
* // .moku/main.ts, written by moku-game dev: the editor's agent module's default export.
|
|
434
|
+
* await startPage(game, config, { agents: [agent0] }); // agent0({ app, name: "mini-game", modules: [] })
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
type PageAgent<App = ScreenAppOfGame<AnyGameApp>> = (page: {
|
|
438
|
+
app: App;
|
|
439
|
+
name: string;
|
|
440
|
+
modules: readonly object[];
|
|
441
|
+
}) => void | Promise<void>;
|
|
442
|
+
/**
|
|
443
|
+
* Builds the system shell from the resolved config: what `systemShellOf` of
|
|
444
|
+
* `@moku-labs/game/app/system` returns over the loaders of the page. The page reaches the shell
|
|
445
|
+
* only through this.
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```ts
|
|
449
|
+
* const factory: SystemShellFactory = systemShellOf({ system: () => import("@moku-labs/system"), back: () => import("@moku-labs/system/back") });
|
|
450
|
+
* await startPage(game, config, { system: factory }); // the page builds the shell of config.system: ["back"]
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
type SystemShellFactory = (config: ResolvedGameConfig, report: (problem: string) => void) => Promise<SystemShell>;
|
|
454
|
+
/**
|
|
455
|
+
* What the generated `.moku/main.ts` hands the page besides the game and its config.
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```ts
|
|
459
|
+
* const system = systemShellOf({ system: () => import("@moku-labs/system"), haptics: () => import("@moku-labs/system/haptics") });
|
|
460
|
+
* const options: PageOptions = { scenarios: { ready }, system, agents: [agent0], devModules: [devModule0] };
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
463
|
+
type PageOptions = {
|
|
464
|
+
/** Prepared saves by name, for `?player=`. Dev only; the build passes none. */scenarios?: Readonly<Record<string, Scenario>>; /** The system shell factory, when `config.ts` names a system plugin or the store save. */
|
|
465
|
+
system?: SystemShellFactory; /** Agents started after the app. Dev only. */
|
|
466
|
+
agents?: readonly PageAgent[]; /** The game's `.dev` modules, handed to the agents. Dev only. */
|
|
467
|
+
devModules?: readonly object[];
|
|
468
|
+
};
|
|
469
|
+
/**
|
|
470
|
+
* What `startPage` resolves to: the running screen app, and the system app for
|
|
471
|
+
* `globalThis.system` (`undefined` without a shell).
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* ```ts
|
|
475
|
+
* const { app } = await startPage(game, config);
|
|
476
|
+
* app.flow.state().path; // "home" once the runner rests
|
|
477
|
+
* ```
|
|
478
|
+
*/
|
|
479
|
+
type StartedPage<Game extends AnyGameApp> = {
|
|
480
|
+
/** The screen app, started. */app: ScreenAppOfGame<Game>; /** The `@moku-labs/system` app, or `undefined` without a shell. Typed `unknown`: see `SystemShell.handle`. */
|
|
481
|
+
system: unknown;
|
|
482
|
+
};
|
|
483
|
+
/**
|
|
484
|
+
* The modules of `@moku-labs/system` the page loads, one loader per name of `config.ts`: the
|
|
485
|
+
* package itself always, and the entry of each plugin the game names, the store for a store save.
|
|
486
|
+
* The generated `main.ts` writes only the loaders of the names, so the page bundles only those
|
|
487
|
+
* plugins and never meets the native package of another. Typed `unknown`: a system type here would
|
|
488
|
+
* land in the `.d.mts` of every game and break the typecheck of a web-only one.
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* ```ts
|
|
492
|
+
* // A page of its own for a game whose config.ts names haptics, with a memory save.
|
|
493
|
+
* const modules: SystemModules = {
|
|
494
|
+
* system: () => import("@moku-labs/system"),
|
|
495
|
+
* haptics: () => import("@moku-labs/system/haptics")
|
|
496
|
+
* };
|
|
497
|
+
* await startPage(game, config, { system: systemShellOf(modules) }); // the bundle holds no store plugin
|
|
498
|
+
* ```
|
|
499
|
+
*/
|
|
500
|
+
type SystemModules = {
|
|
501
|
+
/** `@moku-labs/system` itself, for its `createApp`. Always there. */system: () => Promise<{
|
|
502
|
+
createApp: unknown;
|
|
503
|
+
}>; /** `@moku-labs/system/lifecycle`, when the game names `lifecycle`. */
|
|
504
|
+
lifecycle?: () => Promise<{
|
|
505
|
+
lifecyclePlugin: unknown;
|
|
506
|
+
}>; /** `@moku-labs/system/back`, when the game names `back`. */
|
|
507
|
+
back?: () => Promise<{
|
|
508
|
+
backPlugin: unknown;
|
|
509
|
+
}>; /** `@moku-labs/system/haptics`, when the game names `haptics`. */
|
|
510
|
+
haptics?: () => Promise<{
|
|
511
|
+
hapticsPlugin: unknown;
|
|
512
|
+
}>; /** `@moku-labs/system/keep-awake`, when the game names `keepAwake`. */
|
|
513
|
+
keepAwake?: () => Promise<{
|
|
514
|
+
keepAwakePlugin: unknown;
|
|
515
|
+
}>; /** `@moku-labs/system/store`, when the game names `store` or its save is `"store"`. */
|
|
516
|
+
store?: () => Promise<{
|
|
517
|
+
storePlugin: unknown;
|
|
518
|
+
}>;
|
|
519
|
+
};
|
|
520
|
+
/**
|
|
521
|
+
* The system shell: the `@moku-labs/system` app the config names, and what the engine takes from
|
|
522
|
+
* it. The factory of `systemShellOf` builds it; the page starts it before the game and stops it
|
|
523
|
+
* after.
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* ```ts
|
|
527
|
+
* const build = systemShellOf({ system: () => import("@moku-labs/system"), store: () => import("@moku-labs/system/store") });
|
|
528
|
+
* const shell = await build(resolveConfig({ page: { title: "T" }, save: "store" }), problem => problems.push(problem));
|
|
529
|
+
* game.screen({ platform: shell.platform, provider: shell.save });
|
|
530
|
+
* ```
|
|
531
|
+
*/
|
|
532
|
+
type SystemShell = {
|
|
533
|
+
/**
|
|
534
|
+
* The `@moku-labs/system` app, for `globalThis.system`. Typed `unknown`: a system type here
|
|
535
|
+
* would land in the `.d.mts` of every game and break the typecheck of a web-only one.
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
* ```ts
|
|
539
|
+
* Reflect.set(globalThis, "system", shell.handle); // e2e: system.haptics.selection()
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
handle: unknown;
|
|
543
|
+
/**
|
|
544
|
+
* The engine's platform provider over the system app. A capability the config does not name is
|
|
545
|
+
* inert.
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* ```ts
|
|
549
|
+
* shell.platform.haptic("success"); // the shell plays system.haptics.notify("success")
|
|
550
|
+
* ```
|
|
551
|
+
*/
|
|
552
|
+
platform: PlatformProvider;
|
|
553
|
+
/**
|
|
554
|
+
* The save provider over the system store, set when `config.save` is `"store"`.
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```ts
|
|
558
|
+
* game.screen({ provider: shell.save }); // the save survives a restart of the native app
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
save: PlayerStateProvider | undefined;
|
|
562
|
+
/**
|
|
563
|
+
* Starts the system app. The page starts it before the game, so keep-awake finds it running.
|
|
564
|
+
*
|
|
565
|
+
* @returns Resolves when the system app runs.
|
|
566
|
+
* @example
|
|
567
|
+
* ```ts
|
|
568
|
+
* await shell.start();
|
|
569
|
+
* await app.start();
|
|
570
|
+
* ```
|
|
571
|
+
*/
|
|
572
|
+
start(): Promise<void>;
|
|
573
|
+
/**
|
|
574
|
+
* Stops the system app.
|
|
575
|
+
*
|
|
576
|
+
* @returns Resolves when the system app stopped.
|
|
577
|
+
* @example
|
|
578
|
+
* ```ts
|
|
579
|
+
* await app.stop();
|
|
580
|
+
* await shell.stop();
|
|
581
|
+
* ```
|
|
582
|
+
*/
|
|
583
|
+
stop(): Promise<void>;
|
|
584
|
+
};
|
|
585
|
+
//#endregion
|
|
586
|
+
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 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { A as Api$1, B as ComponentType, D as FilterSlot, H as Entity, O as Point, R as ComponentHandle, Sn as Require, _ as PixiFilter, b as PixiTexture, dt as Api, h as Api$2, s as Events, t as Api$3, v as PixiModule, xn as Events$1, y as PixiSprite } from "./types-ZQLYXo5X.mjs";
|
|
2
|
+
import { x as AnimApi } from "./types-BM4shh8K.mjs";
|
|
3
3
|
import { Log } from "@moku-labs/common/browser";
|
|
4
4
|
import { PluginCtx } from "@moku-labs/core";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as HeadlessApp } from "./headless-
|
|
1
|
+
import { Xt as Json, an as Snapshot } from "./types-ZQLYXo5X.mjs";
|
|
2
|
+
import { t as HeadlessApp } from "./headless-G4nuZ-xz.mjs";
|
|
3
3
|
import { Log } from "@moku-labs/common/browser";
|
|
4
4
|
|
|
5
5
|
//#region src/plugins/flow/doors/types.d.ts
|