@moku-labs/game 0.15.2 → 0.15.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.
@@ -1,7 +1,7 @@
1
- import { Jn as FeaturePlugin, Sr as Json, Xn as AnyFlow, dr as NodeFx, gr as FakeClock, mr as ClockSource, nr as NodeTable, or as Descriptor, ur as Hint } from "./types-BHgJ1F6f.mjs";
2
- import { t as createApp } from "./index-w2uyRKkj.mjs";
3
- import { b as saveOf, v as WithGameState, y as memory } from "./types-DEYQF_tT.mjs";
4
- import { a as createHeadless, i as ReproResult, n as HeadlessGame, o as runRepro, r as Repro, s as stepFrames, t as HeadlessApp } from "./headless-D6BrEwCf.mjs";
1
+ import { Jn as FeaturePlugin, Sr as Json, Xn as AnyFlow, dr as NodeFx, gr as FakeClock, mr as ClockSource, nr as NodeTable, or as Descriptor, ur as Hint } from "./types-DlC3Ytbj.mjs";
2
+ import { t as createApp } from "./index-ByvWj9OX.mjs";
3
+ import { b as saveOf, v as WithGameState, y as memory } from "./types-BApuIqwq.mjs";
4
+ import { a as createHeadless, i as ReproResult, n as HeadlessGame, o as runRepro, r as Repro, s as stepFrames, t as HeadlessApp } from "./headless-D0U5AXiK.mjs";
5
5
  import { AnyPluginInstance } from "@moku-labs/core";
6
6
 
7
7
  //#region src/plugins/clock/fake.d.ts
package/dist/testing.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { in as exit, p as flattenFeatures, rn as refuseStart, sn as type, t as createApp } from "./src-Ca8W8Dvv.mjs";
1
+ import { an as exit, cn as type, in as refuseStart, m as flattenFeatures, t as createApp } from "./src-xChMXzwH.mjs";
2
2
  import { n as saveOf, t as memory } from "./memory-CWlqhS7l.mjs";
3
3
  import { t as fakeClock } from "./fake-6mazmHK3.mjs";
4
4
  import { i as stepFrames, r as runRepro, t as createHeadless } from "./headless-BA6ffzqD.mjs";
@@ -1,5 +1,5 @@
1
- import { Sr as Json, jr as Snapshot } from "./types-BHgJ1F6f.mjs";
2
- import { t as HeadlessApp } from "./headless-D6BrEwCf.mjs";
1
+ import { Sr as Json, jr as Snapshot } from "./types-DlC3Ytbj.mjs";
2
+ import { t as HeadlessApp } from "./headless-D0U5AXiK.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
 
5
5
  //#region src/plugins/flow/doors/types.d.ts
@@ -1,7 +1,7 @@
1
- import { At as Orientation, Dr as PlayerStateProvider, E as Config$2, Jn as FeaturePlugin, Mr as SnapshotOf, Or as ProviderCall, Pn as Config$1, Sr as Json, Xn as AnyFlow, a as Config$3, ar as TypeTag, br as Config, d as Manifest, gr as FakeClock, k as PixiModule, kr as SaveDoc, mr as ClockSource, n as AssetsIo } from "./types-BHgJ1F6f.mjs";
2
- import { o as AudioContextLike, s as Config$4 } from "./types-fpFKYm6E.mjs";
3
- import { H as TextKey, I as I18nApi, V as StringTable } from "./types-Bm7DVtA_.mjs";
4
- import { Ct as audioPlugin, bt as effectsPlugin, dt as platformPlugin, i as screen, pt as PlatformProvider, t as createApp } from "./index-w2uyRKkj.mjs";
1
+ import { At as Orientation, Dr as PlayerStateProvider, E as Config$2, Jn as FeaturePlugin, Mr as SnapshotOf, Or as ProviderCall, Pn as Config$1, Sr as Json, Xn as AnyFlow, a as Config$3, ar as TypeTag, br as Config, d as Manifest, gr as FakeClock, k as PixiModule, kr as SaveDoc, mr as ClockSource, n as AssetsIo } from "./types-DlC3Ytbj.mjs";
2
+ import { o as AudioContextLike, s as Config$4 } from "./types-CsMLBPf-.mjs";
3
+ import { H as TextKey, I as I18nApi, V as StringTable } from "./types-yLdzHyTl.mjs";
4
+ import { ft as platformPlugin, i as screen, mt as PlatformProvider, t as createApp, wt as audioPlugin, xt as effectsPlugin } from "./index-ByvWj9OX.mjs";
5
5
  import { AnyPluginInstance } from "@moku-labs/core";
