@moku-labs/game 0.14.13 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/app/page.d.mts +1 -1
  2. package/dist/app/page.mjs +2 -2
  3. package/dist/app/system.d.mts +3 -3
  4. package/dist/app.d.mts +3 -3
  5. package/dist/app.mjs +1 -1
  6. package/dist/assets.d.mts +1 -1
  7. package/dist/cli.mjs +75 -36
  8. package/dist/{commands-DAETmK86.d.mts → commands-BynjWVVy.d.mts} +7 -6
  9. package/dist/control.d.mts +30 -18
  10. package/dist/control.mjs +2 -2
  11. package/dist/{define-BASd9ZUh.d.mts → define-Ckj_TMWu.d.mts} +2 -2
  12. package/dist/{dev-DbHgx-R-.mjs → dev-BNndV-hU.mjs} +4 -4
  13. package/dist/{headless-cFveciuY.d.mts → headless-B0mH9qYg.d.mts} +1 -1
  14. package/dist/{index-DedbED3T.d.mts → index-CCiTo1_3.d.mts} +16 -4
  15. package/dist/index.d.mts +5 -5
  16. package/dist/index.mjs +2 -2
  17. package/dist/inspect.d.mts +6 -6
  18. package/dist/inspect.mjs +1 -1
  19. package/dist/jsx-dev-runtime.d.mts +1 -1
  20. package/dist/jsx-runtime.d.mts +1 -1
  21. package/dist/project.d.mts +4 -4
  22. package/dist/project.mjs +1 -1
  23. package/dist/{run-BbipFRc6.mjs → run-C07NXds7.mjs} +35 -25
  24. package/dist/{runtime-DoSlRx8X.d.mts → runtime-BV3xv6gx.d.mts} +1 -1
  25. package/dist/{sources-Cdvb0rOS.mjs → sources-OvHvyeJS.mjs} +1 -1
  26. package/dist/{src-BEbJ_sSx.mjs → src-CwVzErzN.mjs} +1921 -652
  27. package/dist/testing.d.mts +4 -4
  28. package/dist/testing.mjs +1 -1
  29. package/dist/{types-GHE4J5Uf.d.mts → types-Bw-qLjFZ.d.mts} +2 -2
  30. package/dist/{types-C73hbmxi.d.mts → types-CpdkyhaC.d.mts} +4 -4
  31. package/dist/{types-DngFQJro.d.mts → types-D44EL4gT.d.mts} +2 -2
  32. package/dist/{types-BGcda5Le.d.mts → types-E2ykwIUO.d.mts} +556 -42
  33. package/dist/{types-7GXcXxxv.d.mts → types-G3ZAAsbd.d.mts} +20 -1
  34. package/dist/{visual-_XhoGeIC.d.mts → visual-DoHwXeQi.d.mts} +6 -6
  35. package/dist/visual.d.mts +1 -1
  36. package/dist/visual.mjs +2 -2
  37. package/dist/{watch-BEdrp80i.mjs → watch-BSgptowj.mjs} +16 -6
  38. package/llms.txt +6 -4
  39. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { Ar as Api, Br as Require, Gt as AnyComponentType, It as TransformValue, Jn as Descriptor, Kt as AnyComponentValue, Qn as Hint, T as Api$3, Wt as AnyComponent, Yt as ComponentType, Zt as Entity, bn as Api$1, bt as ViewportSize, dn as MotionCause, er as Answer, fn as MotionHandle, mn as ProjectionMotion, on as TagType, pn as NumericFields, s as Events$1, sn as ChangeHook, t as Api$4, tr as Api$5, un as Motion, ur as Json, yn as ViewHandle, zt as Api$2 } from "./types-BGcda5Le.mjs";
1
+ import { An as Api$1, At as ViewportSize, Br as Api, Cn as NumericFields, Kt as TransformValue, Sn as MotionHandle, T as Api$3, Xr as Require, Yt as Api$2, _n as ChangeHook, an as ComponentType, bn as Motion, br as Json, cr as Hint, dr as Api$5, en as AnyComponent, gn as TagType, ir as Descriptor, kn as ViewHandle, nn as AnyComponentValue, s as Events$1, sn as Entity, t as Api$4, tn as AnyComponentType, ur as Answer, wn as ProjectionMotion, xn as MotionCause } from "./types-E2ykwIUO.mjs";
2
2
  import { Log } from "@moku-labs/common/browser";
3
3
  import { PluginCtx } from "@moku-labs/core";
4
4
 
