@moku-labs/game 0.15.10 → 0.15.12

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 (38) 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/{commands-DoMH0hnR.d.mts → commands-C3yVtCg1.d.mts} +5 -5
  8. package/dist/component-DGg5DqKK.mjs +44 -0
  9. package/dist/control.d.mts +4 -4
  10. package/dist/{define-B8A5IoeS.d.mts → define-DbxBzO-G.d.mts} +2 -2
  11. package/dist/{flatten-BkXuscAV.mjs → flatten-nUX9PpGo.mjs} +1 -44
  12. package/dist/{headless-CrxofjLt.d.mts → headless-DHtavbqk.d.mts} +1 -1
  13. package/dist/{index-PbX7OQIC.d.mts → index-CsTMiO0x.d.mts} +3 -3
  14. package/dist/index.d.mts +4 -4
  15. package/dist/index.mjs +3 -3
  16. package/dist/inspect.d.mts +6 -7
  17. package/dist/inspect.mjs +1 -1
  18. package/dist/jsx-dev-runtime.d.mts +1 -1
  19. package/dist/jsx-dev-runtime.mjs +1 -1
  20. package/dist/jsx-runtime.d.mts +1 -1
  21. package/dist/jsx-runtime.mjs +1 -1
  22. package/dist/{runtime-Bk_rHR3K.d.mts → runtime-Bxy2zoCG.d.mts} +1 -1
  23. package/dist/{runtime-CBZgWgzx.mjs → runtime-DnM9i73o.mjs} +2 -1
  24. package/dist/{sources-QCowxSpC.mjs → sources-Fbq_PFVH.mjs} +82 -35
  25. package/dist/{src-zXAFOqEc.mjs → src-FIcYaXro.mjs} +17 -452
  26. package/dist/testing.d.mts +4 -4
  27. package/dist/testing.mjs +1 -1
  28. package/dist/{types-B73p1XBn.d.mts → types-CpBnGqDU.d.mts} +3 -3
  29. package/dist/{types-i77Nrrv-.d.mts → types-CuEK0Vam.d.mts} +3733 -3719
  30. package/dist/{types-Dr5uC2fS.d.mts → types-CveBhKTs.d.mts} +4 -4
  31. package/dist/{types-DwktxRrt.d.mts → types-D5Pmo8BD.d.mts} +1 -1
  32. package/dist/{types-YXykgTOp.d.mts → types-_a6-ALzz.d.mts} +2 -2
  33. package/dist/{visual-CQnurNOj.d.mts → visual-B5Sypqal.d.mts} +6 -6
  34. package/dist/{visual-CIUx49C0.mjs → visual-CbGKzlj0.mjs} +450 -3
  35. package/dist/visual.d.mts +1 -1
  36. package/dist/visual.mjs +1 -1
  37. package/llms.txt +1 -1
  38. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
- import { A as PixiModule, Ar as SaveDoc, Cr as Json, D as Config$2, Fn as Config$1, Nr as SnapshotOf, Or as PlayerStateProvider, Yn as FeaturePlugin, Zn as AnyFlow, _r as FakeClock, a as Config$3, d as Manifest, hr as ClockSource, jt as Orientation, kr as ProviderCall, n as AssetsIo, or as TypeTag, xr as Config } from "./types-i77Nrrv-.mjs";
2
- import { o as AudioContextLike, s as Config$4 } from "./types-B73p1XBn.mjs";
3
- import { H as StringTable, L as I18nApi, U as TextKey } from "./types-DwktxRrt.mjs";
4
- import { ft as platformPlugin, i as screen, mt as PlatformProvider, t as createApp, wt as audioPlugin, xt as effectsPlugin } from "./index-PbX7OQIC.mjs";
1
+ import { Ar as ProviderCall, In as Config$1, Pr as SnapshotOf, Qn as AnyFlow, Sr as Config, X as PixiModule, Xn as FeaturePlugin, a as Config$3, d as Manifest, gr as ClockSource, jr as SaveDoc, kr as PlayerStateProvider, n as AssetsIo, ot as Orientation, q as Config$2, sr as TypeTag, vr as FakeClock, wr as Json } from "./types-CuEK0Vam.mjs";
2
+ import { o as AudioContextLike, s as Config$4 } from "./types-CpBnGqDU.mjs";
3
+ import { H as StringTable, L as I18nApi, U as TextKey } from "./types-D5Pmo8BD.mjs";
4
+ import { ft as platformPlugin, i as screen, mt as PlatformProvider, t as createApp, wt as audioPlugin, xt as effectsPlugin } from "./index-CsTMiO0x.mjs";
5
5
  import { AnyPluginInstance } from "@moku-labs/core";