6
6
 
7
7
  //#region src/plugins/model/store/providers/memory.d.ts
@@ -1,5 +1,5 @@
1
- import { A as PixiSprite, An as TrackSegment, Hr as Api$1, Mn as Api$2, Nt as FilterCache, O as PixiFilter, On as ReducedMotion, Ot as FilterSlot, Qr as Require, Rr as Events$5, Sr as Json, T as Api$4, Tn as NumericFields, Zr as Events$2, Zt as Api$3, an as ComponentHandle, ar as TypeTag, in as AnyComponentValue, j as PixiTexture, jt as Point, k as PixiModule, kn as TrackOptions, kt as FilterSlotInputs, ln as Entity, nn as AnyComponent, or as Descriptor, s as Events$1, sn as ComponentType, t as Api$5, v as Events$4, wn as MotionHandle, xn as Ease, xr as Events$3, yr as Api$6 } from "./types-BHgJ1F6f.mjs";
2
- import { I as I18nApi, J as StylesState, S as TextApi, c as UiNode, i as Finding, j as InputApi, n as ElementChange, o as JsxState, r as ElementMotion } from "./types-Bm7DVtA_.mjs";
1
+ import { A as PixiSprite, An as TrackSegment, Hr as Api$1, Mn as Api$2, Nt as FilterCache, O as PixiFilter, On as ReducedMotion, Ot as FilterSlot, Qr as Require, Rr as Events$5, Sr as Json, T as Api$4, Tn as NumericFields, Zr as Events$2, Zt as Api$3, an as ComponentHandle, ar as TypeTag, in as AnyComponentValue, j as PixiTexture, jt as Point, k as PixiModule, kn as TrackOptions, kt as FilterSlotInputs, ln as Entity, nn as AnyComponent, or as Descriptor, s as Events$1, sn as ComponentType, t as Api$5, v as Events$4, wn as MotionHandle, xn as Ease, xr as Events$3, yr as Api$6 } from "./types-DlC3Ytbj.mjs";
2
+ import { I as I18nApi, J as StylesState, S as TextApi, c as UiNode, i as Finding, j as InputApi, n as ElementChange, o as JsxState, r as ElementMotion } from "./types-yLdzHyTl.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
  import { PluginCtx } from "@moku-labs/core";
5
5
  import { Node, Yoga } from "yoga-layout/load";
@@ -167,11 +167,19 @@ type Track = {
167
167
  */
168
168
  readonly reveals: "alpha" | "place" | "both" | undefined;
169
169
  /**
170
- * The patch the track writes, one per track with every field of `to` in it: a frame fills it in
171
- * place instead of building one. A field the frame leaves alone carries its stored value back.
172
- * Rebuilt when the track loses a field.
170
+ * The patch the track writes when a frame moves every field, one per track with every field of
171
+ * `to` in it: a frame fills it in place instead of building one. Rebuilt when the track loses a
172
+ * field.
173
173
  */
174
174
  patch: Record<string, unknown>;
175
+ /**
176
+ * The patch of a frame that moves only some fields (another writer holds one, or one stands
177
+ * still): exactly the fields `partialMoved` flags, filled in place by the next frame that moves
178
+ * the same ones and built again when they change, so a field the track lost, which never moves
179
+ * again, is never in it. `undefined` until the first such frame.
180
+ */
181
+ partial: Record<string, unknown> | undefined; /** The `moved` flags `partial` was built for, by slot. */
182
+ readonly partialMoved: Uint8Array;
175
183
  };
