@moku-labs/game 0.15.3 → 0.15.5

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-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";
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-DnttyLeu.mjs";
2
+ import { t as createApp } from "./index-Bq_93oZv.mjs";
3
+ import { b as saveOf, v as WithGameState, y as memory } from "./types-6inPYTzE.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-DDkRwLnJ.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 { an as exit, cn as type, in as refuseStart, m as flattenFeatures, t as createApp } from "./src-xChMXzwH.mjs";
1
+ import { an as exit, cn as type, in as refuseStart, m as flattenFeatures, t as createApp } from "./src-DnclBdeU.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,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-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";
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-DnttyLeu.mjs";
2
+ import { o as AudioContextLike, s as Config$4 } from "./types-C2g2KYCN.mjs";
3
+ import { H as TextKey, I as I18nApi, V as StringTable } from "./types-CWSMo70I.mjs";
4
+ import { ft as platformPlugin, i as screen, mt as PlatformProvider, t as createApp, wt as audioPlugin, xt as effectsPlugin } from "./index-Bq_93oZv.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-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";
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-DnttyLeu.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-CWSMo70I.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";
@@ -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-DlC3Ytbj.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-DnttyLeu.mjs";
2
2
  import { Log } from "@moku-labs/common/browser";
3
3
  import { PluginCtx } from "@moku-labs/core";
4
4
 
@@ -1694,12 +1694,23 @@ type State = {
1694
1694
  cache: Map<string, TextLayout>; /** What every live label container was last filled from, by container. */
1695
1695
  drawn: WeakMap<object, DrawnLabel>; /** Entities to re-resolve in the next layout phase. */
1696
1696
  dirty: Set<Entity>;
1697
+ /**
1698
+ * The labels whose `Text` came while the world was live since the layout system last ran. The
1699
+ * flush of phase `layout` brings the ones `ui` adds in its solve, and they are resolved right
1700
+ * after it.
1701
+ */
1702
+ added: Entity[];
1703
+ /**
1704
+ * The labels the step after the last layout flush resolved. The next system run writes their
1705
+ * `Text` once more, so `ui` measures the string they show.
1706
+ */
1707
+ rewrite: Entity[];
1697
1708
  /**
1698
1709
  * Counts the dev hot swaps that replaced a font or an inline icon of a live label. A label
1699
1710
  * container drawn under an older count is built again on its next update.
1700
1711
  */
1701
1712
  generation: number; /** Warning keys already written, so a style, a tag, a glyph or a font warns once. */
1702
- warned: Set<string>; /** The system, the two world hooks and the display adapter. */
1713
+ warned: Set<string>; /** The system, the step after the layout flush, the two world hooks and the display adapter. */
1703
1714
  removers: Array<() => void>;
1704
1715
  };
1705
1716
  /**
@@ -2504,12 +2515,6 @@ type Root = {
2504
2515
  h: number;
2505
2516
  } | undefined;
2506
2517
  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;
2513
2518
  };
2514
2519
  /**
2515
2520
  * Where the focus ring was last drawn: the layer and order of its root, the rect grown by the
@@ -2680,9 +2685,7 @@ type JsxState = {
2680
2685
  written: number; /** The wall times of the last taps on a `DebugTrigger` view, five at the most. */
2681
2686
  debugTaps: number[]; /** Whether `ui:debug-trigger-no-agent` was logged: once per app. */
2682
2687
  debugWarned: boolean; /** Elements that carry `Overlay`, from their enter to their despawn (spec 22 O4.5). */
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>;
2688
+ overlays: Set<Entity>;
2686
2689
  };
