@moku-labs/game 0.4.1 → 0.4.3

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 CHANGED
@@ -31,7 +31,7 @@ You write small nodes and edge tables. The engine runs them, saves on the edges
31
31
  ## Status
32
32
 
33
33
  > [!NOTE]
34
- > **`0.1.0`: the first working release.** It runs on our machines, two emulators and one very patient fixture game. The API may still change before `1.0`.
34
+ > **`0.4`: working, still young.** It runs on our machines, two emulators and one very patient fixture game. The API may still change before `1.0`.
35
35
 
36
36
  | Milestone | What it added |
37
37
  |---|---|
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env bun
2
- import { compileStrings, runCli } from "../dist/assets.mjs";
2
+ import { compileStrings, exportStrings, importStrings, runCli } from "../dist/assets.mjs";
3
3
 
4
- process.exitCode = await runCli(process.argv.slice(2), compileStrings);
4
+ process.exitCode = await runCli(process.argv.slice(2), {
5
+ compile: compileStrings,
6
+ exportStrings,
7
+ importStrings
8
+ });
@@ -1151,33 +1151,4 @@ async function captureFrame(mctx, options) {
1151
1151
  return shot === void 0 ? void 0 : answerOf(mctx, shot, options.against);
1152
1152
  }
1153
1153
  //#endregion
1154
- //#region src/plugins/flow/doors/dev.ts
1155
- /**
1156
- * Reads the dev flag at call time. Undefined means off: production is the default.
1157
- *
1158
- * @returns True only when `__MOKU_GAME_DEV__` is exactly `true`.
1159
- * @example
1160
- * ```ts
1161
- * // The dev page set the flag in web/dev.ts before the engine started.
1162
- * globalThis.__MOKU_GAME_DEV__ = true;
1163
- * isDev(); // true
1164
- * ```
1165
- */
1166
- function isDev() {
1167
- return typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ === true;
1168
- }
1169
- /**
1170
- * Builds the error a control command throws outside a dev build.
1171
- *
1172
- * @returns The error, ready to throw.
1173
- * @example
1174
- * ```ts
1175
- * // A production build called a /control command.
1176
- * controlRefused().message; // "[game] Control commands run in dev builds only.\n Define __MOKU_GAME_DEV__ as true in the dev build."
1177
- * ```
1178
- */
1179
- function controlRefused() {
1180
- return /* @__PURE__ */ new Error("[game] Control commands run in dev builds only.\n Define __MOKU_GAME_DEV__ as true in the dev build.");
1181
- }
1182
- //#endregion
1183
- export { serveCaptures as a, collectFlows as c, framePath as d, graphHash as f, pushEntry as h, captureFrame as i, describeGraph as l, compact as m, isDev as n, servePaused as o, slotNames as p, cancelCaptures as r, sheetAlone as s, controlRefused as t, findNode as u };
1154
+ export { sheetAlone as a, findNode as c, slotNames as d, compact as f, servePaused as i, framePath as l, captureFrame as n, collectFlows as o, pushEntry as p, serveCaptures as r, describeGraph as s, cancelCaptures as t, graphHash as u };
@@ -4,9 +4,9 @@ import { t as defineCommand } from "./define-BKWjWgyQ.mjs";
4
4
 
5
5
  //#region src/plugins/flow/doors/dev.d.ts
