@moku-labs/game 0.5.0 → 0.7.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 +6 -3
- package/bin/moku-game-index.mjs +4 -0
- package/dist/lint.d.mts +27 -6
- package/dist/lint.mjs +180 -52
- package/dist/project.d.mts +278 -0
- package/dist/project.mjs +3113 -0
- package/llms.txt +25 -2
- package/package.json +12 -3
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 package is published on npm as `@moku-labs/game`, and this file ships inside it.
|
|
4
4
|
|
|
5
|
-
Facts: Bun only. ESM only. `"sideEffects": false`. `pixi.js` `^8.0.0` is a peer dependency, no V1 code imports it. `playwright-core` (visual tests)
|
|
5
|
+
Facts: Bun only. ESM only. `"sideEffects": false`. `pixi.js` `^8.0.0` is a peer dependency, no V1 code imports it. `playwright-core` (visual tests), `sharp` (the asset pack) and `typescript` (the project index) 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
|
|
|
@@ -13,6 +13,7 @@ Facts: Bun only. ESM only. `"sideEffects": false`. `pixi.js` `^8.0.0` is a peer
|
|
|
13
13
|
- `@moku-labs/game/control`: the write door of the editor, dev builds only. See "Doors for the editor".
|
|
14
14
|
- `@moku-labs/game/hot` (the Bun dev server only): the `bunfig.toml` plugin that hot swaps views in a running page. See "Hot swap".
|
|
15
15
|
- `@moku-labs/game/lint`: the oxlint JS plugin `moku-game` with the game lint rules. See "Lint for games".
|
|
16
|
+
- `@moku-labs/game/project` (node and bun only): the project index, where every engine id of a game is defined, line read fresh at the call. The package bin `moku-game-index` runs it. See "Project index".
|
|
16
17
|
- `@moku-labs/game/fonts/*`: the shipped MSDF body font and its licence. See "Fonts".
|
|
17
18
|
- `@moku-labs/game/jsx-runtime` and `@moku-labs/game/jsx-dev-runtime`: what `"jsxImportSource": "@moku-labs/game"` resolves to. A game never imports them by hand.
|
|
18
19
|
|
|
@@ -145,7 +146,8 @@ type NodeContext = {
|
|
|
145
146
|
"moku-game/dev-imports": "error",
|
|
146
147
|
"moku-game/no-module-state": "error",
|
|
147
148
|
"moku-game/determinism": "error",
|
|
148
|
-
"moku-game/rules-siblings": "error"
|
|
149
|
+
"moku-game/rules-siblings": "error",
|
|
150
|
+
"moku-game/static-keys": "error"
|
|
149
151
|
}
|
|
150
152
|
}
|
|
151
153
|
```
|
|
@@ -156,6 +158,7 @@ type NodeContext = {
|
|
|
156
158
|
- `moku-game/no-module-state` (L5): no module-scope `let` or `var`, no module-scope `new Map/Set/WeakMap/WeakSet`. Default files `**`.
|
|
157
159
|
- `moku-game/determinism` (L3): no `Math.random`, `Date.now`, `performance.now`, `new Date()`, `setTimeout`, `setInterval` in the logic. `new Date(now)` passes.
|
|
158
160
|
- `moku-game/rules-siblings` (L4): a file under `rules/` imports only `./` siblings.
|
|
161
|
+
- `moku-game/static-keys`: a JSX `key` is a literal, a template of such parts, `props.id` or `props.<name>Key`, a call, or a `const` of one. `tabKeys[props.tab]`, `a ?? b`, `a || b`, `a ? b : c` and `props.tab` are reported: pass the key in as a prop, `<TabButton id="tabSound" />`. Default files `**/*.tsx`.
|
|
159
162
|
- Logic = `**/state.ts`, `**/tables.ts`, `**/{nodes,flows,rules,features}/**`. Tests (`tests/**`, `**/__tests__/**`, `*.test.ts`) are skipped by every rule.
|
|
160
163
|
- Every rule takes `["error", { "files": [...], "ignores": [...] }]`. Globs are relative to the directory oxlint runs in; a key given replaces the default.
|
|
161
164
|
|
|
@@ -503,6 +506,26 @@ Two subpaths, neither on the root (V4). An editor, MCP tools and e2e scripts rea
|
|
|
503
506
|
- 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.
|
|
504
507
|
- 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.
|
|
505
508
|
|
|
509
|
+
## Project index
|
|
510
|
+
|
|
511
|
+
`@moku-labs/game/project` maps every engine id of a game to anchors (a path plus a binding, a key or a component), never to line numbers; `find` reads the line from the file on disk at the call. `typescript` (`>=5.5`) is an optional peer: a game that uses the index adds it as a dev dependency. Nothing is persisted. The contract is in `docs/project-index.md`.
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
import { openProject } from "@moku-labs/game/project";
|
|
515
|
+
|
|
516
|
+
const project = await openProject({ root: "tests/integration/merge-game" });
|
|
517
|
+
(await project.find("node:board/merge"))[0]?.line; // 17, in "nodes/merge.ts"
|
|
518
|
+
const stop = project.watch((index, change) => refresh(index.revision, change.moved));
|
|
519
|
+
stop();
|
|
520
|
+
project.close();
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
- `openProject({ root, manifest?, 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.`
|
|
524
|
+
- Keys: `flow:<id>`, `node:<flow>/<node>` (every entry of a `nodes` table: node, sub-flow, slot), `feature:<id>`, `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.
|
|
525
|
+
- 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.
|
|
526
|
+
- `ProjectChange` is `{ revision, files, moved: [{ key, from, to }], removed }`. 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.
|
|
527
|
+
- CLI: `moku-game-index --root <dir> where <key>` prints `<path>:<line>` per place (exit 1 for an unknown key); `--json` prints the index; `--check` exits 1 on a broken file or a key in conflict and lists the unresolved items as info. `--root` is required, `--manifest <path>` is root-relative.
|
|
528
|
+
|
|
506
529
|
## Hot swap
|
|
507
530
|
|
|
508
531
|
Dev only. A save of a view file swaps the module in the running page: same state, same node, no reload. The game's `bunfig.toml` lists the plugin; the dev server runs in the game root, and a production `Bun.build` never loads it:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moku-labs/game",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
"types": "./dist/lint.d.mts",
|
|
36
36
|
"default": "./dist/lint.mjs"
|
|
37
37
|
},
|
|
38
|
+
"./project": {
|
|
39
|
+
"types": "./dist/project.d.mts",
|
|
40
|
+
"default": "./dist/project.mjs"
|
|
41
|
+
},
|
|
38
42
|
"./jsx-runtime": {
|
|
39
43
|
"types": "./dist/jsx-runtime.d.mts",
|
|
40
44
|
"default": "./dist/jsx-runtime.mjs"
|
|
@@ -46,7 +50,8 @@
|
|
|
46
50
|
"./fonts/*": "./fonts/*"
|
|
47
51
|
},
|
|
48
52
|
"bin": {
|
|
49
|
-
"moku-game-assets": "./bin/moku-game-assets.mjs"
|
|
53
|
+
"moku-game-assets": "./bin/moku-game-assets.mjs",
|
|
54
|
+
"moku-game-index": "./bin/moku-game-index.mjs"
|
|
50
55
|
},
|
|
51
56
|
"license": "MIT",
|
|
52
57
|
"files": [
|
|
@@ -97,7 +102,8 @@
|
|
|
97
102
|
"@moku-labs/core": "^1.7.1",
|
|
98
103
|
"pixi.js": "^8.0.0",
|
|
99
104
|
"playwright-core": ">=1.50.0",
|
|
100
|
-
"sharp": "^0.34.0"
|
|
105
|
+
"sharp": "^0.34.0",
|
|
106
|
+
"typescript": ">=5.5"
|
|
101
107
|
},
|
|
102
108
|
"publishConfig": {
|
|
103
109
|
"access": "public"
|
|
@@ -108,6 +114,9 @@
|
|
|
108
114
|
},
|
|
109
115
|
"sharp": {
|
|
110
116
|
"optional": true
|
|
117
|
+
},
|
|
118
|
+
"typescript": {
|
|
119
|
+
"optional": true
|
|
111
120
|
}
|
|
112
121
|
},
|
|
113
122
|
"devDependencies": {
|