2687
2690
  /**
2688
2691
  * One node of the snapshot `tree()` answers with. `rect` is natural: under a `fit` ancestor it
@@ -1,5 +1,5 @@
1
- import { Sr as Json, jr as Snapshot } from "./types-DlC3Ytbj.mjs";
2
- import { t as HeadlessApp } from "./headless-D0U5AXiK.mjs";
1
+ import { Sr as Json, jr as Snapshot } from "./types-DnttyLeu.mjs";
2
+ import { t as HeadlessApp } from "./headless-DDkRwLnJ.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
 
5
5
  //#region src/plugins/flow/doors/types.d.ts
@@ -3365,31 +3365,12 @@ 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";
3386
3368
  /**
3387
3369
  * What a feature brings to the game. The logic keys are typed here; the keys of later milestones
3388
3370
  * pass through the index signature until their plugin types them. `flow` stores them untouched,
3389
3371
  * and `logicOnly` drops them. The V2 screen plugins read five of them through the index signature:
3390
3372
  * `projections`, `systems` and `components` (`world`), `scenes` (`scenes`, `assets`, and the
3391
- * scene-id check of `flow`) and `assets` (`assets`). `world` also reads the typed `systemsRun`.
3392
- * The V3 interface keys are typed below.
3373
+ * scene-id check of `flow`) and `assets` (`assets`). The V3 interface keys are typed below.
3393
3374
  *
3394
3375
  * @example
3395
3376
  * ```ts
@@ -3419,8 +3400,13 @@ type FeatureDescription = {
3419
3400
  strings?: Record<string, CompiledMessagesLike | (() => Promise<unknown>)>; /** Text styles the feature owns, read by `text`. */
3420
3401
  textStyles?: FeatureTextStyles; /** Particle emitters the feature owns, read by `effects`. */
3421
3402
  emitters?: readonly FeatureEmitter[]; /** Filters the feature puts on its interface components, read by `effects`. */
3422
- filters?: readonly FeatureFilter[]; /** When the feature's systems run, read by `world`. Absent is `"always"`. */
3423
- systemsRun?: SystemsRun;
3403
+ filters?: readonly FeatureFilter[];
3404
+ /**
3405
+ * The key of 0.15.3, removed: typed `never`, so a game that still writes it is told. A system
3406
+ * now runs while its query has an entity, and `scenes` on `system()` limits it to some scenes:
3407
+ * delete the key (world README, "When a system runs").
3408
+ */
3409
+ systemsRun?: never;
3424
3410
  [later: string]: unknown;
3425
3411
  };
3426
3412
  /**
@@ -3620,7 +3606,7 @@ declare function to<const TargetName extends string, Payload, Output>(target: Ta
3620
3606
  */
3621
3607
  declare function slot(name: string): SlotNode;
3622
3608
  declare namespace types_d_exports$4 {
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 };
3609
+ 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 };
3624
3610
  }
3625
3611
  /**
3626
3612
  * flow plugin events.
@@ -4313,8 +4299,8 @@ type View$1 = {
4313
4299
  cause: MotionCause;
4314
4300
  };
4315
4301
  /**
4316
- * One mounted projection: its owner, the live views by key, the despawn queue and the items of
4317
- * the last reconcile.
4302
+ * One mounted projection: its owner, the live views by key, the despawn queue, the items of the
4303
+ * last reconcile, its place in the list of its scene and the layer list of that scene.
4318
4304
  *
4319
4305
  * @example
4320
4306
  * ```ts
@@ -4323,7 +4309,8 @@ type View$1 = {
4323
4309
  * live: new Map(),
4324
4310
  * queue: [],
4325
4311
  * items: new Map(),
4326
- * order: -1
4312
+ * order: -1,
4313
+ * layers: Object.freeze([{ name: "items", sort: "y" }])
4327
4314
  * };
4328
4315
  * ```
4329
4316
  */
@@ -4337,6 +4324,11 @@ type Mounted = {
4337
4324
  * minus the length of that list (the roots of one scene take −n … −1, in list order).
4338
4325
  */
4339
4326
  order: number;
4327
+ /**
4328
+ * The layer list (`setLayers`) of the scene the projection was mounted into or kept by last, compared by
4329
+ * identity: a `mount` under the same list is a second mount of one scene, a `mount` under a new one keeps it.
4330
+ */
4331
+ layers: ReadonlyArray<LayerSpec>;
4340
4332
  };
