@moku-labs/game 0.13.1 → 0.13.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/dist/app/page.d.mts +1 -1
- package/dist/app/system.d.mts +3 -3
- package/dist/app.d.mts +2 -2
- package/dist/app.mjs +1 -1
- package/dist/assets.d.mts +1 -1
- package/dist/assets.mjs +1 -1
- package/dist/{capture-fpd83Mi5.mjs → capture-D7XvoHdH.mjs} +78 -1
- package/dist/cli.d.mts +7 -5
- package/dist/cli.mjs +74 -36
- package/dist/{commands-DvFr8Uwp.d.mts → commands-DRhJ_LIH.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/control.mjs +1 -1
- package/dist/{define-CCc0ozKe.d.mts → define-Ckp70_GR.d.mts} +2 -2
- package/dist/{headless-BsNdJs9W.mjs → headless-BA6ffzqD.mjs} +30 -9
- package/dist/{headless-G4nuZ-xz.d.mts → headless-CYgYzro0.d.mts} +1 -1
- package/dist/hot.d.mts +3 -3
- package/dist/hot.mjs +11 -9
- package/dist/{index-rnOYAriI.d.mts → index-CjuObHhF.d.mts} +12 -4
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +1 -1
- package/dist/inspect.d.mts +7 -7
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{memory-DwDhfFWK.d.mts → memory-DKo9rRWW.d.mts} +1 -1
- package/dist/project.mjs +1 -1
- package/dist/{run-wYty_Lwo.mjs → run-BtyrJjmW.mjs} +130 -40
- package/dist/{runtime-B276Gg0s.d.mts → runtime-iTxTcukZ.d.mts} +1 -1
- package/dist/{src-SmT-XxVf.mjs → src-DlyYl_9X.mjs} +1414 -277
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +2 -2
- package/dist/{types-ZQLYXo5X.d.mts → types-BSMmnpFm.d.mts} +138 -29
- package/dist/{types-Bcdo5-2w.d.mts → types-B_Bp2tWK.d.mts} +7 -3
- package/dist/{types-DAdwEJMp.d.mts → types-Bonifz5M.d.mts} +2 -2
- package/dist/{types-DLghaGEy.d.mts → types-DkO3s9YX.d.mts} +4 -4
- package/dist/{types-BrTpz9oL.d.mts → types-Dm5Oh5bH.d.mts} +19 -6
- package/dist/{types-D_2G6Q68.d.mts → types-dJMOXXL3.d.mts} +21 -13
- package/dist/visual.d.mts +6 -6
- package/dist/visual.mjs +1 -1
- package/llms.txt +10 -7
- package/package.json +4 -2
- package/dist/{assets-DYiKxPBX.mjs → assets-BrKSg1O6.mjs} +1 -1
- package/dist/{watch-CB8IWDBi.mjs → watch-CWI_on4T.mjs} +1 -1
package/llms.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 2D puzzle game engine on TypeScript, built as a Layer-2 framework on @moku-labs/core. The game is a deterministic graph of business logic. State commits only on edges. Position is data. Player input and world events are answers a node waits for. The screen is a projection of committed state. V1 is the logic plugins `time`, `lifecycle`, `model`, `clock`, `flow` and a headless testing entry; V2 adds the screen plugins `world`, `renderer`, `input`, `assets`, `scenes`, a Pixi v8 renderer loaded lazily, and the `@moku-labs/game/assets` entry that generates typed asset keys. V3 adds the interface plugins `anim`, `i18n`, `text`, `ui` (`screen` lists all nine) and the opt-in `audio`: choreographies as data, strings as data, text from MSDF fonts, screens in JSX laid out by Yoga, sound as an effect a node awaits. V4 adds two doors for the editor: `@moku-labs/game/inspect` reads a running game, `@moku-labs/game/control` drives a dev build. V5 adds the opt-in plugin `effects`: particles and WGSL/GLSL filters as components. V6 adds the opt-in plugin `platform`: the Back button, haptics and keep-awake through a provider the game passes. The game shell makes a game a folder of data: `@moku-labs/game/app` (`defineGameApp`, `config.ts` as `GameConfig`), the page `@moku-labs/game/app/page`, the system shell `@moku-labs/game/app/system` and the bin `moku-game` that serves, builds, packages and packs the folder. The package is published on npm as `@moku-labs/game`, and this file ships inside it.
|
|
4
4
|
|
|
5
|
-
Facts: Bun only. ESM only. `"sideEffects"
|
|
5
|
+
Facts: Bun only. ESM only. `"sideEffects"` lists the root entry `./dist/index.mjs` only, every other entry has none. `pixi.js` `^8.0.0` is a peer dependency, no V1 code imports it. `playwright-core` (visual tests), `sharp` (the asset pack), `typescript` (the project index), `@moku-labs/system` (the system shell and the store save) and `@moku-labs/native` (`moku-game native`) are optional peers. Node >= 24, Bun >= 1.3.14. TypeScript strict with `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess`. The engine ships no genre rules: no merge, no match-3, no physics. A game brings its own pure rule functions.
|
|
6
6
|
|
|
7
7
|
## Entry points
|
|
8
8
|
|
|
@@ -318,7 +318,7 @@ await startPage(game, config, {
|
|
|
318
318
|
```
|
|
319
319
|
|
|
320
320
|
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.
|
|
321
|
-
- `moku-game dev [--port 3000] [--packed]`: writes `.moku/index.html`, `dev.ts`, `main.ts`, `assets-stamp.ts`, `bunfig.toml` (the hot plugin first, `__MOKU_GAME_DEV__` defined `true`), runs Bun again under that bunfig in the game folder and serves `/`. It prints `› <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 changed or new string hot swaps through `generated/strings.<locale>.ts`, an asset file
|
|
321
|
+
- `moku-game dev [--port 3000] [--packed]`: writes `.moku/index.html`, `dev.ts`, `main.ts`, `assets-stamp.ts`, `bunfig.toml` (the hot plugin first, `__MOKU_GAME_DEV__` defined `true`), runs Bun again under that bunfig in the game folder and serves `/`. It prints `› <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 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.`
|
|
322
322
|
- `moku-game build [--out dist/web]`: packs the assets, bundles the page minified with `__MOKU_GAME_DEV__` defined `false` (no scenario, no agent, no `.dev` module, no `/control`), every link `./`, and copies the pack beside it with `manifest.json` next to `index.html`. Runs from any http sub-path and from Tauri, not from `file://`.
|
|
323
323
|
- `moku-game native build <target> [--simulator]`, `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`. `[game] config.ts has no native section.` without `native`.
|
|
324
324
|
- `moku-game keys [--check]`: `generated/assets.ts`, the compiled strings and the dev manifest `generated/manifest.json`; `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`.
|
|
@@ -474,14 +474,15 @@ The graph. Modules `runner`, `gate`, `inbox`, `fx`, `features`. Runner methods s
|
|
|
474
474
|
- `fx.handle(kind, handler, options?: { runInFast?: boolean }): () => void` registers the single handler of one effect kind. `fx.dispatch(descriptor)` is fire-and-forget. An awaited effect without a handler resolves `undefined` at once.
|
|
475
475
|
- `features.register(name, description)`, `features.all()`, `features.contributions(slotName)`.
|
|
476
476
|
- `FlowState` is `{ running, path, stack, pending: { fx?, gate? }, mode }`.
|
|
477
|
-
- `Bookmark` is `{ path, input, player, session, rng, graph, scene? }`. Serialisable. `scene` is written by the `game.bookmark` door and read by `game.restore`; `flow.restore` ignores it. `
|
|
477
|
+
- `Bookmark` is `{ path, input, player, session, rng, graph, scene?, rest? }`. Serialisable. `scene` is written by the `game.bookmark` door and read by `game.restore`; `flow.restore` ignores it. `path` names a rest node, or the transit node that waits at the gate for an effect that takes answers (a `popup`, or a hand-written descriptor with `answers`); `rest: { path, input }` then names the rest point before it (left out when the graph started at that node or a restore entered it), and the state is the one committed when that node was entered. `restore` needs a running graph. A rest checkpoint is always accepted. Any other node is accepted only when the graph did not change since the bookmark; with another graph a transit bookmark is entered at `rest` instead when that is a checkpoint, and the log gets `flow:restore-fell-back`.
|
|
478
|
+
- `restore` of a rest node resolves when the node is entered. `restore` of a transit node runs it again from its start, so its effects before the gate play again, and resolves once its gate is open. The open gate is not the draw: the popup shows on the next frame.
|
|
478
479
|
- Events: `flow:edge` `{ flow, node, outcome, payload, next, patches: { doc, session }, index, now }` after the commit of an edge. `flow:rest` `{ path, checkpoint }`. `flow:error` `{ path, error, rolledBackTo, retry }`.
|
|
479
480
|
- Config: `mainFlow` default `undefined` and required before `run()`, `safeNode: string | undefined` default `undefined` which means the main flow's `start`, `retries` default `1`, `settleTimeoutMs` default `2000`, `journalLimit` default `500`.
|
|
480
481
|
|
|
481
482
|
### Screen plugins (V2, built)
|
|
482
483
|
|
|
483
484
|
`world`, `renderer`, `input`, `assets`, `scenes`. Each has a README at `src/plugins/<name>/README.md`; the root README lists their APIs, events and config. Rendering: WebGPU preferred, with Pixi's WebGL fallback.
|
|
484
|
-
- `app.scenes`: `current()` the mounted scene id, `expect(id)` the scene the next
|
|
485
|
+
- `app.scenes`: `current()` the mounted scene id, `expect(id)` the scene the next node without its own `scene` mounts, a rest node or a transit node in live mode (the restore door calls it; an unknown id throws).
|
|
485
486
|
- `resource(name, defaults)` clones plain JSON defaults into every world. `resource(name, () => value)` calls the factory once per world on first read, so the value may hold a `Map` or a function: `const Looks = resource("boardLooks", () => ({ shown: new Map<World.Entity, { look: string }>() }))`. A system keeps its own state there; `snapshot()` leaves a value that is not JSON out.
|
|
486
487
|
- `app.world.projection.replace(spec)` swaps the spec of a registered projection; a mounted one runs its new `view` on the next frame. An unknown name throws. The caller is the dev hot swap.
|
|
487
488
|
|
|
@@ -592,7 +593,7 @@ Two subpaths, neither on the root (V4). An editor, MCP tools and e2e scripts rea
|
|
|
592
593
|
- `@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`, `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. `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` throws `[game] The source game.effects needs effectsPlugin.` when that plugin is not installed; the editor lists such a source as not installed.
|
|
593
594
|
- `@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`, `mute`, `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. `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.
|
|
594
595
|
- Commands run in dev builds only. Only `__MOKU_GAME_DEV__ === true` turns the dev build on: `moku-game dev` defines it `true` and sets the global in `.moku/dev.ts`, `moku-game build` defines it `false`; a game without the shell uses a bundler `define`; a test uses `vi.stubGlobal("__MOKU_GAME_DEV__", true)`. Otherwise `run` throws `[game] Control commands run in dev builds only.`
|
|
595
|
-
- 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`. `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.
|
|
596
|
+
- 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`. `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. 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.
|
|
596
597
|
- 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 page and the tests import: the editor's dev page (`preparePage` with agents) imports every `**/*.dev.ts`, `moku-game build` none.
|
|
597
598
|
|
|
598
599
|
## Project index
|
|
@@ -625,9 +626,11 @@ Dev only. A save of a view file swaps the module in the running page: same state
|
|
|
625
626
|
plugins = ["@moku-labs/game/hot"]
|
|
626
627
|
```
|
|
627
628
|
|
|
628
|
-
- View files swap: `.tsx`, `styles.ts`, `view.ts`, `animations.ts`, `effects.ts`, `generated/strings.<locale>.ts`. On the layered layout also any `.ts` directly in a kind folder: `styles/`, `motion/`, `effects/`, `views/`, `world/projections/`, `world/layout/`, in a feature or in `shared/`. Styles, components, function components, projections of a registered name, animations, text styles, emitters and strings. Under `moku-game dev` or the editor's `watchKeys` a save of a strings JSON compiles by itself and its output swaps, a new key too
|
|
629
|
+
- View files swap: `.tsx`, `styles.ts`, `view.ts`, `animations.ts`, `effects.ts`, `generated/strings.<locale>.ts`. On the layered layout also any `.ts` directly in a kind folder: `styles/`, `motion/`, `effects/`, `views/`, `world/projections/`, `world/layout/`, in a feature or in `shared/`. Styles, components, function components, projections of a registered name, animations, text styles, emitters and strings. Under `moku-game dev` or the editor's `watchKeys` a save of a strings JSON compiles by itself and its output swaps, a new key too. A game with a server of its own runs `bun run assets:keys` after a strings JSON edit; its output is what swaps.
|
|
629
630
|
- Logic files reload the page: `rules/`, `nodes/`, `flows/`, `state.ts`, `tables.ts`, `kit.ts`, a feature `index.ts`, `world/components/`, `world/systems/`. A view file that exports a scene, system, ECS component, filter, flow, node, feature, plugin, a new projection or a new animation is refused: `ui` logs `ui:hot-refused` and throws, and Bun reloads the page.
|
|
630
|
-
-
|
|
631
|
+
- Asset files, under `moku-game dev` on the raw files or the editor's `watchKeys`: a texture, a bitmap font (`.fnt` or its page) or a sound saved with new bytes swaps in the loaded bundles, same key, no reload. The keys watch writes `.moku/assets-stamp.ts` `{ files, changed }`, the hot plugin lets the page accept it, and `assets` reads the changed files again. A new, removed or renamed asset file, a changed nine-slice tag too, reloads the page. So does a replace that fails. Not with `--packed`, not in a built game. A bundle that is not loaded reads the new bytes on its next load. A sound that is playing plays on; a running particle effect of a replaced texture starts again.
|
|
632
|
+
- A swap logs `ui:hot-swap` and emits the global event `ui:hot-swap` `{ file, module }`. `effects` hooks it for emitters, `assets` for the stamp module.
|
|
633
|
+
- Event `assets:replaced` `{ bundle, keys }`, dev only: `assets` emits it once per bundle after an asset swap. `keys` are the asset keys with new bytes; `app.assets.texture(key)`, `font(key)` and `audio(key)` answer the new object and the old textures are destroyed. `text`, `effects` and `audio` hook it; a game plugin that built something from a key hooks it with `depends: [assetsPlugin]` and builds it again.
|
|
631
634
|
- Another layout, on a server of the game's own: `export default hot({ include, exclude })` from the game's `hot.ts`, listed as `plugins = ["./hot.ts"]`.
|
|
632
635
|
- Keep a view helper in the module of the projection that uses it. A function another module stored into an object at load time keeps the old version.
|
|
633
636
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moku-labs/game",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"sideEffects":
|
|
6
|
+
"sideEffects": [
|
|
7
|
+
"./dist/index.mjs"
|
|
8
|
+
],
|
|
7
9
|
"module": "./dist/index.mjs",
|
|
8
10
|
"types": "./dist/index.d.mts",
|
|
9
11
|
"exports": {
|
|
@@ -2,8 +2,8 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createBrandConsole } from "@moku-labs/common/cli";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
6
5
|
import { mkdir, open, readFile, readdir, rm, rmdir, stat, writeFile } from "node:fs/promises";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
7
|
import { MaxRectsBin, MaxRectsPacker } from "maxrects-packer";
|
|
8
8
|
/** Bytes per pixel of the decoded images: RGBA, 8 bits each. */
|
|
9
9
|
const CHANNELS = 4;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { watch } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
3
|
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
5
|
//#region src/project/paths.ts
|
|
6
6
|
/**
|
|
7
7
|
* @file project — which files the index reads, and the guard on every path it reads. Paths in and
|