176
184
  /**
177
185
  * A motion handle that can also be advanced by hand: what the timeline cursor needs, so a track
@@ -3365,12 +3365,31 @@ type FeatureTextStyles = {
3365
3365
  readonly kind: "textStyles";
3366
3366
  readonly map: Record<string, object>;
3367
3367
  };
3368
+ /**
3369
+ * When the systems of a feature run. `"always"` (the default): every frame, as before. `"active"`: only while the
3370
+ * feature is active — a node of it is on the flow stack, a scene it declares is mounted, a view of a projection it
3371
+ * registered exists, or `ui` holds a popup or a JSX root of it — leaving included, until the last view is gone.
3372
+ * Skipping changes nothing the flow sees: systems never write the model. The systems the engine's plugins
3373
+ * register always run.
3374
+ *
3375
+ * @example
3376
+ * ```ts
3377
+ * // features/board/index.ts: the board's four systems stop on the ward and on the splash.
3378
+ * export const boardFeature = defineFeature("board", {
3379
+ * flows: [mergeBoard],
3380
+ * systems: [looks, fade, leaving, veil],
3381
+ * systemsRun: "active"
3382
+ * });
3383
+ * ```
3384
+ */
3385
+ type SystemsRun = "always" | "active";
3368
3386
  /**
3369
3387
  * What a feature brings to the game. The logic keys are typed here; the keys of later milestones
3370
3388
  * pass through the index signature until their plugin types them. `flow` stores them untouched,
3371
3389
  * and `logicOnly` drops them. The V2 screen plugins read five of them through the index signature:
3372
3390
  * `projections`, `systems` and `components` (`world`), `scenes` (`scenes`, `assets`, and the
3373
- * scene-id check of `flow`) and `assets` (`assets`). The V3 interface keys are typed below.
3391
+ * scene-id check of `flow`) and `assets` (`assets`). `world` also reads the typed `systemsRun`.
3392
+ * The V3 interface keys are typed below.
3374
3393
  *
3375
3394
  * @example
3376
3395
  * ```ts
@@ -3400,7 +3419,8 @@ type FeatureDescription = {
3400
3419
  strings?: Record<string, CompiledMessagesLike | (() => Promise<unknown>)>; /** Text styles the feature owns, read by `text`. */
3401
3420
  textStyles?: FeatureTextStyles; /** Particle emitters the feature owns, read by `effects`. */
3402
3421
  emitters?: readonly FeatureEmitter[]; /** Filters the feature puts on its interface components, read by `effects`. */
3403
- filters?: readonly FeatureFilter[];
3422
+ filters?: readonly FeatureFilter[]; /** When the feature's systems run, read by `world`. Absent is `"always"`. */
3423
+ systemsRun?: SystemsRun;
3404
3424
  [later: string]: unknown;
3405
3425
  };
3406
3426
  /**
@@ -3600,7 +3620,7 @@ declare function to<const TargetName extends string, Payload, Output>(target: Ta
3600
3620
  */
3601
3621
  declare function slot(name: string): SlotNode;
3602
3622
  declare namespace types_d_exports$4 {
3603
- export { Allow, Answer, AnyFlow, AnyNode, AnyNodeContext, Api$3 as Api, Bookmark, BundlesOf, CheckedEdges, Config$4 as Config, Contribution, DefineNode, Deps$4 as Deps, Descriptor, Edges, Effect, EffectHandler, EffectOptions, EmitterIdOf, EnterCallback, Events$4 as Events, Exit, FeatureDescription, FeatureEmitter, FeatureFilter, FeaturePlugin, FeatureTree, FeaturesApi, FlowCtx, FlowDefinition, FlowEntry, FlowGraph, FlowKit, FlowSpec, FlowState, FxApi, FxHandler, GameState, GameTypes, GateApi, GraphError, GraphNode, GuideOptions, Hint, HudKeysOf, InboxApi, JournalEntry, KernelSlice$4 as KernelSlice, LifecycleChanged, Mapped, NodeContext, NodeDefinition, NodeFx, NodeInfo, NodeSpec, NodeTable, OutcomeTags, Position, RecoverContext, RegisteredFeature, Result, RouteStep, RunnerApi, SceneIdOf, SlotNode, Stage, State$4 as State, StringTableLike, Target, TextStylesOf, TypeTag, WorldEvent };
3623
+ export { Allow, Answer, AnyFlow, AnyNode, AnyNodeContext, Api$3 as Api, Bookmark, BundlesOf, CheckedEdges, Config$4 as Config, Contribution, DefineNode, Deps$4 as Deps, Descriptor, Edges, Effect, EffectHandler, EffectOptions, EmitterIdOf, EnterCallback, Events$4 as Events, Exit, FeatureDescription, FeatureEmitter, FeatureFilter, FeaturePlugin, FeatureTree, FeaturesApi, FlowCtx, FlowDefinition, FlowEntry, FlowGraph, FlowKit, FlowSpec, FlowState, FxApi, FxHandler, GameState, GameTypes, GateApi, GraphError, GraphNode, GuideOptions, Hint, HudKeysOf, InboxApi, JournalEntry, KernelSlice$4 as KernelSlice, LifecycleChanged, Mapped, NodeContext, NodeDefinition, NodeFx, NodeInfo, NodeSpec, NodeTable, OutcomeTags, Position, RecoverContext, RegisteredFeature, Result, RouteStep, RunnerApi, SceneIdOf, SlotNode, Stage, State$4 as State, StringTableLike, SystemsRun, Target, TextStylesOf, TypeTag, WorldEvent };
3604
3624
  }