4341
4333
  /**
4342
4334
  * One track the projection started on the driver. The world keeps the component name and the
@@ -4510,17 +4502,30 @@ type ProjectionApi = {
4510
4502
  layers(): ReadonlyArray<LayerSpec>;
4511
4503
  /**
4512
4504
  * Marks projections mounted under an owner and reconciles them at once, direct: a scene brings
4513
- * its own transition. Nothing is mounted when the call throws.
4505
+ * its own transition. A screen root draws at the `Order` of its place in the list, −n … −1. A
4506
+ * projection a scene before mounted (under another layer list, `setLayers`) stays mounted when
4507
+ * the new list declares its `layer` and `lift` alike: its views keep their entities and their
4508
+ * motions, no hook runs, and its screen roots take the `Order` of their place in this list. One
4509
+ * whose layer the new list declares otherwise is unmounted and mounted again. One mounted under
4510
+ * the current layer list already is a second mount of one scene: it warns
4511
+ * `world:already-mounted` and changes nothing. Nothing is mounted when the call throws.
4514
4512
  *
4515
4513
  * @param names - Names of the projections to mount.
4516
4514
  * @param owner - Who owns the entities of these projections.
4517
4515
  * @throws {Error} For an unknown name, or a `layer` or `lift` the scene does not declare.
4518
4516
  * @example
4519
4517
  * ```ts
4520
- * // `scenes` mounts the projections of the board scene it just switched to.
4518
+ * // `scenes` mounts the board scene it just switched to: its HUD screen is listed last, at -1.
4521
4519
  * const world = ctx.require(worldPlugin);
4522
- * world.projection.mount(["board.cells", "board.items"], { kind: "plugin", name: "scenes" });
4520
+ * world.projection.mount(["board.items", "hud"], { kind: "plugin", name: "scenes" });
4523
4521
  * world.projection.entityOf("board.items", "i7"); // 1048576, the picture is already there
4522
+ *
4523
+ * // The shop lists the HUD too and declares `ui` alike: the shop's layers, the board's items
4524
+ * // out, the shop's list in.
4525
+ * world.projection.setLayers([{ name: "ui", sort: "order" }]);
4526
+ * world.projection.unmount(["board.items"]);
4527
+ * world.projection.mount(["hud", "shop.shelf"], { kind: "plugin", name: "scenes" });
4528
+ * world.ecs.get(world.projection.entityOf("hud", "hud") ?? 0, Order)?.value; // -2: the same entity, first of two
4524
4529
  * ```
4525
4530
  */
4526
4531
  mount(names: readonly string[], owner: Owner): void;
@@ -5236,15 +5241,18 @@ type SystemContext = {
5236
5241
  readonly snapshot: Snapshot;
5237
5242
  readonly time: Readonly<Time>;
5238
5243
  /**
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.
5244
+ * True on the first run after one or more of this system's runs were skipped: its query had no entity, or none of
5245
+ * its `scenes` was mounted or leaving. Nothing was kept for it while it slept: `world.changed()` holds this frame's
5246
+ * changes only, so a system that caches what it drew, or remembers what it saw last, starts over from the
5247
+ * components when this is true. False on every other run. Read it during the run.
5243
5248
  */
5244
5249
  readonly resumed: boolean;
5245
5250
  };