6
6
 
7
7
  //#region src/plugins/model/store/providers/memory.d.ts
@@ -1,4 +1,4 @@
1
- import { $r as Require, Cn as Motion, Cr as Json, Dn as ProjectionMotion, E as Api$3, En as NumericFields, Mn as ViewHandle, Nn as Api$1, Nt as ViewportSize, Qt as Api$2, Tn as MotionHandle, Ur as Api, Yt as TransformValue, an as AnyComponentValue, bn as ChangeHook, cn as ComponentType, dr as Hint, in as AnyComponentType, mr as Api$5, pr as Answer, rn as AnyComponent, s as Events$1, sr as Descriptor, t as Api$4, un as Entity, wn as MotionCause, yn as TagType } from "./types-i77Nrrv-.mjs";
1
+ import { $t as Api$2, Dn as NumericFields, En as MotionHandle, K as Api$3, Nn as ViewHandle, On as ProjectionMotion, Pn as Api$1, Tn as MotionCause, U as TransformValue, Wr as Api, an as AnyComponentType, bn as TagType, cr as Descriptor, ct as ViewportSize, dn as Entity, ei as Require, fr as Hint, hr as Api$5, in as AnyComponent, ln as ComponentType, mr as Answer, on as AnyComponentValue, s as Events$1, t as Api$4, wn as Motion, wr as Json, xn as ChangeHook } from "./types-CuEK0Vam.mjs";
2
2
  import { Log } from "@moku-labs/common/browser";
3
3
  import { PluginCtx } from "@moku-labs/core";
4
4
 
@@ -1,5 +1,5 @@
1
- import { Cr as Json, Mr as Snapshot } from "./types-i77Nrrv-.mjs";
2
- import { t as HeadlessApp } from "./headless-CrxofjLt.mjs";
1
+ import { Nr as Snapshot, wr as Json } from "./types-CuEK0Vam.mjs";
2
+ import { t as HeadlessApp } from "./headless-DHtavbqk.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
 
5
5
  //#region src/plugins/flow/doors/types.d.ts
@@ -1,9 +1,9 @@
1
- import { E as Api, Lr as Api$3, Qt as Api$1, br as Api$2 } from "./types-i77Nrrv-.mjs";
2
- import { P as AnimApi, r as UiApi } from "./types-B73p1XBn.mjs";
3
- import { M as InputApi } from "./types-DwktxRrt.mjs";
4
- import { r as Repro } from "./headless-CrxofjLt.mjs";
5
- import { a as InputOf, r as ControlApp } from "./types-YXykgTOp.mjs";
6
- import { t as commands } from "./commands-DoMH0hnR.mjs";
1
+ import { $t as Api$1, K as Api, Rr as Api$3, xr as Api$2 } from "./types-CuEK0Vam.mjs";
2
+ import { P as AnimApi, r as UiApi } from "./types-CpBnGqDU.mjs";
3
+ import { M as InputApi } from "./types-D5Pmo8BD.mjs";
4
+ import { r as Repro } from "./headless-DHtavbqk.mjs";
5
+ import { a as InputOf, r as ControlApp } from "./types-_a6-ALzz.mjs";
6
+ import { t as commands } from "./commands-C3yVtCg1.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. */
@@ -1,3 +1,4 @@
1
+ import { t as defineComponent } from "./component-DGg5DqKK.mjs";
1
2
  //#region src/plugins/world/ecs/define.ts
2
3
  /**
3
4
  * Declares a component type. The defaults give the TypeScript type, the JSON shape and the
@@ -212,7 +213,8 @@ const overlayDefaults = {
212
213
  dimColor: 0,
213
214
  cover: "none",
214
215
  hud: "all",
215
- fade: 250
216
+ fade: 250,
217
+ hold: false
216
218
  };
217
219
  const displayDefaults = { object: void 0 };
218
220
  const shapeDefaults = {
@@ -292,12 +294,15 @@ const Shape = /*#__PURE__*/ component("Shape", shapeDefaults);
292
294
  * Marks the view an overlay is drawn from. Everything drawn before the root of that view is kept as one picture while
293
295
  * the overlay is there, and drawn again in the same frame anything in it changes; `cover: "opaque"` draws nothing
294
296
  * under it while it is fully shown. The engine draws its dim and fades the HUD parts it does not keep, with the root's
295
- * own enter and exit motion (`fade` when it has none).
297
+ * own enter and exit motion (`fade` when it has none). `hold: true` holds the endless loops of what lies under it
298
+ * while it stands, so a dimmed sheet over a sharp scene draws no frame at rest.
296
299
  *