3605
3625
  /**
3606
3626
  * flow plugin events.
@@ -5206,6 +5226,7 @@ type QueryTuple<Terms extends readonly QueryTerm[]> = [Entity, ...QueryValues<Te
5206
5226
  * const run = (entities: Iterable<[Entity]>, ctx: SystemContext): void => {
5207
5227
  * ctx.res(Pointer).x = 0;
5208
5228
  * ctx.time.delta; // 16
5229
+ * ctx.resumed; // false: the system ran in the frame before
5209
5230
  * };
5210
5231
  * ```
5211
5232
  */
@@ -5214,6 +5235,13 @@ type SystemContext = {
5214
5235
  res<Value extends object>(resource: ResourceType<Value>): Value;
5215
5236
  readonly snapshot: Snapshot;
5216
5237
  readonly time: Readonly<Time>;
5238
+ /**
5239
+ * True on the first run after one or more runs of this system were skipped because its feature was not active
5240
+ * (`systemsRun: "active"`). Nothing was kept for it while it slept: `world.changed()` holds this frame's changes
5241
+ * only, so a system that caches what it drew rebuilds from the components when this is true. Always false for a
5242
+ * system that always runs. Read it during the run.
5243
+ */
5244
+ readonly resumed: boolean;
5217
5245
  };
5218
5246
  /**
5219
5247
  * A system definition, typed over its query tuple.
@@ -5297,20 +5325,49 @@ type AnySystem = {
5297
5325
  run(entities: Iterable<readonly unknown[]>, ctx: SystemContext): void;
5298
5326
  };
5299
5327
  /**
5300
- * One registered system, the frame it was registered in, its query resolved once and the walk the
5301
- * runner resets for every run. A system registered during a frame runs from the next frame on.
5302
- * Spec 28 (deferred) extends it with the system's scope.
5328
+ * One registered system, the frame it was registered in, its query resolved once, the walk the
5329
+ * runner resets for every run and its scope. A system registered during a frame runs from the
5330
+ * next frame on. Module state: no game reads it.
5303
5331
  */
5304
5332
  type SystemEntry = {
5305
5333
  readonly definition: AnySystem;
5306
5334
  readonly frame: number;
5307
5335
  readonly plan: QueryPlan;
5336
+ /**
5337
+ * The index of its feature in `ScopeState` for a system of a feature with `systemsRun: "active"`, -1 for a
5338
+ * system that always runs.
5339
+ */
5340
+ readonly scope: number; /** The system's last chance to run was skipped because its feature was not active. */
5341
+ skipped: boolean;
5308
5342
  /**
5309
5343
  * The walk of the declared query, made at registration; `undefined` for an empty query, which
5310
5344
  * the runner never walks. An entry built without one gets it on its first run.
5311
5345
  */
5312
5346
  walk?: SystemWalk | undefined;
5313
5347
  };
5348
+ /**
5349
+ * Which features are active, kept so a phase reads one byte per scoped system. Indexed by the feature's position in
5350
+ * `flow.features.all()`. Filled in `onStart`, sized once; nothing in it grows per frame.
5351
+ */
5352
+ type ScopeState = {
5353
+ /** Feature names by index, registration order. */names: string[]; /** Feature name to index. */
5354
+ index: Map<string, number>; /** Projection name to the index of the feature that registered it (rule V). */
5355
+ projections: Map<string, number>; /** Flow id to node name to the owner's index (rule N); built from one `describe()` when first needed. */
5356
+ owners: Map<string, Map<string, number>> | undefined; /** `describe()` threw: every feature counts as on the stack while the stack is not empty (fail-safe). */
5357
+ ownersFailed: boolean; /** The `flow.state()` object the stack flags were read from. */
5358
+ flow: FlowState | undefined; /** 1 while a frame of the stack addresses a node the feature owns. */
5359
+ onStack: Uint8Array; /** The holds `scenes` and `ui` keep on the feature. */
5360
+ holds: Int32Array; /** The live and leaving views of the projections the feature registered. */
5361
+ views: Int32Array; /** 1 while the feature is active, as of the last evaluation. */
5362
+ active: Uint8Array; /** A hold or a view count moved since the last evaluation. */
5363
+ dirty: boolean; /** At least one registered system runs only while its feature is active. */
5364
+ scoped: boolean;
5365
+ /**
5366
+ * Moves on at every start and every stop: a hold's release remembers the generation it was
5367
+ * taken in, and a release from an older one changes nothing (spec 28 W7.1).
5368
+ */
5369
+ generation: number;
5370
+ };
5314
5371
  /**
5315
5372
  * A structural change queued while a phase runs. Applied in call order when the phase ends.
5316
5373
  *
@@ -5363,7 +5420,8 @@ type PhaseContext = {
5363
5420
  /** The ecs API the systems of this world receive. */readonly world: EcsApi; /** The resource reader, one function per world. */