5246
5251
  /**
5247
- * A system definition, typed over its query tuple.
5252
+ * A system definition, typed over its query tuple. A system with a query runs only while its query
5253
+ * can match: before each run the world looks at the store of every term, and when one of them holds
5254
+ * no entity the run is skipped (no walk, no `mut()` mark, no timing). A system without a query
5255
+ * always runs. `scenes` limits either kind to some scenes.
5248
5256
  *
5249
5257
  * @example
5250
5258
  * ```ts
@@ -5263,10 +5271,42 @@ type SystemDefinition<Terms extends readonly QueryTerm[]> = {
5263
5271
  readonly phase: WorldPhase;
5264
5272
  readonly query: Terms;
5265
5273
  /**
5266
- * Runs the system once a frame in its phase. `entities` walks the declared `query` once; every
5267
- * row it hands out is the same array, rewritten for the next row, so a row is valid until the
5268
- * next row. A system that keeps a row copies what it keeps: `const [entity, transform] = row`.
5269
- * `ecs.query()` called inside `run` hands out a new array per row.
5274
+ * The scenes the system runs on: it runs only while one of them is mounted or leaving. A scene
5275
+ * leaves from the switch that replaces it until nothing carries `Exiting` any more, so the exits
5276
+ * of its JSX elements play to the end; any exit counts, also one of the next scene, so a leave may
5277
+ * last longer than the scene's own exits. The next scene's entities already stand during a leave:
5278
+ * a query that matches them too hands them to the system, so it writes only its own scene's
5279
+ * entities. Left out, the system runs on every scene. Read once, when the system registers. A dev
5280
+ * build throws at start for a name no feature declares as a scene. For a system without a query,
5281
+ * or one whose entities stand on every screen.
5282
+ *
5283
+ * @example
5284
+ * ```ts
5285
+ * // features/board/world/systems/idle.ts: no query, so `scenes` alone keeps it to the board.
5286
+ * const BoardIdle = resource("BoardIdle", { ms: 0 });
5287
+ * export const idle = system({
5288
+ * name: "board.idle",
5289
+ * phase: "animate",
5290
+ * query: [],
5291
+ * scenes: ["board"],
5292
+ * run: (_rows, { res, time }) => {
5293
+ * res(BoardIdle).ms += time.delta;
5294
+ * }
5295
+ * });
5296
+ * // On the ward it is not run and BoardIdle.ms stands still. On the board it grows by each
5297
+ * // frame's delta. After the switch to the ward it runs while the board's elements play their
5298
+ * // exit, then not any more.
5299
+ * ```
5300
+ */
5301
+ readonly scenes?: readonly string[];
5302
+ /**
5303
+ * Runs the system once a frame in its phase, unless it is skipped: a term of its `query` has no
5304
+ * entity, or none of its `scenes` is mounted or leaving. `entities` walks the declared `query`
5305
+ * once; every row it hands out is the same array, rewritten for the next row, so a row is valid
5306
+ * until the next row. A system that keeps a row copies what it keeps:
5307
+ * `const [entity, transform] = row`. `ecs.query()` called inside `run` hands out a new array per
5308
+ * row. Work that must happen when the query has no entity (end a press, reset a resource) belongs
5309
+ * in a system without a query.
5270
5310
  */
5271
5311
  run(entities: Iterable<QueryTuple<Terms>>, ctx: SystemContext): void;
5272
5312
  };
@@ -5321,23 +5361,28 @@ type SystemFor<Player, Session> = <const Terms extends readonly QueryTerm[]>(def
5321
5361
  type AnySystem = {
5322
5362
  readonly name: string;
5323
5363
  readonly phase: WorldPhase;
5324
- readonly query: readonly QueryTerm[]; /** As `SystemDefinition.run`: one row array per walk, valid until the next row. */
5364
+ readonly query: readonly QueryTerm[]; /** As `SystemDefinition.scenes`: the scenes it runs on, every scene when left out. */
5365
+ readonly scenes?: readonly string[]; /** As `SystemDefinition.run`: one row array per walk, valid until the next row. */
5325
5366
  run(entities: Iterable<readonly unknown[]>, ctx: SystemContext): void;
5326
5367
  };
5368
+ /** One component store, keyed by entity: a component's value, `true` for a tag. */
5369
+ type ComponentStore = Map<Entity, object | true>;
5327
5370
  /**
5328
5371
  * 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.
5372
+ * runner resets for every run, the stores its check reads and its scenes. A system registered
5373
+ * during a frame runs from the next frame on. Module state: no game reads it.
5331
5374
  */
5332
5375
  type SystemEntry = {
5333
5376
  readonly definition: AnySystem;
5334
5377
  readonly frame: number;
5335
5378
  readonly plan: QueryPlan;
5336
5379
  /**
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.
5380
+ * The store of each term, in term order, kept once it exists: a store lives as long as the
5381
+ * systems of its world, so the check before a run reads sizes and looks nothing up.
5382
+ * `undefined` for a term no entity carried yet.
5339
5383
  */
5340
- readonly scope: number; /** The system's last chance to run was skipped because its feature was not active. */
5384
+ readonly stores: Array<ComponentStore | undefined>; /** The scenes it runs on, read once at registration; `undefined` for every scene. */
5385
+ readonly scenes: readonly string[] | undefined; /** Its last chance to run was skipped: the next run is told so with `resumed`. */
5341
5386
  skipped: boolean;
5342
5387
  /**
5343
5388
  * The walk of the declared query, made at registration; `undefined` for an empty query, which
@@ -5346,27 +5391,14 @@ type SystemEntry = {
5346
5391
  walk?: SystemWalk | undefined;
5347
5392
  };
5348
5393
  /**
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.
5394
+ * Which scene is mounted and which are still leaving, as `scenes` tells the world with
5395
+ * `ecs.setScene`: what a system with `scenes` runs on. Changed at a scene switch only; a phase
5396
+ * reads it.
5351
5397
  */
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;
5398
+ type SceneState$1 = {
5399
+ /** The mounted scene, `undefined` before the first switch. */mounted: string | undefined; /** The scenes a switch replaced while something still carries `Exiting`, oldest first; no repeats. */
5400
+ leaving: string[]; /** The scene ids the features declare, read at start for the dev check of `scenes`; `undefined` before. */
5401
+ declared: ReadonlySet<string> | undefined;
5370
5402
  };
5371
5403
  /**
5372
5404
  * A structural change queued while a phase runs. Applied in call order when the phase ends.
@@ -5420,7 +5452,7 @@ type PhaseContext = {
5420
5452
  /** The ecs API the systems of this world receive. */readonly world: EcsApi; /** The resource reader, one function per world. */
5421
5453
  readonly res: <Value extends object>(resource: ResourceType<Value>) => Value; /** The frozen model snapshot of the running frame. */
5422
5454
  snapshot: Snapshot; /** The running frame's `Time`. */
5423
- time: Readonly<Time>; /** Written before every run: the system's runs before this one were skipped for its scope. */
5455
+ time: Readonly<Time>; /** Written before every run: the system's last chance to run before this one was skipped. */
5424
5456
  resumed: boolean;
5425
5457
  };
5426
5458
  /**
@@ -5623,8 +5655,8 @@ type EcsState = {
5623
5655
  walkReported: boolean; /** The plans of `ecs.query()` by the type of their first term, made once per tuple of types. */
5624
5656
  queryPlans: Map<AnyComponentType, KnownPlan[]>; /** The entity lists of ended `ecs.query()` walks, taken by the next walks: no key copy per call. */
5625
5657
  walkLists: Entity[][]; /** How many entities were despawned since the world began: the sweep looks for dead elements when it moved. */
5626
- despawns: number; /** Which features are active, for the systems of the features with `systemsRun: "active"`. */
5627
- scope: ScopeState;
5658
+ despawns: number; /** The mounted scene and the leaving ones, for the systems with `scenes`. */
5659
+ scene: SceneState$1;
5628
5660
  };
