@moku-labs/game 0.15.18 → 0.15.22

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 (37) hide show
  1. package/dist/app/page.d.mts +1 -1
  2. package/dist/app/page.mjs +1 -1
  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/{capture-D7XvoHdH.mjs → capture-PTJXDpxK.mjs} +117 -16
  8. package/dist/{commands-CQt3jo0G.d.mts → commands-BvNp27KS.d.mts} +5 -5
  9. package/dist/control.d.mts +4 -4
  10. package/dist/control.mjs +1 -1
  11. package/dist/{define-BAuGN0UM.d.mts → define-DAVDb7dr.d.mts} +2 -2
  12. package/dist/{headless-uUUH54Ij.d.mts → headless-BCwsb1wR.d.mts} +1 -1
  13. package/dist/{headless-BA6ffzqD.mjs → headless-Bxxl_mQX.mjs} +1 -1
  14. package/dist/{index-ChqvcZwY.d.mts → index-CKXNW8_t.d.mts} +26 -4
  15. package/dist/index.d.mts +5 -5
  16. package/dist/index.mjs +3 -3
  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/{run-BqYmcNo7.mjs → run-DRcc2SKF.mjs} +2 -2
  22. package/dist/{runtime-BNP4wbDk.d.mts → runtime-Dt7VdhqR.d.mts} +1 -1
  23. package/dist/{sources-BYKXYPhd.mjs → sources-Dpc4NXvG.mjs} +1 -1
  24. package/dist/{src-B6WrtDtL.mjs → src-BcfQy5vB.mjs} +1302 -280
  25. package/dist/testing.d.mts +4 -4
  26. package/dist/testing.mjs +2 -2
  27. package/dist/{types-C4igewxk.d.mts → types-B02c7d6g.d.mts} +21 -5
  28. package/dist/{types-bwefoJES.d.mts → types-BFGyVykz.d.mts} +213 -19
  29. package/dist/{types-DMsvVZZT.d.mts → types-C8JK9u1Y.d.mts} +2 -2
  30. package/dist/{types-BP99ij9v.d.mts → types-COWBfy2L.d.mts} +8 -5
  31. package/dist/{types-DGvpNGxm.d.mts → types-YxQts-fd.d.mts} +4 -4
  32. package/dist/{visual-BdapVgBW.d.mts → visual-Dv67ITPu.d.mts} +6 -6
  33. package/dist/{visual-DNM5oFYx.mjs → visual-kEC9nb-o.mjs} +20 -2
  34. package/dist/visual.d.mts +1 -1
  35. package/dist/visual.mjs +2 -2
  36. package/llms.txt +2 -2
  37. package/package.json +1 -1
@@ -265,6 +265,7 @@ const Sprite = /*#__PURE__*/ component("Sprite", spriteDefaults);
265
265
  /**
266
266
  * A stretchable panel, sized in reference units, with its own alpha and tint. `debug: true` draws
267
267
  * the slice outline over it. `clip: true` masks the children of the entity to the panel box.
268
+ * `hollow: true` draws its rim alone, never the centre cell.
268
269
  */