5364
5421
  readonly res: <Value extends object>(resource: ResourceType<Value>) => Value; /** The frozen model snapshot of the running frame. */
5365
5422
  snapshot: Snapshot; /** The running frame's `Time`. */
5366
- time: Readonly<Time>;
5423
+ time: Readonly<Time>; /** Written before every run: the system's runs before this one were skipped for its scope. */
5424
+ resumed: boolean;
5367
5425
  };
5368
5426
  /**
5369
5427
  * The JSON kind of one component field, read from its default value: what the editor's component
@@ -5565,7 +5623,8 @@ type EcsState = {
5565
5623
  walkReported: boolean; /** The plans of `ecs.query()` by the type of their first term, made once per tuple of types. */
5566
5624
  queryPlans: Map<AnyComponentType, KnownPlan[]>; /** The entity lists of ended `ecs.query()` walks, taken by the next walks: no key copy per call. */
5567
5625
  walkLists: Entity[][]; /** How many entities were despawned since the world began: the sweep looks for dead elements when it moved. */
5568
- despawns: number;
5626
+ despawns: number; /** Which features are active, for the systems of the features with `systemsRun: "active"`. */
5627
+ scope: ScopeState;
5569
5628
  };
5570
5629
  /**
5571
5630
  * ecs module API, `app.world.ecs`: the screen as data. Plain-object components in one store per
@@ -5597,6 +5656,40 @@ type EcsApi = {
5597
5656
  * ```
5598
5657
  */
5599
5658
  system(definition: AnySystem): () => void;
5659
+ /**
5660
+ * Keeps a feature active while something of it is shown that the world cannot see: `scenes` holds the feature that
5661
+ * declares the mounted scene, `ui` the feature of each popup or JSX root it holds. Holds are counted; the returned
5662
+ * release is idempotent. A name that is not a registered feature holds nothing (a dev warning
5663
+ * `world:hold-unknown-feature`). Takes effect at the next phase that runs systems.
5664
+ *
5665
+ * @param feature - The feature to keep active.
5666
+ * @returns The release.
5667
+ * @example
5668
+ * ```ts
5669
+ * // scenes, as it mounts the scene "ward" that the feature "screens" declares:
5670
+ * const release = ctx.require(worldPlugin).ecs.hold("screens");
5671
+ * app.world.ecs.isActive("screens"); // true
5672
+ * release(); // the next scene replaced the ward
5673
+ * app.world.ecs.isActive("screens"); // false: no node, view or hold of "screens" is left
5674
+ * ```
5675
+ */
5676
+ hold(feature: string): () => void;
5677
+ /**
5678
+ * Whether a feature is active now: a node of it is on the flow stack, a scene it declares is mounted, a view of a
5679
+ * projection it registered exists (leaving included), or `ui` holds a popup or JSX root of it. Answers for any
5680
+ * feature, whatever its `systemsRun`; the systems of a feature with `systemsRun: "active"` run exactly while this
5681
+ * is true. False for a name that is not a feature.
5682
+ *
5683
+ * @param feature - The feature name.
5684
+ * @returns True while it is active.
5685
+ * @example
5686
+ * ```ts
5687
+ * // A test of the board: the graph rests at the board's awaitIntent and the shop sheet is closed.
5688
+ * app.world.ecs.isActive("board"); // true: a node of the board is on the stack, its views are drawn
5689
+ * app.world.ecs.isActive("shop"); // false: no view of the sheet, no node of the shop on the stack
5690
+ * ```
5691
+ */
5692
+ isActive(feature: string): boolean;
5600
5693
  /**
5601
5694
  * Creates an entity. The id is reserved and returned at once, also inside a phase, where the
5602
5695
  * components are attached when the phase ends.
@@ -6037,6 +6130,22 @@ type EcsInternal = {
6037
6130
  * Drops every entity, component, resource, system and hook.
6038
6131
  */
6039
6132
  clear(): void;