5629
5661
  /**
5630
5662
  * ecs module API, `app.world.ecs`: the screen as data. Plain-object components in one store per
@@ -5641,55 +5673,23 @@ type EcsState = {
5641
5673
  type EcsApi = {
5642
5674
  /**
5643
5675
  * Registers a system at the end of its phase list. A system registered during a frame runs
5644
- * from the next frame on.
5676
+ * from the next frame on. In every frame the system is skipped while a term of its query has no
5677
+ * entity, or while none of its `scenes` is mounted or leaving.
5645
5678
  *
5646
5679
  * @param definition - The system, built with `system()`.
5647
5680
  * @returns The remover; calling it twice is a no-op.
5648
- * @throws {Error} When a system of that name is already registered.
5681
+ * @throws {Error} When a system of that name is already registered, and in a dev build after
5682
+ * start for a name in `scenes` that no feature declares as a scene.
5649
5683
  * @example
5650
5684
  * ```ts
5651
5685
  * // `text` registers its layout system when the plugin starts.
5652
5686
  * const world = ctx.require(worldPlugin);
5653
- * const off = world.ecs.system(measureLabels); // runs from the next frame
5687
+ * const off = world.ecs.system(measureLabels); // runs from the next frame on which a label exists
5654
5688
  *
5655
5689
  * off(); // the plugin stops
5656
5690
  * ```
5657
5691
  */
5658
5692
  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;
5693
5693
  /**
5694
5694
  * Creates an entity. The id is reserved and returned at once, also inside a phase, where the
5695
5695
  * components are attached when the phase ends.
@@ -5990,6 +5990,24 @@ type EcsApi = {
5990
5990
  * ```
5991
5991
  */
5992
5992
  setMode(mode: WorldMode): void;
