@moku-labs/game 0.9.0 → 0.10.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 +41 -0
- package/dist/app/page.mjs +376 -0
- package/dist/app/system.d.mts +129 -0
- package/dist/app/system.mjs +307 -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 +1650 -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-xxSxKJJW.d.mts} +17 -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-DSl1eNB1.mjs} +14 -292
- 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-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/types-hQgI_wrq.d.mts +544 -0
- package/dist/visual.d.mts +6 -6
- package/dist/visual.mjs +5 -5
- package/llms.txt +119 -43
- package/package.json +30 -2
package/llms.txt
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
# @moku-labs/game
|
|
2
2
|
|
|
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.
|
|
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": false`. `pixi.js` `^8.0.0` is a peer dependency, no V1 code imports it. `playwright-core` (visual tests), `sharp` (the asset pack)
|
|
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), `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
|
|
|
9
9
|
- `@moku-labs/game`: everything a game imports.
|
|
10
|
+
- `@moku-labs/game/app`: `defineGameApp`, `startMoment` and the types of a game's `index.ts` and `config.ts`. No `node:` import, no system or native package. See "The game shell".
|
|
11
|
+
- `@moku-labs/game/app/page` (the browser): `startPage`, the page the generated `.moku/main.ts` calls.
|
|
12
|
+
- `@moku-labs/game/app/system` (the browser, the native shell): `systemShell`, `fromSystem`, `storeSave`, `createSystemApp` over the optional peer `@moku-labs/system`. The one entry that reaches it.
|
|
13
|
+
- `@moku-labs/game/cli` (node and bun only): `runCli`, the bin `moku-game`, and `preparePage`, the editor's dev page.
|
|
10
14
|
- `@moku-labs/game/testing`: headless play, repro runs, isolated feature tests (`isolate`, `stub`), fake clock, in-memory save provider. No `node:` import.
|
|
11
15
|
- `@moku-labs/game/visual` (node and bun only): the visual tests `defineVisualTest`, `runVisualTests`, `parseVisualArgv`.
|
|
12
16
|
- `@moku-labs/game/assets` (node and bun only): the asset key scanner, the production packer and the string compiler. The package bin `moku-game-assets` runs it.
|
|
13
17
|
- `@moku-labs/game/inspect`: the read door of the editor, safe in a production build. See "Doors for the editor".
|
|
14
18
|
- `@moku-labs/game/control`: the write door of the editor, dev builds only. See "Doors for the editor".
|
|
15
|
-
- `@moku-labs/game/hot` (the Bun dev server only): the
|
|
19
|
+
- `@moku-labs/game/hot` (the Bun dev server only): the Bun plugin that hot swaps views in a running page; `moku-game dev` lists it. See "Hot swap".
|
|
16
20
|
- `@moku-labs/game/lint`: the oxlint JS plugin `moku-game` with the game lint rules. See "Lint for games".
|
|
17
21
|
- `@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. It follows the tsconfig `paths` of the game. The package bin `moku-game-index` runs it. See "Project index".
|
|
18
22
|
- `@moku-labs/game/fonts/*`: the shipped MSDF body font and its licence. See "Fonts".
|
|
@@ -27,7 +31,7 @@ Root exports:
|
|
|
27
31
|
- Effects helpers from the root (V5): `defineEmitter`, `Emitter`, `defineFilter`, `Glow`, `Outline`, `Blur`, `ColorMatrix`, `Noise`, `Displacement`, `Alpha`.
|
|
28
32
|
- Platform from the root (V6): the types `PlatformProvider`, `BackResult`, `HapticKind` and the frozen list `HAPTIC_KINDS`.
|
|
29
33
|
- `defineGame<Types>()` returns, typed by the game's `player`, `session`, `assets`, `bundles`, `scenes`, `strings` and `textStyles`: `defineNode`, `defineFlow`, `defineFeature`, `projection`, `sprite`, `Sprite`, `NineSlice`, `defineBundles`, `load`, `defineScene`, `defineAnimation`, `frames`, `sfx`, `play`, `tr`, `label`, `defineTextStyles`, `defineComponent`, `defineStyle`, `defineTokens`, `popup`, `music`, `Frames`, `defineEmitter`, `Emitter`, `Displacement`. `Types` is `{ player; session; assets: string; bundles?: string; scenes?: string; strings: Record<string, unknown>; textStyles?: string; emitters?: string }`.
|
|
30
|
-
- `@moku-labs/game/assets`: `scanAssets(options)`, `emitKeys`, `emitManifest`, `compileStrings(root, out, options?)`, `checkStrings`, `packAssets(options)`, `exportStrings`, `importStrings`, `runCli(argv, strings)` with `strings: StringsTools` = `{ compile: compileStrings, exportStrings, importStrings }`. Audio files are `.mp3` or `.m4a`. Asset key = feature (or layer name) + "." + path inside `assets/`. `runCli` (a game's `bun run assets:keys`) scans the assets and compiles `features/*/strings/<locale>.json`, and the `strings/` of every layer, into `generated/strings.ts` (the `Strings` type for `defineGame`) and one `generated/strings.<locale>.ts` per locale; `--check` fails when any output is out of date; `--pack <dir>` writes the production build. `--layer <folder>[=<name>]`, repeatable and good with every mode, scans `<root>/<folder>/` like one more feature under `<name>` (`<folder>` alone maps to itself): its `assets.ts`, `assets/` and `strings/<locale>.json`. A game on the layered layout passes `--layer shared=ui`, so `shared/assets/*` keeps the `ui.*` keys. A missing layer folder is a warning. `scanAssets`, `compileStrings`, `exportStrings` and `importStrings` take the same map as `layers: { shared: "ui" }`. `--pseudo` also writes the pseudo-locale `en-XA`. `--export <dir>` and `--import <dir>` exchange strings with translators; `--source <locale>` names the locale they read from, `"en"` by default. The package bin `moku-game-assets` is this CLI: a
|
|
34
|
+
- `@moku-labs/game/assets`: `scanAssets(options)`, `emitKeys`, `emitManifest`, `compileStrings(root, out, options?)`, `checkStrings`, `packAssets(options)`, `exportStrings`, `importStrings`, `runCli(argv, strings)` with `strings: StringsTools` = `{ compile: compileStrings, exportStrings, importStrings }`. Audio files are `.mp3` or `.m4a`. Asset key = feature (or layer name) + "." + path inside `assets/`. `runCli` (a game's `bun run assets:keys`) scans the assets and compiles `features/*/strings/<locale>.json`, and the `strings/` of every layer, into `generated/strings.ts` (the `Strings` type for `defineGame`) and one `generated/strings.<locale>.ts` per locale; `--check` fails when any output is out of date; `--pack <dir>` writes the production build. `--layer <folder>[=<name>]`, repeatable and good with every mode, scans `<root>/<folder>/` like one more feature under `<name>` (`<folder>` alone maps to itself): its `assets.ts`, `assets/` and `strings/<locale>.json`. A game on the layered layout passes `--layer shared=ui`, so `shared/assets/*` keeps the `ui.*` keys. A missing layer folder is a warning. `scanAssets`, `compileStrings`, `exportStrings` and `importStrings` take the same map as `layers: { shared: "ui" }`. `--pseudo` also writes the pseudo-locale `en-XA`. `--export <dir>` and `--import <dir>` exchange strings with translators; `--source <locale>` names the locale they read from, `"en"` by default. The package bin `moku-game-assets` is this CLI: a script is `"assets:keys": "moku-game-assets --root src --manifest public/assets/manifest.json --keys src/generated/assets.ts"`. A game with `config.ts` runs `moku-game keys` and `moku-game pack` instead: they pass its paths and its layers.
|
|
31
35
|
- Type namespaces: `Time`, `Lifecycle`, `Model`, `Clock`, `Flow`, `World`, `Renderer`, `Input`, `Assets`, `Scenes`, `Anim`, `I18n`, `TextTypes`, `Ui`, `Audio`, `Effects`, `Platform`. Use `import type { Flow } from "@moku-labs/game"` and then `Flow.RouteStep`, `Flow.NodeContext`, `Model.Json`, `Model.PlayerStateProvider`, `Clock.ClockSource`, `Time.Phase`, `Lifecycle.PauseReason`, `Anim.Target`, `I18n.Message`. `Text` is the component, so the text types are `TextTypes`.
|
|
32
36
|
- `defineNode` and `defineFlow` are NOT root exports. They come from `defineGame<Types>()`.
|
|
33
37
|
|
|
@@ -157,8 +161,8 @@ type NodeContext = {
|
|
|
157
161
|
```
|
|
158
162
|
|
|
159
163
|
- `moku-game/lazy-imports` (L2): no static value import of `pixi.js` or `yoga-layout`. `import type` and `import()` pass. Default files `**`.
|
|
160
|
-
- `moku-game/native-imports` (L13): no `@moku-labs/system`, `@moku-labs/native`, `@tauri-apps/*` in the logic, `kit.ts` and `plugins/`.
|
|
161
|
-
- `moku-game/dev-imports`: `@moku-labs/editor` and `@moku-labs/game/control` only in `web/main.ts`, `web/dev*.ts`, `web/editor*.ts
|
|
164
|
+
- `moku-game/native-imports` (L13): no `@moku-labs/system`, `@moku-labs/native`, `@tauri-apps/*` in the logic, the root `index.ts` and `config.ts`, `kit.ts` and `plugins/`. A game imports no native package: it names the capability in `config.ts` `system`, and the engine page wires it.
|
|
165
|
+
- `moku-game/dev-imports`: `@moku-labs/editor` and `@moku-labs/game/control` only in `*.dev.ts(x)`, tests and the generated `.moku/` page (and `web/main.ts`, `web/dev*.ts`, `web/editor*.ts` of a game that still has `web/`).
|
|
162
166
|
- `moku-game/no-module-state` (L5): no module-scope `let` or `var`, no module-scope `new Map/Set/WeakMap/WeakSet`. Default files `**`.
|
|
163
167
|
- `moku-game/determinism` (L3): no `Math.random`, `Date.now`, `performance.now`, `new Date()`, `setTimeout`, `setInterval` in the logic. `new Date(now)` passes. Plugin folders, `**/{plugin,plugins}/**`, are the effect side and are skipped: a plugin answers `schedule` with a timer.
|
|
164
168
|
- `moku-game/rules-siblings` (L4): a file under `rules/` imports only `./` siblings, `@core/types` and `@shared/rules`, by alias or by relative path. `@core/kit`, `../tables` and `import type … from "@moku-labs/game"` are reported.
|
|
@@ -168,7 +172,7 @@ type NodeContext = {
|
|
|
168
172
|
- `moku-game/test-suffix`: a file in a test kind folder ends with its suffix. `tests/e2e/` `.e2e.ts`, `tests/visual/` `.visual.ts`, `tests/editor/` `.editor.ts`, `__tests__/`, `__tests__/unit/` and `__tests__/integration/` `.test.ts`, `__tests__/isolated/` `.isolated.ts`, `__tests__/visual/` `.visual.ts`; a `.tsx` file adds an `x`. Helpers go to `tests/helpers/` or `__tests__/fixtures/`.
|
|
169
173
|
- Layers under the game root: `core/**`, `shared/**`, `features/<f>/**`, `plugins/<p>/**`, `generated/**`, `game.ts`, `tests/**`. A file outside them (`kit.ts`, `nodes/`, `web/`) keeps the layout rules quiet, so a game on the template layout lints as before.
|
|
170
174
|
- Aliases: `layer-imports`, `feature-door` and `rules-siblings` read the `compilerOptions.paths` of `tsconfig.json` (comments, trailing commas and relative `extends` included), the way the project index does. Without `paths` they read the v15 table: `@core/*` → `core/*`, `@shared` → `shared/index.ts`, `@shared/rules` → `shared/rules/index.ts`, `@features` → `features/index.ts`, `@features/*` → `features/*/index.ts`, `@plugins` → `plugins/index.ts`, `@generated/*` → `generated/*`, `@tests/*` → `tests/*`. `aliasTargetsOf(tsconfig, root, specifier)` from `@moku-labs/game/lint` answers what they read: for a game without a tsconfig, `aliasTargetsOf("/game/tsconfig.json", "/game", "@features/orders")` is `["/game/features/orders/index.ts"]`.
|
|
171
|
-
- Logic = `**/state.ts`, `**/tables.ts`, `**/game.ts`, `**/{core,nodes,flows,rules,features,shared}/**`. Tests (`tests/**`, `**/__tests__/**`, `*.test.ts`) are skipped by every rule but `test-suffix`.
|
|
175
|
+
- Logic = the root `index.ts`, `**/state.ts`, `**/tables.ts`, `**/game.ts`, `**/{core,nodes,flows,rules,features,shared}/**`. Tests (`tests/**`, `**/__tests__/**`, `*.test.ts`) are skipped by every rule but `test-suffix`.
|
|
172
176
|
- Every rule takes `["error", { "files": [...], "ignores": [...] }]`. Globs are relative to the directory oxlint runs in; a key given replaces the default. `layer-imports`, `feature-door` and `rules-siblings` also take `root` (the folder of `core/`, `shared/`, `features/`, `plugins/` and `game.ts`, default `.`) and `tsconfig` (default `tsconfig.json`); `test-suffix` takes `root` and `suffixes` (kind folder to suffix, replaces the table whole).
|
|
173
177
|
|
|
174
178
|
## Minimal game
|
|
@@ -228,25 +232,96 @@ export const mainFlow = defineFlow("main", {
|
|
|
228
232
|
```
|
|
229
233
|
|
|
230
234
|
```ts
|
|
231
|
-
//
|
|
232
|
-
import {
|
|
235
|
+
// index.ts: the game as one data object
|
|
236
|
+
import { defineGameApp } from "@moku-labs/game/app";
|
|
233
237
|
import { mainFlow } from "./flow";
|
|
234
238
|
|
|
235
|
-
export
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
239
|
+
export default defineGameApp({
|
|
240
|
+
flow: mainFlow,
|
|
241
|
+
safeNode: "home",
|
|
242
|
+
player: { coins: 0, lastRoll: 0 },
|
|
243
|
+
session: { rolls: 0 }
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
// config.ts: the page, as plain data
|
|
249
|
+
import type { GameConfig } from "@moku-labs/game/app";
|
|
250
|
+
|
|
251
|
+
export default { page: { title: "Dice" } } satisfies GameConfig;
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The game never calls `flow.run()`. The page runs the graph after `app.start()`; a test hands the app to `createHeadless`, which runs it. The screen sends answers with `app.flow.gate.answer({ intent: "roll" })`.
|
|
255
|
+
|
|
256
|
+
## The game shell
|
|
257
|
+
|
|
258
|
+
A game is a folder: `index.ts` (`export default defineGameApp({ ... })`), `config.ts` (plain data, `satisfies GameConfig`), `core/`, `shared/`, `features/`, `plugins/`, `generated/`, `tests/` with `tests/scenarios/<name>.ts`. No `web/`, no `native.ts`, no `platform-bridge.ts`, no `bunfig.toml`, no `createApp` call. `.moku/` holds what `moku-game` writes; the game's `.gitignore` lists it. Scripts of the game's `package.json`: `"dev": "moku-game dev"`, `"build": "moku-game build"`, `"native:ios-sim": "moku-game native build ios --simulator"`.
|
|
259
|
+
|
|
260
|
+
- `defineGameApp(definition)` returns the game, `{ headless(seams?), screen(seams?) }`, and creates no app. Every type comes from the object; no generic at the call site. Keys: `flow` (required), `safeNode` (default: the start of the main flow), `player` and `session` (required), `referenceLong` (1920), `seed` (42), `shared` (composed first among the features), `features`, `plugins` (their APIs land on `screen().app`), `headless: { features?, plugins? }`, `pluginConfigs`.
|
|
261
|
+
- `pluginConfigs` takes every plugin of the screen app but the keys the shell writes. A seam-owned key is a compile error: `model.playerProvider`, `initialPlayer`, `initialSession`, `seed` (the game keeps `schemaVersion`, `migrations`); all of `clock` and `platform`; `flow.mainFlow`, `safeNode`; `renderer.mount`; `assets.manifest`, `io`; `audio.context`.
|
|
262
|
+
- `game.headless(seams?)` composes the core plugins, `logicOnly` of `headless.features`, then `headless.plugins`. `game.screen(seams?)` composes the screen set, `audio`, `effects`, `platform`, then `shared`, the features and the game's plugins. Both return `{ app, clock, provider }`, the app not started; each call is fresh. Defaults: `fakeClock(startMoment)` (`startMoment` is `1_000_000`), a fresh `memory()` save, the seed of the definition or 42.
|
|
263
|
+
- Seams of both: `seed`, `clock`, `provider`, `player`, `session`. Screen only: `manifest` (a URL or the parsed file), `io`, `platform`, `keepAwake` (false), `audio: { context?, journal? }`, `renderer: { mount?, loadPixi?, preference? }`. Without a mount the renderer is inert, so `game.screen()` runs in plain Bun.
|
|
264
|
+
- `defineGameApp` throws `[game] defineGameApp needs flow.` and `[game] defineGameApp: features[2] is not a feature.` for what TypeScript cannot stop.
|
|
265
|
+
- A game's exit plugin calls `ctx.require(platformPlugin).exit()` itself.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
// a test of the dice game
|
|
269
|
+
import { startMoment } from "@moku-labs/game/app";
|
|
270
|
+
import { createHeadless } from "@moku-labs/game/testing";
|
|
271
|
+
import game from "./index";
|
|
272
|
+
|
|
273
|
+
const { app, clock, provider } = game.headless();
|
|
274
|
+
const run = await createHeadless(app);
|
|
275
|
+
|
|
276
|
+
run.state().path; // "home"
|
|
277
|
+
clock.now() === startMoment; // true
|
|
278
|
+
provider.calls.map(call => call.method); // ["load", "commit"]: a new player
|
|
279
|
+
await run.walk([{ at: "home", intent: "roll" }, { at: "home", intent: "roll" }]);
|
|
280
|
+
app.model.store.snapshot().session; // { rolls: 2 }
|
|
281
|
+
await run.stop();
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`config.ts` keys and defaults: `page.title` (required, never empty), `page.lang` (`"en"`), `page.background` (`"#000000"`: the page behind the canvas, the `theme-color` meta and the native window; the canvas clear colour stays `pluginConfigs.renderer.background`), `page.orientation` (`"portrait"`, or `"landscape"`, `"any"`: the native build locks it), `page.icons: { favicon?, appleTouch? }` (files relative to the game, `<link>` tags), `page.head` (raw tags, verbatim), `native: { name, identifier, icon?, targets? }` (absent: no native build), `system` (`[]`; names `lifecycle`, `back`, `haptics`, `keepAwake`, `store`), `save` (`"memory"`, or `"local"`, `"store"`), `assets.layers` (`{}`; `{ shared: "ui" }` becomes `--layer shared=ui`). A refused value throws, for example `[game] config.save is "disk".` or `[game] config.system names "tray", which the game shell does not wire.`
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
// config.ts of a game in a native shell
|
|
288
|
+
import type { GameConfig } from "@moku-labs/game/app";
|
|
289
|
+
|
|
290
|
+
export default {
|
|
291
|
+
page: { title: "Лесной городок", lang: "ru", background: "#10161d", orientation: "portrait", icons: { favicon: "assets/icon.png" } },
|
|
292
|
+
native: { name: "Лесной городок", identifier: "com.mokulabs.timber", icon: "assets/icon.png" },
|
|
293
|
+
system: ["lifecycle", "back", "haptics", "keepAwake"],
|
|
294
|
+
save: "memory",
|
|
295
|
+
assets: { layers: { shared: "ui" } }
|
|
296
|
+
} satisfies GameConfig;
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The page. `moku-game dev` and `build` write a `main.ts` that calls `startPage(game, config, { scenarios?, system?, agents?, devModules? })`. In order it resolves the config; picks the start; builds the system shell when the page has one and picks the save; makes `game.screen()` with the canvas on `#game`, `manifest.json` next to the page, the device clock and the shell's platform; sets `globalThis.game` (the app), `globalThis.system` (the system app or `undefined`) and `globalThis.doors` (`read`, `watch`, `sources`; `run`, `commands` in dev); follows reduced motion; starts the shell, the app, and runs the graph (a stop is logged `[game] The graph stopped.`); in dev starts the agents with `{ app, name, modules }`.
|
|
300
|
+
- `?player=<name>` starts from the scenario `tests/scenarios/<name>.ts` on a fresh memory save, never on the stored one. An unknown name logs `[game] No scenario "<name>".` and starts from the starting player. A scenario is `Scenario<Player>`: `(now) => ({ player, session? })`, its default export.
|
|
301
|
+
- `?renderer=webgl` asks Pixi for WebGL.
|
|
302
|
+
- Save kinds: `"memory"` lives for the page only; `"local"` is `localStorage` under `<native.identifier>:save` or `moku-game:save`, and falls back to memory with a warning where storage is refused; `"store"` is the system store (idb on the web, the Tauri store native), needs the shell, rejects a load it cannot read instead of starting a new player.
|
|
303
|
+
- System: the generated `main.ts` imports `systemShell` of `@moku-labs/game/app/system` only when `system` is not empty or `save` is `"store"`, so a web-only page bundles no system code. The shell loads each named `@moku-labs/system` plugin with its own `import()`. Not installed: `[game] config.system needs @moku-labs/system.` with `bun add @moku-labs/system@^0.3.1`.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
// .moku/main.ts of the engine's mini game, written by moku-game dev
|
|
307
|
+
// Written by moku-game dev. Do not edit.
|
|
308
|
+
import "./dev.ts";
|
|
309
|
+
import { startPage } from "@moku-labs/game/app/page";
|
|
310
|
+
import game from "../index.ts";
|
|
311
|
+
import config from "../config.ts";
|
|
312
|
+
import scenario0 from "../tests/scenarios/ready.ts";
|
|
313
|
+
|
|
314
|
+
await startPage(game, config, {
|
|
315
|
+
scenarios: { "ready": scenario0 }
|
|
316
|
+
});
|
|
247
317
|
```
|
|
248
318
|
|
|
249
|
-
|
|
319
|
+
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.
|
|
320
|
+
- `moku-game dev [--port 3000] [--packed]`: writes `.moku/index.html`, `dev.ts`, `main.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`. A taken port: `[game] dev: port 3000 is in use.` with `Pass --port 0 for a free port.`
|
|
321
|
+
- `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://`.
|
|
322
|
+
- `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`.
|
|
323
|
+
- `moku-game keys [--check]`: `generated/assets.ts`, the compiled strings and `manifest.json`. `moku-game pack [--no-cache]`: `dist/assets`. Both pass the layers of `config.ts`.
|
|
324
|
+
- `preparePage(root, { agents?, preload?, servePlugins? })` of `@moku-labs/game/cli` writes the dev page of `moku-game dev` and resolves `{ html, bunfig }`, both absolute, for a server the caller starts: `bun --config=<bunfig>`. With agents the page also imports the game's `**/*.dev.ts` and hands them to the agents. The editor calls it: `await preparePage("games/timber", { agents: ["@moku-labs/editor/agent/page"] })`.
|
|
250
325
|
|
|
251
326
|
## More patterns
|
|
252
327
|
|
|
@@ -493,7 +568,7 @@ Cosmetic rendering extras as data, drawn by `effectsPlugin`: `plugins: [...scree
|
|
|
493
568
|
|
|
494
569
|
### platform (V6, Standard, opt-in)
|
|
495
570
|
|
|
496
|
-
The phone as a provider, last in the array: `plugins: [...screen, effectsPlugin, audioPlugin, platformPlugin]` with `pluginConfigs: { platform: { provider, keepAwake: true } }`.
|
|
571
|
+
The phone as a provider, last in the array: `plugins: [...screen, effectsPlugin, audioPlugin, platformPlugin]` with `pluginConfigs: { platform: { provider, keepAwake: true } }`. `game.screen()` composes it and takes the provider as its `platform` seam. A game imports no native package: the engine page builds the provider from the `system` list of `config.ts` through `@moku-labs/game/app/system`, and turns keep-awake on when the list names `keepAwake`. The full reference is `src/plugins/platform/README.md`.
|
|
497
572
|
- `PlatformProvider` has six members: `onPause(fn)`, `onResume(fn)`, `onBack(fn)` (each returns its remover), `haptic(kind)`, `keepAwake(on)`, `exit()`. Pause and resume push and pop the `"background"` reason of `lifecycle`. A provider method that throws is logged, never thrown into the frame.
|
|
498
573
|
- Back chain, `app.platform.back(): BackResult`. First an Escape press: a popup with an `escape` button closes, `"popup"`. Then `flow.gate.answer({ intent: "back" })` when the resting node lists `back`, `"intent"`. Then `provider.exit()`, `"exit"`. Without a provider `"none"`.
|
|
499
574
|
- Haptics: `await fx(haptic("success"))` in a node, or `haptic("light")` as a timeline step. The kinds are `HAPTIC_KINDS`: `light`, `medium`, `heavy`, `selection`, `success`, `warning`, `error`. A fast walk is silent.
|
|
@@ -512,9 +587,9 @@ Two subpaths, neither on the root (V4). An editor, MCP tools and e2e scripts rea
|
|
|
512
587
|
|
|
513
588
|
- `@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.
|
|
514
589
|
- `@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.
|
|
515
|
-
- Commands run in dev builds only. Only `__MOKU_GAME_DEV__ === true` turns the dev build on:
|
|
590
|
+
- 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.`
|
|
516
591
|
- 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.
|
|
517
|
-
- 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
|
|
592
|
+
- 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.
|
|
518
593
|
|
|
519
594
|
## Project index
|
|
520
595
|
|
|
@@ -539,7 +614,7 @@ project.close();
|
|
|
539
614
|
|
|
540
615
|
## Hot swap
|
|
541
616
|
|
|
542
|
-
Dev only. A save of a view file swaps the module in the running page: same state, same node, no reload.
|
|
617
|
+
Dev only. A save of a view file swaps the module in the running page: same state, same node, no reload. `moku-game dev` lists the plugin first in its `.moku/bunfig.toml` and runs the server in the game root, so a game sets nothing; `moku-game build` never loads it. A game with a server of its own lists it in its own `bunfig.toml`:
|
|
543
618
|
|
|
544
619
|
```toml
|
|
545
620
|
[serve.static]
|
|
@@ -549,7 +624,7 @@ plugins = ["@moku-labs/game/hot"]
|
|
|
549
624
|
- 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. After a strings JSON edit run `bun run assets:keys`; its output is what swaps.
|
|
550
625
|
- 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.
|
|
551
626
|
- A swap logs `ui:hot-swap` and emits the global event `ui:hot-swap` `{ file, module }`.
|
|
552
|
-
- Another layout: `export default hot({ include, exclude })` from the game's
|
|
627
|
+
- 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"]`.
|
|
553
628
|
- 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.
|
|
554
629
|
|
|
555
630
|
## Fonts
|
|
@@ -581,20 +656,20 @@ Exports of `@moku-labs/game/testing`: `createHeadless`, `runRepro`, `stepFrames`
|
|
|
581
656
|
import type { Flow } from "@moku-labs/game";
|
|
582
657
|
import { createHeadless } from "@moku-labs/game/testing";
|
|
583
658
|
import { expect, it } from "vitest";
|
|
584
|
-
import
|
|
659
|
+
import game from "./index";
|
|
585
660
|
|
|
586
661
|
const rollOnce: Flow.RouteStep = { at: "home", intent: "roll" };
|
|
587
662
|
|
|
588
663
|
it("plays two rolls without a screen", async () => {
|
|
589
|
-
const app =
|
|
590
|
-
const
|
|
664
|
+
const { app } = game.headless({ seed: 42 });
|
|
665
|
+
const run = await createHeadless(app);
|
|
591
666
|
|
|
592
|
-
const state = await
|
|
667
|
+
const state = await run.walk([rollOnce, rollOnce]);
|
|
593
668
|
|
|
594
669
|
expect(state.path).toBe("home");
|
|
595
670
|
expect(app.model.store.snapshot().session).toEqual({ rolls: 2 });
|
|
596
671
|
|
|
597
|
-
await
|
|
672
|
+
await run.stop();
|
|
598
673
|
});
|
|
599
674
|
```
|
|
600
675
|
|
|
@@ -623,21 +698,22 @@ game.state().path; // "main/home"
|
|
|
623
698
|
await game.stop();
|
|
624
699
|
```
|
|
625
700
|
|
|
626
|
-
For timers,
|
|
701
|
+
For timers, take the clock of `game.headless()`, a `fakeClock(startMoment)`: call `clock.advance(ms)`, then yield the microtask queue before reading the state. The seed is the definition's, else 42; `game.headless({ seed: 7 })` pins another. The headless app composes `logicOnly` of `headless.features`. An app made with `createApp` by hand passes `clock: { source: fakeClock(1_000_000) }`, `model: { seed: 42 }` and `plugins: [feature.logicOnly]` itself.
|
|
627
702
|
|
|
628
703
|
## File layout of a game
|
|
629
704
|
|
|
630
705
|
```
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
tests/
|
|
706
|
+
index.ts export default defineGameApp({ ... }): the game as one data object
|
|
707
|
+
config.ts export default { page, native?, system?, save?, assets? } satisfies GameConfig
|
|
708
|
+
core/ the kit (the defineGame call) and its types
|
|
709
|
+
shared/ the shared layer
|
|
710
|
+
features/<f>/ one feature: flow/, rules/, views/, styles/, motion/, world/, assets/, strings/
|
|
711
|
+
plugins/ the game's own plugins
|
|
712
|
+
generated/ what moku-game keys writes
|
|
713
|
+
tests/ the tests; tests/scenarios/<name>.ts are the saves of ?player=<name>
|
|
714
|
+
.moku/ what moku-game writes, ignored by git
|
|
639
715
|
```
|
|
640
716
|
|
|
641
|
-
|
|
717
|
+
Rules are pure functions in `rules/`: no engine import, no clock, no random. The tsconfig aliases are `@core/*`, `@shared`, `@features/*`. The lint rules `layer-imports`, `feature-door` and `test-suffix`, the hot kind folders, the `assets.layers` of `config.ts` and the project index follow the layers `core/`, `shared/`, `features/` and `plugins/`.
|
|
642
718
|
|
|
643
|
-
|
|
719
|
+
The worked example is the merge game in moku-labs/demos (https://github.com/moku-labs/demos), written on the public API only, with its e2e and visual tests. The engine repository keeps a smaller fixture, `tests/fixtures/mini-game/` (`index.ts`, `config.ts`, `tests/scenarios/ready.ts`, one rest node, one popup flow, two features), which is not in the npm package. A feature with a screen adds `view.tsx` (projections and components), `animations.ts` and `strings/<locale>.json`. Each plugin has a README: `src/plugins/<name>/README.md` for `time`, `lifecycle`, `model`, `clock`, `flow`, `world`, `renderer`, `input`, `assets`, `scenes`, `anim`, `i18n`, `text`, `ui`, `audio`, `effects`, `platform`. Moku Core specification: https://github.com/moku-labs/core/tree/main/specification
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moku-labs/game",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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,
|
|
@@ -15,6 +15,22 @@
|
|
|
15
15
|
"types": "./dist/testing.d.mts",
|
|
16
16
|
"default": "./dist/testing.mjs"
|
|
17
17
|
},
|
|
18
|
+
"./app": {
|
|
19
|
+
"types": "./dist/app.d.mts",
|
|
20
|
+
"default": "./dist/app.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./app/page": {
|
|
23
|
+
"types": "./dist/app/page.d.mts",
|
|
24
|
+
"default": "./dist/app/page.mjs"
|
|
25
|
+
},
|
|
26
|
+
"./app/system": {
|
|
27
|
+
"types": "./dist/app/system.d.mts",
|
|
28
|
+
"default": "./dist/app/system.mjs"
|
|
29
|
+
},
|
|
30
|
+
"./cli": {
|
|
31
|
+
"types": "./dist/cli.d.mts",
|
|
32
|
+
"default": "./dist/cli.mjs"
|
|
33
|
+
},
|
|
18
34
|
"./visual": {
|
|
19
35
|
"types": "./dist/visual.d.mts",
|
|
20
36
|
"default": "./dist/visual.mjs"
|
|
@@ -54,6 +70,7 @@
|
|
|
54
70
|
"./fonts/*": "./fonts/*"
|
|
55
71
|
},
|
|
56
72
|
"bin": {
|
|
73
|
+
"moku-game": "./bin/moku-game.mjs",
|
|
57
74
|
"moku-game-assets": "./bin/moku-game-assets.mjs",
|
|
58
75
|
"moku-game-index": "./bin/moku-game-index.mjs"
|
|
59
76
|
},
|
|
@@ -88,7 +105,8 @@
|
|
|
88
105
|
"test:integration": "bun --bun vitest run --project integration",
|
|
89
106
|
"test:coverage": "bun --bun vitest run --project unit --project integration --coverage",
|
|
90
107
|
"build": "tsdown",
|
|
91
|
-
"mini:pack": "bun
|
|
108
|
+
"mini:pack": "bun tests/fixtures/moku-game.ts pack --root tests/fixtures/mini-game",
|
|
109
|
+
"mini:dev": "bun tests/fixtures/moku-game.ts dev --root tests/fixtures/mini-game",
|
|
92
110
|
"mini:visual": "bun tests/visual/run.ts",
|
|
93
111
|
"validate": "publint && attw --pack . --profile esm-only",
|
|
94
112
|
"release:setup": "moku-release setup",
|
|
@@ -103,6 +121,8 @@
|
|
|
103
121
|
"peerDependencies": {
|
|
104
122
|
"@moku-labs/common": "^0.3.4",
|
|
105
123
|
"@moku-labs/core": "^1.7.1",
|
|
124
|
+
"@moku-labs/native": "^0.3.2",
|
|
125
|
+
"@moku-labs/system": "^0.3.1",
|
|
106
126
|
"pixi.js": "^8.0.0",
|
|
107
127
|
"playwright-core": ">=1.50.0",
|
|
108
128
|
"sharp": "^0.34.0",
|
|
@@ -112,6 +132,12 @@
|
|
|
112
132
|
"access": "public"
|
|
113
133
|
},
|
|
114
134
|
"peerDependenciesMeta": {
|
|
135
|
+
"@moku-labs/native": {
|
|
136
|
+
"optional": true
|
|
137
|
+
},
|
|
138
|
+
"@moku-labs/system": {
|
|
139
|
+
"optional": true
|
|
140
|
+
},
|
|
115
141
|
"playwright-core": {
|
|
116
142
|
"optional": true
|
|
117
143
|
},
|
|
@@ -130,6 +156,8 @@
|
|
|
130
156
|
"@moku-labs/ci": "1.2.6",
|
|
131
157
|
"@moku-labs/common": "0.3.4",
|
|
132
158
|
"@moku-labs/core": "1.7.1",
|
|
159
|
+
"@moku-labs/native": "0.3.2",
|
|
160
|
+
"@moku-labs/system": "0.3.1",
|
|
133
161
|
"@types/bun": "1.3.14",
|
|
134
162
|
"@vitest/coverage-istanbul": "4.0.18",
|
|
135
163
|
"eslint": "9.39.3",
|