@moku-labs/game 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -22
- package/bin/moku-game.mjs +4 -0
- package/dist/app/page.d.mts +44 -0
- package/dist/app/page.mjs +379 -0
- package/dist/app/system.d.mts +144 -0
- package/dist/app/system.mjs +329 -0
- package/dist/app.d.mts +37 -0
- package/dist/app.mjs +235 -0
- package/dist/assets-BHwTLVta.mjs +7391 -0
- package/dist/assets.d.mts +1 -1
- package/dist/assets.mjs +1 -7390
- package/dist/cli.d.mts +70 -0
- package/dist/cli.mjs +1685 -0
- package/dist/{commands-DuwCxK5t.d.mts → commands-C4QlqijJ.d.mts} +5 -5
- package/dist/config-CdARrrhU.mjs +81 -0
- package/dist/control.d.mts +11 -10
- package/dist/control.mjs +3 -3
- package/dist/{define-B6pHpdBi.d.mts → define-CCc0ozKe.d.mts} +2 -2
- package/dist/{dev-wKLgOw7c.mjs → dev-Cc50Dk7H.mjs} +1 -1
- package/dist/drafts-BawpSaVp.mjs +156 -0
- package/dist/fake-MpzwZhIZ.mjs +87 -0
- package/dist/{headless-SCQXJxQf.d.mts → headless-G4nuZ-xz.d.mts} +1 -1
- package/dist/hot.d.mts +4 -6
- package/dist/hot.mjs +8 -9
- package/dist/{index-BO9kZzY3.d.mts → index-8TiJQjQA.d.mts} +30 -17
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +1 -1
- package/dist/inspect-la_KkjCS.mjs +1 -0
- package/dist/inspect.d.mts +7 -7
- package/dist/inspect.mjs +3 -2
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/lint.mjs +10 -6
- package/dist/memory-CWlqhS7l.mjs +136 -0
- package/dist/memory-DwDhfFWK.d.mts +50 -0
- package/dist/project.mjs +3 -3
- package/dist/{run-CofJEdYx.mjs → run-wYty_Lwo.mjs} +2 -2
- package/dist/{runtime-CdNJ0UBm.d.mts → runtime-DOBYM6g4.d.mts} +1 -1
- package/dist/{input-z_35Jwfn.mjs → session-DgukhR_7.mjs} +20 -20
- package/dist/{sources-DNnzyCYf.mjs → sources-Duzj6lhV.mjs} +2 -2
- package/dist/{src-536arXjV.mjs → src-BSbtHZWk.mjs} +35 -300
- package/dist/testing.d.mts +4 -50
- package/dist/testing.mjs +3 -87
- package/dist/{types-VZVWwqCg.d.mts → types-BM4shh8K.d.mts} +259 -259
- package/dist/{types-xXfyk56Q.d.mts → types-BUwDHKXm.d.mts} +1377 -1377
- package/dist/types-Ca_z_4tQ.d.mts +586 -0
- package/dist/{types-CQueODzo.d.mts → types-CefgYzFt.d.mts} +2 -2
- package/dist/{types-DUMoYPYe.d.mts → types-DAdwEJMp.d.mts} +2 -2
- package/dist/{types-B0v5VVa3.d.mts → types-ZQLYXo5X.d.mts} +304 -304
- package/dist/visual.d.mts +6 -6
- package/dist/visual.mjs +5 -5
- package/llms.txt +120 -43
- package/package.json +30 -2
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
2
|
+
import { r as systemSource, t as memory } from "./memory-CWlqhS7l.mjs";
|
|
3
|
+
import { a as finishDrafts, i as enableDraftPatches, n as deepFreeze, o as openDrafts, r as dropDrafts } from "./drafts-BawpSaVp.mjs";
|
|
2
4
|
import { c as findNode, d as slotNames, f as compact, i as servePaused, l as framePath, n as captureFrame, o as collectFlows, p as pushEntry, r as serveCaptures, s as describeGraph, t as cancelCaptures, u as graphHash } from "./capture-fpd83Mi5.mjs";
|
|
3
5
|
import { A as system, C as Exiting, D as component, E as Tree, T as Order, _ as Parent, a as visualOf$1, b as Transform, c as Text, g as NineSlice, h as Display, j as tag, k as resource, m as textFor, n as fitScaleOf, o as visualRectOf, p as readStyle, r as samePose, s as Countdown, t as CONTENT, u as builtInStyles, v as Shape, w as Layer, x as componentsFor, y as Sprite } from "./visual-dMW8iqAE.mjs";
|
|
4
|
-
import { n as isDev } from "./dev-
|
|
6
|
+
import { n as isDev } from "./dev-Cc50Dk7H.mjs";
|
|
5
7
|
import { i as isComponentDefinition, n as flatten, r as defineComponent } from "./flatten-BkXuscAV.mjs";
|
|
6
8
|
import { envPlugin, logPlugin } from "@moku-labs/common/browser";
|
|
7
9
|
import { createCoreConfig } from "@moku-labs/core";
|
|
8
|
-
import { applyPatches, createDraft, enablePatches, finishDraft } from "immer";
|
|
9
10
|
/**
|
|
10
11
|
* Core config of the engine: `log` and `env` on every plugin context.
|
|
11
12
|
*/
|
|
@@ -127,37 +128,6 @@ function createClockApi(ctx) {
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
//#endregion
|
|
130
|
-
//#region src/plugins/clock/system.ts
|
|
131
|
-
/**
|
|
132
|
-
* Tells whether a handle was produced by this source.
|
|
133
|
-
*
|
|
134
|
-
* @param handle - Handle given back to `clearTimer`.
|
|
135
|
-
* @returns `true` when the handle carries a platform timer.
|
|
136
|
-
* @example
|
|
137
|
-
* ```ts
|
|
138
|
-
* isSystemTimer({ timer: 7 }); // true
|
|
139
|
-
* isSystemTimer(7); // false: a handle of another source is ignored
|
|
140
|
-
* ```
|
|
141
|
-
*/
|
|
142
|
-
function isSystemTimer(handle) {
|
|
143
|
-
return typeof handle === "object" && handle !== null && "timer" in handle;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Creates the system source: epoch milliseconds from the device clock and real timers.
|
|
147
|
-
*
|
|
148
|
-
* @returns A source reading `Date.now` and scheduling with `setTimeout`.
|
|
149
|
-
*/
|
|
150
|
-
function systemSource() {
|
|
151
|
-
return {
|
|
152
|
-
now: () => Date.now(),
|
|
153
|
-
setTimer: (callback, delayMs) => ({ timer: setTimeout(callback, delayMs) }),
|
|
154
|
-
clearTimer: (handle) => {
|
|
155
|
-
if (!isSystemTimer(handle)) return;
|
|
156
|
-
clearTimeout(handle.timer);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
//#endregion
|
|
161
131
|
//#region src/plugins/clock/state.ts
|
|
162
132
|
/**
|
|
163
133
|
* @file clock plugin — state factory.
|
|
@@ -765,263 +735,6 @@ function createRngState() {
|
|
|
765
735
|
return {};
|
|
766
736
|
}
|
|
767
737
|
//#endregion
|
|
768
|
-
//#region src/plugins/model/store/drafts.ts
|
|
769
|
-
/** Root order of a commit result, so two commits never report the same roots in another order. */
|
|
770
|
-
const rootOrder = Object.freeze([
|
|
771
|
-
"player",
|
|
772
|
-
"session",
|
|
773
|
-
"rng"
|
|
774
|
-
]);
|
|
775
|
-
/**
|
|
776
|
-
* Turns on Immer's patch recording. Idempotent, and called from the state factory rather than at
|
|
777
|
-
* module scope: a module-scope call would run on import, even when no app is created.
|
|
778
|
-
*
|
|
779
|
-
*/
|
|
780
|
-
function enableDraftPatches() {
|
|
781
|
-
enablePatches();
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Freezes a tree and every branch of it. Immer freezes what it produces; this covers the trees
|
|
785
|
-
* that never went through a draft — the initial trees and the ones handed to `restore()`.
|
|
786
|
-
*
|
|
787
|
-
* @param tree - The tree to freeze, in place.
|
|
788
|
-
* @returns The same reference, now frozen.
|
|
789
|
-
* @example
|
|
790
|
-
* ```ts
|
|
791
|
-
* const tree = deepFreeze({ player: { bag: ["key"] } });
|
|
792
|
-
* Object.isFrozen(tree.player.bag); // true
|
|
793
|
-
* ```
|
|
794
|
-
*/
|
|
795
|
-
function deepFreeze(tree) {
|
|
796
|
-
if (!tree || typeof tree !== "object") return tree;
|
|
797
|
-
for (const branch of Object.values(tree)) deepFreeze(branch);
|
|
798
|
-
return Object.freeze(tree);
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* Maps one Immer patch to our own structural patch, so no Immer type reaches a public type.
|
|
802
|
-
* A removal carries no value, and `exactOptionalPropertyTypes` forbids an explicit `undefined`.
|
|
803
|
-
*
|
|
804
|
-
* @param patch - The Immer patch.
|
|
805
|
-
* @param path - Path of the patch inside its own tree, without the container segment.
|
|
806
|
-
* @returns The structural patch.
|
|
807
|
-
* @example
|
|
808
|
-
* ```ts
|
|
809
|
-
* toPatch({ op: "remove", path: ["doc", "player", "key"] }, ["player", "key"]);
|
|
810
|
-
* // { op: "remove", path: ["player", "key"] }
|
|
811
|
-
* ```
|
|
812
|
-
*/
|
|
813
|
-
function toPatch(patch, path) {
|
|
814
|
-
if (patch.op === "remove") return {
|
|
815
|
-
op: patch.op,
|
|
816
|
-
path
|
|
817
|
-
};
|
|
818
|
-
return {
|
|
819
|
-
op: patch.op,
|
|
820
|
-
path,
|
|
821
|
-
value: patch.value
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
/**
|
|
825
|
-
* Derives the touched roots from the patches of one commit, in a fixed order.
|
|
826
|
-
*
|
|
827
|
-
* @param docPatches - Patches of the save document.
|
|
828
|
-
* @param sessionPatches - Patches of the session tree.
|
|
829
|
-
* @returns The roots a listener has to reconcile.
|
|
830
|
-
* @example
|
|
831
|
-
* ```ts
|
|
832
|
-
* rootsOf([{ op: "add", path: ["rng", "streams", "dice"], value: 7 }], []); // ["rng"]
|
|
833
|
-
* rootsOf([], [{ op: "replace", path: ["rolls"], value: 1 }]); // ["session"]
|
|
834
|
-
* ```
|
|
835
|
-
*/
|
|
836
|
-
function rootsOf(docPatches, sessionPatches) {
|
|
837
|
-
const touched = /* @__PURE__ */ new Set();
|
|
838
|
-
for (const patch of docPatches) {
|
|
839
|
-
const [head] = patch.path;
|
|
840
|
-
if (head === "player" || head === "rng") touched.add(head);
|
|
841
|
-
}
|
|
842
|
-
if (sessionPatches.length > 0) touched.add("session");
|
|
843
|
-
return rootOrder.filter((root) => touched.has(root));
|
|
844
|
-
}
|
|
845
|
-
/**
|
|
846
|
-
* Opens mutable drafts of the frozen save document and session tree.
|
|
847
|
-
* Both live in one draft container, so one `finishDraft` closes the transaction and the patches
|
|
848
|
-
* arrive in one list, tagged by their container key.
|
|
849
|
-
*
|
|
850
|
-
* @param doc - Frozen save document.
|
|
851
|
-
* @param session - Frozen session tree.
|
|
852
|
-
* @returns The open drafts of one transaction.
|
|
853
|
-
* @example
|
|
854
|
-
* ```ts
|
|
855
|
-
* const pair = openDrafts({ player: { coins: 1 }, rng: { seed: 7, streams: {} } }, { rolls: 0 });
|
|
856
|
-
* pair.session; // a mutable draft of { rolls: 0 }: a write never reaches the frozen input
|
|
857
|
-
* ```
|
|
858
|
-
*/
|
|
859
|
-
function openDrafts(doc, session) {
|
|
860
|
-
return createDraft({
|
|
861
|
-
doc,
|
|
862
|
-
session
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* Finishes the drafts: the new frozen trees, the patches per tree and the touched roots.
|
|
867
|
-
* The drafts are revoked, so a draft that leaked into a view throws on the next touch instead of
|
|
868
|
-
* corrupting the committed tree in silence.
|
|
869
|
-
*
|
|
870
|
-
* @param pair - Open drafts of one transaction.
|
|
871
|
-
* @returns The frozen trees, the patches split by tree, and the touched roots.
|
|
872
|
-
*/
|
|
873
|
-
function finishDrafts(pair) {
|
|
874
|
-
const collected = [];
|
|
875
|
-
const finished = finishDraft(pair, (patches) => {
|
|
876
|
-
collected.push(...patches);
|
|
877
|
-
});
|
|
878
|
-
const docPatches = [];
|
|
879
|
-
const sessionPatches = [];
|
|
880
|
-
for (const patch of collected) {
|
|
881
|
-
const [container, ...path] = patch.path;
|
|
882
|
-
(container === "doc" ? docPatches : sessionPatches).push(toPatch(patch, path));
|
|
883
|
-
}
|
|
884
|
-
return {
|
|
885
|
-
doc: finished.doc,
|
|
886
|
-
session: finished.session,
|
|
887
|
-
patches: {
|
|
888
|
-
doc: docPatches,
|
|
889
|
-
session: sessionPatches
|
|
890
|
-
},
|
|
891
|
-
roots: rootsOf(docPatches, sessionPatches)
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
/**
|
|
895
|
-
* Applies the patches of one commit to a document, the way a save seam stores what it was handed.
|
|
896
|
-
* A whole-document patch carries the path `[]` and replaces the document. The input is left as it
|
|
897
|
-
* is: the result is a new frozen document.
|
|
898
|
-
*
|
|
899
|
-
* @param document - The document the patches apply to.
|
|
900
|
-
* @param patches - The patches of one commit, in order.
|
|
901
|
-
* @returns The document after the patches.
|
|
902
|
-
* @throws {Error} When a patch names a path the document does not have.
|
|
903
|
-
* @example
|
|
904
|
-
* ```ts
|
|
905
|
-
* applyTo({ player: { coins: 0 } }, [{ op: "replace", path: ["player", "coins"], value: 4 }]);
|
|
906
|
-
* // { player: { coins: 4 } }
|
|
907
|
-
* ```
|
|
908
|
-
*/
|
|
909
|
-
function applyTo(document, patches) {
|
|
910
|
-
enablePatches();
|
|
911
|
-
return applyPatches(document, patches);
|
|
912
|
-
}
|
|
913
|
-
/**
|
|
914
|
-
* Drops the drafts without a result: the base trees stay as they are and the drafts are revoked.
|
|
915
|
-
*
|
|
916
|
-
* @param pair - Open drafts of one transaction.
|
|
917
|
-
*/
|
|
918
|
-
function dropDrafts(pair) {
|
|
919
|
-
finishDraft(pair);
|
|
920
|
-
}
|
|
921
|
-
//#endregion
|
|
922
|
-
//#region src/plugins/model/store/providers/memory.ts
|
|
923
|
-
/** Seed of a save built by `saveOf` when the caller does not pin one. */
|
|
924
|
-
const defaultSeed = 1;
|
|
925
|
-
/** The base a patch applies to when the provider was never handed a document. */
|
|
926
|
-
const noDocument = {};
|
|
927
|
-
/**
|
|
928
|
-
* Creates an in-memory provider that keeps what it was committed and records every call.
|
|
929
|
-
* It is the default when a game configures no `playerProvider`, and the double every test uses:
|
|
930
|
-
* `provider.calls` is the whole persistence protocol of one run, in order. The document lives as
|
|
931
|
-
* long as the provider does, so a second app over the same instance reads what the first one
|
|
932
|
-
* wrote, and nothing survives the process.
|
|
933
|
-
*
|
|
934
|
-
* `load()` reads the fixture, or everything committed since; without either, the player is new.
|
|
935
|
-
* `commitDurable()` resolves at once: there is no disk behind it.
|
|
936
|
-
*
|
|
937
|
-
* @param fixture - The save `load()` starts from. Omitted: a new player.
|
|
938
|
-
* @param fixture.state - The saved document.
|
|
939
|
-
* @param fixture.version - Schema version of the saved document.
|
|
940
|
-
* @returns A provider that keeps its document and records its calls.
|
|
941
|
-
* @example
|
|
942
|
-
* ```ts
|
|
943
|
-
* // A new player is saved at once. One roll reaches the provider at the next rest node.
|
|
944
|
-
* const provider = memory();
|
|
945
|
-
* const game = await createHeadless(createGame(provider));
|
|
946
|
-
*
|
|
947
|
-
* await game.walk([{ at: "home", intent: "roll" }]);
|
|
948
|
-
* provider.calls.map(call => call.method); // ["load", "commit", "commit"]
|
|
949
|
-
* ```
|
|
950
|
-
*/
|
|
951
|
-
function memory(fixture) {
|
|
952
|
-
const calls = [];
|
|
953
|
-
const held = {
|
|
954
|
-
document: fixture?.state,
|
|
955
|
-
version: fixture?.version ?? 0
|
|
956
|
-
};
|
|
957
|
-
/**
|
|
958
|
-
* Applies the patches of one commit to the held document.
|
|
959
|
-
*
|
|
960
|
-
* @param patches - Doc patches since the last commit.
|
|
961
|
-
* @param version - Schema version this build writes.
|
|
962
|
-
* @throws {Error} When a patch names a path the held document does not have.
|
|
963
|
-
*/
|
|
964
|
-
const hold = (patches, version) => {
|
|
965
|
-
held.document = applyTo(held.document ?? noDocument, patches);
|
|
966
|
-
held.version = version;
|
|
967
|
-
};
|
|
968
|
-
return {
|
|
969
|
-
calls,
|
|
970
|
-
load: async () => {
|
|
971
|
-
calls.push({ method: "load" });
|
|
972
|
-
const document = held.document;
|
|
973
|
-
if (document === void 0) return null;
|
|
974
|
-
return {
|
|
975
|
-
state: document,
|
|
976
|
-
version: held.version
|
|
977
|
-
};
|
|
978
|
-
},
|
|
979
|
-
commit: (patches, version) => {
|
|
980
|
-
calls.push({
|
|
981
|
-
method: "commit",
|
|
982
|
-
patches,
|
|
983
|
-
version
|
|
984
|
-
});
|
|
985
|
-
hold(patches, version);
|
|
986
|
-
},
|
|
987
|
-
commitDurable: async (patches, txId, version) => {
|
|
988
|
-
calls.push({
|
|
989
|
-
method: "commitDurable",
|
|
990
|
-
patches,
|
|
991
|
-
txId,
|
|
992
|
-
version
|
|
993
|
-
});
|
|
994
|
-
hold(patches, version);
|
|
995
|
-
},
|
|
996
|
-
flush: async () => {
|
|
997
|
-
calls.push({ method: "flush" });
|
|
998
|
-
}
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
/**
|
|
1002
|
-
* Builds a save document from a player tree, with no rng streams drawn yet.
|
|
1003
|
-
* Short fixtures in tests read as `saveOf({ coins: 5 })` instead of spelling out the rng branch.
|
|
1004
|
-
*
|
|
1005
|
-
* @param player - Player tree of the save.
|
|
1006
|
-
* @param seed - Rng seed of the save.
|
|
1007
|
-
* @returns The save document.
|
|
1008
|
-
* @example
|
|
1009
|
-
* ```ts
|
|
1010
|
-
* // A returning player with 5 coins. The test starts from this save, not from `initialPlayer`.
|
|
1011
|
-
* saveOf({ coins: 5 }, 42); // { player: { coins: 5 }, rng: { seed: 42, streams: {} } }
|
|
1012
|
-
* const provider = memory({ state: saveOf({ coins: 5 }, 42), version: 1 });
|
|
1013
|
-
* ```
|
|
1014
|
-
*/
|
|
1015
|
-
function saveOf(player, seed = defaultSeed) {
|
|
1016
|
-
return {
|
|
1017
|
-
player,
|
|
1018
|
-
rng: {
|
|
1019
|
-
seed,
|
|
1020
|
-
streams: {}
|
|
1021
|
-
}
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
//#endregion
|
|
1025
738
|
//#region src/plugins/model/store/state.ts
|
|
1026
739
|
/** Seed of the trees that exist before `load()` ran. A save or a new player replaces it. */
|
|
1027
740
|
const preloadSeed = 0;
|
|
@@ -23777,13 +23490,24 @@ function setAwake(ctx, provider, on) {
|
|
|
23777
23490
|
//#endregion
|
|
23778
23491
|
//#region src/plugins/platform/api.ts
|
|
23779
23492
|
/**
|
|
23780
|
-
* @file platform plugin — API factory
|
|
23781
|
-
* first that takes it wins: the Escape button of the top popup, the intent `back` of the
|
|
23782
|
-
* node, the provider leaving the app.
|
|
23493
|
+
* @file platform plugin — API factory, the Back chain and the exit. One press runs three steps and
|
|
23494
|
+
* the first that takes it wins: the Escape button of the top popup, the intent `back` of the
|
|
23495
|
+
* resting node, the provider leaving the app. `exit()` is that last step alone.
|
|
23783
23496
|
*/
|
|
23784
23497
|
/** The intent a resting node lists to take the Back press itself. */
|
|
23785
23498
|
const BACK_INTENT = "back";
|
|
23786
23499
|
/**
|
|
23500
|
+
* Leaves the app through the provider, guarded like every provider call. Without a provider it
|
|
23501
|
+
* does nothing. The last step of the Back chain and `exit()` both go through here.
|
|
23502
|
+
*
|
|
23503
|
+
* @param ctx - Kernel context of the platform plugin.
|
|
23504
|
+
*/
|
|
23505
|
+
function leave(ctx) {
|
|
23506
|
+
const provider = ctx.config.provider;
|
|
23507
|
+
if (provider === void 0) return;
|
|
23508
|
+
safely(ctx.log, "exit", () => provider.exit());
|
|
23509
|
+
}
|
|
23510
|
+
/**
|
|
23787
23511
|
* Runs the Back chain once. `back()` and the provider's `onBack` both go through here, so a test
|
|
23788
23512
|
* and a real press end the same way. Escape is what ui answers with the `escape` button of its
|
|
23789
23513
|
* top root; it also ends the editing of a text field. While the graph moves between nodes the gate
|
|
@@ -23794,22 +23518,24 @@ const BACK_INTENT = "back";
|
|
|
23794
23518
|
* @returns What took the press, `"none"` without a provider.
|
|
23795
23519
|
*/
|
|
23796
23520
|
function runBack(ctx) {
|
|
23797
|
-
|
|
23798
|
-
if (provider === void 0) return "none";
|
|
23521
|
+
if (ctx.config.provider === void 0) return "none";
|
|
23799
23522
|
if (ctx.require(inputPlugin).pressKey("Escape")) return "popup";
|
|
23800
23523
|
const gate = ctx.require(flowPlugin).gate;
|
|
23801
23524
|
if (gate.answer({ intent: BACK_INTENT }) || !gate.state().open) return "intent";
|
|
23802
|
-
|
|
23525
|
+
leave(ctx);
|
|
23803
23526
|
return "exit";
|
|
23804
23527
|
}
|
|
23805
23528
|
/**
|
|
23806
|
-
* Creates the platform API: the Back chain on demand.
|
|
23529
|
+
* Creates the platform API: the Back chain and the exit on demand.
|
|
23807
23530
|
*
|
|
23808
23531
|
* @param ctx - Kernel context of the platform plugin.
|
|
23809
23532
|
* @returns The plugin API.
|
|
23810
23533
|
*/
|
|
23811
23534
|
function createPlatformApi(ctx) {
|
|
23812
|
-
return {
|
|
23535
|
+
return {
|
|
23536
|
+
back: () => runBack(ctx),
|
|
23537
|
+
exit: () => leave(ctx)
|
|
23538
|
+
};
|
|
23813
23539
|
}
|
|
23814
23540
|
//#endregion
|
|
23815
23541
|
//#region src/plugins/platform/state.ts
|
|
@@ -34532,21 +34258,30 @@ function defineMotion(spec) {
|
|
|
34532
34258
|
* | effects | maxPasses | 24, render passes per frame above which one dev warning fires per crossing |
|
|
34533
34259
|
* | effects | phone | "auto", a coarse pointer and a short side of at most 820 CSS px |
|
|
34534
34260
|
* | effects | blur | { quality: 2, phoneResolution: 0.5 }, what a Blur with quality 0 and resolution 0 resolves to; shallow merge: a game that sets `blur` gives both fields |
|
|
34535
|
-
* | platform | provider | undefined, inert: no subscription, no haptic handler, `back()` answers "none" |
|
|
34261
|
+
* | platform | provider | undefined, inert: no subscription, no haptic handler, `back()` answers "none"; the engine page passes the system shell's provider |
|
|
34536
34262
|
* | platform | keepAwake | false |
|
|
34537
34263
|
*
|
|
34538
34264
|
* Subpaths next to the root:
|
|
34539
34265
|
*
|
|
34540
34266
|
* | Subpath | What |
|
|
34541
34267
|
* |---|---|
|
|
34268
|
+
* | `@moku-labs/game/app` | `defineGameApp`, the game as one data object with `headless()` and `screen()`; `startMoment`; the types of `index.ts` and `config.ts` (`GameConfig`) |
|
|
34269
|
+
* | `@moku-labs/game/app/page` | `startPage`, the page the generated `.moku/main.ts` calls: `?player=` scenarios, the system shell, the dev agents |
|
|
34270
|
+
* | `@moku-labs/game/app/system` | the system shell over the optional peer `@moku-labs/system`: `systemShellOf`, `fromSystem`, `storeSave`, `createSystemApp` |
|
|
34271
|
+
* | `@moku-labs/game/cli` | `runCli` of the bin `moku-game` and `preparePage` for the editor, node and bun only |
|
|
34542
34272
|
* | `@moku-labs/game/testing` | headless play, repro runs, isolated feature tests (`isolate`, `stub`), the in-memory save, the fake clock |
|
|
34543
34273
|
* | `@moku-labs/game/visual` | the visual tests, node and bun only |
|
|
34544
34274
|
* | `@moku-labs/game/assets` | the build-time asset key scanner and the production packer (`packAssets`, `--pack <dir>`), node and bun only |
|
|
34545
34275
|
* | `@moku-labs/game/inspect` | the editor's read door: `read`, `watch`, `defineSource`, the `sources` catalogue |
|
|
34546
34276
|
* | `@moku-labs/game/control` | the editor's write door, dev builds only: `run`, `defineCommand`, the `commands` catalogue |
|
|
34277
|
+
* | `@moku-labs/game/hot` | the Bun plugin that hot swaps views; `moku-game dev` lists it in its bunfig |
|
|
34278
|
+
* | `@moku-labs/game/lint` | the oxlint JS plugin `moku-game` with the game lint rules |
|
|
34279
|
+
* | `@moku-labs/game/project` | the project index of a game's sources, node and bun only |
|
|
34547
34280
|
* | `@moku-labs/game/jsx-runtime`, `/jsx-dev-runtime` | the JSX runtimes `jsxImportSource: "@moku-labs/game"` names |
|
|
34548
34281
|
* | `@moku-labs/game/fonts/*` | the MSDF body font `font-body.fnt` + `.png` and its OFL licence; a game copies them into `features/ui/assets/` |
|
|
34282
|
+
* | bin `moku-game` | `dev`, `build`, `native <verb>`, `keys`, `pack`: serves, builds, packages and packs a game folder, bun only |
|
|
34549
34283
|
* | bin `moku-game-assets` | the asset scanner CLI, bun only |
|
|
34284
|
+
* | bin `moku-game-index` | the project index CLI, bun only |
|
|
34550
34285
|
*
|
|
34551
34286
|
* @file The package root: the composed framework and its public exports.
|
|
34552
34287
|
* @example
|
|
@@ -34641,4 +34376,4 @@ const screen = [
|
|
|
34641
34376
|
uiPlugin
|
|
34642
34377
|
];
|
|
34643
34378
|
//#endregion
|
|
34644
|
-
export { sfx as $, types_exports$13 as A, defineFilter as At, defineStyle as B, flowPlugin as Bt, types_exports$6 as C, Alpha as Ct, types_exports$10 as D, Glow as Dt, types_exports$9 as E, Displacement as Et, resolve as F, AnimPlayer as Ft, external as G, modelPlugin as Gt, platformPlugin as H, slot as Ht, Box as I, Animation as It, mark as J,
|
|
34379
|
+
export { sfx as $, types_exports$13 as A, defineFilter as At, defineStyle as B, flowPlugin as Bt, types_exports$6 as C, Alpha as Ct, types_exports$10 as D, Glow as Dt, types_exports$9 as E, Displacement as Et, resolve as F, AnimPlayer as Ft, external as G, modelPlugin as Gt, platformPlugin as H, slot as Ht, Box as I, Animation as It, mark as J, timePlugin as Jt, frames as K, SaveUnreadableError as Kt, LocalWrite as L, Frames as Lt, types_exports$15 as M, scenesPlugin as Mt, types_exports$16 as N, assetsPlugin as Nt, types_exports$11 as O, Noise as Ot, uiPlugin as P, animPlugin as Pt, set as Q, popup as R, rendererPlugin as Rt, types_exports$5 as S, Emitter as St, types_exports$8 as T, ColorMatrix as Tt, HAPTIC_KINDS as U, to as Ut, textPlugin as V, exit as Vt, defineAnimation as W, type as Wt, play$1 as X, parallel as Y, clockPlugin as Yt, sequence as Z, types_exports as _, Touchable as _t, defineMotion as a, wait as at, types_exports$3 as b, i18nPlugin as bt, schedule as c, DropTarget as ct, tr as d, Pointer as dt, spawn as et, defineFeature as f, PointerOver as ft, load as g, Tappable as gt, defineBundles as h, Swipeable as ht, screen as i, use as it, types_exports$14 as j, audioPlugin as jt, types_exports$12 as k, Outline as kt, projection as l, Held as lt, music as m, Pressed as mt, createPlugin as n, stagger as nt, guide as o, inputPlugin as ot, defineEmitter as p, Pressable as pt, haptic as q, lifecyclePlugin as qt, defineGame as r, tween as rt, hint as s, Draggable as st, createApp as t, spawned as tt, defineScene as u, Hovered as ut, types_exports$1 as v, Traceable as vt, types_exports$7 as w, Blur as wt, types_exports$4 as x, effectsPlugin as xt, types_exports$2 as y, Traced as yt, defineTokens as z, worldPlugin as zt };
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { t as
|
|
1
|
+
import { Dt as AnyFlow, Ht as ClockSource, Nt as NodeTable, Wt as FakeClock, Xt as Json, ht as FeaturePlugin } from "./types-ZQLYXo5X.mjs";
|
|
2
|
+
import { t as createApp } from "./index-8TiJQjQA.mjs";
|
|
3
|
+
import { n as saveOf, t as memory } from "./memory-DwDhfFWK.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-G4nuZ-xz.mjs";
|
|
4
5
|
import { AnyPluginInstance } from "@moku-labs/core";
|
|
5
6
|
|
|
6
7
|
//#region src/plugins/clock/fake.d.ts
|
|
@@ -25,53 +26,6 @@ import { AnyPluginInstance } from "@moku-labs/core";
|
|
|
25
26
|
*/
|
|
26
27
|
declare function fakeClock(start?: number): FakeClock;
|
|
27
28
|
//#endregion
|
|
28
|
-
//#region src/plugins/model/store/providers/memory.d.ts
|
|
29
|
-
/**
|
|
30
|
-
* Creates an in-memory provider that keeps what it was committed and records every call.
|
|
31
|
-
* It is the default when a game configures no `playerProvider`, and the double every test uses:
|
|
32
|
-
* `provider.calls` is the whole persistence protocol of one run, in order. The document lives as
|
|
33
|
-
* long as the provider does, so a second app over the same instance reads what the first one
|
|
34
|
-
* wrote, and nothing survives the process.
|
|
35
|
-
*
|
|
36
|
-
* `load()` reads the fixture, or everything committed since; without either, the player is new.
|
|
37
|
-
* `commitDurable()` resolves at once: there is no disk behind it.
|
|
38
|
-
*
|
|
39
|
-
* @param fixture - The save `load()` starts from. Omitted: a new player.
|
|
40
|
-
* @param fixture.state - The saved document.
|
|
41
|
-
* @param fixture.version - Schema version of the saved document.
|
|
42
|
-
* @returns A provider that keeps its document and records its calls.
|
|
43
|
-
* @example
|
|
44
|
-
* ```ts
|
|
45
|
-
* // A new player is saved at once. One roll reaches the provider at the next rest node.
|
|
46
|
-
* const provider = memory();
|
|
47
|
-
* const game = await createHeadless(createGame(provider));
|
|
48
|
-
*
|
|
49
|
-
* await game.walk([{ at: "home", intent: "roll" }]);
|
|
50
|
-
* provider.calls.map(call => call.method); // ["load", "commit", "commit"]
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
declare function memory(fixture?: {
|
|
54
|
-
state: SaveDoc;
|
|
55
|
-
version: number;
|
|
56
|
-
}): PlayerStateProvider & {
|
|
57
|
-
calls: ProviderCall[];
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Builds a save document from a player tree, with no rng streams drawn yet.
|
|
61
|
-
* Short fixtures in tests read as `saveOf({ coins: 5 })` instead of spelling out the rng branch.
|
|
62
|
-
*
|
|
63
|
-
* @param player - Player tree of the save.
|
|
64
|
-
* @param seed - Rng seed of the save.
|
|
65
|
-
* @returns The save document.
|
|
66
|
-
* @example
|
|
67
|
-
* ```ts
|
|
68
|
-
* // A returning player with 5 coins. The test starts from this save, not from `initialPlayer`.
|
|
69
|
-
* saveOf({ coins: 5 }, 42); // { player: { coins: 5 }, rng: { seed: 42, streams: {} } }
|
|
70
|
-
* const provider = memory({ state: saveOf({ coins: 5 }, 42), version: 1 });
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
declare function saveOf(player: Json, seed?: number): SaveDoc;
|
|
74
|
-
//#endregion
|
|
75
29
|
//#region src/testing/isolate.d.ts
|
|
76
30
|
/**
|
|
77
31
|
* A stub for one node or sub-flow of the isolated flow: the outcome it ends with, and its payload.
|
package/dist/testing.mjs
CHANGED
|
@@ -1,91 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Vt as exit, Wt as type, t as createApp } from "./src-BSbtHZWk.mjs";
|
|
2
|
+
import { n as saveOf, t as memory } from "./memory-CWlqhS7l.mjs";
|
|
3
|
+
import { t as fakeClock } from "./fake-MpzwZhIZ.mjs";
|
|
2
4
|
import { i as stepFrames, r as runRepro, t as createHeadless } from "./headless-BsNdJs9W.mjs";
|
|
3
|
-
//#region src/plugins/clock/fake.ts
|
|
4
|
-
/**
|
|
5
|
-
* Removes and returns the timer that is due first, up to and including `until`.
|
|
6
|
-
* Timers due at the same moment come out in the order they were scheduled.
|
|
7
|
-
*
|
|
8
|
-
* @param state - Innards of the fake clock.
|
|
9
|
-
* @param until - Latest moment that counts as due.
|
|
10
|
-
* @returns The timer to fire, or `undefined` when nothing is due.
|
|
11
|
-
*/
|
|
12
|
-
function takeDue(state, until) {
|
|
13
|
-
let earliest;
|
|
14
|
-
for (const timer of state.timers) {
|
|
15
|
-
if (timer.at > until) continue;
|
|
16
|
-
if (earliest === void 0 || timer.at < earliest.at) earliest = timer;
|
|
17
|
-
}
|
|
18
|
-
if (earliest === void 0) return void 0;
|
|
19
|
-
const due = earliest;
|
|
20
|
-
state.timers = state.timers.filter((timer) => timer !== due);
|
|
21
|
-
return due;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Moves the clock to `until`, firing every timer that falls due on the way, in due order.
|
|
25
|
-
* A timer scheduled inside a callback fires in the same run when it is due before `until`.
|
|
26
|
-
*
|
|
27
|
-
* @param state - Innards of the fake clock.
|
|
28
|
-
* @param until - Moment to stop at.
|
|
29
|
-
*/
|
|
30
|
-
function runDue(state, until) {
|
|
31
|
-
let due = takeDue(state, until);
|
|
32
|
-
while (due !== void 0) {
|
|
33
|
-
state.now = Math.max(state.now, due.at);
|
|
34
|
-
due.callback();
|
|
35
|
-
due = takeDue(state, until);
|
|
36
|
-
}
|
|
37
|
-
state.now = Math.max(state.now, until);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Creates a fake source for tests. Timers fire synchronously inside `advance`, in due order. A
|
|
41
|
-
* negative delay counts as zero and an unknown handle is ignored.
|
|
42
|
-
*
|
|
43
|
-
* @param start - Starting moment in epoch milliseconds. Defaults to 0.
|
|
44
|
-
* @returns A clock source with the test-only controls `advance` and `set`.
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* // An energy point refills 60 s after it was spent. The test moves the clock, not the minute.
|
|
48
|
-
* const clock = fakeClock(1000);
|
|
49
|
-
* const app = createApp({ pluginConfigs: { clock: { source: clock } } });
|
|
50
|
-
* const seen: number[] = [];
|
|
51
|
-
*
|
|
52
|
-
* app.clock.onElapsed(({ now }) => seen.push(now));
|
|
53
|
-
* app.clock.scheduleAt(61_000);
|
|
54
|
-
* clock.advance(60_000); // the timer fires inside advance, nothing waits
|
|
55
|
-
* seen; // [61000]
|
|
56
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
function fakeClock(start = 0) {
|
|
59
|
-
const state = {
|
|
60
|
-
now: start,
|
|
61
|
-
nextId: 1,
|
|
62
|
-
timers: []
|
|
63
|
-
};
|
|
64
|
-
return {
|
|
65
|
-
now: () => state.now,
|
|
66
|
-
setTimer: (callback, delayMs) => {
|
|
67
|
-
const id = state.nextId;
|
|
68
|
-
state.nextId += 1;
|
|
69
|
-
state.timers.push({
|
|
70
|
-
id,
|
|
71
|
-
at: state.now + Math.max(0, delayMs),
|
|
72
|
-
callback
|
|
73
|
-
});
|
|
74
|
-
return id;
|
|
75
|
-
},
|
|
76
|
-
clearTimer: (handle) => {
|
|
77
|
-
if (typeof handle !== "number") return;
|
|
78
|
-
state.timers = state.timers.filter((timer) => timer.id !== handle);
|
|
79
|
-
},
|
|
80
|
-
advance: (ms) => {
|
|
81
|
-
runDue(state, state.now + Math.max(0, ms));
|
|
82
|
-
},
|
|
83
|
-
set: (moment) => {
|
|
84
|
-
state.now = moment;
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
//#endregion
|
|
89
5
|
//#region src/testing/isolate.ts
|
|
90
6
|
const noPayload = null;
|
|
91
7
|
/** Id of the harness main flow. */
|