5993
+ /**
5994
+ * Tells the world which scene is mounted now, for the systems that list `scenes`. The scene
5995
+ * mounted before it, and any scene still leaving, keep their systems running until nothing
5996
+ * carries `Exiting` any more: the elements of their JSX views played their exit (the views themselves
5997
+ * go at the switch). Each system reads it
5998
+ * before its next run. The same scene again changes nothing. `scenes` calls it in the block that
5999
+ * mounts a scene; a game never needs to.
6000
+ *
6001
+ * @param scene - The id of the scene now mounted.
6002
+ * @example
6003
+ * ```ts
6004
+ * // scenes, in the block that replaces the ward with the board:
6005
+ * ctx.require(worldPlugin).ecs.setScene("board");
6006
+ * // In the next frame a system with scenes: ["board"] runs. One with scenes: ["ward"] runs
6007
+ * // until the ward's elements played their exit, then sleeps.
6008
+ * ```
6009
+ */
6010
+ setScene(scene: string): void;
5993
6011
  /**
5994
6012
  * The component type behind a name. A type registers itself on first use, so a name nobody
5995
6013
  * wrote, read or queried yet is unknown.
@@ -6130,22 +6148,6 @@ type EcsInternal = {
6130
6148
  * Drops every entity, component, resource, system and hook.
6131
6149
  */
6132
6150
  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;
6149
6151
  };
6150
6152
  //#endregion
6151
6153
  //#region src/plugins/world/ecs/systems.d.ts