6
6
  /**
7
- * @file flow/doors — the dev flag. `__MOKU_GAME_DEV__` is a global the engine never replaces: a
8
- * game's dev build defines it `true` (bundler `define`) or sets `globalThis.__MOKU_GAME_DEV__`
9
- * before the engine runs; undefined means a production build.
7
+ * @file flow/doors — the dev flag and the errors a door throws. `__MOKU_GAME_DEV__` is a global
8
+ * the engine never replaces: a game's dev build defines it `true` (bundler `define`) or sets
9
+ * `globalThis.__MOKU_GAME_DEV__` before the engine runs; undefined means a production build.
10
10
  *
11
11
  * Bun does not inline `isDev()` across modules (checked on Bun 1.3.14), so a branch that must
12
12
  * vanish from a production bundle writes the guard inline:
package/dist/control.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as controlRefused } from "./dev-CQYJPmcD.mjs";
2
- import { n as commands, t as run } from "./run-BfuMLahc.mjs";
1
+ import { t as controlRefused } from "./dev-B5p9_3fz.mjs";
2
+ import { n as commands, t as run } from "./run-C7O77UQR.mjs";
3
3
  import { s as defineCommand } from "./input-CK4nxBO1.mjs";
4
4
  export { commands, controlRefused, defineCommand, run };
@@ -0,0 +1,44 @@
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 web/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
+ * Builds the error a control command throws outside a dev build.
18
+ *
19
+ * @returns The error, ready to throw.
20
+ * @example
21
+ * ```ts
22
+ * // A production build called a /control command.
23
+ * controlRefused().message; // "[game] Control commands run in dev builds only.\n Define __MOKU_GAME_DEV__ as true in the dev build."
24
+ * ```
25
+ */
26
+ function controlRefused() {
27
+ return /* @__PURE__ */ new Error("[game] Control commands run in dev builds only.\n Define __MOKU_GAME_DEV__ as true in the dev build.");
28
+ }
29
+ /**
30
+ * Builds the error a door source throws when the opt-in plugin it reads is not installed.
31
+ *
32
+ * @param id - The id of the source.
33
+ * @param plugin - The export name of the plugin it reads.
34
+ * @returns The error, ready to throw.
35
+ * @example
36
+ * ```ts
37
+ * notInstalled("game.sounds", "audioPlugin").message; // "[game] The source game.sounds needs audioPlugin.\n Add audioPlugin to createApp({ plugins })."
38
+ * ```
39
+ */
40
+ function notInstalled(id, plugin) {
41
+ return /* @__PURE__ */ new Error(`[game] The source ${id} needs ${plugin}.\n Add ${plugin} to createApp({ plugins }).`);
42
+ }
43
+ //#endregion
44
+ export { isDev as n, notInstalled as r, controlRefused as t };
package/dist/index.d.mts CHANGED
@@ -1396,7 +1396,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
1396
1396
  rolledBackTo: string;
1397
1397
  retry: boolean;
1398
1398
  };