269
270
  const NineSlice = /*#__PURE__*/ component("NineSlice", {
270
271
  texture: "",
@@ -1197,6 +1198,22 @@ function contentOf(props) {
1197
1198
  return props.content ?? "";
1198
1199
  }
1199
1200
  /**
1201
+ * Whether a nine-slice draws its rim alone: `true` while its style says so, `false` once it did and
1202
+ * no longer does, and no field on a panel that was never hollow, so a game's describe and saved
1203
+ * values stay as they were without it.
1204
+ *
1205
+ * @param element - The element framed by the nine-slice.
1206
+ * @param hollow - The style's `hollow`.
1207
+ * @returns `{ hollow }`, or nothing.
1208
+ */
1209
+ function hollowOf(element, hollow) {
1210
+ if (hollow === true) {
1211
+ element.hollowed = true;
1212
+ return { hollow: true };
1213
+ }
1214
+ return element.hollowed === true ? { hollow: false } : {};
1215
+ }
1216
+ /**
1200
1217
  * The box a text wraps at: its laid-out width while it wraps at it, 0 once it fits again, so a
1201
1218
  * text that wrapped draws on one line, and no field on a text that never wrapped.
1202
1219
  *
@@ -1213,7 +1230,7 @@ function boxOf(element) {
1213
1230
  /**
1214
1231
  * The visual component of an element: a sprite for an image or an icon, the text for a text (with
1215
1232
  * its laid-out width as the box a `"box"` style wraps at), the nine-slice of its style (outlined
1216
- * when the style sets `debug`), or a rounded rectangle, a triangle with `shape: "triangle"`, its
1233
+ * when the style sets `debug`, its rim alone when it sets `hollow`), or a rounded rectangle, a triangle with `shape: "triangle"`, its
1217
1234
  * stroke dashed with `dash`, or an arc with `shape: "arc"`
1218
1235
  * over the `sweep` of its style, ended by its `cap`. A clipping element (`scroll`,
1219
1236
  * `overflow: "hidden"`) keeps the rectangle, which carries the clip; a text field's rectangle
@@ -1263,7 +1280,8 @@ function visualOf(element) {
1263
1280
  alpha,
1264
1281
  tint,
1265
1282
  debug: style.debug ?? false,
1266
- clip: type === "input"
1283
+ clip: type === "input",
1284
+ ...hollowOf(element, style.hollow)
1267
1285
  })];
1268
1286
  const filled = style.fill !== void 0 || style.stroke !== void 0;
1269
1287
  const invisible = isContainer(type) && !filled;
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-BdapVgBW.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-Dv67ITPu.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-BYKXYPhd.mjs";
2
- import { n as commands, t as run } from "./run-BqYmcNo7.mjs";
1
+ import { n as read, t as sources } from "./sources-Dpc4NXvG.mjs";
2
+ import { n as commands, t as run } from "./run-DRcc2SKF.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";
package/llms.txt CHANGED
@@ -44,7 +44,7 @@ Root exports:
44
44
  - A pure wait ends in two ways. A gate answer whose `intent` is an outcome name: the answer payload becomes the outcome payload. Or a world event whose `type` is listed in the node's `inbox`: the event payload becomes the outcome payload.
45
45
  - Transition: rest node to rest node. It is a transaction. On an error the runner discards, rolls back to the last rest point, retries `retries` times, then enters `safeNode`. A failure inside `safeNode` rejects `run()`.
46
46
  - Three ways to run an effect in a node. `await fx(descriptor)`: the node waits for it. `fx.start(descriptor)`: it starts now and the node goes on; write it in place of `void fx(...)`. A failure of a started effect is the log entry `flow:fx-start-failed` and never fails the node. A `cosmetic` descriptor (`sfx`, `play`, `haptic`, `music`) that fails, awaited or started, is the warn entry `flow:fx-cosmetic-failed` with `{ kind, message }` and resolves `undefined`; a descriptor with `answers` and a `guide` are refused, they have to be awaited. `fx.emit(hint(...))`: released after the commit of the edge, dropped in fast mode.
47
- - `await fx(descriptor)` is typed by the descriptor (`Flow.Descriptor<Result>`): `schedule`, `guide`, `music`, `sfx`, `haptic`, `play` resolve `void`; `load` resolves `Assets.LoadResult | undefined` (`undefined` without `assets`, as in `game.headless()`); `popup(Component, props)` resolves `Flow.Answer<outcome names>`, so comparing it with an intent the component does not declare is a compile error; a descriptor written by hand resolves `unknown`. A game declares its own: `const envRead = defineEffect<{ notice: boolean }, EnvRead>("view-env.read", { fast: "run" })`, then `await fx(envRead({ notice: true }))` is `EnvRead` and `envRead.handle(ctx.require(flowPlugin).fx, payload => ...)` registers the one handler (returns the remover). Only `{ fast: "run" }` without `cosmetic` resolves `Result`; any other declaration resolves `Result | undefined`. `NodeFx` is generic: a hand-written fake is typed `as NodeFx`, or use `fakeFx`.
47
+ - `await fx(descriptor)` is typed by the descriptor (`Flow.Descriptor<Result>`): `schedule`, `guide`, `music`, `sfx`, `haptic`, `play` resolve `void`; `load` resolves `Assets.LoadResult | undefined` (`undefined` without `assets`, as in `game.headless()`); `ready` resolves `boolean | undefined` (`true` when every bundle is loaded and on the GPU, `undefined` without `assets`, so compare `=== false`); `popup(Component, props)` resolves `Flow.Answer<outcome names>`, so comparing it with an intent the component does not declare is a compile error; a descriptor written by hand resolves `unknown`. A game declares its own: `const envRead = defineEffect<{ notice: boolean }, EnvRead>("view-env.read", { fast: "run" })`, then `await fx(envRead({ notice: true }))` is `EnvRead` and `envRead.handle(ctx.require(flowPlugin).fx, payload => ...)` registers the one handler (returns the remover). Only `{ fast: "run" }` without `cosmetic` resolves `Result`; any other declaration resolves `Result | undefined`. `NodeFx` is generic: a hand-written fake is typed `as NodeFx`, or use `fakeFx`.
48
48
  - Every failure is written into the log by the engine: an `error` entry `flow:error` with `{ path, rolledBackTo, retry, error: { message, stack } }`. Read it with `app.log.trace()`. A game needs no plugin for it.
49
49
  - `recover` on a rest node: `recover: async ({ player, session, rng, fx, signal, now }) => { ... }`. It runs only when the runner gave up on a transition and enters this node as `safeNode`, before the node is entered. The rollback left the state of the failed rest point; `recover` writes the drafts so the safe node can draw itself. What it wrote commits as one edge with `outcome: "recover"` and becomes the rest point. A `recover` that throws rejects `run()`. A retry does not run it. So the safe node stays a pure wait: no `run` body, no extra outcome, no self edge.
50
50
  - Entering a rest node calls `model.store.markRest()`: the save provider gets the patches. Leaving a `barrier` node calls `markBarrier(txId)`: a durable write, and rollback cannot cross it.
@@ -526,7 +526,7 @@ The graph. Modules `runner`, `gate`, `inbox`, `fx`, `features`. Runner methods s
526
526
  - HUD: a scene layer `{ above: "overlays" }` is the HUD layer, drawn above every overlay and never in a picture; a scene with a HUD layer and no `ui` gets `ui` right under it. Its roots never take the keyboard: Escape and the Back press reach the top sheet under it. `HudPart({ key })` tags a HUD view; an overlay's `hud` (`"all"`, `"none"` or keys) names the parts that stay shown, the others fade with its level and take no tap. Keys are typed by `defineGame<{ …, hud: "energy" | "gems" }>()`.
527
527
  - Endless loops of views under an opaque or blurred overlay write nothing (`renderer.sync.loopsHeld(entity)`, the seam anim asks before an endless loop writes); their phase follows time.
528
528
  - A scene names bundles on top of its `bundle` from the committed state: `defineScene("ward", { bundle: "episodes-kit", bundles: ({ session }) => [session.episode], layers: {}, projections: [wardRoom] })`. `bundles` is read each time a node enters the scene, and every way in (a live entry, a restore, the `safeNode` recovery) waits for all of them before the scene is mounted and the node's gate opens. The keys are `BundleKey`s of `generated/assets.ts`.
529
- - `app.assets`: `load(bundle)`, `unload(bundle)`, `isLoaded(bundle)`, `has(bundle)` (the manifest carries it, loaded or not), `texture(key)`, `font(key)`, `audio(key)`, `usage()`. Per bundle in a feature's `assets.ts`: `defineBundles({ ep01: { tier: "feature", release: "leave", mipmaps: ["items/**"] } })`. `release: "leave"` also unloads the bundle, with reason `"leave"`, at the next rest once nothing holds it: not pinned by the node entered, not named by the mounted scene, no node of its feature on the stack; a bundle the outgoing scene named leaves one rest later. The default `"budget"` leaves only under the texture budget. `mipmaps` (`true` or globs relative to `assets/`) uploads the textures with their full chain; such a texture stays loose in a pack. A sub-feature with resources gets its own bundles, named like its folder, with `dir` and `parent` in the manifest; a code-only one has none. `companions.add({ load(bundle), unload(bundle) })` adds what loads and unloads with every bundle; `i18n` adds the strings of a sub-feature that way, only when `assets` is composed. Events `assets:bundle-loaded`, `assets:bundle-progress`, `assets:bundle-unloaded` (`reason: "budget" | "request" | "leave"`), `assets:replaced` (dev). Config: `manifest`, `textureBudgetMb: 192`, `preloadDepth: 2`, `baseUrl`, `io`.
529
+ - `app.assets`: `load(bundle)`, `warm(bundle)` (loads it if needed, then puts its textures and atlas pages on the GPU through `renderer.sync.textures.upload`, `renderer.uploads` a drawn frame; joins a running or finished warm; an unload forgets it; resolves also when the renderer could not upload; headless at once), `unload(bundle)`, `isLoaded(bundle)`, `has(bundle)` (the manifest carries it, loaded or not), `texture(key)`, `font(key)`, `audio(key)`, `usage()`. Per bundle in a feature's `assets.ts`: `defineBundles({ ep01: { tier: "feature", release: "leave", mipmaps: ["items/**"] } })`. `release: "leave"` also unloads the bundle, with reason `"leave"`, at the next rest once nothing holds it: not pinned by the node entered, not named by the mounted scene, no node of its feature on the stack; a bundle the outgoing scene named leaves one rest later. The default `"budget"` leaves only under the texture budget. `mipmaps` (`true` or globs relative to `assets/`) uploads the textures with their full chain; such a texture stays loose in a pack. A sub-feature with resources gets its own bundles, named like its folder, with `dir` and `parent` in the manifest; a code-only one has none. The effect `ready(bundle | bundles)` (root export, also from `defineGame`) lets a node rest on a load and stay answerable: its handler (fast mode too) answers `true` headless, throws for an unknown bundle, answers `true` when every bundle is loaded and warmed or its last warm failed (`ctx.log.warn("assets:ready-failed", { bundle, error })`, cleared by a later successful load), otherwise starts `warm` for what is missing, answers `false` and posts `{ type: "ready" }` into `flow.inbox` once all of it settled. Recipe: a rest node `inbox: ["elapsed", "ready"]` with outcomes `ready` and `back`, and a transit node `if ((await fx(ready(list))) === false) return out.wait();`. `companions.add({ load(bundle), unload(bundle) })` adds what loads and unloads with every bundle; `i18n` adds the strings of a sub-feature that way, only when `assets` is composed. Events `assets:bundle-loaded`, `assets:bundle-progress`, `assets:bundle-unloaded` (`reason: "budget" | "request" | "leave"`), `assets:replaced` (dev). Config: `manifest`, `textureBudgetMb: 192`, `preloadDepth: 2`, `baseUrl`, `io`.
530
530
  - `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.
531
531
  - `Now` is the engine resource of the frame's moment: `world` writes `clock.now()` into it at start and first thing in every frame, headless too, so every system of a frame reads one moment: `const { now } = res(Now)`; a fake clock makes it exact. A system that shows time writes its component only when its picture changes. `snapshot().resources` holds `Now: { now }`. Reserved resource names: `Now`, `Pointer`, `AnimPlayer`, `UiCounters`; a game resource of one of them throws `[game] world: the resource name "Now" is the engine's (the frame's moment).`
532
532
  - When a system runs: before each run the world checks it. A system with a `query` is skipped while a term of its query has no entity (the smallest store among the terms is empty): no walk, no `mut()` mark, no timing. A system without a query always runs. `system({ ..., scenes: ["board"] })` runs only while one of those scenes is mounted or leaving: `scenes` calls `app.world.ecs.setScene(id)` at every switch, and the scene before leaves until nothing carries `Exiting` (the elements of its JSX views played their exit; the next scene's entities already stand then, so a `scenes` system writes only its own scene's entities). A dev build throws at start for a name in `scenes` no feature declares: `[game] System "board.idle" runs on the scene "bord", which no feature declares.` `ctx.resumed` is true on the first run after a skip (a cache or a remembered cue starts over then). The same for every system, the engine's included; 0 B a frame. A run with no rows does not happen any more: work a system did besides its rows (a hover, a drop or a press that ends, a resource reset, something that outlives its entities) goes to a system with `query: []` and `scenes`, reading its rows with `world.query()`. From 0.15.3: delete `systemsRun` from every feature (the type refuses it), rewrite the systems that work outside their rows, add `scenes` only to a system whose entities stand on every screen; `ecs.hold` and `ecs.isActive` are gone (a test asks `app.scenes.current()` or whether the system ran). World README, "When a system runs".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/game",
3
- "version": "0.15.18",
3
+ "version": "0.15.22",
4
4
  "description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
5
5
  "type": "module",
6
6
  "sideEffects": [