@@ -9321,9 +9323,9 @@ type HostState = {
9321
9323
  */
9322
9324
  uninstallPasses: (() => void) | undefined;
9323
9325
  /**
9324
- * Swaps Pixi's own bitmap text pipe back in. Set once per host state when the renderer's pipe,
9325
- * whose SDF shader applies the alpha once, went into Pixi's registry, so a restore reuses it;
9326
- * `undefined` while inert and when the module lacked the pieces.
9326
+ * Swaps Pixi's own bitmap text and batch pipes back in and drops the SDF batcher. Set once per host state when the
9327
+ * renderer's batched distance-field text went into Pixi's registry, so a restore reuses it; `undefined` while inert
9328
+ * and when the module lacked the pieces.
9327
9329
  */
9328
9330
  uninstallSdfText: (() => void) | undefined;
9329
9331
  /**
@@ -10536,9 +10538,9 @@ declare namespace types_d_exports$2 {
10536
10538
  * the render textures and matrices the kept pictures are drawn with (an `AlphaFilter` runs their pre-pass), and the
10537
10539
  * extension registry with the seven classes the counting of a dev or debug build swaps
10538
10540
  * (the three WebGPU draw classes, the filter system, both texture systems and the batcher pipe whose `buildStart`
10539
- * begins every instruction rebuild; production swaps the encoder alone, to count the passes) and the bitmap text
10540
- * pipe `host` swaps for one whose SDF shader applies the alpha once (with the WGSL and GLSL shader
10541
- * pieces it compiles that shader from). The module object
10541
+ * begins every instruction rebuild; production swaps the encoder alone, to count the passes), and the pieces of
10542
+ * batched distance-field text `host` installs: Pixi's `Batcher` with its geometry and buffers, the bitmap text pipe
10543
+ * and the batcher pipe it subclasses, and the WGSL and GLSL shader pieces of the batch's SDF shader. The module object
10542
10544
  * never arrives through a static import: `config.loadPixi()` returns it, so a game without a screen carries no
10543
10545
  * Pixi in its bundle.
10544
10546
  *
@@ -10550,10 +10552,11 @@ declare namespace types_d_exports$2 {
10550
10552
  * pixi.FilterSystem.extension.name; // "filter"
10551
10553
  * pixi.BatcherPipe.extension.name; // "batch"
10552
10554
  * pixi.BitmapTextPipe.extension.name; // "bitmapText"
10555
+ * pixi.ExtensionType.Batcher; // "batcher"
10553
10556
  * pixi.defaultFilterVert.startsWith("in vec2 aPosition;"); // true
10554
10557
  * ```
10555
10558
  */
10556
- type PixiModule = Pick<typeof import("pixi.js"), "AlphaFilter" | "Application" | "BatcherPipe" | "BitmapFont" | "BitmapText" | "BitmapTextPipe" | "BlurFilter" | "Bounds" | "Cache" | "ColorMatrixFilter" | "Container" | "DisplacementFilter" | "Filter" | "FilterSystem" | "GlProgram" | "GlTextureSystem" | "GpuBatchAdaptor" | "GpuEncoderSystem" | "GpuGraphicsAdaptor" | "GpuProgram" | "GpuTextureSystem" | "Graphics" | "Matrix" | "NineSliceSprite" | "NoiseFilter" | "Particle" | "ParticleContainer" | "Rectangle" | "RenderGroupPipe" | "RenderTexture" | "Shader" | "Sprite" | "Texture" | "TexturePool" | "UniformGroup" | "bitmapFontTextParser" | "bitmapFontXMLStringParser" | "colorBit" | "colorBitGl" | "compileHighShaderGlProgram" | "compileHighShaderGpuProgram" | "defaultFilterVert" | "extensions" | "generateTextureBatchBit" | "generateTextureBatchBitGl" | "getBatchSamplersUniformGroup" | "localUniformMSDFBit" | "localUniformMSDFBitGl" | "mSDFBit" | "mSDFBitGl" | "roundPixelsBit" | "roundPixelsBitGl">;
10559
+ type PixiModule = Pick<typeof import("pixi.js"), "AlphaFilter" | "Application" | "Batcher" | "BatcherPipe" | "BitmapFont" | "BitmapText" | "BitmapTextPipe" | "BlurFilter" | "Bounds" | "Buffer" | "BufferUsage" | "Cache" | "ColorMatrixFilter" | "Container" | "DefaultBatcher" | "DisplacementFilter" | "ExtensionType" | "Filter" | "FilterSystem" | "Geometry" | "GlProgram" | "GlTextureSystem" | "GpuBatchAdaptor" | "GpuEncoderSystem" | "GpuGraphicsAdaptor" | "GpuProgram" | "GpuTextureSystem" | "Graphics" | "Matrix" | "NineSliceSprite" | "NoiseFilter" | "Particle" | "ParticleContainer" | "Rectangle" | "RenderGroupPipe" | "RenderTexture" | "Shader" | "Sprite" | "Texture" | "TexturePool" | "UniformGroup" | "bitmapFontTextParser" | "bitmapFontXMLStringParser" | "colorBit" | "colorBitGl" | "compileHighShaderGlProgram" | "compileHighShaderGpuProgram" | "defaultFilterVert" | "extensions" | "generateTextureBatchBit" | "generateTextureBatchBitGl" | "getBatchSamplersUniformGroup" | "mSDFBit" | "mSDFBitGl" | "roundPixelsBit" | "roundPixelsBitGl">;
10557
10560
  /**
10558
10561
  * One Pixi application: renderer, canvas and stage.
10559
10562
  */
@@ -11184,13 +11187,7 @@ type State$1 = {
11184
11187
  */
11185
11188
  named: readonly string[];
11186
11189
  owner: SceneOwner; /** The remover of the `onEnter` callback: set in `onStart`, run in `onStop`. */
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;
11190
+ teardown: (() => void) | undefined;
11194
11191
  };
11195
11192
  /**
11196
11193
  * scenes plugin events.
@@ -1,9 +1,9 @@
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";
1
+ import { Ir as Api$3, T as Api, Zt as Api$1, yr as Api$2 } from "./types-DnttyLeu.mjs";
2
+ import { P as AnimApi, r as UiApi } from "./types-C2g2KYCN.mjs";
3
+ import { j as InputApi } from "./types-CWSMo70I.mjs";
4
+ import { r as Repro } from "./headless-DDkRwLnJ.mjs";
5
+ import { a as InputOf, r as ControlApp } from "./types-DFoxDq-g.mjs";
6
+ import { t as commands } from "./commands-e_I5i8dK.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-G6F0Mi9H.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-DalSHy9z.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,8 +101,7 @@ 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
+ // systemsRun?: never; // the 0.15.3 key, removed: a system runs while its query has an entity
106
105
  // animations?: readonly AnimationDefinition[]; // V3, read by anim
107
106
  // ui?: readonly ComponentDefinition[]; // V3, read by ui (defineComponent results)
108
107
  // strings?: Record<locale, CompiledMessages | (() => Promise<unknown>)>; // V3, read by i18n
@@ -529,7 +528,7 @@ The graph. Modules `runner`, `gate`, `inbox`, `fx`, `features`. Runner methods s
529
528
  - `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`.
530
529
  - `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
530
  - `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"`.
531
+ - 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".
533
532
  - 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.
534
533
  - `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.
535
534
  - `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.