@@ -1211,6 +1211,25 @@ type InputApi = {
1211
1211
  * ```
1212
1212
  */
1213
1213
  cursor(): string;
1214
+ /**
1215
+ * How many listeners and controls are registered now: every `onTap`, `onPointer` and `onKey`
1216
+ * listener and every `controls.add` registration whose remover has not run. The pointer and key
1217
+ * listeners input itself opens on the canvas are not counted. A screen entered and left returns
1218
+ * the count to where it was; a count that grows with every visit is a listener left behind. It
1219
+ * reads four lengths and allocates nothing, so a monitor may call it every second.
1220
+ *
1221
+ * @returns The number of registered listeners and controls.
1222
+ * @example
1223
+ * ```ts
1224
+ * // A test checks that a popup takes its Escape listener back when it closes.
1225
+ * const before = app.input.listeners();
1226
+ * const off = app.input.onKey(key => key.key === "Escape");
1227
+ * app.input.listeners() - before; // 1
1228
+ * off();
1229
+ * app.input.listeners() - before; // 0
1230
+ * ```
1231
+ */
1232
+ listeners(): number;
1214
1233
  /**
1215
1234
  * The components that make a view a control besides input's own: the cursor shows
1216
1235
  * `config.cursor.control` over a view that carries one of them.
@@ -1,9 +1,9 @@
1
- import { T as Api, sr as Api$2, wr as Api$3, zt as Api$1 } from "./types-BGcda5Le.mjs";
2
- import { P as AnimApi, r as UiApi } from "./types-GHE4J5Uf.mjs";
3
- import { j as InputApi } from "./types-7GXcXxxv.mjs";
4
- import { r as Repro } from "./headless-cFveciuY.mjs";
5
- import { a as InputOf, r as ControlApp } from "./types-DngFQJro.mjs";
6
- import { t as commands } from "./commands-DAETmK86.mjs";
1
+ import { Pr as Api$3, T as Api, Yt as Api$1, _r as Api$2 } from "./types-E2ykwIUO.mjs";
2
+ import { P as AnimApi, r as UiApi } from "./types-Bw-qLjFZ.mjs";
3
+ import { j as InputApi } from "./types-G3ZAAsbd.mjs";
4
+ import { r as Repro } from "./headless-B0mH9qYg.mjs";
5
+ import { a as InputOf, r as ControlApp } from "./types-D44EL4gT.mjs";
6
+ import { t as commands } from "./commands-BynjWVVy.mjs";
7
7
 
8
8
  //#region src/visual/types.d.ts
9
9
  /** The base commands of the `/control` door, keyed by their short names. */
package/dist/visual.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as VisualApp, c as VisualRenderer, d as VisualStart, f as VisualStep, h as VisualTolerance, i as CheckpointResult, l as VisualReport, m as VisualTestResult, n as runVisualTests, o as VisualOptions, p as VisualTest, r as defineVisualTest, s as VisualPage, t as parseVisualArgv, u as VisualSetup } from "./visual-_XhoGeIC.mjs";
1
+ import { a as VisualApp, c as VisualRenderer, d as VisualStart, f as VisualStep, h as VisualTolerance, i as CheckpointResult, l as VisualReport, m as VisualTestResult, n as runVisualTests, o as VisualOptions, p as VisualTest, r as defineVisualTest, s as VisualPage, t as parseVisualArgv, u as VisualSetup } from "./visual-DoHwXeQi.mjs";
2
2
  export { type CheckpointResult, type VisualApp, type VisualOptions, type VisualPage, type VisualRenderer, type VisualReport, type VisualSetup, type VisualStart, type VisualStep, type VisualTest, type VisualTestResult, type VisualTolerance, defineVisualTest, parseVisualArgv, runVisualTests };
package/dist/visual.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { n as read, t as sources } from "./sources-Cdvb0rOS.mjs";
2
- import { n as commands, t as run } from "./run-BbipFRc6.mjs";
1
+ import { n as read, t as sources } from "./sources-OvHvyeJS.mjs";
2
+ import { n as commands, t as run } from "./run-C07NXds7.mjs";
3
3
  import path from "node:path";
4
4
  import { createBrandConsole } from "@moku-labs/common/cli";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
@@ -161,7 +161,8 @@ const DEFINERS = [
161
161
  "defineEmitter",
162
162
  "defineTextStyles",
163
163
  "defineStyle",
164
- "defineComponent"
164
+ "defineComponent",
165
+ "system"
165
166
  ];
166
167
  /**
167
168
  * Whether a name is a definer whose calls make keys.
@@ -1859,6 +1860,11 @@ const ID_PREFIXES = {
1859
1860
  defineEmitter: "emitter",
1860
1861
  defineComponent: "component"
1861
1862
  };
1863
+ /** The key prefix of each definer that takes its id as the `name` of its first-argument options. */
1864
+ const NAME_PREFIXES = {
1865
+ projection: "projection",
1866
+ system: "system"
1867
+ };
1862
1868
  /**
1863
1869
  * The text of a string literal or a template without holes.
1864
1870
  *
@@ -2040,20 +2046,23 @@ function readIdCall(reading, call, definer, prefix) {
2040
2046
  if (definer === "defineFlow") readTable(reading, call, id);
2041
2047
  }
2042
2048
  /**
2043
- * Reads a projection: its key is its `name`, its line the `name` property.
2049
+ * Reads a definer named by its options, a projection or a system: its key is its `name`, its line
2050
+ * the `name` property.
2044
2051
  *
2045
2052
  * @param reading - The file being read.
2046
2053
  * @param call - The call.
2054
+ * @param definer - The definer.
2055
+ * @param prefix - The key prefix.
2047
2056
  */
2048
- function readProjection(reading, call) {
2057
+ function readNameCall(reading, call, definer, prefix) {
2049
2058
  const { typescript } = reading;
2050
2059
  const spec = objectOf(typescript, call.arguments[0]);
2051
2060
  const name = spec === void 0 ? void 0 : literalOf(typescript, propertyValue(typescript, spec, "name"));
2052
2061
  if (name === void 0) {
2053
- unresolve(reading, "projection name is not a string literal");
2062
+ unresolve(reading, `${definer} name is not a string literal`);
2054
2063
  return;
2055
2064
  }
2056
- define(reading, call, `projection:${name}`, name, { key: "name" });
2065
+ define(reading, call, `${prefix}:${name}`, name, { key: "name" });
2057
2066
  }
2058
2067
  /**
2059
2068
  * Reads a text style table: every literal key is a key.
@@ -2174,8 +2183,9 @@ function readStyle(reading, call) {
2174
2183
  */
2175
2184
  function readCall(reading, call, definer) {
2176
2185
  const prefix = ID_PREFIXES[definer];
2186
+ const namePrefix = NAME_PREFIXES[definer];
2177
2187
  if (prefix !== void 0) readIdCall(reading, call, definer, prefix);
2178
- if (definer === "projection") readProjection(reading, call);
2188
+ if (namePrefix !== void 0) readNameCall(reading, call, definer, namePrefix);
2179
2189
  if (definer === "defineTextStyles") readTextStyles(reading, call);
2180
2190
  if (definer === "defineStyle") readStyle(reading, call);
2181
2191
  }
package/llms.txt CHANGED
@@ -328,7 +328,7 @@ await startPage(game, config, {
328
328
  Commands of the bin `moku-game` (bun). Every command takes `--root <dir>` (default `.`), `--preload <path>` and `--serve-plugin <path>` (both repeat), before or after the command word. Exit 0 on success, else 1 with a `[game] …` line.
329
329
  - `moku-game dev [--port 3000] [--packed]`: writes `.moku/index.html`, `script.ts` (the `moku:script` startup mark), `dev.ts` (sets `__MOKU_GAME_BUILD__` `"dev"`, the engine id `__MOKU_GAME_ENGINE__`, `__MOKU_GAME_CONTROL__`, `__MOKU_GAME_AGENT__` and last `__MOKU_GAME_DEV__`), `main.ts`, `assets-stamp.ts`, `bunfig.toml` (the hot plugin first, `__MOKU_GAME_DEV__`, `__MOKU_GAME_AGENT__` and `__MOKU_GAME_CONTROL__` defined `true`), runs Bun again under that bunfig in the game folder and serves `/`. It prints `› engine <id>` (and the patch file when there is one), then `› <folder>: dev server, raw assets. Ctrl+C stops it.` and then the bound URL alone on its own line, `http://localhost:3000/`. `--port 0` takes a free port. `--packed` serves `dist/assets`. A new or removed file in `tests/scenarios/` rewrites `main.ts`. On the raw files it keeps `generated/` fresh: it runs the scan of `keys` once before the page is written, and a save of an asset file (`.png`, `.webp`, `.fnt`, `.mp3`, `.m4a`) or of a `strings/<locale>.json` runs it again 100 ms after the last file event and prints `› keys: <path>` or `› keys: <path> and <n> more`; a folder that becomes or stops being a sub-feature (it gains or loses its door, `assets/`, `assets.ts` or `strings/`) scans too, `› keys: features/episodes/ep03/`; a changed or new string hot swaps through `generated/strings.<locale>.ts`, an asset file saved with new bytes swaps in the running page and a new or removed one reloads it, both through `.moku/assets-stamp.ts`, which `main.ts` imports (see "Hot swap"), and files answer `Cache-Control: no-store`. A failed scan is one warning `[game] keys: <message>` and the server runs on. A changed layer in `config.ts` needs a restart; `--packed` watches nothing. A taken port: `[game] dev: port 3000 is in use.` with `Pass --port 0 for a free port.` It watches the installed engine: 300 ms after a new version or dist it warns `[game] dev: the engine changed (<old id> -> <new id>), restarting the dev server.`, stops the child with SIGTERM, writes the page again with the new id and starts a new child (a fixed port keeps its URL, `--port 0` prints a new one); a patch file that changed while the engine did not is one warning that asks for `bun install`.
330
330
  - `moku-game build [--out dist/web]`: packs the assets, bundles the page minified with `__MOKU_GAME_DEV__`, `__MOKU_GAME_AGENT__` and `__MOKU_GAME_CONTROL__` defined `false` (no scenario, no agent, no `.dev` module, no `/control`, no engine id), every link `./`, and copies the pack beside it with `manifest.json` next to `index.html`. Every page marks `moku:script` first and `moku:main` first after its imports. A release page whose `.js` carries `"/__editor/hello"` or `"[moku-editor]"` (the editor's agent) fails with `[game] build: the release page carries the editor agent (…)` and its output folder is removed. Runs from any http sub-path and from Tauri, not from `file://`.
331
- - `moku-game build --debug[=full] [--editor host:port]`: a debug page, never shipped: the release page plus `@moku-labs/editor/agent/page` (resolved from the game, else `[game] build --debug needs @moku-labs/editor in the game.`) and the game's `.dev` modules, started after the app; DEV `false`, AGENT `true`, CONTROL `false` (`--debug`, read and cosmetic commands) or `true` (`--debug=full`, every command); `__MOKU_GAME_BUILD__` `debug-<UTC stamp>` or `debug-full-<stamp>`, `__MOKU_GAME_ENGINE__` the engine id (`<version>+<dist hash>[.patch.<hash>]`, `+src` from sources), `__MOKU_GAME_EDITOR__` the `--editor` address (default `127.0.0.1:47600`); the title reads `<title> Debug`; no hot swap, no frame recorder, no audio journal, no scenario. It prints one warning naming the profile, the build id, the engine and the editor. `MOKU_NATIVE_PROFILE=store` with `--debug` is refused.
331
+ - `moku-game build --debug[=read] [--editor host:port]`: a debug page, never shipped: the release page plus `@moku-labs/editor/agent/page` (resolved from the game, else `[game] build --debug needs @moku-labs/editor in the game.`) and the game's `.dev` modules, started after the app; DEV `false`, AGENT `true`, CONTROL `true` (`--debug`: every command, cheats too; `--debug=full` is an alias that prints one line) or `false` (`--debug=read`, the rare read-only page: read and cosmetic commands); `__MOKU_GAME_BUILD__` `debug-<UTC stamp>` for both, `__MOKU_GAME_ENGINE__` the engine id (`<version>+<dist hash>[.patch.<hash>]`, `+src` from sources), `__MOKU_GAME_EDITOR__` the `--editor` address (default `127.0.0.1:47600`); the title reads `<title> Debug`; no hot swap, no frame recorder, no audio journal, no scenario. It prints one warning naming the profile, the build id, the engine and the editor. `MOKU_NATIVE_PROFILE=store` with `--debug` is refused.
332
332
  - `moku-game native build <target> [--simulator] [--debug[=full] [--editor host:port]]`, `native dev <target>`, `native doctor [<target>]`, `native clean [<target>]`: one verb of `@moku-labs/native` (a dev dependency, `^0.3.2`) over `config.ts`. App name, identifier and icon from `native`, orientation and background colour from `page`; the web build is `moku-game build`, the dev server `moku-game dev --port 5173`; system rows for `back`, `haptics`, `store`; the Tauri project in `.moku/tauri`, the apps in `dist-native`; an Android app composes native's `shell` system first. `native build android --debug` builds a debug app (`debug: true` to native, which installs it beside the store app; the web build is `moku-game build --debug` with the same profile and `--editor`); other verbs and targets refuse `--debug`. `[game] config.ts has no native section.` without `native`.
333
333
  - `moku-game keys [--check]`: `generated/assets.ts`, the compiled strings (`strings.ts`, `strings.<locale>.ts` for the top-level features and the layers, and `strings.<feature>.<locale>.ts` for every sub-feature with string files) and the dev manifest `generated/manifest.json` (a sub-feature's bundle carries its `dir` and `parent`); `dev` runs the same scan at start and on save and serves the manifest on `/manifest.json`, `visual` stops without it. `moku-game pack [--no-cache]`: `dist/assets`. Both pass the layers of `config.ts`.
334
334
  - `moku-game visual [--update] [--only <name>] [--no-pixels | --pixels] [--webgl] [--dir <path>] [--tests <file>] [--url <url>]`: runs `runVisualTests` with the default export of `tests/visual/index.ts`, `{ app, tests }` (`app` is a `VisualSetup` `{ app, page? }`: `export default { app: { app: fixtureApp }, tests }`), against `tests/visual/baselines/`. The flags go through `parseVisualArgv`. The pixel leg runs on a Mac only unless `--pixels`/`--no-pixels`; without `--url` the command writes its own page into `.moku/visual/` (the page of `dev` and of the editor in `.moku/` stays), runs Bun again under `.moku/visual/bunfig.toml` as `dev` does (hot plugin, `--serve-plugin`, `--preload`), and the child serves the page on a free port, then stops it. No `manifest.json`: `[game] visual: no manifest.json in "<game>".` Exit 0 when the report is ok, else 1. `[game] visual: <file> must export default { app, tests }.` for a missing module or another shape, a bare app factory too.
@@ -534,6 +534,8 @@ The graph. Modules `runner`, `gate`, `inbox`, `fx`, `features`. Runner methods s
534
534
  - `pluginConfigs.renderer.onDemand: true` draws a frame only when something visible changed (a written view, a filter value, a resize, a wake, a capture); `app.renderer.stats().drawn` is the frames drawn per second (`fps` without it); a game that writes into its own `Display` object calls `app.renderer.invalidate(entity)` before the write; `renderer.sync.debug.verifyFrames(true)` (dev/debug) checks skipped frames by pixels and logs `renderer:missed-frame`; a dev build warns `renderer:missed-mark` for a write without the call.
535
535
  - `app.renderer.stats()` adds `textureMbSplit: { art, targets, kept }` (MiB). `app.renderer.host.resolution()` is the resolution drawn at: `override ?? min(devicePixelRatio, maxResolution)`; `maxResolution` is a number (default 2), a change of `devicePixelRatio` is followed from the next frame, and `app.renderer.monitor.setResolution(n | undefined)` overrides it in every build, the way to apply a player's graphics setting from an effect (0.5 to 4, else a `RangeError`).
536
536
  - `app.renderer.monitor` (agent builds; nothing otherwise): `stream(listener)` the performance log, one frozen JSON line at a time (`PerfLogLine`, `v` = `PERF_LOG_VERSION` = 1, `t` page epoch ms, `build`): `header`, `startup`, a `sample` a second, `spike` (a frame over its budget, with its passes, GPU time, systems, compiles, filters per kind, long frames), `longFrame`, `event`, `mark`, `thermal`; `frames()` the frame ring (typed-array columns, `read(into, cursor)` copies without allocating; `frameAt(ring, age)` gives one record); `mark(label)`; `event(name, detail?)`; `beginAgent()`/`endAgent()` for engine plugins. Config `renderer.monitor: { budgetMs: "rate", frames: 1024 }`: `"rate"` makes the spike budget one frame of the frame's rate (8.33 ms at 120 fps, 16.7 at 60). The engine sends nothing anywhere.
537
+ - The telemetry of a debug build (0.15, PerfLog still `v: 1`, every field additive): the header carries `app: { id }` (`__MOKU_GAME_APP__`, `"com.mokulabs.timber.debug"` in a native debug build, else `null`) and `debug: { overdraw }`. The pass ring (`frames().passes`) gains `owner` (the entity whose filter drew the pass, 0 none), `kind2` (the filter kind, 1-based: `frames().kindNames[kind2 - 1]`, 0 none), `drawsAt` (the frame's draws when the pass began; WebGPU), `format` and `resolution` of its target; `frames().systemNames` names `sysIdx` (a new array when a system registers). A spike's `passes[]` add `owner`, `view` (the owner's view label), `filter`, `draws`, `format`, `resolution`, `kept`. Root exports `PASS_KINDS` (`passes.kind` by index), `SHADER_WHATS`, `PROBLEM_RULES`, types `ShaderEventDetail`, `ProblemEventDetail`, `PerfCounts`. Event `shader` `{ what: "compile" | "module", label, ms, frame, boot, during: { pass, filter, owner, view } }`, one per pipeline or shader module, written at the flush, boot compiles included (`boot: true`); the old `compile { frame, count }` stays. Event `problem` once per crossing, judged once a second: `filter-still` (a filtered view the kept cache does not keep stood still `stillFrames` drawn frames, filtered in every one over `stillMpx`; `reason: "budget" | "not-eligible"`), `target-churn` (render targets made and dropped `churnPerSecond` a second for `churnSeconds`, or made while idle, or within `churnAfterTrimMs` after a pool trim; `reason: "rate" | "idle" | "after-trim"`), `retakes` (a kept view taken again 3 times in 2 s; `value` per second); `{ rule, cleared: true, owner, view }` when it stops. Thresholds are flat keys of `renderer.monitor`: `stillFrames: 60, stillMpx: 0.25, churnPerSecond: 4, churnSeconds: 3, churnAfterTrimMs: 1000`. A `sample` adds `counts: { entities, views, timers, frameListeners, clockListeners, …registered }` (`timers` = `clock.pending()`; input registers `inputListeners`) and `targets: { created, destroyed }` (WebGPU, else `null`); `stats().counts` reads the same. `monitor.counts.register(name, read)` adds a count (agent builds; at most 16, the 17th throws `[game] Monitor count table is full (16).`; the renderer's `onStop` drops them). Event `debug { overdraw }` marks the overdraw switch. The editor's collector writes `telemetry` and `gap` lines into its copy of a log (`PerfTelemetryLine`, `PerfGapLine`); the engine never does.
538
+ - Overdraw heat map (dev and debug builds): `app.renderer.sync.debug.overdraw(true)` (or `game.debug { overdraw: true }`) draws every frame so a pixel's brightness counts the layers drawn there: 1/16 white a layer, white at sixteen. While on, every frame is drawn (also with `onDemand`), kept pictures draw live and filters are off; only the stage pass changes, the tree between frames stays the game's, and off draws the game again from the next frame. Texels a texture leaves transparent count nothing.
537
539
  - `app.renderer.viewport.size()` answers one frozen object, replaced only when the frame changes (a resize, a rotation, new insets, a new scale): compare frames with `===`; a write into it throws a `TypeError`.
538
540
  - `pluginConfigs.renderer.warm` (default `1`, 0 to 16) is the pipeline warm-up: while a game loads, each drawn frame first draws that many invisible probes (sprites, graphics, text, particles, every filter kind and stage `effects` registers, on the canvas and on a texture, with and without a clip), wiped by the stage pass, so no screen creates a GPU pipeline in the frame it opens in; `0` turns it off. `app.renderer.sync.filters.warm(filter)` warms a game's own filter (answers `true` once drawn). An agent build writes a `warm` event `{ draws }` when the queue empties.
539
541
  - `defineFilter(id, { area: value => [x, y, width, height] })` declares the box, in the entity's own units, outside of which the filter draws nothing: Pixi renders and fills only that box (a lens over a full-screen cover fills its disc, not the screen). The view gets the union of its enabled filters' areas, none when an enabled filter declares none (`renderer.sync.filters.area(entity, area)`). Distance-field text uses device pixels for its edge: sharper at a resolution above 1.
@@ -651,8 +653,8 @@ Two subpaths, neither on the root (V4). An editor, MCP tools and e2e scripts rea
651
653
 
652
654
  - `@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`, `audioMuted`, `effects`, `filters`, `assets`, `log`, `explain`, `diff`, `schema`, `at`; their ids are `game.<key>`. `filters` answers `{ kinds, off }` of `effects.diagnostics`. `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. `audioMuted` answers whether the master bus is muted. `sounds` throws `[game] The source game.sounds needs audioPlugin.`, `audioMuted` throws `[game] The source game.audioMuted needs audioPlugin.` and `effects` and `filters` throw `[game] The source game.effects needs effectsPlugin.` (`game.filters` for `filters`) when that plugin is not installed; the editor lists such a source as not installed.
653
655
  - `@moku-labs/game/control`: `run(app, command, input?)` resolves `{ value, state }`, `state` being `{ path, frame, tainted }`; `defineCommand`; `controlRefused`; `controlReadOnly`; the frozen catalogue `commands`; the types `Command`, `Ran`, `ControlReadOnlyError`. Keys of `commands`: `answer`, `tap`, `drag`, `key`, `fill`, `walk`, `bookmark`, `restore`, `saveExport`, `saveImport`, `step`, `pause`, `resume`, `capture`, `debug`, `reducedMotion`, `timeScale`, `frameRate`, `mute`, `trace`, `filter`. `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. `frameRate` takes `{ fps? }`, calls `time.setRate(fps ?? "auto")` and answers `time.rate()`. `mute` takes `{ muted }`, mutes the master bus and answers the flag. Without `audioPlugin` it throws `[game] The command game.mute needs audioPlugin.` `trace` takes `{ path }`, a list of views by projection key, and answers whether the gate took the answer. `filter` takes `{ kind, on }`, switches the filter kind through `effects.diagnostics` (cosmetic) and answers the off kinds; an unknown kind throws `[game] No filter kind "fx.nope".` naming the known ones; without `effectsPlugin` it throws `[game] The command game.filter needs effectsPlugin.`
654
- - Commands run in dev builds and debug builds. `__MOKU_GAME_DEV__ === true` (the dev page, a test's `vi.stubGlobal("__MOKU_GAME_DEV__", true)`) or `__MOKU_GAME_CONTROL__ === true` (`--debug=full`) runs every command; `__MOKU_GAME_AGENT__ === true` alone (`--debug`) runs the `read` and `cosmetic` ones and `run` refuses the others with `controlReadOnly(id)`, an error with `reason: "read-only"`: `[game] The command game.tap changes the game: this debug build runs read and cosmetic commands only.`; no flag: `run` throws `[game] Control commands run in dev builds only.` `moku-game build` defines the three flags `false`; a game without the shell uses a bundler `define` and should add `__MOKU_GAME_AGENT__: "false"` and `__MOKU_GAME_CONTROL__: "false"`. `saveExport` answers the committed `{ player, session, rng }` (read); `saveImport` takes `{ save }` of `saveExport`, restores it at the main flow's start and flushes the store (raw, `--debug=full` only).
655
- - 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 `restore` and `saveImport` are `raw`; `capture` with `diff` writes the state too, so a `--debug` page refuses it. `bookmark` answers `flow.bookmark()` plus `scene`, the mounted scene; `restore` calls `scenes.expect(scene)` before `flow.restore`, so a bookmark at a popup restores the scene under it, also on a page that shows another scene. A bookmark taken while an effect that takes answers waits names the waiting transit node (`info/show` in the mini game) and carries `rest`; `restore` of it answers once that node's gate is open, with `pending.gate` in its value, and the popup shows on the next frame.
656
+ - Commands run in dev builds and debug builds. `__MOKU_GAME_DEV__ === true` (the dev page, a test's `vi.stubGlobal("__MOKU_GAME_DEV__", true)`) or `__MOKU_GAME_CONTROL__ === true` (`--debug`) runs every command; `__MOKU_GAME_AGENT__ === true` alone (`--debug=read`) runs the `read` and `cosmetic` ones and `run` refuses the others with `controlReadOnly(id)`, an error with `reason: "read-only"`: `[game] The command game.tap changes the game: this debug build runs read and cosmetic commands only.`; no flag: `run` throws `[game] Control commands run in dev builds only.` `moku-game build` defines the three flags `false`; a game without the shell uses a bundler `define` and should add `__MOKU_GAME_AGENT__: "false"` and `__MOKU_GAME_CONTROL__: "false"`. `saveExport` answers the committed `{ player, session, rng }` (read); `saveImport` takes `{ save }` of `saveExport`, restores it at the main flow's start and flushes the store (raw: not on a `--debug=read` page).
657
+ - 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 `restore` and `saveImport` are `raw`; `capture` with `diff` writes the state too, so a `--debug=read` page refuses it. `bookmark` answers `flow.bookmark()` plus `scene`, the mounted scene; `restore` calls `scenes.expect(scene)` before `flow.restore`, so a bookmark at a popup restores the scene under it, also on a page that shows another scene. A bookmark taken while an effect that takes answers waits names the waiting transit node (`info/show` in the mini game) and carries `rest`; `restore` of it answers once that node's gate is open, with `pending.gate` in its value, and the popup shows on the next frame.
656
658
  - A game's own: `defineSource({ id, title, input, changes, read })` with `changes` `"frame"`, `"commit"` or `"edge"`, and `defineCommand({ id, title, input, effect, run, overlay?, value? })`: `overlay: "toggle"` (input exactly `{ on: "boolean" }` and `value: app => boolean`, the switch's state) or `overlay: "button"` (no required input, no value) lists the command in the editor's overlay, which shows only such commands; any other combination is a type error. 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();`: it runs in dev and is refused on a debug page. A `.dev` command meant for a debug page writes the agent form for a read or cosmetic effect, `if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();`, and the control form (`__MOKU_GAME_CONTROL__` in place of `__MOKU_GAME_AGENT__`) for the others. Keep them in `.dev` modules that only the dev page, a debug page and the tests import: the editor's dev page (`preparePage` with agents) and `moku-game build --debug` import every `**/*.dev.ts`, `moku-game build` none.
657
659
 
658
660
  ## Project index
@@ -671,7 +673,7 @@ project.close();
671
673
 
672
674
  - `openProject({ root, manifest?, tsconfig?, debounceMs? })` resolves `ProjectApi`: `index` (frozen, replaced per batch), `find(key)` (a `Found[]`: the anchor plus `line`, `range` `[startLine, startCol, endLine, endCol]` 1-based with an exclusive end column, `hash`, and `broken: true` when the file does not parse now), `watch(onIndex)` (one call per batch that changed bytes, with `(index, change)`; returns the stop function), `changed(path)` (re-index one root-relative path now, no watch call), `close()`. Without `typescript` it rejects with `[game] The project index needs the "typescript" package.`
673
675
  - Aliases: the index follows imports through the `compilerOptions.paths` of `tsconfig.json` at the root (or the root-relative `tsconfig`), `extends` included, as TypeScript does, so `@core/kit`, `@shared` and `@features/home` reach their files, and `@features/episodes/ep01` the door of a sub-feature. `index.tsconfig` names that file when it holds `paths`. `@moku-labs/game` stays a package. A tsconfig edit rebuilds the aliases in the next watch batch.
674
- - Keys: `flow:<id>`, `node:<flow>/<node>` (every entry of a `nodes` table: node, sub-flow, slot), `feature:<id>` (at any depth; a feature made by a factory has no `feature:` key, its folder is in `features`), `scene:<id>`, `projection:<name>`, `emitter:<id>`, `textStyle:<key>`, `style:<path>#<binding>` (a module-level `const` of `defineStyle`), `style:<path>#<function>` (every `defineStyle` call inside a module-level function or function-valued const; `find` answers each call) and `style:<path>#<function>.<property>` (a call under an object property there; a style in a class, an object const or at module scope stays unresolved), `component:<Name>` (in a `.tsx` file a module-level upper-case `function Name` or function-valued `const Name`; anywhere `defineComponent("Name", …)`, bound to its const; `uses` are the files that render `<binding>`), `jsx:<key>`. Definers are recognised by binding from the kit's `defineGame()` destructure, renamed imports too. A non-literal id goes to `index.unresolved` with the reason, never guessed.
676
+ - Keys: `flow:<id>`, `node:<flow>/<node>` (every entry of a `nodes` table: node, sub-flow, slot), `feature:<id>` (at any depth; a feature made by a factory has no `feature:` key, its folder is in `features`), `scene:<id>`, `projection:<name>`, `system:<name>`, `emitter:<id>`, `textStyle:<key>`, `style:<path>#<binding>` (a module-level `const` of `defineStyle`), `style:<path>#<function>` (every `defineStyle` call inside a module-level function or function-valued const; `find` answers each call) and `style:<path>#<function>.<property>` (a call under an object property there; a style in a class, an object const or at module scope stays unresolved), `component:<Name>` (in a `.tsx` file a module-level upper-case `function Name` or function-valued `const Name`; anywhere `defineComponent("Name", …)`, bound to its const; `uses` are the files that render `<binding>`), `jsx:<key>`. Definers are recognised by binding from the kit's `defineGame()` destructure, renamed imports too. A non-literal id goes to `index.unresolved` with the reason, never guessed.
675
677
  - JSX keys are patterns: literal text stays, same-file consts and functions are followed, a key-carrying prop (`id`, or a name that ends in `Key`) becomes a hole of its name, `props.id` reads `{id}` and `props.amountKey` reads `{amountKey}`, any other hole `*`. `` key={`${id}Picture`} `` reads `jsx:card*Picture` (stem `card`); `id="settingsBoard"` on `<Signboard>` reads `jsx:settingsBoard` (kind `idProp`, component `Signboard`, prop `id`); `amountKey="giftReward"` on `<Amount>` reads `jsx:giftReward` (prop `amountKey`). `find("jsx:settingsBoardClose")`, a key `game.locate` reports, answers the `{id}Close` pattern of the kit and the `id=` prop that fills it; a hole takes only the props of its own name.
676
678
  - `index.features` holds every feature by name, its folder and, for a sub-feature, its parent: `{ ep01: { dir: "features/episodes/ep01", parent: "episodes" }, home: { dir: "features/home" } }`. The feature owners and the code-only sub-features a door lists are in it; a folder that becomes or stops being a sub-feature rebuilds the index and its name is in `change.features`.
677
679
  - `ProjectChange` is `{ revision, files, moved: [{ key, from, to }], removed, features? }`. A broken file keeps its last good keys and gets `files[path].state === "broken"`. Paths are root-relative POSIX; a path that leaves the root is refused.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/game",
3
- "version": "0.14.13",
3
+ "version": "0.15.1",
4
4
  "description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
5
5
  "type": "module",
6
6
  "sideEffects": [