6133
+ /**
6134
+ * Counts a new view of a projection for the feature that registered it (rule V of `systemsRun`).
6135
+ *
6136
+ * @param projection - The projection name.
6137
+ */
6138
+ viewCame(projection: string): void;
6139
+ /**
6140
+ * Takes back the count of a despawned view; never below 0.
6141
+ *
6142
+ * @param projection - The projection name.
6143
+ */
6144
+ viewLeft(projection: string): void;
6145
+ /**
6146
+ * Drops every view count: the projections were cleared.
6147
+ */
6148
+ viewsCleared(): void;
6040
6149
  };
6041
6150
  //#endregion
6042
6151
  //#region src/plugins/world/ecs/systems.d.ts
@@ -6800,6 +6909,12 @@ type KeptState = {
6800
6909
  verifySkipped: number; /** True while `bypass(true)` draws everything live for an extract. */
6801
6910
  bypassed: boolean; /** Render passes the last flush cost: the estimate `sync.renderPasses()` adds (P9.2). */
6802
6911
  flushPasses: number;
6912
+ /**
6913
+ * The frame `collect` read had no mark and no whole-frame reason: nothing a kept picture reads changed, so a kept
6914
+ * record that owes nothing is drawn from its picture as it stands, without its step (E5). Set by `collect`, spent by
6915
+ * the next flush.
6916
+ */
6917
+ calm: boolean;
6803
6918
  };
6804
6919
  //#endregion
6805
6920
  //#region src/plugins/renderer/viewport/types.d.ts
@@ -7900,6 +8015,12 @@ type SyncInternal = {
7900
8015
  */
7901
8016
  layerOf(entity: Entity): string | undefined; /** The overlays of this frame: modes, ranges, reasons, dims, HUD parts, and the pictures to take (spec 22). */
7902
8017
  stepOverlays(): void;
8018
+ /**
8019
+ * Hands Pixi again the filters of every view whose filters were switched on or off since they were written: Pixi
8020
+ * holds only the enabled ones (E3). Run by the draw callback before anything reads the tree, so a switch is drawn in
8021
+ * the frame it was made. Allocates nothing when no filter was switched.
8022
+ */
8023
+ stepFilters(): void;
7903
8024
  /**
7904
8025
  * The `Graphics` paths the engine drew since the last call (shapes, clip masks, nine-slice outlines), and zeroes the
7905
8026
  * count: the monitor's `stats().redraws` (spec 26 R2.3).
@@ -8229,14 +8350,16 @@ type SyncState = {
8229
8350
  overlay: OverlayState; /** `Graphics` paths drawn since the monitor last took them: shapes, clip masks, outlines (spec 26 R2.3). */
8230
8351
  redraws: number;
8231
8352
  /**
8232
- * Where the walk of `shown` stopped for the entities whose loops asked since the last sync pass (spec 26 R1.7.3):
8233
- * an entity with several endless loops is walked once a frame. Cleared by every pass, which may change the tree.
8353
+ * Where the walk of `shown` stopped for the entities whose loops asked (spec 26 R1.7.3), at the `passesVersion` it
8354
+ * was taken: an answer stands while the version does, so an entity with several endless loops is walked once, and a
8355
+ * still board not at all (E4). One record per entity, made at its first ask, dropped with its view.
8234
8356
  */
8235
- held: Map<Entity, Unseen>;
8357
+ held: Map<Entity, HeldUnseen>;
8236
8358
  /**
8237
- * Moves on with every change that may change the render passes of a frame: a filter set or forgotten, a node's
8238
- * `renderable` flipped, a view hung, wrapped or dropped (a pass that did work), a kept picture taken or let go, a
8239
- * write a plugin announced with `invalidate` (a filter switched on or off).
8359
+ * Moves on with every change that may change the render passes of a frame or where a walk of `shown` stops: a filter
8360
+ * set, forgotten or switched, a node's `renderable` flipped (every hidden reason, the alpha's too), a view hung,
8361
+ * moved, wrapped or dropped (a pass that did work), a kept picture taken or let go, a HUD factor, a new viewport, a
8362
+ * write a plugin announced with `invalidate`.
8240
8363
  */
8241
8364
  passesVersion: number; /** The filtered views' passes of `renderPassesOf`, counted at `passesVersion` `at` under `root`. */
8242
8365
  passes: PassCount; /** The name of the scene's HUD layer (`above: "overlays"`), kept with the layers; `undefined` without one. */
@@ -8256,6 +8379,11 @@ type OverdrawState = {
8256
8379
  matrix: PixiFilter | undefined;
8257
8380
  listeners: Array<(on: boolean) => void>;
8258
8381
  };
8382
+ /** Where the walk of `shown` stopped for one entity, and the `passesVersion` it was taken at. */
8383
+ type HeldUnseen = {
8384
+ answer: Unseen;
8385
+ at: number;
8386
+ };
8259
8387
  /**
8260
8388
  * The filtered views' part of the render passes, counted once and read every frame while nothing it reads changed: the
8261
8389
  * version and the root it was counted at, and every filter of the filter table with its `enabled` then (a plugin may
@@ -11056,7 +11184,13 @@ type State$1 = {
11056
11184
  */
11057
11185
  named: readonly string[];
11058
11186
  owner: SceneOwner; /** The remover of the `onEnter` callback: set in `onStart`, run in `onStop`. */
11059
- teardown: (() => void) | undefined;
11187
+ teardown: (() => void) | undefined; /** Scene id to the feature that declares it, filled in `onStart`. */
11188
+ owners: Map<string, string>;
11189
+ /**
11190
+ * The release of the hold on the mounted scene's feature (`world.ecs.hold`), so the systems of a
11191
+ * feature with `systemsRun: "active"` run while its scene is mounted.
11192
+ */
11193
+ held: (() => void) | undefined;
11060
11194
  };