297
300
  * @example
298
301
  * ```tsx
299
302
  * // The case file: opaque once it has faded in, the ward under it is not drawn until it starts to leave.
300
303
  * <panel key="caseSheet" motion={sheetFade} components={[Overlay({ cover: "opaque" })]} />
304
+ * // The shop: the ward dims to 56 % and its glints rest while the shop stands; the energy and the gems stay shown.
305
+ * <stack key="shopDimLayer" components={[Overlay({ dim: 0.56, hold: true, hud: ["energy", "gems"] })]} />
301
306
  * ```
302
307
  */
303
308
  const Overlay = /*#__PURE__*/ component("Overlay", overlayDefaults);
@@ -365,6 +370,250 @@ function componentsFor() {
365
370
  HudPart
366
371
  };
367
372
  }
373
+ //#endregion
374
+ //#region src/plugins/renderer/sync/pose.ts
375
+ /**
376
+ * @file renderer/sync — the pose helpers: where an entity really is, in root (reference) space,
377
+ * and the local pose that puts it there under a parent. Pure component math over `world.ecs.get`,
378
+ * the same math the renderer draws with: `position` is where the `pivot` lands, and the pivot is
379
+ * the local point the view turns and scales around.
380
+ *
381
+ * Engine-internal, not a root export. `input`, `anim` and `ui` import `rootPoseOf` and
382
+ * `localPoseOf` from this module path (`../renderer/sync/pose`) and never walk the `Parent` chain
383
+ * themselves; a per-frame reader (`effects`' following emitters) writes the root pose into its own
384
+ * object with `rootPoseInto`, which allocates nothing; `input` places a carried view under the last pose of a despawned held view with
385
+ * `poseUnder`; `sync` places a box with `rootPointOf`. `input` places the drop lookup of an aimed
386
+ * view with `applyPose`.
387
+ */
388
+ /** How deep a parent chain is followed; deeper is treated as a loop. */
389
+ const MAX_DEPTH = 32;
390
+ /**
391
+ * The parent an entity moves with.
392
+ *
393
+ * @param ecs - The world read.
394
+ * @param entity - The entity.
395
+ * @returns The parent entity, or `0` when it has none or names itself.
396
+ */
397
+ function parentOf(ecs, entity) {
398
+ const parent = ecs.get(entity, Parent)?.entity ?? 0;
399
+ return parent === entity ? 0 : parent;
400
+ }
401
+ /**
402
+ * Tells whether an entity is on its way out: it or an ancestor of its `Parent` chain carries `Exiting` (a ui element
403
+ * whose exit plays, a world view in its exit queue). Its despawn is coming; nothing else of the teardown is owed.
404
+ *
405
+ * @param ecs - The world read.
406
+ * @param entity - The entity.
407
+ * @returns True while it, or what it hangs under, leaves.
408
+ */
409
+ function isLeaving(ecs, entity) {
410
+ let current = entity;
411
+ for (let depth = 0; depth < MAX_DEPTH && current !== 0; depth += 1) {
412
+ if (ecs.has(current, Exiting)) return true;
413
+ current = parentOf(ecs, current);
414
+ }
415
+ return false;
416
+ }
417
+ /**
418
+ * A fresh copy of a pose, so a caller may change what it got without touching the world.
419
+ *
420
+ * @param pose - The pose to copy.
421
+ * @returns The same numbers in new objects.
422
+ * @example
423
+ * ```ts
424
+ * copyPose({ x: 1, y: 2, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }).pivot.x; // 0
425
+ * ```
426
+ */
427
+ function copyPose(pose) {
428
+ return {
429
+ x: pose.x,
430
+ y: pose.y,
431
+ rotation: pose.rotation,
432
+ scale: pose.scale,
433
+ pivot: {
434
+ x: pose.pivot.x,
435
+ y: pose.pivot.y
436
+ }
437
+ };
438
+ }
439
+ /**
440
+ * Moves a point out of the local space of a pose, into the space the pose sits in.
441
+ *
442
+ * @param pose - The pose whose local space the point is in.
443
+ * @param point - The local point.
444
+ * @returns The point one space higher.
445
+ * @example
446
+ * ```ts
447
+ * applyPose({ x: 10, y: 0, rotation: 0, scale: 2, pivot: { x: 5, y: 0 } }, { x: 5, y: 1 });
448
+ * // { x: 10, y: 2 }
449
+ * ```
450
+ */
451
+ function applyPose(pose, point) {
452
+ const cos = Math.cos(pose.rotation);
453
+ const sin = Math.sin(pose.rotation);
454
+ const dx = point.x - pose.pivot.x;
455
+ const dy = point.y - pose.pivot.y;
456
+ return {
457
+ x: pose.x + pose.scale * (dx * cos - dy * sin),
458
+ y: pose.y + pose.scale * (dx * sin + dy * cos)
459
+ };
460
+ }
461
+ /**
462
+ * Places a local pose under a parent pose: the pose in the space the parent sits in. The step
463
+ * `rootPoseOf` takes once per parent: the position through the parent's pivot, rotation, scale
464
+ * and position, the rotations added and the scales multiplied.
465
+ *
466
+ * @param above - The parent pose.
467
+ * @param local - The pose under it.
468
+ * @returns The composed pose; it keeps the local pivot.
469
+ * @example
470
+ * ```ts
471
+ * poseUnder(
472
+ * { x: 400, y: 300, rotation: 0, scale: 2, pivot: { x: 0, y: 0 } },
473
+ * { x: 0, y: 30, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }
474
+ * ); // { x: 400, y: 360, rotation: 0, scale: 2, pivot: { x: 0, y: 0 } }
475
+ * ```
476
+ */
477
+ function poseUnder(above, local) {
478
+ const landed = applyPose(above, local);
479
+ return {
480
+ x: landed.x,
481
+ y: landed.y,
482
+ rotation: local.rotation + above.rotation,
483
+ scale: local.scale * above.scale,
484
+ pivot: {
485
+ x: local.pivot.x,
486
+ y: local.pivot.y
487
+ }
488
+ };
489
+ }
490
+ /**
491
+ * Where an entity really is, written into the caller's pose: its `Transform` composed through the `Parent` chain,
492
+ * every pivot on the way applied, the numbers of `rootPoseOf` (the same steps in the same order). The answer keeps the
493
+ * entity's own pivot. Allocates nothing: a reader that asks every frame passes the same object.
494
+ *
495
+ * @param ecs - The world read, `world.ecs`.
496
+ * @param entity - The entity to place.
497
+ * @param out - The pose to write, its `pivot` object included; it may be `own`.
498
+ * @param own - A pose to use in place of the entity's own `Transform`, such as its rest pose.
499
+ * @returns `out`, in root space. An entity without a `Transform` sits at the identity pose.
500
+ * @example
501
+ * ```ts
502
+ * // `effects` follows a host every frame: the slot sits at (40, 60) scaled 0.5, the host at (100, 200) inside it.
503
+ * const pose = { x: 0, y: 0, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } };
504
+ * rootPoseInto(ctx.require(worldPlugin).ecs, host, pose);
505
+ * // pose is { x: 90, y: 160, rotation: 0, scale: 0.5, pivot: { x: 0, y: 0 } }
506
+ * ```
507
+ */
508
+ function rootPoseInto(ecs, entity, out, own) {
509
+ const start = own ?? ecs.get(entity, Transform) ?? Transform.defaults;
510
+ out.x = start.x;
511
+ out.y = start.y;
512
+ out.rotation = start.rotation;
513
+ out.scale = start.scale;
514
+ out.pivot.x = start.pivot.x;
515
+ out.pivot.y = start.pivot.y;
516
+ let current = entity;
517
+ for (let depth = 0; depth < MAX_DEPTH; depth += 1) {
518
+ const parent = parentOf(ecs, current);
519
+ if (parent === 0) break;
520
+ const above = ecs.get(parent, Transform) ?? Transform.defaults;
521
+ const cos = Math.cos(above.rotation);
522
+ const sin = Math.sin(above.rotation);
523
+ const dx = out.x - above.pivot.x;
524
+ const dy = out.y - above.pivot.y;
525
+ out.x = above.x + above.scale * (dx * cos - dy * sin);
526
+ out.y = above.y + above.scale * (dx * sin + dy * cos);
527
+ out.rotation = out.rotation + above.rotation;
528
+ out.scale = out.scale * above.scale;
529
+ current = parent;
530
+ }
531
+ return out;
532
+ }
533
+ /**
534
+ * Where an entity really is: its `Transform` composed through the `Parent` chain, every pivot on
535
+ * the way applied. The answer keeps the entity's own pivot, so it can be written straight into the
536
+ * `Transform` of an entity without a parent and the view does not move.
537
+ *
538
+ * @param ecs - The world read, `world.ecs`.
539
+ * @param entity - The entity to place.
540
+ * @param own - A pose to use in place of the entity's own `Transform`, such as its rest pose.
541
+ * @returns The pose in root space, a fresh object. An entity without a `Transform` sits at the identity pose.
542
+ * @example
543
+ * ```ts
544
+ * // `input` lifts a board cell out of its slot: the slot sits at (40, 60) scaled 0.5,
545
+ * // the cell at (100, 200) inside it.
546
+ * rootPoseOf(ctx.require(worldPlugin).ecs, cell);
547
+ * // { x: 90, y: 160, rotation: 0, scale: 0.5, pivot: { x: 0, y: 0 } }
548
+ * ```
549
+ */
550
+ function rootPoseOf(ecs, entity, own) {
551
+ return rootPoseInto(ecs, entity, {
552
+ x: 0,
553
+ y: 0,
554
+ rotation: 0,
555
+ scale: 1,
556
+ pivot: {
557
+ x: 0,
558
+ y: 0
559
+ }
560
+ }, own);
561
+ }
562
+ /**
563
+ * The inverse of `rootPoseOf`: the local pose under `parent` that puts an entity at a root pose.
564
+ * The pivot is kept, as it is a local point of the entity itself.
565
+ *
566
+ * @param ecs - The world read, `world.ecs`.
567
+ * @param parent - The entity to hang under; `0` for none, which answers the root pose itself.
568
+ * @param root - The pose in root space.
569
+ * @returns The local pose. A parent collapsed to scale 0 counts as scale 1, so the answer stays
570
+ * finite.
571
+ * @example
572
+ * ```ts
573
+ * // `input` drops the cell back into the slot at (40, 60) scaled 0.5, where the finger left it.
574
+ * localPoseOf(ctx.require(worldPlugin).ecs, slot, {
575
+ * x: 90, y: 160, rotation: 0, scale: 0.5, pivot: { x: 0, y: 0 }
576
+ * });
577
+ * // { x: 100, y: 200, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }
578
+ * ```
579
+ */
580
+ function localPoseOf(ecs, parent, root) {
581
+ if (parent === 0) return copyPose(root);
582
+ const above = rootPoseOf(ecs, parent);
583
+ const scale = above.scale === 0 ? 1 : above.scale;
584
+ const cos = Math.cos(-above.rotation);
585
+ const sin = Math.sin(-above.rotation);
586
+ const dx = root.x - above.x;
587
+ const dy = root.y - above.y;
588
+ return {
589
+ x: above.pivot.x + (dx * cos - dy * sin) / scale,
590
+ y: above.pivot.y + (dx * sin + dy * cos) / scale,
591
+ rotation: root.rotation - above.rotation,
592
+ scale: root.scale / scale,
593
+ pivot: {
594
+ x: root.pivot.x,
595
+ y: root.pivot.y
596
+ }
597
+ };
598
+ }
599
+ /**
600
+ * Where a local point of an entity lands in root (reference) space: the point posed through the
601
+ * entity's own `Transform` and its whole `Parent` chain, the forward walk of the hit test's
602
+ * `localPoseOf`. The scale is the real one, so a view collapsed to scale 0 lands on one point.
603
+ *
604
+ * @param ecs - The world read, `world.ecs`.
605
+ * @param entity - The entity whose local space the point is in.
606
+ * @param point - The local point.
607
+ * @returns The point in root space, a fresh object.
608
+ * @example
609
+ * ```ts
610
+ * // The corner of a 64 x 64 cell at (100, 200) inside a slot at (40, 60) scaled 0.5.
611
+ * rootPointOf(ctx.require(worldPlugin).ecs, cell, { x: -32, y: -32 }); // { x: 74, y: 144 }
612
+ * ```
613
+ */
614
+ function rootPointOf(ecs, entity, point) {
615
+ return applyPose(rootPoseOf(ecs, entity), point);
616
+ }
368
617
  /**
369
618
  * Words on the screen: a string or a message, the style it is drawn in, and the string `text`
370
619
  * resolved out of it. A game writes `content`, `style`, `bind`, `anchor` and `alpha`; `resolved`
@@ -662,6 +911,204 @@ function textFor() {
662
911
  };
663
912
  }
664
913
  //#endregion
914
+ //#region src/plugins/ui/styles/define.ts
915
+ /**
916
+ * Declares one style. The work is the type: a field outside the vocabulary, or a value outside
917
+ * its union, is an error where the style is written, not where it is used.
918
+ *
919
+ * @param style - The style, with its `is` and `when` variants.
920
+ * @returns The same object, frozen.
921
+ * @example
922
+ * ```ts
923
+ * defineStyle({ direction: "row", gap: 12 }).gap; // 12
924
+ * ```
925
+ */
926
+ function defineStyle(style) {
927
+ return Object.freeze(style);
928
+ }
929
+ //#endregion
930
+ //#region src/plugins/ui/styles/tokens.ts
931
+ /**
932
+ * Declares the design tokens of a game: spaces, colours, radii and font keys, flat and plain.
933
+ *
934
+ * @param tokens - The four groups; every key inside them is the game's own.
935
+ * @returns The same object, frozen.
936
+ * @example
937
+ * ```ts
938
+ * defineTokens({ space: { md: 16 } }).space.md; // 16
939
+ * ```
940
+ */
941
+ function defineTokens(tokens) {
942
+ return Object.freeze(tokens);
943
+ }
944
+ //#endregion
945
+ //#region src/plugins/ui/components.ts
946
+ /**
947
+ * Who owns every entity `ui` spawns. The projection module checks it before it hands out a view
948
+ * handle, so only `ui` may move a ui element.
949
+ */
950
+ const UI_OWNER = {
951
+ kind: "plugin",
952
+ name: "ui"
953
+ };
954
+ /** Fresh defaults for the patch, so no two buttons share one object. */
955
+ const localWriteDefaults = { patch: {} };
956
+ /**
957
+ * The rect of a ui element in root coordinates. Written by the solve, read by `tree()`, `lint()`
958
+ * and the hit test of `guide`.
959
+ */
960
+ const Box = /*#__PURE__*/ component("Box", {
961
+ x: 0,
962
+ y: 0,
963
+ w: 0,
964
+ h: 0
965
+ });
966
+ /**
967
+ * On a button that writes local state instead of naming an intent. `input.onTap` reports the tap
968
+ * and `ui` merges the patch into the nearest component instance.
969
+ */
970
+ const LocalWrite = /*#__PURE__*/ component("LocalWrite", localWriteDefaults);
971
+ /**
972
+ * On a scroll container. Internal to `ui`: not exported from the package root.
973
+ */
974
+ const Scroll = /*#__PURE__*/ component("Scroll", {
975
+ axis: "y",
976
+ offset: 0,
977
+ min: 0
978
+ });
979
+ /**
980
+ * On every scroll container: where it stands and whether it moves, for a game system and the
981
+ * read door. `ui` writes it in a frame where the offset or `max` changed, and once more with
982
+ * `moving: false` when it stops; a game reads it and never writes it.
983
+ *
984
+ * @example
985
+ * ```ts
986
+ * // A board system dims the events of the order strip while it rests on the orders.
987
+ * export const stripMist = system({
988
+ * name: "board.stripMist",
989
+ * phase: "animate",
990
+ * query: [ScrollState, OrderStrip],
991
+ * run: entities => {
992
+ * for (const [entity, scroll] of entities) {
993
+ * // scroll: { offset: 318, max: 390, moving: false }
994
+ * }
995
+ * }
996
+ * });
997
+ * ```
998
+ */
999
+ const ScrollState = /*#__PURE__*/ component("ScrollState", {
1000
+ offset: 0,
1001
+ max: 0,
1002
+ moving: false
1003
+ });
1004
+ /**
1005
+ * On the root entity of a popup kept beneath another popup (`popup(..., { over })`). Internal to
1006
+ * `ui`: not exported from the package root. Every element of that root resolves `is.covered`.
1007
+ */
1008
+ const Covered = /*#__PURE__*/ tag("Covered");
1009
+ /**
1010
+ * On a button with the `escape` prop: the control the Escape key taps while its root is the top
1011
+ * one, meant for the close button or the backdrop of a dismissable popup. Internal to `ui`: the
1012
+ * game writes the prop, never the tag.
1013
+ */
1014
+ const Escapable = /*#__PURE__*/ tag("Escapable");
1015
+ /**
1016
+ * On the view a player taps five times within three seconds to open the editor's overlay on a debug build: the
1017
+ * version line of the settings, usually. One helper on top of `app.debug.overlay.open()`; a game that wants another
1018
+ * gesture calls that itself. The view must take a press (a `button`, or any element with `Touchable`); a `button`
1019
+ * that names no intent is the plain choice. Without the agent flag the tag does nothing.
1020
+ *
1021
+ * @example
1022
+ * ```tsx
1023
+ * // The version line of the settings sheet. Five taps open the overlay of `moku-game build --debug` on the phone.
1024
+ * <button key="versionLine" style={versionLineStyle} components={[DebugTrigger()]}>
1025
+ * <text key="version" style="settings.version" content={tr("settings.version", { version: "1.4.0" })} />
1026
+ * </button>
1027
+ * ```
1028
+ */
1029
+ const DebugTrigger = /*#__PURE__*/ tag("DebugTrigger");
1030
+ /**
1031
+ * The three Yoga counters, the reconcile count of the frame, the range re-renders of the windowed
1032
+ * lists, and the style and write passes of the reconcile. Written by the two systems of `ui`, read
1033
+ * by the acceptance tests. Not part of `app.ui`.
1034
+ */
1035
+ const UiCounters = /*#__PURE__*/ resource("UiCounters", {
1036
+ solves: 0,
1037
+ nodes: 0,
1038
+ measured: 0,
1039
+ reconciles: 0,
1040
+ windowRenders: 0,
1041
+ styled: 0,
1042
+ written: 0
1043
+ });
1044
+ /**
1045
+ * Builds the effect a node awaits to show a popup. The gate opens for the outcomes of the
1046
+ * component, so the node resolves with the intent one of its buttons answered. `over` names the
1047
+ * component of a popup that stays mounted beneath this one, drawn covered, until this one is gone.
1048
+ *
1049
+ * @param component - A component declared with `outcomes`.
1050
+ * @param props - What its view is called with.
1051
+ * @param options - How the popup stands to the others.
1052
+ * @param options.over - The component name of the popup kept beneath this one.
1053
+ * @returns The descriptor `fx()` takes.
1054
+ * @example
1055
+ * ```ts
1056
+ * const Reward = defineComponent("Reward", {
1057
+ * outcomes: { claim: {} },
1058
+ * view: () => ({ type: "panel", props: {}, children: [] })
1059
+ * });
1060
+ * popup(Reward, { gold: 5 });
1061
+ * // { kind: "popup", payload: { component: "Reward", props: { gold: 5 } }, answers: ["claim"] }
1062
+ * await fx(popup(Reward, { gold: 5 })); // Answer<"claim">: { intent: "claim" }
1063
+ * ```
1064
+ * @example
1065
+ * ```ts
1066
+ * // The settings node asks before a reset; the settings stay beneath, covered.
1067
+ * const Confirm = defineComponent("Confirm", {
1068
+ * outcomes: { reset: {}, cancel: {} },
1069
+ * view: () => ({ type: "panel", props: {}, children: [] })
1070
+ * });
1071
+ * popup(Confirm, {}, { over: "Settings" });
1072
+ * // { kind: "popup", payload: { component: "Confirm", props: {}, over: "Settings" },
1073
+ * // answers: ["reset", "cancel"] }
1074
+ * ```
1075
+ */
1076
+ function popup(component, props, options) {
1077
+ const over = options?.over;
1078
+ return {
1079
+ kind: "popup",
1080
+ payload: over === void 0 ? {
1081
+ component: component.name,
1082
+ props
1083
+ } : {
1084
+ component: component.name,
1085
+ props,
1086
+ over
1087
+ },
1088
+ answers: Object.keys(component.outcomes)
1089
+ };
1090
+ }
1091
+ /**
1092
+ * Binds the four ui helpers to one game. Type-only: the same four functions, plus the intrinsic
1093
+ * table this game's tags are checked against.
1094
+ *
1095
+ * @returns The helpers and the intrinsic type carrier.
1096
+ * @example
1097
+ * ```ts
1098
+ * const { defineStyle } = uiFor<"ui.coin", "digits", "hud.coins">();
1099
+ * defineStyle({ gap: 8 }).gap; // 8
1100
+ * ```
1101
+ */
1102
+ function uiFor() {
1103
+ return {
1104
+ defineComponent,
1105
+ defineStyle,
1106
+ defineTokens,
1107
+ popup,
1108
+ intrinsics: {}
1109
+ };
1110
+ }
1111
+ //#endregion
665
1112
  //#region src/plugins/ui/visual.ts
