@moku-labs/game 0.14.12 → 0.14.13
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 +13 -12
- package/dist/app/page.d.mts +8 -3
- package/dist/app/page.mjs +57 -12
- package/dist/app/system.d.mts +5 -3
- package/dist/app/system.mjs +2 -0
- package/dist/app.d.mts +15 -7
- package/dist/app.mjs +131 -27
- package/dist/{assets-BrKSg1O6.mjs → assets-CEMfBogh.mjs} +471 -120
- package/dist/assets.d.mts +30 -13
- package/dist/assets.mjs +1 -1
- package/dist/cli.d.mts +105 -8
- package/dist/cli.mjs +1036 -128
- package/dist/{commands-BaCx2B9j.d.mts → commands-DAETmK86.d.mts} +43 -7
- package/dist/{config-CdARrrhU.mjs → config-B87aFTW-.mjs} +25 -4
- package/dist/control.d.mts +130 -17
- package/dist/control.mjs +4 -4
- package/dist/{define-Ce8SVEN-.d.mts → define-BASd9ZUh.d.mts} +32 -4
- package/dist/dev-DbHgx-R-.mjs +89 -0
- package/dist/{fake-MpzwZhIZ.mjs → fake-6mazmHK3.mjs} +20 -2
- package/dist/features-DowEilZm.mjs +266 -0
- package/dist/{headless-CMHNsKjW.d.mts → headless-cFveciuY.d.mts} +1 -1
- package/dist/hot.d.mts +4 -2
- package/dist/hot.mjs +7 -4
- package/dist/{index-CLQ8NOTw.d.mts → index-DedbED3T.d.mts} +317 -69
- package/dist/index.d.mts +5 -6
- package/dist/index.mjs +3 -3
- package/dist/inspect.d.mts +12 -7
- package/dist/inspect.mjs +2 -2
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/lint.d.mts +8 -3
- package/dist/lint.mjs +432 -20
- package/dist/project.d.mts +15 -1
- package/dist/project.mjs +1 -1
- package/dist/{run-Bf2hKyjZ.mjs → run-BbipFRc6.mjs} +250 -51
- package/dist/{runtime-BCnKn0xf.d.mts → runtime-DoSlRx8X.d.mts} +1 -1
- package/dist/{session-DgukhR_7.mjs → session-CeKgO2hy.mjs} +30 -2
- package/dist/{sources-Duzj6lhV.mjs → sources-Cdvb0rOS.mjs} +33 -6
- package/dist/{src-CtC8w3_z.mjs → src-BEbJ_sSx.mjs} +35536 -17789
- package/dist/testing.d.mts +23 -13
- package/dist/testing.mjs +10 -6
- package/dist/{types-DOlpeuGx.d.mts → types-7GXcXxxv.d.mts} +174 -242
- package/dist/{types-BjTtGSFb.d.mts → types-BGcda5Le.d.mts} +4459 -732
- package/dist/{types-gisvSHoC.d.mts → types-C73hbmxi.d.mts} +129 -17
- package/dist/{types-CAPv9l29.d.mts → types-DngFQJro.d.mts} +43 -5
- package/dist/{types-Y_rCuXtj.d.mts → types-GHE4J5Uf.d.mts} +1025 -191
- package/dist/{visual-dMW8iqAE.mjs → visual-2m4wJL_z.mjs} +111 -7
- package/dist/visual-_XhoGeIC.d.mts +345 -0
- package/dist/visual.d.mts +1 -332
- package/dist/visual.mjs +130 -20
- package/dist/{watch-CWI_on4T.mjs → watch-BEdrp80i.mjs} +149 -30
- package/llms.txt +99 -48
- package/package.json +8 -6
- package/dist/dev-Cc50Dk7H.mjs +0 -47
- package/dist/memory-pUQ7YZWC.d.mts +0 -50
- package/dist/types-BX4Ge_j5.d.mts +0 -656
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { a as sheetAlone } from "./capture-D7XvoHdH.mjs";
|
|
2
|
-
import {
|
|
1
|
+
import { a as sheetAlone, d as graphHash } from "./capture-D7XvoHdH.mjs";
|
|
2
|
+
import { a as isDev, i as isControl, n as controlRefused, o as notInstalled, r as isAgent, t as controlReadOnly } from "./dev-DbHgx-R-.mjs";
|
|
3
3
|
import { n as reproBookmark } from "./headless-BA6ffzqD.mjs";
|
|
4
|
-
import { a as readTarget, i as isProjectionTarget, n as isTainted, o as inputOrEmpty, r as recordCheat, s as defineCommand } from "./session-
|
|
4
|
+
import { a as readTarget, i as isProjectionTarget, n as isTainted, o as inputOrEmpty, r as recordCheat, s as defineCommand } from "./session-CeKgO2hy.mjs";
|
|
5
5
|
//#region src/plugins/anim/control.ts
|
|
6
6
|
/**
|
|
7
|
-
* @file anim plugin — the anim command of the `/control` door: switch reduced motion. Dev
|
|
8
|
-
*
|
|
9
|
-
* logs the `moku:dev` marker.
|
|
7
|
+
* @file anim plugin — the anim command of the `/control` door: switch reduced motion. Dev and
|
|
8
|
+
* debug builds: the body starts with the inline agent guard of a cosmetic command, so a bundler
|
|
9
|
+
* `define` of `false` drops it, and logs the `moku:dev` marker.
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
12
12
|
* Switches reduced motion on or off, the way the player's "Less motion" setting does. Answers
|
|
@@ -24,7 +24,7 @@ const reducedMotionCommand = defineCommand({
|
|
|
24
24
|
input: { on: "boolean" },
|
|
25
25
|
effect: "cosmetic",
|
|
26
26
|
run: (app, { on }) => {
|
|
27
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
27
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
28
28
|
app.log.debug("moku:dev", {
|
|
29
29
|
command: "game.reducedMotion",
|
|
30
30
|
on
|
|
@@ -36,9 +36,9 @@ const reducedMotionCommand = defineCommand({
|
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region src/plugins/audio/control.ts
|
|
38
38
|
/**
|
|
39
|
-
* @file audio plugin — the audio command of the `/control` door: mute the whole game. Dev
|
|
40
|
-
*
|
|
41
|
-
* logs the `moku:dev` marker.
|
|
39
|
+
* @file audio plugin — the audio command of the `/control` door: mute the whole game. Dev and
|
|
40
|
+
* debug builds: the body starts with the inline agent guard of a cosmetic command, so a bundler
|
|
41
|
+
* `define` of `false` drops it, and logs the `moku:dev` marker.
|
|
42
42
|
*/
|
|
43
43
|
/**
|
|
44
44
|
* Mutes or unmutes the `master` bus, so music and sfx both go silent; the stored volumes stay.
|
|
@@ -57,7 +57,7 @@ const muteCommand = defineCommand({
|
|
|
57
57
|
input: { muted: "boolean" },
|
|
58
58
|
effect: "cosmetic",
|
|
59
59
|
run: (app, { muted }) => {
|
|
60
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
60
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
61
61
|
if (app.audio === void 0) throw notInstalled("game.mute", "audioPlugin", "command");
|
|
62
62
|
app.log.debug("moku:dev", {
|
|
63
63
|
command: "game.mute",
|
|
@@ -68,12 +68,54 @@ const muteCommand = defineCommand({
|
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
70
|
//#endregion
|
|
71
|
+
//#region src/plugins/effects/control.ts
|
|
72
|
+
/**
|
|
73
|
+
* @file effects plugin — the filter command of the `/control` door: turn one filter kind off or on through
|
|
74
|
+
* `effects.diagnostics`. Dev and debug builds: the body starts with the inline guard of a cosmetic command, so a
|
|
75
|
+
* bundler `define` of `false` drops it, and logs the `moku:dev` marker.
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* Turns one filter kind off or on, through `effects.diagnostics`. Answers the kinds that are off afterwards.
|
|
79
|
+
* Cosmetic: it runs on a `--debug` page. A door for the MCP and the tools page; it carries no `overlay` marker.
|
|
80
|
+
*
|
|
81
|
+
* @throws {Error} `[game] No filter kind "fx.nope".` naming every known kind, and `notInstalled` without
|
|
82
|
+
* `effectsPlugin`.
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* // The editor's MCP turns the glow off on the phone, then on again.
|
|
86
|
+
* (await run(app, commands.filter, { kind: "effects.glow", on: false })).value; // ["effects.glow"]
|
|
87
|
+
* (await run(app, commands.filter, { kind: "effects.glow", on: true })).value; // []
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
const filterCommand = defineCommand({
|
|
91
|
+
id: "game.filter",
|
|
92
|
+
title: "Filter kind on or off",
|
|
93
|
+
input: {
|
|
94
|
+
kind: "string",
|
|
95
|
+
on: "boolean"
|
|
96
|
+
},
|
|
97
|
+
effect: "cosmetic",
|
|
98
|
+
run: (app, { kind, on }) => {
|
|
99
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
100
|
+
app.log.debug("moku:dev", {
|
|
101
|
+
command: "game.filter",
|
|
102
|
+
kind,
|
|
103
|
+
on
|
|
104
|
+
});
|
|
105
|
+
if (app.effects === void 0) throw notInstalled("game.filter", "effectsPlugin", "command");
|
|
106
|
+
const { diagnostics } = app.effects;
|
|
107
|
+
if (!(on ? diagnostics.enable(kind) : diagnostics.disable(kind))) throw new Error(`[game] No filter kind "${kind}".\n Known: ${diagnostics.kinds().join(", ")}.`);
|
|
108
|
+
return diagnostics.disabled();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
//#endregion
|
|
71
112
|
//#region src/plugins/input/control.ts
|
|
72
113
|
/**
|
|
73
114
|
* @file input plugin — the input commands of the `/control` door: a drag from one view onto
|
|
74
115
|
* another, a trace through many views and a key press. Each goes through `app.input`, so the gate
|
|
75
|
-
* decides and the session stays clean. Dev builds
|
|
76
|
-
* guard, so a bundler `define` of `false` drops it,
|
|
116
|
+
* decides and the session stays clean. Dev builds and `--debug=full` builds: every body starts
|
|
117
|
+
* with the inline control guard of a route command, so a bundler `define` of `false` drops it,
|
|
118
|
+
* and logs the `moku:dev` marker. The targets
|
|
77
119
|
* are read with `target.ts`; `readTarget` is re-exported here for the `game.tap` command of ui,
|
|
78
120
|
* which taps a view by the same projection key.
|
|
79
121
|
*/
|
|
@@ -111,7 +153,7 @@ const dragCommand = defineCommand({
|
|
|
111
153
|
},
|
|
112
154
|
effect: "route",
|
|
113
155
|
run: (app, { from, to }) => {
|
|
114
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
156
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_CONTROL__ === "undefined" || !__MOKU_GAME_CONTROL__)) throw controlRefused();
|
|
115
157
|
app.log.debug("moku:dev", { command: "game.drag" });
|
|
116
158
|
return app.input.drag(readTarget(from), readTarget(to));
|
|
117
159
|
}
|
|
@@ -133,7 +175,7 @@ const traceCommand = defineCommand({
|
|
|
133
175
|
input: { path: "json" },
|
|
134
176
|
effect: "route",
|
|
135
177
|
run: (app, { path }) => {
|
|
136
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
178
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_CONTROL__ === "undefined" || !__MOKU_GAME_CONTROL__)) throw controlRefused();
|
|
137
179
|
app.log.debug("moku:dev", { command: "game.trace" });
|
|
138
180
|
return app.input.trace(readPath(path));
|
|
139
181
|
}
|
|
@@ -158,7 +200,7 @@ const keyCommand = defineCommand({
|
|
|
158
200
|
},
|
|
159
201
|
effect: "route",
|
|
160
202
|
run: (app, { key, shift = false }) => {
|
|
161
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
203
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_CONTROL__ === "undefined" || !__MOKU_GAME_CONTROL__)) throw controlRefused();
|
|
162
204
|
app.log.debug("moku:dev", {
|
|
163
205
|
command: "game.key",
|
|
164
206
|
key
|
|
@@ -171,8 +213,8 @@ const keyCommand = defineCommand({
|
|
|
171
213
|
/**
|
|
172
214
|
* @file lifecycle plugin — the lifecycle commands of the `/control` door: pause and resume the
|
|
173
215
|
* game for the editor, through the `devtools` pause reason, so another reason keeps its hold.
|
|
174
|
-
* Dev builds
|
|
175
|
-
* drops it, and logs the `moku:dev` marker.
|
|
216
|
+
* Dev and debug builds: every body starts with the inline agent guard of a cosmetic command, so
|
|
217
|
+
* a bundler `define` of `false` drops it, and logs the `moku:dev` marker.
|
|
176
218
|
*/
|
|
177
219
|
/**
|
|
178
220
|
* Pauses the game with the pause reason `devtools`. Answers whether the game is paused.
|
|
@@ -190,7 +232,7 @@ const pauseCommand = defineCommand({
|
|
|
190
232
|
input: {},
|
|
191
233
|
effect: "cosmetic",
|
|
192
234
|
run: (app) => {
|
|
193
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
235
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
194
236
|
app.log.debug("moku:dev", { command: "game.pause" });
|
|
195
237
|
app.lifecycle.push("devtools");
|
|
196
238
|
return app.lifecycle.isPaused();
|
|
@@ -212,7 +254,7 @@ const resumeCommand = defineCommand({
|
|
|
212
254
|
input: {},
|
|
213
255
|
effect: "cosmetic",
|
|
214
256
|
run: (app) => {
|
|
215
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
257
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
216
258
|
app.log.debug("moku:dev", { command: "game.resume" });
|
|
217
259
|
app.lifecycle.pop("devtools");
|
|
218
260
|
return app.lifecycle.isPaused();
|
|
@@ -476,8 +518,10 @@ function readRepro(value) {
|
|
|
476
518
|
//#region src/plugins/renderer/control.ts
|
|
477
519
|
/**
|
|
478
520
|
* @file renderer plugin — the renderer commands of the `/control` door: take a picture of the
|
|
479
|
-
* canvas, with its options, and switch the debug drawing. Dev builds
|
|
480
|
-
* the inline
|
|
521
|
+
* canvas, with its options, and switch the debug drawing. Dev and debug builds: every body starts with
|
|
522
|
+
* the inline agent guard of a read or cosmetic command, so a bundler `define` of `false` drops it,
|
|
523
|
+
* and logs the `moku:dev` marker. A capture with `diff` writes the state, so it also takes the
|
|
524
|
+
* control guard: a `--debug` page refuses it.
|
|
481
525
|
*/
|
|
482
526
|
/** The input fields of `game.capture`, in the order its `moku:dev` entry lists them. */
|
|
483
527
|
const CAPTURE_FIELDS = [
|
|
@@ -729,7 +773,8 @@ async function captureDiff(app, options, diff, raw) {
|
|
|
729
773
|
* itself as a raw write (`tainted` turns true), restores the bookmark and waits for its gate, takes
|
|
730
774
|
* that picture, restores where the game stood and waits again, and answers the pixel diff of now
|
|
731
775
|
* against then. While the game is paused it steps each frame it waits on, 1000/60 ms of game time.
|
|
732
|
-
* `undefined` while the renderer is inert, as in a headless test.
|
|
776
|
+
* `undefined` while the renderer is inert, as in a headless test. A `--debug` page takes the
|
|
777
|
+
* picture and refuses `diff` with the read-only error of `controlReadOnly`.
|
|
733
778
|
*
|
|
734
779
|
* @example
|
|
735
780
|
* ```ts
|
|
@@ -751,7 +796,7 @@ const captureCommand = defineCommand({
|
|
|
751
796
|
},
|
|
752
797
|
effect: "read",
|
|
753
798
|
run: (app, input) => {
|
|
754
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
799
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
755
800
|
const given = givenFields(input);
|
|
756
801
|
app.log.debug("moku:dev", given.length === 0 ? { command: "game.capture" } : {
|
|
757
802
|
command: "game.capture",
|
|
@@ -759,42 +804,58 @@ const captureCommand = defineCommand({
|
|
|
759
804
|
});
|
|
760
805
|
const options = readOptions(input);
|
|
761
806
|
if (input.diff === void 0) return app.renderer.capture(options);
|
|
807
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_CONTROL__ === "undefined" || !__MOKU_GAME_CONTROL__)) throw controlReadOnly("game.capture diff");
|
|
762
808
|
const diff = readDiff(input.diff);
|
|
763
809
|
if (options.sheet !== void 0) throw sheetAlone();
|
|
764
810
|
return captureDiff(app, options, diff, input.diff);
|
|
765
811
|
}
|
|
766
812
|
});
|
|
767
813
|
/**
|
|
768
|
-
* Switches the debug drawing: `nineSlice` outlines every nine-slice and the lines Pixi
|
|
769
|
-
* texture at
|
|
814
|
+
* Switches the debug drawing and checks: `nineSlice` outlines every nine-slice and the lines Pixi
|
|
815
|
+
* cuts its texture at, `verifyFrames` compares every frame the renderer skips (and every kept
|
|
816
|
+
* picture) with the same frame drawn live, `kept` draws every filtered view live (false) or lets
|
|
817
|
+
* the renderer keep pictures (true). Each field given is applied, one left out keeps its switch; a
|
|
818
|
+
* call with none answers the switches unchanged. Answers the switches as they are afterwards.
|
|
770
819
|
*
|
|
771
820
|
* @example
|
|
772
821
|
* ```ts
|
|
773
822
|
* // Check where the settings popup cuts its parchment.
|
|
774
|
-
* (await run(app, commands.debug, { nineSlice: true })).value; // { nineSlice: true }
|
|
823
|
+
* (await run(app, commands.debug, { nineSlice: true })).value; // { nineSlice: true, verifyFrames: false, kept: true }
|
|
824
|
+
* // The visual runner checks the skipped frames of a test in the browser.
|
|
825
|
+
* (await run(app, commands.debug, { verifyFrames: true })).value; // { nineSlice: true, verifyFrames: true, kept: true }
|
|
826
|
+
* // The editor measures the memory screen without kept pictures.
|
|
827
|
+
* (await run(app, commands.debug, { kept: false })).value; // { nineSlice: true, verifyFrames: true, kept: false }
|
|
775
828
|
* ```
|
|
776
829
|
*/
|
|
777
830
|
const debugCommand = defineCommand({
|
|
778
831
|
id: "game.debug",
|
|
779
832
|
title: "Debug drawing",
|
|
780
|
-
input: {
|
|
833
|
+
input: {
|
|
834
|
+
nineSlice: "boolean?",
|
|
835
|
+
verifyFrames: "boolean?",
|
|
836
|
+
kept: "boolean?"
|
|
837
|
+
},
|
|
781
838
|
effect: "cosmetic",
|
|
782
|
-
run: (app, { nineSlice }) => {
|
|
783
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
839
|
+
run: (app, { nineSlice, verifyFrames, kept }) => {
|
|
840
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
784
841
|
app.log.debug("moku:dev", {
|
|
785
842
|
command: "game.debug",
|
|
786
|
-
nineSlice
|
|
843
|
+
...nineSlice === void 0 ? {} : { nineSlice },
|
|
844
|
+
...verifyFrames === void 0 ? {} : { verifyFrames },
|
|
845
|
+
...kept === void 0 ? {} : { kept }
|
|
787
846
|
});
|
|
788
|
-
app.renderer.sync.debug.nineSlice(nineSlice);
|
|
847
|
+
if (nineSlice !== void 0) app.renderer.sync.debug.nineSlice(nineSlice);
|
|
848
|
+
if (verifyFrames !== void 0) app.renderer.sync.debug.verifyFrames(verifyFrames);
|
|
849
|
+
if (kept !== void 0) app.renderer.sync.debug.kept(kept);
|
|
789
850
|
return app.renderer.sync.debug.state();
|
|
790
851
|
}
|
|
791
852
|
});
|
|
792
853
|
//#endregion
|
|
793
854
|
//#region src/plugins/time/control.ts
|
|
794
855
|
/**
|
|
795
|
-
* @file time plugin — the time commands of the `/control` door: step frames by hand
|
|
796
|
-
* time scale. Dev builds
|
|
797
|
-
* of `false` drops it, and logs the `moku:dev` marker.
|
|
856
|
+
* @file time plugin — the time commands of the `/control` door: step frames by hand, set the
|
|
857
|
+
* time scale and force the frame rate. Dev and debug builds: every body starts with the inline agent guard of a cosmetic
|
|
858
|
+
* command, so a bundler `define` of `false` drops it, and logs the `moku:dev` marker.
|
|
798
859
|
*/
|
|
799
860
|
/** The length of one frame at 60 fps, the step a frame count takes by default. */
|
|
800
861
|
const FRAME_MS = 1e3 / 60;
|
|
@@ -839,7 +900,7 @@ const stepCommand = defineCommand({
|
|
|
839
900
|
},
|
|
840
901
|
effect: "cosmetic",
|
|
841
902
|
run: (app, { frames, deltaMs = FRAME_MS }) => {
|
|
842
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
903
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
843
904
|
app.log.debug("moku:dev", {
|
|
844
905
|
command: "game.step",
|
|
845
906
|
frames
|
|
@@ -865,7 +926,7 @@ const timeScaleCommand = defineCommand({
|
|
|
865
926
|
input: { scale: "number" },
|
|
866
927
|
effect: "cosmetic",
|
|
867
928
|
run: (app, { scale }) => {
|
|
868
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
929
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
869
930
|
app.log.debug("moku:dev", {
|
|
870
931
|
command: "game.timeScale",
|
|
871
932
|
scale
|
|
@@ -875,14 +936,40 @@ const timeScaleCommand = defineCommand({
|
|
|
875
936
|
return app.time.snapshot();
|
|
876
937
|
}
|
|
877
938
|
});
|
|
939
|
+
/**
|
|
940
|
+
* Forces the frame rate, or gives it back to the config without `fps`, and answers the rate after
|
|
941
|
+
* the call. For an A/B run on the phone: the same screen at 120 and at 60. A wrong `fps` is
|
|
942
|
+
* refused by `setRate` with its own message.
|
|
943
|
+
*
|
|
944
|
+
* @example
|
|
945
|
+
* ```ts
|
|
946
|
+
* (await run(app, commands.frameRate, { fps: 60 })).value.fps; // 60
|
|
947
|
+
* (await run(app, commands.frameRate, {})).value.mode; // "auto" with adaptive, "fixed" without
|
|
948
|
+
* ```
|
|
949
|
+
*/
|
|
950
|
+
const frameRateCommand = defineCommand({
|
|
951
|
+
id: "game.frameRate",
|
|
952
|
+
title: "Frame rate",
|
|
953
|
+
input: { fps: "number?" },
|
|
954
|
+
effect: "cosmetic",
|
|
955
|
+
run: (app, { fps }) => {
|
|
956
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
957
|
+
app.log.debug("moku:dev", {
|
|
958
|
+
command: "game.frameRate",
|
|
959
|
+
fps
|
|
960
|
+
});
|
|
961
|
+
return app.time.setRate(fps === void 0 ? "auto" : fps);
|
|
962
|
+
}
|
|
963
|
+
});
|
|
878
964
|
//#endregion
|
|
879
965
|
//#region src/plugins/ui/control.ts
|
|
880
966
|
/**
|
|
881
967
|
* @file ui plugin — the two ui commands of the `/control` door: a tap on a keyed element or on a
|
|
882
968
|
* view, and the text typed into a field. The tap goes through `app.input`, so the gate decides;
|
|
883
969
|
* the text goes through `app.ui.fill`, which writes a component's local and answers nothing. The
|
|
884
|
-
* session stays clean. Dev builds
|
|
885
|
-
* `define` of `false` drops it, and logs the
|
|
970
|
+
* session stays clean. Dev builds and `--debug=full` builds: each body starts with the inline
|
|
971
|
+
* control guard of a route command, so a bundler `define` of `false` drops it, and logs the
|
|
972
|
+
* `moku:dev` marker.
|
|
886
973
|
*/
|
|
887
974
|
/**
|
|
888
975
|
* Finds the entity of a keyed ui element on screen.
|
|
@@ -917,7 +1004,7 @@ const tapCommand = defineCommand({
|
|
|
917
1004
|
},
|
|
918
1005
|
effect: "route",
|
|
919
1006
|
run: (app, { key, target }) => {
|
|
920
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
1007
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_CONTROL__ === "undefined" || !__MOKU_GAME_CONTROL__)) throw controlRefused();
|
|
921
1008
|
app.log.debug("moku:dev", {
|
|
922
1009
|
command: "game.tap",
|
|
923
1010
|
key
|
|
@@ -948,7 +1035,7 @@ const fillCommand = defineCommand({
|
|
|
948
1035
|
},
|
|
949
1036
|
effect: "route",
|
|
950
1037
|
run: (app, { key, value }) => {
|
|
951
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
1038
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_CONTROL__ === "undefined" || !__MOKU_GAME_CONTROL__)) throw controlRefused();
|
|
952
1039
|
app.log.debug("moku:dev", {
|
|
953
1040
|
command: "game.fill",
|
|
954
1041
|
key
|
|
@@ -958,6 +1045,7 @@ const fillCommand = defineCommand({
|
|
|
958
1045
|
});
|
|
959
1046
|
//#endregion
|
|
960
1047
|
//#region src/plugins/flow/control.ts
|
|
1048
|
+
const noPayload = null;
|
|
961
1049
|
/**
|
|
962
1050
|
* Restores a bookmark with its `scene` expected first, so the node the restore enters mounts it.
|
|
963
1051
|
* A refused restore puts the record back to the mounted scene, when one is mounted: no later
|
|
@@ -1006,6 +1094,46 @@ async function restoreFrom(app, input) {
|
|
|
1006
1094
|
throw new Error("[game] game.restore takes a bookmark or a repro.\n Pass exactly one of { bookmark } and { repro }.");
|
|
1007
1095
|
}
|
|
1008
1096
|
/**
|
|
1097
|
+
* Reads the save `game.saveImport` got: an object with the three trees of `game.saveExport`.
|
|
1098
|
+
*
|
|
1099
|
+
* @param save - The JSON the editor sent.
|
|
1100
|
+
* @returns The player, the session and the rng.
|
|
1101
|
+
* @throws {Error} When it is not an object with `player`, `session` and `rng`.
|
|
1102
|
+
* @example
|
|
1103
|
+
* ```ts
|
|
1104
|
+
* readSave({ player: { coins: 40 }, session: {}, rng: { seed: 42, streams: {} } }).player; // { coins: 40 }
|
|
1105
|
+
* ```
|
|
1106
|
+
*/
|
|
1107
|
+
function readSave(save) {
|
|
1108
|
+
if (typeof save === "object" && save !== null && !Array.isArray(save)) {
|
|
1109
|
+
const { player, session, rng } = save;
|
|
1110
|
+
if (player !== void 0 && session !== void 0 && rng !== void 0) return {
|
|
1111
|
+
player,
|
|
1112
|
+
session,
|
|
1113
|
+
rng
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
throw new Error("[game] game.saveImport needs { player, session, rng } of game.saveExport.");
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Builds the bookmark a save is imported at: the start of the main flow with the save's trees and
|
|
1120
|
+
* the hash of the graph that runs now. The start may be a transit node: `flow.restore` enters
|
|
1121
|
+
* one of the graph it was made for and plays it, as a fresh start does, on the imported trees.
|
|
1122
|
+
*
|
|
1123
|
+
* @param app - The started app.
|
|
1124
|
+
* @param save - The player, the session and the rng of the save.
|
|
1125
|
+
* @returns The bookmark, as JSON for `restoreFrom`.
|
|
1126
|
+
*/
|
|
1127
|
+
function startBookmark(app, save) {
|
|
1128
|
+
const graph = app.flow.describe();
|
|
1129
|
+
return {
|
|
1130
|
+
path: graph.flows[graph.main]?.start ?? "",
|
|
1131
|
+
input: noPayload,
|
|
1132
|
+
...save,
|
|
1133
|
+
graph: graphHash(graph)
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1009
1137
|
* Answers the gate, the way a tap on a button does. Goes through the graph: the session stays
|
|
1010
1138
|
* clean.
|
|
1011
1139
|
*
|
|
@@ -1026,7 +1154,7 @@ const answerCommand = defineCommand({
|
|
|
1026
1154
|
},
|
|
1027
1155
|
effect: "route",
|
|
1028
1156
|
run: (app, { intent, payload }) => {
|
|
1029
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
1157
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_CONTROL__ === "undefined" || !__MOKU_GAME_CONTROL__)) throw controlRefused();
|
|
1030
1158
|
app.log.debug("moku:dev", {
|
|
1031
1159
|
command: "game.answer",
|
|
1032
1160
|
intent
|
|
@@ -1053,7 +1181,7 @@ const walkCommand = defineCommand({
|
|
|
1053
1181
|
input: { route: "json" },
|
|
1054
1182
|
effect: "route",
|
|
1055
1183
|
run: (app, { route }) => {
|
|
1056
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
1184
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_CONTROL__ === "undefined" || !__MOKU_GAME_CONTROL__)) throw controlRefused();
|
|
1057
1185
|
app.log.debug("moku:dev", { command: "game.walk" });
|
|
1058
1186
|
return app.flow.walk(readRoute(route));
|
|
1059
1187
|
}
|
|
@@ -1080,7 +1208,7 @@ const bookmarkCommand = defineCommand({
|
|
|
1080
1208
|
input: {},
|
|
1081
1209
|
effect: "read",
|
|
1082
1210
|
run: (app) => {
|
|
1083
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
1211
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
1084
1212
|
app.log.debug("moku:dev", { command: "game.bookmark" });
|
|
1085
1213
|
const bookmark = app.flow.bookmark();
|
|
1086
1214
|
const scene = app.scenes?.current();
|
|
@@ -1122,11 +1250,70 @@ const restoreCommand = defineCommand({
|
|
|
1122
1250
|
},
|
|
1123
1251
|
effect: "raw",
|
|
1124
1252
|
run: (app, input) => {
|
|
1125
|
-
if (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) throw controlRefused();
|
|
1253
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_CONTROL__ === "undefined" || !__MOKU_GAME_CONTROL__)) throw controlRefused();
|
|
1126
1254
|
app.log.debug("moku:dev", { command: "game.restore" });
|
|
1127
1255
|
return restoreFrom(app, input);
|
|
1128
1256
|
}
|
|
1129
1257
|
});
|
|
1258
|
+
/**
|
|
1259
|
+
* Answers the committed state as the save holds it: the player, the session and the rng, plain
|
|
1260
|
+
* JSON. A read: it runs on a `--debug` page too, so a tester's progress can leave the store app.
|
|
1261
|
+
*
|
|
1262
|
+
* @example
|
|
1263
|
+
* ```ts
|
|
1264
|
+
* // The editor takes the tester's progress from the store app on the phone.
|
|
1265
|
+
* (await run(app, commands.saveExport, {})).value; // { player: { coins: 40, … }, session: { … }, rng: { seed: 42, streams: {} } }
|
|
1266
|
+
* ```
|
|
1267
|
+
*/
|
|
1268
|
+
const saveExportCommand = defineCommand({
|
|
1269
|
+
id: "game.saveExport",
|
|
1270
|
+
title: "Export the save",
|
|
1271
|
+
input: {},
|
|
1272
|
+
effect: "read",
|
|
1273
|
+
run: (app) => {
|
|
1274
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)) throw controlRefused();
|
|
1275
|
+
app.log.debug("moku:dev", { command: "game.saveExport" });
|
|
1276
|
+
const { player, session, rng } = app.model.store.snapshot();
|
|
1277
|
+
return {
|
|
1278
|
+
player,
|
|
1279
|
+
session,
|
|
1280
|
+
rng
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
/**
|
|
1285
|
+
* Puts a save of `game.saveExport` in place and writes it, so the next start of the app reads it:
|
|
1286
|
+
* the trees are restored at the main flow's start, and the game lands where a fresh start with
|
|
1287
|
+
* them lands. A start that is a rest node is entered; a transit node runs again on the imported
|
|
1288
|
+
* trees, rng included, up to the first gate it opens, at a rest node or a popup. Then the store
|
|
1289
|
+
* flushes. Replaces the state outside the graph, so the session is tainted and the
|
|
1290
|
+
* command journaled. A `--debug` page refuses it; `--debug=full` and the dev page run it.
|
|
1291
|
+
*
|
|
1292
|
+
* @throws {Error} `[game] game.saveImport needs { player, session, rng } of game.saveExport.` for
|
|
1293
|
+
* a value without the three trees.
|
|
1294
|
+
* @example
|
|
1295
|
+
* ```ts
|
|
1296
|
+
* // The debug app beside the store app gets the save exported from the store app. Its main
|
|
1297
|
+
* // flow starts at "boot", a transit node: boot runs again on the save and the game rests at home.
|
|
1298
|
+
* const ran = await run(app, commands.saveImport, { save: exported });
|
|
1299
|
+
* ran.value; // { restored: true }
|
|
1300
|
+
* ran.state.path; // "home"
|
|
1301
|
+
* ```
|
|
1302
|
+
*/
|
|
1303
|
+
const saveImportCommand = defineCommand({
|
|
1304
|
+
id: "game.saveImport",
|
|
1305
|
+
title: "Import a save",
|
|
1306
|
+
input: { save: "json" },
|
|
1307
|
+
effect: "raw",
|
|
1308
|
+
run: async (app, { save }) => {
|
|
1309
|
+
if ((typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) && (typeof __MOKU_GAME_CONTROL__ === "undefined" || !__MOKU_GAME_CONTROL__)) throw controlRefused();
|
|
1310
|
+
app.log.debug("moku:dev", { command: "game.saveImport" });
|
|
1311
|
+
await restoreFrom(app, { bookmark: startBookmark(app, readSave(save)) });
|
|
1312
|
+
await app.flow.walk([]);
|
|
1313
|
+
await app.model.store.flush();
|
|
1314
|
+
return { restored: true };
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1130
1317
|
//#endregion
|
|
1131
1318
|
//#region src/plugins/flow/doors/commands.ts
|
|
1132
1319
|
/**
|
|
@@ -1136,7 +1323,8 @@ const restoreCommand = defineCommand({
|
|
|
1136
1323
|
*/
|
|
1137
1324
|
/**
|
|
1138
1325
|
* Every command of the engine, keyed by its short name: what an editor lists as buttons and MCP
|
|
1139
|
-
* tools. Frozen. Each runs
|
|
1326
|
+
* tools. Frozen. Each runs on the dev page and on a debug page, by the profile `run` checks: a
|
|
1327
|
+
* `--debug` page runs the read and cosmetic ones.
|
|
1140
1328
|
*
|
|
1141
1329
|
* @example
|
|
1142
1330
|
* ```ts
|
|
@@ -1153,6 +1341,8 @@ const commands = Object.freeze({
|
|
|
1153
1341
|
walk: walkCommand,
|
|
1154
1342
|
bookmark: bookmarkCommand,
|
|
1155
1343
|
restore: restoreCommand,
|
|
1344
|
+
saveExport: saveExportCommand,
|
|
1345
|
+
saveImport: saveImportCommand,
|
|
1156
1346
|
step: stepCommand,
|
|
1157
1347
|
pause: pauseCommand,
|
|
1158
1348
|
resume: resumeCommand,
|
|
@@ -1161,8 +1351,10 @@ const commands = Object.freeze({
|
|
|
1161
1351
|
reducedMotion: reducedMotionCommand,
|
|
1162
1352
|
fill: fillCommand,
|
|
1163
1353
|
timeScale: timeScaleCommand,
|
|
1354
|
+
frameRate: frameRateCommand,
|
|
1164
1355
|
mute: muteCommand,
|
|
1165
|
-
trace: traceCommand
|
|
1356
|
+
trace: traceCommand,
|
|
1357
|
+
filter: filterCommand
|
|
1166
1358
|
});
|
|
1167
1359
|
//#endregion
|
|
1168
1360
|
//#region src/plugins/flow/doors/run.ts
|
|
@@ -1180,14 +1372,17 @@ function envelopeOf(app) {
|
|
|
1180
1372
|
};
|
|
1181
1373
|
}
|
|
1182
1374
|
/**
|
|
1183
|
-
* Runs a command in a dev build. A
|
|
1184
|
-
*
|
|
1375
|
+
* Runs a command in a dev build or a debug build. A debug build of `--debug` runs the read and
|
|
1376
|
+
* cosmetic commands only; `--debug=full` and the dev page run every command. A `cheat` or `raw`
|
|
1377
|
+
* command taints the session and is journaled before it runs, so a failing one still counts.
|
|
1185
1378
|
*
|
|
1186
1379
|
* @param app - The app the command acts on.
|
|
1187
1380
|
* @param command - The command.
|
|
1188
1381
|
* @param input - Its input; left out when every field of its schema is optional.
|
|
1189
1382
|
* @returns The command's value and the envelope read after it.
|
|
1190
|
-
* @throws {Error} Outside
|
|
1383
|
+
* @throws {Error} Outside dev builds and debug builds (read and cosmetic commands; every command
|
|
1384
|
+
* with `--debug=full`), the read-only refusal of `controlReadOnly` for a `route`, `cheat` or
|
|
1385
|
+
* `raw` command on a `--debug` page, and whatever the command throws.
|
|
1191
1386
|
* @example
|
|
1192
1387
|
* ```ts
|
|
1193
1388
|
* // An e2e script: load the bug report, then tap Play.
|
|
@@ -1195,10 +1390,14 @@ function envelopeOf(app) {
|
|
|
1195
1390
|
* const ran = await run(app, commands.answer, { intent: "play" });
|
|
1196
1391
|
* ran.value; // true
|
|
1197
1392
|
* ran.state; // { path: "board/awaitIntent", frame: 14, tainted: true }
|
|
1393
|
+
* // On a --debug page: await run(app, commands.tap, { key: "play" }); // throws "[game] The command game.tap changes the game: …"
|
|
1198
1394
|
* ```
|
|
1199
1395
|
*/
|
|
1200
1396
|
async function run(app, command, ...input) {
|
|
1201
|
-
if (!isDev()
|
|
1397
|
+
if (!isDev() && !isControl()) {
|
|
1398
|
+
if (!isAgent()) throw controlRefused();
|
|
1399
|
+
if (command.effect !== "read" && command.effect !== "cosmetic") throw controlReadOnly(command.id);
|
|
1400
|
+
}
|
|
1202
1401
|
const given = inputOrEmpty(input[0]);
|
|
1203
1402
|
if (command.effect === "cheat" || command.effect === "raw") recordCheat(app, command.id, given, app.time.snapshot().frame);
|
|
1204
1403
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as JsxChild,
|
|
1
|
+
import { a as JsxChild, h as UiIntrinsicElements, t as DescriptionNode } from "./types-7GXcXxxv.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/plugins/ui/jsx/runtime.d.ts
|
|
4
4
|
/** What the compiler passes as props: the children plus whatever the tag declared. */
|
|
@@ -39,8 +39,13 @@ function defineSource(source) {
|
|
|
39
39
|
return Object.freeze(source);
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
* Declares a command:
|
|
43
|
-
*
|
|
42
|
+
* Declares a command: an action the editor lists and runs on the dev page and on a debug page.
|
|
43
|
+
* Its body starts with the guard of its effect: the agent form for `read` and `cosmetic`, the
|
|
44
|
+
* control form for the others (see docs/doors.md), so a production build drops it. A game's
|
|
45
|
+
* `.dev` command with only the `__MOKU_GAME_DEV__` guard runs in dev and is refused on a debug
|
|
46
|
+
* page. `overlay` shows it in the editor's overlay: `"toggle"` with the input `{ on: "boolean" }`
|
|
47
|
+
* and a `value(app)` the switch is drawn from, `"button"` with no required input and no value;
|
|
48
|
+
* any other combination is a type error.
|
|
44
49
|
*
|
|
45
50
|
* @param command - The descriptor.
|
|
46
51
|
* @returns The same descriptor, frozen.
|
|
@@ -60,6 +65,29 @@ function defineSource(source) {
|
|
|
60
65
|
* });
|
|
61
66
|
* (await run(app, openShop)).state.path; // "shop"
|
|
62
67
|
* ```
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* // features/ward/glow.dev.ts — the overlay's "Glow" switch of this game.
|
|
71
|
+
* export const glowSwitch = defineCommand({
|
|
72
|
+
* id: "ward.glow",
|
|
73
|
+
* title: "Glow",
|
|
74
|
+
* input: { on: "boolean" },
|
|
75
|
+
* effect: "cosmetic",
|
|
76
|
+
* overlay: "toggle",
|
|
77
|
+
* value: (app: { readonly effects: Effects.EffectsApi }) =>
|
|
78
|
+
* !app.effects.diagnostics.disabled().includes("effects.glow"),
|
|
79
|
+
* run: (app, { on }) => {
|
|
80
|
+
* if (
|
|
81
|
+
* (typeof __MOKU_GAME_DEV__ === "undefined" || !__MOKU_GAME_DEV__) &&
|
|
82
|
+
* (typeof __MOKU_GAME_AGENT__ === "undefined" || !__MOKU_GAME_AGENT__)
|
|
83
|
+
* ) {
|
|
84
|
+
* throw controlRefused();
|
|
85
|
+
* }
|
|
86
|
+
* return on ? app.effects.diagnostics.enable("effects.glow") : app.effects.diagnostics.disable("effects.glow");
|
|
87
|
+
* }
|
|
88
|
+
* });
|
|
89
|
+
* // The overlay draws a switch "Glow", on; a tap runs it with { on: false } and the next frame draws no glow.
|
|
90
|
+
* ```
|
|
63
91
|
*/
|
|
64
92
|
function defineCommand(command) {
|
|
65
93
|
checkId("command", command.id);
|