11061
11195
  /**
11062
11196
  * scenes plugin events.
@@ -1,4 +1,4 @@
1
- import { Cn as MotionCause, En as ProjectionMotion, Hr as Api, Jt as TransformValue, Mn as Api$1, Mt as ViewportSize, Qr as Require, Sn as Motion, Sr as Json, T as Api$3, Tn as NumericFields, Zt as Api$2, fr as Answer, in as AnyComponentValue, jn as ViewHandle, ln as Entity, nn as AnyComponent, or as Descriptor, pr as Api$5, rn as AnyComponentType, s as Events$1, sn as ComponentType, t as Api$4, ur as Hint, vn as TagType, wn as MotionHandle, yn as ChangeHook } from "./types-BHgJ1F6f.mjs";
1
+ import { Cn as MotionCause, En as ProjectionMotion, Hr as Api, Jt as TransformValue, Mn as Api$1, Mt as ViewportSize, Qr as Require, Sn as Motion, Sr as Json, T as Api$3, Tn as NumericFields, Zt as Api$2, fr as Answer, in as AnyComponentValue, jn as ViewHandle, ln as Entity, nn as AnyComponent, or as Descriptor, pr as Api$5, rn as AnyComponentType, s as Events$1, sn as ComponentType, t as Api$4, ur as Hint, vn as TagType, wn as MotionHandle, yn as ChangeHook } from "./types-DlC3Ytbj.mjs";
2
2
  import { Log } from "@moku-labs/common/browser";
3
3
  import { PluginCtx } from "@moku-labs/core";
4
4
 
@@ -2504,6 +2504,12 @@ type Root = {
2504
2504
  h: number;
2505
2505
  } | undefined;
2506
2506
  pending?: boolean;
2507
+ /**
2508
+ * The release of the hold on the feature the root shows (`world.ecs.hold`): the feature of a
2509
+ * popup's component, or of a JSX projection. Called when the sweep drops the root. Optional
2510
+ * like `pending`: `mountRoot` always writes it.
2511
+ */
2512
+ held?: (() => void) | undefined;
2507
2513
  };
2508
2514
  /**
2509
2515
  * Where the focus ring was last drawn: the layer and order of its root, the rect grown by the
@@ -2674,7 +2680,9 @@ type JsxState = {
2674
2680
  written: number; /** The wall times of the last taps on a `DebugTrigger` view, five at the most. */
2675
2681
  debugTaps: number[]; /** Whether `ui:debug-trigger-no-agent` was logged: once per app. */
2676
2682
  debugWarned: boolean; /** Elements that carry `Overlay`, from their enter to their despawn (spec 22 O4.5). */
2677
- overlays: Set<Entity>;
2683
+ overlays: Set<Entity>; /** Component name to the feature whose `ui` key lists it: the feature a popup root holds. */
2684
+ featureOfComponent: Map<string, string>; /** Projection name to the feature that registered it: the feature a JSX root holds. */
2685
+ featureOfProjection: Map<string, string>;
2678
2686
  };