666
1113
  /**
667
1114
  * @file ui plugin — the look of an element: the visual component it is drawn with, and where a
@@ -915,4 +1362,4 @@ function drawnRect(pose, local) {
915
1362
  };
916
1363
  }
917
1364
  //#endregion
918
- export { Now as A, Sprite as C, sprite as D, componentsFor as E, resource as F, system as I, systemsFor as L, Tree as M, component as N, Exiting as O, mut as P, tag as R, Shape as S, arcDefaults as T, HudPart as _, scaleByFits as a, OverlayLevel as b, Countdown as c, builtInStyles as d, defineTextStyles as f, Display as g, textFor as h, samePose as i, Order as j, Layer as k, Text as l, readStyle as m, drawnRect as n, visualOf as o, label as p, fitScaleOf as r, visualRectOf as s, CONTENT as t, bind as u, NineSlice as v, Transform as w, Parent as x, Overlay as y };
1365
+ export { component as $, localPoseOf as A, OverlayLevel as B, builtInStyles as C, textFor as D, readStyle as E, rootPoseOf as F, arcDefaults as G, Shape as H, Display as I, Exiting as J, componentsFor as K, HudPart as L, poseUnder as M, rootPointOf as N, applyPose as O, rootPoseInto as P, Tree as Q, NineSlice as R, bind as S, label as T, Sprite as U, Parent as V, Transform as W, Now as X, Layer as Y, Order as Z, uiFor as _, visualOf as a, Countdown as b, Covered as c, LocalWrite as d, mut as et, Scroll as f, popup as g, UiCounters as h, samePose as i, tag as it, parentOf as j, isLeaving as k, DebugTrigger as l, UI_OWNER as m, drawnRect as n, system as nt, visualRectOf as o, ScrollState as p, sprite as q, fitScaleOf as r, systemsFor as rt, Box as s, CONTENT as t, resource as tt, Escapable as u, defineTokens as v, defineTextStyles as w, Text as x, defineStyle as y, Overlay as z };
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-CQnurNOj.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-B5Sypqal.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,4 +1,4 @@
1
- import { n as read, t as sources } from "./sources-QCowxSpC.mjs";
1
+ import { n as read, t as sources } from "./sources-Fbq_PFVH.mjs";
2
2
  import { n as commands, t as run } from "./run-BqYmcNo7.mjs";
3
3
  import path from "node:path";
4
4
  import { createBrandConsole } from "@moku-labs/common/cli";
package/llms.txt CHANGED
@@ -522,7 +522,7 @@ The graph. Modules `runner`, `gate`, `inbox`, `fx`, `features`. Runner methods s
522
522
  `world`, `renderer`, `input`, `assets`, `scenes`. Each has a README at `src/plugins/<name>/README.md`; the root README lists their APIs, events and config. Rendering: WebGPU preferred, with Pixi's WebGL fallback.
523
523
  - `app.scenes`: `current()` the mounted scene id, `expect(id)` the scene the next node without its own `scene` mounts, a rest node or a transit node in live mode (the restore door calls it; an unknown id throws). An `over` node takes it the same way, also over another mounted scene.
524
524
  - `Shape({ kind, w, h, fill, fillAlpha, alpha, radius, stroke, strokeWidth, dash, clip, sweep?, cap? })` (`kind: "rect"`; `sweep` and `cap` are optional, `1` and `"round"` when left out; only an arc reads them; a rectangle or a triangle ignores them, also when an earlier arc left them on the `Shape`): a rounded rectangle, `kind: "triangle"` (fills its box pointing right) or `kind: "arc"`: a stroke on the circle inscribed in the box (`min(w, h) − strokeWidth` across, centred) from 12 o'clock clockwise over `sweep` (0..1, clamped; 0 draws nothing, 1 a closed ring), ended by `cap` (`"round" | "butt"`); an arc draws no fill and ignores `radius` and `dash`. The path is built again only when a field changes: a still arc costs nothing a frame. `Shape({ kind: "arc", w: 28, h: 28, stroke: 0xc8a15a, strokeWidth: 2.2, sweep: 0.42 })` is a slot timer's ring with 42 % left.
525
- - Overlays: `Overlay({ dim, dimColor, cover, hud, fade })` on the root of an overlay (in `components`) keeps everything drawn before it as one picture, retaken in the frame anything in it changes, and with `cover: "opaque"` draws none of it once landed at alpha 1; the engine draws the dim (`dim × level`, right under the root); the level follows the root's own enter and exit motion, `fade` (250 ms) only when it has none; level and landing are read through `app.renderer.sync.debug.overlays()`. A `backdrop: true` filter inside the kept range runs no pass while the picture stands (its look is in the picture) and is drawn again in the frame the overlay is gone.
525
+ - Overlays: `Overlay({ dim, dimColor, cover, hud, fade, hold })` on the root of an overlay (in `components`) keeps everything drawn before it as one picture, retaken in the frame anything in it changes, and with `cover: "opaque"` draws none of it once landed at alpha 1; `hold: true` (default `false`) holds the endless loops under it while it stands, as under a cover or a blurred picture (their phase follows time), so a dimmed sheet over a sharp scene draws no frame at rest; the engine draws the dim (`dim × level`, right under the root); the level follows the root's own enter and exit motion, `fade` (250 ms) only when it has none; level and landing are read through `app.renderer.sync.debug.overlays()`. A `backdrop: true` filter inside the kept range runs no pass while the picture stands (its look is in the picture) and is drawn again in the frame the overlay is gone.
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`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/game",
3
- "version": "0.15.10",
3
+ "version": "0.15.12",
4
4
  "description": "2D puzzle game engine on TypeScript and PixiJS, built on @moku-labs/core.",
5
5
  "type": "module",
6
6
  "sideEffects": [