1399
- }> & Record<never, never>) | ExtraPlugins[number], [...ExtraPlugins], import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common/browser").LogConfig, import("@moku-labs/common/browser").LogState, import("@moku-labs/common/browser").LogApi>, import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common/browser").EnvConfig, import("@moku-labs/common/browser").EnvState, import("@moku-labs/common/browser").EnvApi>]>> | undefined) => import("@moku-labs/core").App<Config$9, Events$1, (import("@moku-labs/core").PluginInstance<"time", Config$7, State$7, Api$5, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"lifecycle", Config$4, State$4, Api$3, {
1399
+ }> & Record<never, never>) | ExtraPlugins[number], [...ExtraPlugins], import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common/browser").LogConfig, import("@moku-labs/common/browser").LogState, import("@moku-labs/common/browser").LogApi>, import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common/browser").EnvConfig, import("@moku-labs/common/browser").EnvState, import("@moku-labs/common/browser").EnvApi>]>, import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common/browser").LogConfig, import("@moku-labs/common/browser").LogState, import("@moku-labs/common/browser").LogApi> | import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common/browser").EnvConfig, import("@moku-labs/common/browser").EnvState, import("@moku-labs/common/browser").EnvApi>> | undefined) => import("@moku-labs/core").App<Config$9, Events$1, (import("@moku-labs/core").PluginInstance<"time", Config$7, State$7, Api$5, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"lifecycle", Config$4, State$4, Api$3, {
1400
1400
  "lifecycle:changed": {
1401
1401
  reason: PauseReason;
1402
1402
  action: "push" | "pop";
package/dist/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
2
  import { a as enableDraftPatches, i as dropDrafts, o as finishDrafts, r as deepFreeze, s as openDrafts, t as memory } from "./memory-CvgdnsQO.mjs";
3
- import { a as serveCaptures, c as collectFlows, d as framePath, f as graphHash, h as pushEntry, i as captureFrame, l as describeGraph, m as compact, n as isDev, o as servePaused, p as slotNames, r as cancelCaptures, u as findNode } from "./dev-CQYJPmcD.mjs";
3
+ import { c as findNode, d as slotNames, f as compact, i as servePaused, l as framePath, n as captureFrame, o as collectFlows, p as pushEntry, r as serveCaptures, s as describeGraph, t as cancelCaptures, u as graphHash } from "./capture-fpd83Mi5.mjs";
4
4
  import { A as system, C as Exiting, D as component, E as Tree, O as mut, S as sprite, T as Order, _ as Parent, a as visualOf$1, b as Transform, c as Text, d as defineTextStyles, f as label, g as NineSlice, h as Display, j as tag, k as resource, l as bind, m as textFor, n as fitScaleOf, o as visualRectOf, p as readStyle, r as samePose, s as Countdown, t as CONTENT, u as builtInStyles, v as Shape, w as Layer, x as componentsFor, y as Sprite } from "./visual-DUvvPPCt.mjs";
5
+ import { n as isDev } from "./dev-B5p9_3fz.mjs";
5
6
  import { n as flatten, r as defineComponent } from "./flatten-BkXuscAV.mjs";
6
7
  import { envPlugin, logPlugin } from "@moku-labs/common/browser";
7
8
  import { createCoreConfig } from "@moku-labs/core";
@@ -95,10 +95,10 @@ declare const sources: Readonly<{
95
95
  sounds: Source<{
96
96
  last: "number?";
97
97
  }, readonly SoundEntry[], HeadlessApp & {
98
- readonly audio: AudioApi;
98
+ readonly audio?: AudioApi;
99
99
  }>;
100
100
  effects: Source<{}, EffectsStats, HeadlessApp & {
101
- readonly effects: EffectsApi;
101
+ readonly effects?: EffectsApi;
102
102
  }>;
103
103
  assets: Source<{}, Usage, HeadlessApp & {
104
104
  readonly assets: Api$3;
package/dist/inspect.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { c as defineSource } from "./input-CK4nxBO1.mjs";
2
- import { n as read, r as watch, t as sources } from "./sources-C1uGjz4l.mjs";
2
+ import { n as read, r as watch, t as sources } from "./sources-CTGuY3Fe.mjs";
3
3
  export { defineSource, read, sources, watch };
@@ -1,4 +1,5 @@
1
- import { f as graphHash, n as isDev, s as sheetAlone, t as controlRefused } from "./dev-CQYJPmcD.mjs";
1
+ import { a as sheetAlone, u as graphHash } from "./capture-fpd83Mi5.mjs";
2
+ import { n as isDev, t as controlRefused } from "./dev-B5p9_3fz.mjs";
2
3
  import { a as isProjectionTarget, i as recordCheat, o as readTarget, r as isTainted, s as defineCommand, t as inputOrEmpty } from "./input-CK4nxBO1.mjs";
3
4
  //#region src/plugins/flow/headless.ts
4
5
  const noPayload = null;
@@ -1,4 +1,5 @@
1
1
  import { i as scaleByFits, w as Layer } from "./visual-DUvvPPCt.mjs";
2
+ import { r as notInstalled } from "./dev-B5p9_3fz.mjs";
2
3
  import { c as defineSource, n as cheatsOf, o as readTarget, r as isTainted, t as inputOrEmpty } from "./input-CK4nxBO1.mjs";
3
4
  //#region src/plugins/flow/doors/read.ts
4
5
  /**
@@ -105,6 +106,7 @@ const assetsSource = defineSource({
105
106
  * The sounds that started, oldest first: every sfx played and every music track begun, all of
106
107
  * them or the last few. Empty unless `pluginConfigs.audio.journal` is above 0.
107
108
  *
109
+ * @throws {Error} `[game] The source game.sounds needs audioPlugin.` when the app has no `audioPlugin`.
108
110
  * @example
109
111
  * ```ts
110
112
  * // After a tap on Deliver, on the dev page that sets `journal: 200`.
@@ -118,6 +120,7 @@ const soundsSource = defineSource({
118
120
  input: { last: "number?" },
119
121
  changes: "frame",
120
122
  read: (app, { last }) => {
123
+ if (app.audio === void 0) throw notInstalled("game.sounds", "audioPlugin");
121
124
  const entries = app.audio.journal();
122
125
  return last === void 0 ? entries : entries.slice(Math.max(0, entries.length - last));
123
126
  }
@@ -132,6 +135,7 @@ const soundsSource = defineSource({
132
135
  * What the effects draw now: live particles, emitter instances with orphans, filter instances
133
136
  * and the render passes of the frame.
134
137
  *
138
+ * @throws {Error} `[game] The source game.effects needs effectsPlugin.` when the app has no `effectsPlugin`.
135
139
  * @example
136
140
  * ```ts
137
141
  * // The editor's stats panel while the board rests: one steam stream and the glows of 24 cards.
@@ -144,7 +148,10 @@ const effectsSource = defineSource({
144
148
  title: "Effects",
145
149
  input: {},
146
150
  changes: "frame",
147
- read: (app) => app.effects.stats()
151
+ read: (app) => {
152
+ if (app.effects === void 0) throw notInstalled("game.effects", "effectsPlugin");
153
+ return app.effects.stats();
154
+ }
148
155
  });
149
156
  //#endregion
150
157
  //#region src/plugins/model/inspect.ts
package/dist/testing.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as saveOf, t as memory } from "./memory-CvgdnsQO.mjs";
2
- import { a as stepFrames, i as runRepro, n as commands, r as createHeadless, t as run } from "./run-BfuMLahc.mjs";
3
- import { n as read, t as sources } from "./sources-C1uGjz4l.mjs";
2
+ import { a as stepFrames, i as runRepro, n as commands, r as createHeadless, t as run } from "./run-C7O77UQR.mjs";
3
+ import { n as read, t as sources } from "./sources-CTGuY3Fe.mjs";
4
4
  import { createBrandConsole } from "@moku-labs/common/cli";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
6
6
  import path from "node:path";
package/llms.txt CHANGED
@@ -466,8 +466,8 @@ A plugin that owns a resource stores it in its state and frees it in `onStop: ({
466
466
 
467
467
  Two subpaths, neither on the root (V4). An editor, MCP tools and e2e scripts reach a running game through them; `docs/doors.md` in the engine repository has the full catalogue.
468
468
 
469
- - `@moku-labs/game/inspect`: `read(app, source, input?)` reads once; `watch(app, source, input, fn)` reads on the `signals` phase when the source changes and returns the stop function; `defineSource`; the frozen catalogue `sources`; the types `Source`, `InputSchema`, `InputOf`. Every source only reads, so the door is safe in a production build. Keys of `sources`: `graph`, `position`, `history`, `tainted`, `cheats`, `model`, `entities`, `projections`, `ui`, `rect`, `render`, `effects`, `sounds`, `assets`, `log`; their ids are `game.<key>`.
470
- - `@moku-labs/game/control`: `run(app, command, input?)` resolves `{ value, state }`, `state` being `{ path, frame, tainted }`; `defineCommand`; `controlRefused`; the frozen catalogue `commands`; the types `Command`, `Ran`. Keys of `commands`: `answer`, `tap`, `drag`, `key`, `fill`, `walk`, `bookmark`, `restore`, `step`, `pause`, `resume`, `capture`, `debug`, `reducedMotion`.
469
+ - `@moku-labs/game/inspect`: `read(app, source, input?)` reads once; `watch(app, source, input, fn)` reads on the `signals` phase when the source changes and returns the stop function; `defineSource`; the frozen catalogue `sources`; the types `Source`, `InputSchema`, `InputOf`. Every source only reads, so the door is safe in a production build. Keys of `sources`: `graph`, `position`, `history`, `tainted`, `cheats`, `model`, `entities`, `projections`, `ui`, `locate`, `render`, `sounds`, `effects`, `assets`, `log`, `explain`, `diff`, `schema`, `at`; their ids are `game.<key>`. `locate` takes `{ key }` (a ui element) or `{ target }` (a view by projection key) and answers its box in CSS px, the numbers Playwright clicks. `at` takes `{ x, y }` in CSS px and lists the views under that point, topmost first. `explain` takes `{ entity }` and answers that entity in full, `undefined` for a stale id. `diff` takes `{ from, to }` frames and answers what changed per entity; it throws in a production build and for a frame outside the last 120. `schema` lists every component and tag type the world met, with the JSON kind of each field. `sounds` throws `[game] The source game.sounds needs audioPlugin.` and `effects` throws `[game] The source game.effects needs effectsPlugin.` when that plugin is not installed; the editor lists such a source as not installed.
470
+ - `@moku-labs/game/control`: `run(app, command, input?)` resolves `{ value, state }`, `state` being `{ path, frame, tainted }`; `defineCommand`; `controlRefused`; the frozen catalogue `commands`; the types `Command`, `Ran`. Keys of `commands`: `answer`, `tap`, `drag`, `key`, `fill`, `walk`, `bookmark`, `restore`, `step`, `pause`, `resume`, `capture`, `debug`, `reducedMotion`, `timeScale`, `trace`. `capture` resolves `{ png }`, and `{ png, legend }` with `legend: true`; `undefined` while the renderer is inert. `timeScale` takes `{ scale }`; 0 freezes game time while the frames keep running. `trace` takes `{ path }`, a list of views by projection key, and answers whether the gate took the answer.
471
471
  - Commands run in dev builds only. Only `__MOKU_GAME_DEV__ === true` turns the dev build on: a bundler `define` (`true` in dev, `false` in production), `globalThis.__MOKU_GAME_DEV__ = true` in the dev entry, or `vi.stubGlobal("__MOKU_GAME_DEV__", true)` in a test. Otherwise `run` throws `[game] Control commands run in dev builds only.`
472
472
  - A command's effect is `read`, `route`, `cosmetic`, `cheat` or `raw`. A `cheat` or `raw` command taints the session and is journaled; read it with `sources.tainted` and `sources.cheats`. Of the base commands only `restore` is `raw`.
473
473
  - A game's own: `defineSource({ id, title, input, changes, read })` with `changes` `"frame"`, `"commit"` or `"edge"`, and `defineCommand({ id, title, input, effect, run })`. The id is camelCase words joined by dots, at least two: `dice.rolls`. An input schema maps field names to `"string"`, `"number"`, `"boolean"` or `"json"`; a trailing `?` makes one optional. A command body starts with `if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();`. Keep them in `.dev` modules that only the dev entry and the tests import.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/game",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -79,11 +79,21 @@
79
79
  "release:doctor": "moku-release doctor",
80
80
  "release": "moku-release"
81
81
  },
82
+ "dependencies": {
83
+ "immer": "11.1.18",
84
+ "maxrects-packer": "2.7.3",
85
+ "yoga-layout": "3.2.1"
86
+ },
82
87
  "peerDependencies": {
88
+ "@moku-labs/common": "^0.3.4",
89
+ "@moku-labs/core": "^1.7.1",
83
90
  "pixi.js": "^8.0.0",
84
91
  "playwright-core": ">=1.50.0",
85
92
  "sharp": "^0.34.0"
86
93
  },
94
+ "publishConfig": {
95
+ "access": "public"
96
+ },
87
97
  "peerDependenciesMeta": {
88
98
  "playwright-core": {
89
99
  "optional": true
@@ -98,6 +108,8 @@
98
108
  "@biomejs/biome": "2.4.16",
99
109
  "@formatjs/icu-messageformat-parser": "3.5.20",
100
110
  "@moku-labs/ci": "1.2.6",
111
+ "@moku-labs/common": "0.3.4",
112
+ "@moku-labs/core": "1.7.1",
101
113
  "@moku-labs/native": "0.3.0",
102
114
  "@moku-labs/system": "0.3.0",
103
115
  "@tauri-apps/api": "2.12.1",
@@ -120,15 +132,5 @@
120
132
  "typescript": "6.0.3",
121
133
  "typescript-eslint": "8.58.0",
122
134
  "vitest": "4.0.18"
123
- },
124
- "dependencies": {
125
- "@moku-labs/common": "0.3.3",
126
- "@moku-labs/core": "1.7.0",
127
- "immer": "11.1.18",
128
- "maxrects-packer": "2.7.3",
129
- "yoga-layout": "3.2.1"
130
- },
131
- "publishConfig": {
132
- "access": "public"
133
135
  }
134
136
  }