2679
2687
  /**
2680
2688
  * One node of the snapshot `tree()` answers with. `rect` is natural: under a `fit` ancestor it
@@ -1,9 +1,9 @@
1
- import { Ir as Api$3, T as Api, Zt as Api$1, yr as Api$2 } from "./types-BHgJ1F6f.mjs";
2
- import { P as AnimApi, r as UiApi } from "./types-fpFKYm6E.mjs";
3
- import { j as InputApi } from "./types-Bm7DVtA_.mjs";
4
- import { r as Repro } from "./headless-D6BrEwCf.mjs";
5
- import { a as InputOf, r as ControlApp } from "./types-jiWJhLPE.mjs";
6
- import { t as commands } from "./commands-C_GccOFq.mjs";
1
+ import { Ir as Api$3, T as Api, Zt as Api$1, yr as Api$2 } from "./types-DlC3Ytbj.mjs";
2
+ import { P as AnimApi, r as UiApi } from "./types-CsMLBPf-.mjs";
3
+ import { j as InputApi } from "./types-yLdzHyTl.mjs";
4
+ import { r as Repro } from "./headless-D0U5AXiK.mjs";
5
+ import { a as InputOf, r as ControlApp } from "./types-B0bmoyRT.mjs";
6
+ import { t as commands } from "./commands-31d2BEwc.mjs";
7
7
 
8
8
  //#region src/visual/types.d.ts
9
9
  /** The base commands of the `/control` door, keyed by their short names. */
package/dist/visual.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as VisualApp, c as VisualRenderer, d as VisualStart, f as VisualStep, h as VisualTolerance, i as CheckpointResult, l as VisualReport, m as VisualTestResult, n as runVisualTests, o as VisualOptions, p as VisualTest, r as defineVisualTest, s as VisualPage, t as parseVisualArgv, u as VisualSetup } from "./visual-BvkKJOSz.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-G6F0Mi9H.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/llms.txt CHANGED
@@ -101,6 +101,8 @@ function defineFeature(name: string, description: FeatureDescription): FeaturePl
101
101
  // contribute?: Record<string, { flow: AnyFlow; order: number; when?: (snapshot: Snapshot) => boolean }>;
102
102
  // features?: readonly FeaturePlugin[]; // sub-features, composed right after this one
103
103
  // projections?, systems?, components?, scenes?, assets?; // V2 keys, read by world, scenes and assets
104
+ // systemsRun?: "always" | "active"; // read by world. "always" (default) or "active":
105
+ // // the feature's systems run only while a node, scene, view or ui root of it is there
104
106
  // animations?: readonly AnimationDefinition[]; // V3, read by anim
105
107
  // ui?: readonly ComponentDefinition[]; // V3, read by ui (defineComponent results)
106
108
  // strings?: Record<locale, CompiledMessages | (() => Promise<unknown>)>; // V3, read by i18n
@@ -527,6 +529,7 @@ The graph. Modules `runner`, `gate`, `inbox`, `fx`, `features`. Runner methods s
527
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`.
528
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.
529
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
+ - Systems that run where their feature is: `defineFeature("board", { systems, systemsRun: "active" })` runs the feature's systems only while it is active: a node of it on the flow stack (outer frames of sub-flows included), a scene it declares mounted (`scenes` holds it), a view of a projection it registered, leaving included, or a popup or JSX root of it that `ui` holds until its exit ended. `"always"` (default) and the systems the engine's plugins or `ecs.system` register run every frame. A skipped system keeps its slot, walks no query, marks nothing and allocates nothing; `ctx.resumed` is true on its first run after a skip (a cache it drew is rebuilt then). `app.world.ecs.isActive(feature)` answers for any feature; `ecs.hold(feature)` returns a release (`scenes` and `ui` call it). The active set is read at the start of each phase that runs systems. Dev warnings `world:feature-never-active`, `world:hold-unknown-feature`, `world:bad-feature-entry` with `key: "systemsRun"`.
530
533
  - Motion hooks are told why they play: `enter(view, item, hint, cause)` and `exit(view, item, hint, cause)` get `World.MotionCause`, `"mount"` (the view comes or goes with its screen: a mount, an unmount, a load, a restore, a paused or fast world) or `"change"` (a commit). A world projection plays them only for `"change"`; `ui` plays them for both. `app.world.projection.causeOf(entity)` answers how a view came or how it is leaving, also in a removal hook of its despawn, until the next frame.
531
534
  - `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.
532
535
  - `input`: one followed pointer. A finger that has not started a gesture (pressed, inside the tap slop, no long press, no trace) gives way to the next finger that goes down, wherever it rests, and its press is cancelled; a finger that started a gesture keeps it. `Pointer.id` is the followed `pointerId` (`-1` at rest): a system that measures the finger's travel starts over when it changes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/game",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
5
5
  "type": "module",
6
6
  "sideEffects": [