@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
|
@@ -71,211 +71,243 @@ type Require = <Plugin extends {
|
|
|
71
71
|
};
|
|
72
72
|
}>(plugin: Plugin) => ApiOf<Plugin>;
|
|
73
73
|
declare namespace types_d_exports$7 {
|
|
74
|
-
export { Api$7 as Api,
|
|
74
|
+
export { Api$7 as Api, Config$7 as Config, FrameCallback, Phase, State$7 as State, Time, TimeCtx };
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
|
-
*
|
|
78
|
-
* must come from somewhere else, for example a server.
|
|
77
|
+
* Frame phases, in call order.
|
|
79
78
|
*
|
|
80
79
|
* @example
|
|
81
80
|
* ```ts
|
|
82
|
-
*
|
|
83
|
-
* // at most one timer alive, so the source remembers one.
|
|
84
|
-
* const serverClock = (serverNow: number): ClockSource => {
|
|
85
|
-
* const syncedAt = performance.now();
|
|
86
|
-
* let timer: ReturnType<typeof setTimeout> | undefined;
|
|
87
|
-
*
|
|
88
|
-
* return {
|
|
89
|
-
* now: () => serverNow + (performance.now() - syncedAt),
|
|
90
|
-
* setTimer: (callback, delayMs) => (timer = setTimeout(callback, delayMs)),
|
|
91
|
-
* clearTimer: () => clearTimeout(timer)
|
|
92
|
-
* };
|
|
93
|
-
* };
|
|
94
|
-
*
|
|
95
|
-
* createApp({ pluginConfigs: { clock: { source: serverClock(1_790_000_000_000) } } });
|
|
81
|
+
* const phase: Phase = "animate";
|
|
96
82
|
* ```
|
|
97
83
|
*/
|
|
98
|
-
type
|
|
99
|
-
/**
|
|
100
|
-
* Reads the current moment.
|
|
101
|
-
*
|
|
102
|
-
* @returns Epoch milliseconds. A fraction is cut by the plugin; going back is tolerated.
|
|
103
|
-
*/
|
|
104
|
-
now(): number;
|
|
105
|
-
/**
|
|
106
|
-
* Schedules one callback. The plugin keeps at most one timer alive.
|
|
107
|
-
*
|
|
108
|
-
* @param callback - Function to run when the delay has passed.
|
|
109
|
-
* @param delayMs - Delay in milliseconds, never negative.
|
|
110
|
-
* @returns A handle of any shape; the plugin only hands it back to `clearTimer`.
|
|
111
|
-
*/
|
|
112
|
-
setTimer(callback: () => void, delayMs: number): unknown;
|
|
113
|
-
/**
|
|
114
|
-
* Cancels a pending timer. A handle this source did not produce must be ignored.
|
|
115
|
-
*
|
|
116
|
-
* @param handle - Handle returned by `setTimer`.
|
|
117
|
-
*/
|
|
118
|
-
clearTimer(handle: unknown): void;
|
|
119
|
-
};
|
|
84
|
+
type Phase = "input" | "animate" | "layout" | "sync" | "signals" | "render";
|
|
120
85
|
/**
|
|
121
|
-
*
|
|
86
|
+
* The Time resource, in scaled milliseconds. `idle` tells whether the loop currently runs at the
|
|
87
|
+
* lowered idle cap.
|
|
122
88
|
*
|
|
123
89
|
* @example
|
|
124
90
|
* ```ts
|
|
125
|
-
*
|
|
126
|
-
* const clock = fakeClock(1000);
|
|
127
|
-
* const app = createApp({ pluginConfigs: { clock: { source: clock } } });
|
|
128
|
-
*
|
|
129
|
-
* app.clock.scheduleAt(61_000);
|
|
130
|
-
* clock.advance(60_000); // the listeners of onElapsed get { now: 61000 }
|
|
91
|
+
* const time: Time = { delta: 16, elapsed: 1600, scale: 1, frame: 100, idle: false };
|
|
131
92
|
* ```
|
|
132
93
|
*/
|
|
133
|
-
type
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
* ```ts
|
|
140
|
-
* clock.advance(5000); // now() is 5000 later, a timer due in 3000 has fired
|
|
141
|
-
* ```
|
|
142
|
-
*/
|
|
143
|
-
advance(ms: number): void;
|
|
144
|
-
/**
|
|
145
|
-
* Jumps to a moment without firing any timer. Moving back is allowed: that is a player who
|
|
146
|
-
* sets the device clock by hand.
|
|
147
|
-
*
|
|
148
|
-
* @param moment - Moment in epoch milliseconds.
|
|
149
|
-
* @example
|
|
150
|
-
* ```ts
|
|
151
|
-
* clock.set(400); // the device clock went back; app.clock.now() does not decrease
|
|
152
|
-
* ```
|
|
153
|
-
*/
|
|
154
|
-
set(moment: number): void;
|
|
94
|
+
type Time = {
|
|
95
|
+
delta: number;
|
|
96
|
+
elapsed: number;
|
|
97
|
+
scale: number;
|
|
98
|
+
frame: number;
|
|
99
|
+
idle: boolean;
|
|
155
100
|
};
|
|
156
101
|
/**
|
|
157
|
-
*
|
|
102
|
+
* Callback run once per frame in its phase.
|
|
158
103
|
*
|
|
159
104
|
* @example
|
|
160
105
|
* ```ts
|
|
161
|
-
* const
|
|
106
|
+
* const advanceTweens: FrameCallback = time => tweens.advance(time.delta);
|
|
162
107
|
* ```
|
|
163
108
|
*/
|
|
164
|
-
type
|
|
165
|
-
now: number;
|
|
166
|
-
};
|
|
109
|
+
type FrameCallback = (time: Readonly<Time>) => void;
|
|
167
110
|
/**
|
|
168
|
-
*
|
|
111
|
+
* time plugin config.
|
|
169
112
|
*
|
|
170
113
|
* @example
|
|
171
114
|
* ```ts
|
|
172
|
-
* createApp({ pluginConfigs: {
|
|
115
|
+
* createApp({ pluginConfigs: { time: { maxFps: 30 } } });
|
|
173
116
|
* ```
|
|
174
117
|
*/
|
|
175
118
|
type Config$7 = {
|
|
176
119
|
/**
|
|
177
|
-
*
|
|
120
|
+
* Frame rate cap. The default is 60. 120 is for WebViews that really deliver 120 Hz frames:
|
|
121
|
+
* Android today; WKWebView on iOS and macOS is capped at 60 by WebKit (bug 294338).
|
|
178
122
|
*/
|
|
179
|
-
|
|
123
|
+
maxFps: 30 | 60 | 120;
|
|
124
|
+
/**
|
|
125
|
+
* Upper bound of one frame's delta in milliseconds.
|
|
126
|
+
*/
|
|
127
|
+
maxDeltaMs: number;
|
|
128
|
+
/**
|
|
129
|
+
* Frame rate cap of an idle screen, when nothing woke the clock for `idleAfterMs`. The default
|
|
130
|
+
* is 30; 0 turns the idle cap off and every frame runs at `maxFps`.
|
|
131
|
+
*/
|
|
132
|
+
idleFps: 0 | 30;
|
|
133
|
+
/**
|
|
134
|
+
* Unscaled milliseconds without a `wake()` after which the loop drops to `idleFps`.
|
|
135
|
+
*/
|
|
136
|
+
idleAfterMs: number;
|
|
180
137
|
};
|
|
181
138
|
/**
|
|
182
|
-
*
|
|
139
|
+
* time plugin state.
|
|
183
140
|
*/
|
|
184
141
|
type State$7 = {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
142
|
+
callbacks: Record<Phase, FrameCallback[]>;
|
|
143
|
+
/**
|
|
144
|
+
* Scratch array of a frame: the six callback lists as they were when the frame started.
|
|
145
|
+
*/
|
|
146
|
+
captured: (readonly FrameCallback[])[];
|
|
147
|
+
time: Time;
|
|
148
|
+
paused: boolean;
|
|
149
|
+
running: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* True while a frame runs; guards `step` re-entry.
|
|
152
|
+
*/
|
|
153
|
+
stepping: boolean;
|
|
154
|
+
rafId: number | undefined;
|
|
155
|
+
lastTimestamp: number | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* Elapsed time of the frame source in unscaled milliseconds, the clock of the idle timer: it
|
|
158
|
+
* keeps running while `scale` is 0.
|
|
159
|
+
*/
|
|
160
|
+
unscaledElapsedMs: number;
|
|
161
|
+
/**
|
|
162
|
+
* Unscaled elapsed time of the last wake.
|
|
163
|
+
*/
|
|
164
|
+
lastWakeMs: number;
|
|
165
|
+
/**
|
|
166
|
+
* True while the loop runs at `idleFps`.
|
|
167
|
+
*/
|
|
168
|
+
idle: boolean;
|
|
190
169
|
};
|
|
191
170
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
171
|
+
* time plugin API, `app.time`. The one clock of the screen: frame callbacks per phase, the `Time`
|
|
172
|
+
* resource, the time scale, and `step` for tests and tools.
|
|
194
173
|
*
|
|
195
174
|
* @example
|
|
196
175
|
* ```ts
|
|
197
|
-
* //
|
|
198
|
-
* app.
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* }
|
|
202
|
-
*
|
|
176
|
+
* // Frame work is a callback in a phase. A test has no frame source: it drives the frames itself.
|
|
177
|
+
* const off = app.time.onFrame("animate", time => counter.advance(time.delta));
|
|
178
|
+
*
|
|
179
|
+
* app.time.step(16);
|
|
180
|
+
* // the callback gets { delta: 16, elapsed: 16, scale: 1, frame: 1, idle: false }
|
|
181
|
+
* off();
|
|
203
182
|
* ```
|
|
204
183
|
*/
|
|
205
184
|
type Api$7 = {
|
|
206
185
|
/**
|
|
207
|
-
*
|
|
208
|
-
* the
|
|
186
|
+
* Registers a frame callback. Callbacks of one phase run in registration order; a callback
|
|
187
|
+
* registered during a frame runs from the next frame on.
|
|
209
188
|
*
|
|
210
|
-
* @
|
|
189
|
+
* @param phase - Phase the callback belongs to.
|
|
190
|
+
* @param callback - Function called once per frame with the current `Time`.
|
|
191
|
+
* @returns Unsubscribe function; calling it twice is a no-op.
|
|
211
192
|
* @example
|
|
212
193
|
* ```ts
|
|
213
|
-
* //
|
|
214
|
-
*
|
|
215
|
-
*
|
|
194
|
+
* // The coin counter rolls up while the reward popup is open.
|
|
195
|
+
* const off = app.time.onFrame("animate", time => counter.advance(time.delta));
|
|
196
|
+
*
|
|
197
|
+
* off(); // the popup is closed
|
|
216
198
|
* ```
|
|
217
199
|
*/
|
|
218
|
-
|
|
200
|
+
onFrame(phase: Phase, callback: FrameCallback): () => void;
|
|
219
201
|
/**
|
|
220
|
-
*
|
|
221
|
-
* it fires on the next macrotask of the source.
|
|
202
|
+
* Returns a snapshot of the current `Time`, so a caller cannot write into the frame state.
|
|
222
203
|
*
|
|
223
|
-
* @
|
|
204
|
+
* @returns A copy of the current `Time`.
|
|
224
205
|
* @example
|
|
225
206
|
* ```ts
|
|
226
|
-
* //
|
|
227
|
-
* app.
|
|
228
|
-
* app.
|
|
207
|
+
* // Stamp the start of a combo window in game time, which stands still during a pause.
|
|
208
|
+
* const startedAt = app.time.snapshot().elapsed; // 1600
|
|
209
|
+
* app.time.snapshot(); // { delta: 16, elapsed: 1600, scale: 1, frame: 100, idle: false }
|
|
229
210
|
* ```
|
|
230
211
|
*/
|
|
231
|
-
|
|
212
|
+
snapshot(): Readonly<Time>;
|
|
232
213
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
214
|
+
* Sets the time scale. Every frame delta is multiplied by it; 0 freezes the game time
|
|
215
|
+
* while the frames keep running. A negative scale is clamped to 0.
|
|
235
216
|
*
|
|
236
|
-
* @param
|
|
237
|
-
* @returns The unsubscribe function.
|
|
217
|
+
* @param scale - New time scale, 0 or greater.
|
|
238
218
|
* @example
|
|
239
219
|
* ```ts
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
220
|
+
* // Slow motion while the last match of the level resolves.
|
|
221
|
+
* app.time.setScale(0.5); // a frame of 20 ms now has delta 10
|
|
222
|
+
* app.time.setScale(1);
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
setScale(scale: number): void;
|
|
226
|
+
/**
|
|
227
|
+
* Pauses the clock: no phase runs and `elapsed` stops advancing. Called by `lifecycle`.
|
|
243
228
|
*
|
|
244
|
-
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* // The lifecycle plugin owns the pause policy: the first pause reason stops the clock.
|
|
232
|
+
* // A game pauses through app.lifecycle.push(reason), never here.
|
|
233
|
+
* const time = ctx.require(timePlugin);
|
|
234
|
+
* if (reasons.length === 1) time.pause(); // app.time.isPaused() is true, no phase runs
|
|
245
235
|
* ```
|
|
246
236
|
*/
|
|
247
|
-
|
|
237
|
+
pause(): void;
|
|
248
238
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
239
|
+
* Resumes the clock and drops the stale timestamp, so the first frame after the pause has
|
|
240
|
+
* a normal delta instead of the whole pause. Called by `lifecycle`.
|
|
251
241
|
*
|
|
252
242
|
* @example
|
|
253
243
|
* ```ts
|
|
254
|
-
* // The
|
|
255
|
-
*
|
|
256
|
-
*
|
|
244
|
+
* // The lifecycle plugin resumes when the last pause reason is gone.
|
|
245
|
+
* const time = ctx.require(timePlugin);
|
|
246
|
+
* if (reasons.length === 0) time.resume(); // the next frame has a normal delta, not the whole pause
|
|
257
247
|
* ```
|
|
258
248
|
*/
|
|
259
|
-
|
|
249
|
+
resume(): void;
|
|
260
250
|
/**
|
|
261
|
-
*
|
|
251
|
+
* Tells whether the clock is paused.
|
|
262
252
|
*
|
|
263
|
-
* @returns
|
|
253
|
+
* @returns True while paused.
|
|
264
254
|
* @example
|
|
265
255
|
* ```ts
|
|
266
|
-
*
|
|
267
|
-
* app.
|
|
268
|
-
*
|
|
269
|
-
* app.clock.dueAt(); // undefined
|
|
256
|
+
* // A test checks that a pause reason really stopped the frames.
|
|
257
|
+
* app.lifecycle.push("background");
|
|
258
|
+
* app.time.isPaused(); // true
|
|
270
259
|
* ```
|
|
271
260
|
*/
|
|
272
|
-
|
|
261
|
+
isPaused(): boolean;
|
|
262
|
+
/**
|
|
263
|
+
* Tells whether a real frame source drives the loop. False in plain Bun, where a test
|
|
264
|
+
* drives the frames with `step`.
|
|
265
|
+
*
|
|
266
|
+
* @returns True while the loop runs.
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* // Without a frame source no frame ever comes: finish the fly-in at once instead of waiting.
|
|
270
|
+
* if (!app.time.isRunning()) coin.moveTo(target); // false in plain Bun, true in a browser
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
isRunning(): boolean;
|
|
274
|
+
/**
|
|
275
|
+
* Resets the idle timer: the next frame runs at `maxFps` again, even after a long idle. Cheap,
|
|
276
|
+
* idempotent and safe inside a frame callback. Every plugin that gives the player something to
|
|
277
|
+
* look at calls it: a pointer sample, a starting animation, a flow edge, a finished load, a
|
|
278
|
+
* scene switch, a locale change, a reconcile.
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```ts
|
|
282
|
+
* // The flow runner walked an edge, so the screen has work again and the cap goes back up.
|
|
283
|
+
* const time = ctx.require(timePlugin);
|
|
284
|
+
*
|
|
285
|
+
* time.wake(); // app.time.snapshot().idle is false from here on
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
wake(): void;
|
|
289
|
+
/**
|
|
290
|
+
* Runs exactly one frame with the given unscaled delta, ignoring the fps cap, the pause
|
|
291
|
+
* flag and `maxDeltaMs`. The time scale still applies. For tests and tools.
|
|
292
|
+
*
|
|
293
|
+
* @param deltaMs - Unscaled delta of the frame in milliseconds.
|
|
294
|
+
* @throws {Error} When called from inside a frame callback.
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* // A test plays two frames without a browser.
|
|
298
|
+
* app.time.step(16);
|
|
299
|
+
* app.time.step(4);
|
|
300
|
+
* app.time.snapshot(); // { delta: 4, elapsed: 20, scale: 1, frame: 2, idle: false }
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
step(deltaMs: number): void;
|
|
273
304
|
};
|
|
274
305
|
/**
|
|
275
|
-
* Domain context of the
|
|
306
|
+
* Domain context of the time plugin.
|
|
276
307
|
*/
|
|
277
|
-
type
|
|
308
|
+
type TimeCtx = PluginCtx<Config$7, State$7> & {
|
|
278
309
|
readonly global: object;
|
|
310
|
+
readonly log: Log.LogApi;
|
|
279
311
|
};
|
|
280
312
|
declare namespace types_d_exports$6 {
|
|
281
313
|
export { Api$6 as Api, Config$6 as Config, Events$5 as Events, LifecycleCtx, PauseReason, State$6 as State };
|
|
@@ -1056,244 +1088,212 @@ type ModelCtx = PluginCtx<Config$5, State$5, Events$4> & {
|
|
|
1056
1088
|
readonly global: object;
|
|
1057
1089
|
readonly log: Log.LogApi;
|
|
1058
1090
|
};
|
|
1059
|
-
declare namespace types_d_exports$4 {
|
|
1060
|
-
export { Api$4 as Api, Config$4 as Config,
|
|
1061
|
-
}
|
|
1091
|
+
declare namespace types_d_exports$4 {
|
|
1092
|
+
export { Api$4 as Api, ClockCtx, ClockSource, Config$4 as Config, Elapsed, FakeClock, State$4 as State };
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Source of time and timers. The system source is the default; a game passes its own when time
|
|
1096
|
+
* must come from somewhere else, for example a server.
|
|
1097
|
+
*
|
|
1098
|
+
* @example
|
|
1099
|
+
* ```ts
|
|
1100
|
+
* // Server time: the device clock only measures the distance from the last sync. The plugin keeps
|
|
1101
|
+
* // at most one timer alive, so the source remembers one.
|
|
1102
|
+
* const serverClock = (serverNow: number): ClockSource => {
|
|
1103
|
+
* const syncedAt = performance.now();
|
|
1104
|
+
* let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1105
|
+
*
|
|
1106
|
+
* return {
|
|
1107
|
+
* now: () => serverNow + (performance.now() - syncedAt),
|
|
1108
|
+
* setTimer: (callback, delayMs) => (timer = setTimeout(callback, delayMs)),
|
|
1109
|
+
* clearTimer: () => clearTimeout(timer)
|
|
1110
|
+
* };
|
|
1111
|
+
* };
|
|
1112
|
+
*
|
|
1113
|
+
* createApp({ pluginConfigs: { clock: { source: serverClock(1_790_000_000_000) } } });
|
|
1114
|
+
* ```
|
|
1115
|
+
*/
|
|
1116
|
+
type ClockSource = {
|
|
1117
|
+
/**
|
|
1118
|
+
* Reads the current moment.
|
|
1119
|
+
*
|
|
1120
|
+
* @returns Epoch milliseconds. A fraction is cut by the plugin; going back is tolerated.
|
|
1121
|
+
*/
|
|
1122
|
+
now(): number;
|
|
1123
|
+
/**
|
|
1124
|
+
* Schedules one callback. The plugin keeps at most one timer alive.
|
|
1125
|
+
*
|
|
1126
|
+
* @param callback - Function to run when the delay has passed.
|
|
1127
|
+
* @param delayMs - Delay in milliseconds, never negative.
|
|
1128
|
+
* @returns A handle of any shape; the plugin only hands it back to `clearTimer`.
|
|
1129
|
+
*/
|
|
1130
|
+
setTimer(callback: () => void, delayMs: number): unknown;
|
|
1131
|
+
/**
|
|
1132
|
+
* Cancels a pending timer. A handle this source did not produce must be ignored.
|
|
1133
|
+
*
|
|
1134
|
+
* @param handle - Handle returned by `setTimer`.
|
|
1135
|
+
*/
|
|
1136
|
+
clearTimer(handle: unknown): void;
|
|
1137
|
+
};
|
|
1062
1138
|
/**
|
|
1063
|
-
*
|
|
1139
|
+
* Fake source for tests, from `@moku-labs/game/testing`. It needs no real timer.
|
|
1064
1140
|
*
|
|
1065
1141
|
* @example
|
|
1066
1142
|
* ```ts
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1069
|
-
|
|
1070
|
-
type Phase = "input" | "animate" | "layout" | "sync" | "signals" | "render";
|
|
1071
|
-
/**
|
|
1072
|
-
* The Time resource, in scaled milliseconds. `idle` tells whether the loop currently runs at the
|
|
1073
|
-
* lowered idle cap.
|
|
1143
|
+
* // An energy point refills after 60 s. The test does not wait: it moves the clock.
|
|
1144
|
+
* const clock = fakeClock(1000);
|
|
1145
|
+
* const app = createApp({ pluginConfigs: { clock: { source: clock } } });
|
|
1074
1146
|
*
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
* const time: Time = { delta: 16, elapsed: 1600, scale: 1, frame: 100, idle: false };
|
|
1147
|
+
* app.clock.scheduleAt(61_000);
|
|
1148
|
+
* clock.advance(60_000); // the listeners of onElapsed get { now: 61000 }
|
|
1078
1149
|
* ```
|
|
1079
1150
|
*/
|
|
1080
|
-
type
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1151
|
+
type FakeClock = ClockSource & {
|
|
1152
|
+
/**
|
|
1153
|
+
* Moves the clock forward and fires every timer that falls due on the way, in due order.
|
|
1154
|
+
*
|
|
1155
|
+
* @param ms - Milliseconds to move forward. A negative amount moves nothing.
|
|
1156
|
+
* @example
|
|
1157
|
+
* ```ts
|
|
1158
|
+
* clock.advance(5000); // now() is 5000 later, a timer due in 3000 has fired
|
|
1159
|
+
* ```
|
|
1160
|
+
*/
|
|
1161
|
+
advance(ms: number): void;
|
|
1162
|
+
/**
|
|
1163
|
+
* Jumps to a moment without firing any timer. Moving back is allowed: that is a player who
|
|
1164
|
+
* sets the device clock by hand.
|
|
1165
|
+
*
|
|
1166
|
+
* @param moment - Moment in epoch milliseconds.
|
|
1167
|
+
* @example
|
|
1168
|
+
* ```ts
|
|
1169
|
+
* clock.set(400); // the device clock went back; app.clock.now() does not decrease
|
|
1170
|
+
* ```
|
|
1171
|
+
*/
|
|
1172
|
+
set(moment: number): void;
|
|
1086
1173
|
};
|
|
1087
1174
|
/**
|
|
1088
|
-
*
|
|
1175
|
+
* Input delivered to `onElapsed` listeners when a due moment arrives or the game resumes.
|
|
1089
1176
|
*
|
|
1090
1177
|
* @example
|
|
1091
1178
|
* ```ts
|
|
1092
|
-
* const
|
|
1179
|
+
* const input: Elapsed = { now: 1_790_000_060_000 };
|
|
1093
1180
|
* ```
|
|
1094
1181
|
*/
|
|
1095
|
-
type
|
|
1182
|
+
type Elapsed = {
|
|
1183
|
+
now: number;
|
|
1184
|
+
};
|
|
1096
1185
|
/**
|
|
1097
|
-
*
|
|
1186
|
+
* clock plugin config.
|
|
1098
1187
|
*
|
|
1099
1188
|
* @example
|
|
1100
1189
|
* ```ts
|
|
1101
|
-
* createApp({ pluginConfigs: {
|
|
1190
|
+
* createApp({ pluginConfigs: { clock: { source: fakeClock(1000) } } });
|
|
1102
1191
|
* ```
|
|
1103
1192
|
*/
|
|
1104
1193
|
type Config$4 = {
|
|
1105
1194
|
/**
|
|
1106
|
-
*
|
|
1107
|
-
* Android today; WKWebView on iOS and macOS is capped at 60 by WebKit (bug 294338).
|
|
1108
|
-
*/
|
|
1109
|
-
maxFps: 30 | 60 | 120;
|
|
1110
|
-
/**
|
|
1111
|
-
* Upper bound of one frame's delta in milliseconds.
|
|
1112
|
-
*/
|
|
1113
|
-
maxDeltaMs: number;
|
|
1114
|
-
/**
|
|
1115
|
-
* Frame rate cap of an idle screen, when nothing woke the clock for `idleAfterMs`. The default
|
|
1116
|
-
* is 30; 0 turns the idle cap off and every frame runs at `maxFps`.
|
|
1117
|
-
*/
|
|
1118
|
-
idleFps: 0 | 30;
|
|
1119
|
-
/**
|
|
1120
|
-
* Unscaled milliseconds without a `wake()` after which the loop drops to `idleFps`.
|
|
1195
|
+
* Time source. `undefined` means the system source: `Date.now` and `setTimeout`.
|
|
1121
1196
|
*/
|
|
1122
|
-
|
|
1197
|
+
source: ClockSource | undefined;
|
|
1123
1198
|
};
|
|
1124
1199
|
/**
|
|
1125
|
-
*
|
|
1200
|
+
* clock plugin state.
|
|
1126
1201
|
*/
|
|
1127
1202
|
type State$4 = {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
time: Time;
|
|
1134
|
-
paused: boolean;
|
|
1135
|
-
running: boolean;
|
|
1136
|
-
/**
|
|
1137
|
-
* True while a frame runs; guards `step` re-entry.
|
|
1138
|
-
*/
|
|
1139
|
-
stepping: boolean;
|
|
1140
|
-
rafId: number | undefined;
|
|
1141
|
-
lastTimestamp: number | undefined;
|
|
1142
|
-
/**
|
|
1143
|
-
* Elapsed time of the frame source in unscaled milliseconds, the clock of the idle timer: it
|
|
1144
|
-
* keeps running while `scale` is 0.
|
|
1145
|
-
*/
|
|
1146
|
-
unscaledElapsedMs: number;
|
|
1147
|
-
/**
|
|
1148
|
-
* Unscaled elapsed time of the last wake.
|
|
1149
|
-
*/
|
|
1150
|
-
lastWakeMs: number;
|
|
1151
|
-
/**
|
|
1152
|
-
* True while the loop runs at `idleFps`.
|
|
1153
|
-
*/
|
|
1154
|
-
idle: boolean;
|
|
1203
|
+
source: ClockSource;
|
|
1204
|
+
last: number;
|
|
1205
|
+
dueAt: number | undefined;
|
|
1206
|
+
handle: unknown;
|
|
1207
|
+
listeners: Array<(input: Elapsed) => void>;
|
|
1155
1208
|
};
|
|
1156
1209
|
/**
|
|
1157
|
-
*
|
|
1158
|
-
*
|
|
1210
|
+
* clock plugin API, `app.clock`. Trusted time for rules that depend on real time: energy refill,
|
|
1211
|
+
* generators, daily rewards. The clock holds ONE pending moment; the rules decide the next one.
|
|
1159
1212
|
*
|
|
1160
1213
|
* @example
|
|
1161
1214
|
* ```ts
|
|
1162
|
-
* //
|
|
1163
|
-
*
|
|
1164
|
-
*
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1215
|
+
* // The whole cycle: ask the rules for the nearest moment, wait for it, apply, ask again.
|
|
1216
|
+
* app.clock.onElapsed(({ now }) => {
|
|
1217
|
+
* applyRefills(now);
|
|
1218
|
+
* app.clock.scheduleAt(nextRefillAt(now)); // undefined when nothing is pending
|
|
1219
|
+
* });
|
|
1220
|
+
* app.clock.scheduleAt(nextRefillAt(app.clock.now()));
|
|
1168
1221
|
* ```
|
|
1169
1222
|
*/
|
|
1170
1223
|
type Api$4 = {
|
|
1171
1224
|
/**
|
|
1172
|
-
*
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1175
|
-
* @param phase - Phase the callback belongs to.
|
|
1176
|
-
* @param callback - Function called once per frame with the current `Time`.
|
|
1177
|
-
* @returns Unsubscribe function; calling it twice is a no-op.
|
|
1178
|
-
* @example
|
|
1179
|
-
* ```ts
|
|
1180
|
-
* // The coin counter rolls up while the reward popup is open.
|
|
1181
|
-
* const off = app.time.onFrame("animate", time => counter.advance(time.delta));
|
|
1182
|
-
*
|
|
1183
|
-
* off(); // the popup is closed
|
|
1184
|
-
* ```
|
|
1185
|
-
*/
|
|
1186
|
-
onFrame(phase: Phase, callback: FrameCallback): () => void;
|
|
1187
|
-
/**
|
|
1188
|
-
* Returns a snapshot of the current `Time`, so a caller cannot write into the frame state.
|
|
1189
|
-
*
|
|
1190
|
-
* @returns A copy of the current `Time`.
|
|
1191
|
-
* @example
|
|
1192
|
-
* ```ts
|
|
1193
|
-
* // Stamp the start of a combo window in game time, which stands still during a pause.
|
|
1194
|
-
* const startedAt = app.time.snapshot().elapsed; // 1600
|
|
1195
|
-
* app.time.snapshot(); // { delta: 16, elapsed: 1600, scale: 1, frame: 100, idle: false }
|
|
1196
|
-
* ```
|
|
1197
|
-
*/
|
|
1198
|
-
snapshot(): Readonly<Time>;
|
|
1199
|
-
/**
|
|
1200
|
-
* Sets the time scale. Every frame delta is multiplied by it; 0 freezes the game time
|
|
1201
|
-
* while the frames keep running. A negative scale is clamped to 0.
|
|
1202
|
-
*
|
|
1203
|
-
* @param scale - New time scale, 0 or greater.
|
|
1204
|
-
* @example
|
|
1205
|
-
* ```ts
|
|
1206
|
-
* // Slow motion while the last match of the level resolves.
|
|
1207
|
-
* app.time.setScale(0.5); // a frame of 20 ms now has delta 10
|
|
1208
|
-
* app.time.setScale(1);
|
|
1209
|
-
* ```
|
|
1210
|
-
*/
|
|
1211
|
-
setScale(scale: number): void;
|
|
1212
|
-
/**
|
|
1213
|
-
* Pauses the clock: no phase runs and `elapsed` stops advancing. Called by `lifecycle`.
|
|
1225
|
+
* Reads trusted time. It never decreases, whatever the device clock does, so a player who sets
|
|
1226
|
+
* the clock back gains nothing.
|
|
1214
1227
|
*
|
|
1228
|
+
* @returns The current moment in epoch milliseconds, an integer.
|
|
1215
1229
|
* @example
|
|
1216
1230
|
* ```ts
|
|
1217
|
-
* //
|
|
1218
|
-
*
|
|
1219
|
-
* const
|
|
1220
|
-
* if (reasons.length === 1) time.pause(); // app.time.isPaused() is true, no phase runs
|
|
1231
|
+
* // Stamp a generator when it is used, and compare later.
|
|
1232
|
+
* player.generator.usedAt = app.clock.now(); // 1790000000000
|
|
1233
|
+
* const ready = app.clock.now() - player.generator.usedAt >= 60_000;
|
|
1221
1234
|
* ```
|
|
1222
1235
|
*/
|
|
1223
|
-
|
|
1236
|
+
now(): number;
|
|
1224
1237
|
/**
|
|
1225
|
-
*
|
|
1226
|
-
*
|
|
1238
|
+
* Replaces the single pending due moment. A moment in the past is not delivered synchronously:
|
|
1239
|
+
* it fires on the next macrotask of the source.
|
|
1227
1240
|
*
|
|
1241
|
+
* @param moment - Moment in epoch milliseconds, or `undefined` to cancel.
|
|
1228
1242
|
* @example
|
|
1229
1243
|
* ```ts
|
|
1230
|
-
* //
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1244
|
+
* // A generator refills in 60 s. Only one moment is pending: the nearest one.
|
|
1245
|
+
* app.clock.scheduleAt(app.clock.now() + 60_000);
|
|
1246
|
+
* app.clock.scheduleAt(undefined); // nothing is due any more: cancel
|
|
1233
1247
|
* ```
|
|
1234
1248
|
*/
|
|
1235
|
-
|
|
1249
|
+
scheduleAt(moment: number | undefined): void;
|
|
1236
1250
|
/**
|
|
1237
|
-
*
|
|
1251
|
+
* Registers a listener for the `elapsed` input: a due moment arrived, or `poke` was called.
|
|
1252
|
+
* The clock does not reschedule by itself.
|
|
1238
1253
|
*
|
|
1239
|
-
* @
|
|
1254
|
+
* @param listener - Called with `{ now }`, the trusted moment of the delivery.
|
|
1255
|
+
* @returns The unsubscribe function.
|
|
1240
1256
|
* @example
|
|
1241
1257
|
* ```ts
|
|
1242
|
-
*
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
* ```
|
|
1246
|
-
*/
|
|
1247
|
-
isPaused(): boolean;
|
|
1248
|
-
/**
|
|
1249
|
-
* Tells whether a real frame source drives the loop. False in plain Bun, where a test
|
|
1250
|
-
* drives the frames with `step`.
|
|
1258
|
+
* const off = app.clock.onElapsed(({ now }) => {
|
|
1259
|
+
* applyRefills(now); // now: 1790000060000
|
|
1260
|
+
* });
|
|
1251
1261
|
*
|
|
1252
|
-
*
|
|
1253
|
-
* @example
|
|
1254
|
-
* ```ts
|
|
1255
|
-
* // Without a frame source no frame ever comes: finish the fly-in at once instead of waiting.
|
|
1256
|
-
* if (!app.time.isRunning()) coin.moveTo(target); // false in plain Bun, true in a browser
|
|
1262
|
+
* off(); // the screen that showed the timer is closed
|
|
1257
1263
|
* ```
|
|
1258
1264
|
*/
|
|
1259
|
-
|
|
1265
|
+
onElapsed(listener: (input: Elapsed) => void): () => void;
|
|
1260
1266
|
/**
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
1263
|
-
* look at calls it: a pointer sample, a starting animation, a flow edge, a finished load, a
|
|
1264
|
-
* scene switch, a locale change, a reconcile.
|
|
1267
|
+
* Delivers `elapsed` right now, without touching the pending due moment. The engine calls it
|
|
1268
|
+
* when the game resumes; a game calls it after it changed something the rules depend on.
|
|
1265
1269
|
*
|
|
1266
1270
|
* @example
|
|
1267
1271
|
* ```ts
|
|
1268
|
-
* // The
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
* time.wake(); // app.time.snapshot().idle is false from here on
|
|
1272
|
+
* // The game synced with its server and the time source jumped forward: let the rules catch up.
|
|
1273
|
+
* // On resume from background the engine pokes by itself, a game never does that.
|
|
1274
|
+
* app.clock.poke(); // onElapsed listeners get { now } at once; the pending moment stays
|
|
1272
1275
|
* ```
|
|
1273
1276
|
*/
|
|
1274
|
-
|
|
1277
|
+
poke(): void;
|
|
1275
1278
|
/**
|
|
1276
|
-
*
|
|
1277
|
-
* flag and `maxDeltaMs`. The time scale still applies. For tests and tools.
|
|
1279
|
+
* Reads the pending due moment, for inspection and tests.
|
|
1278
1280
|
*
|
|
1279
|
-
* @
|
|
1280
|
-
* @throws {Error} When called from inside a frame callback.
|
|
1281
|
+
* @returns The pending moment, or `undefined` when nothing is scheduled.
|
|
1281
1282
|
* @example
|
|
1282
1283
|
* ```ts
|
|
1283
|
-
*
|
|
1284
|
-
* app.
|
|
1285
|
-
* app.
|
|
1286
|
-
* app.
|
|
1284
|
+
* app.clock.scheduleAt(1500);
|
|
1285
|
+
* app.clock.dueAt(); // 1500
|
|
1286
|
+
* await app.stop();
|
|
1287
|
+
* app.clock.dueAt(); // undefined
|
|
1287
1288
|
* ```
|
|
1288
1289
|
*/
|
|
1289
|
-
|
|
1290
|
+
dueAt(): number | undefined;
|
|
1290
1291
|
};
|
|
1291
1292
|
/**
|
|
1292
|
-
* Domain context of the
|
|
1293
|
+
* Domain context of the clock plugin.
|
|
1293
1294
|
*/
|
|
1294
|
-
type
|
|
1295
|
+
type ClockCtx = PluginCtx<Config$4, State$4> & {
|
|
1295
1296
|
readonly global: object;
|
|
1296
|
-
readonly log: Log.LogApi;
|
|
1297
1297
|
};
|
|
1298
1298
|
//#endregion
|
|
1299
1299
|
//#region src/plugins/flow/gate/types.d.ts
|
|
@@ -2828,9 +2828,9 @@ type Api$3 = RunnerApi & {
|
|
|
2828
2828
|
* shared by the runner, gate, inbox and fx.
|
|
2829
2829
|
*/
|
|
2830
2830
|
type Deps$3 = {
|
|
2831
|
-
time: Api$
|
|
2831
|
+
time: Api$7;
|
|
2832
2832
|
model: Api$5;
|
|
2833
|
-
clock: Api$
|
|
2833
|
+
clock: Api$4;
|
|
2834
2834
|
};
|
|
2835
2835
|
/**
|
|
2836
2836
|
* What the kernel context offers before the deps are attached. `emit` is the kernel's: `index.ts`
|
|
@@ -4840,7 +4840,7 @@ type Api$2 = {
|
|
|
4840
4840
|
* Resolved dependency APIs.
|
|
4841
4841
|
*/
|
|
4842
4842
|
type Deps$2 = {
|
|
4843
|
-
time: Api$
|
|
4843
|
+
time: Api$7;
|
|
4844
4844
|
model: Api$5;
|
|
4845
4845
|
flow: Api$3;
|
|
4846
4846
|
};
|
|
@@ -6409,10 +6409,10 @@ type Under = {
|
|
|
6409
6409
|
* Resolved dependency APIs. `clock` is the time source of the frame counters.
|
|
6410
6410
|
*/
|
|
6411
6411
|
type Deps$1 = {
|
|
6412
|
-
time: Api$
|
|
6412
|
+
time: Api$7;
|
|
6413
6413
|
lifecycle: Api$6;
|
|
6414
6414
|
world: Api$2;
|
|
6415
|
-
clock: Api$
|
|
6415
|
+
clock: Api$4;
|
|
6416
6416
|
};
|
|
6417
6417
|
/**
|
|
6418
6418
|
* What the kernel context offers before the deps are attached.
|
|
@@ -7153,7 +7153,7 @@ type Api = {
|
|
|
7153
7153
|
type Deps = {
|
|
7154
7154
|
flow: Api$3;
|
|
7155
7155
|
renderer: Api$1;
|
|
7156
|
-
time: Api$
|
|
7156
|
+
time: Api$7;
|
|
7157
7157
|
};
|
|
7158
7158
|
/**
|
|
7159
7159
|
* What the kernel context offers before the deps are attached.
|
|
@@ -7181,4 +7181,4 @@ type FlowRest = {
|
|
|
7181
7181
|
checkpoint: boolean;
|
|
7182
7182
|
};
|
|
7183
7183
|
//#endregion
|
|
7184
|
-
export {
|
|
7184
|
+
export { DescriptionNode as $, types_d_exports$5 as $t, Api$2 as A, FlowGraph as At, ComponentType as B, Answer as Bt, types_d_exports$1 as C, slot as Ct, FilterSlot as D, AnyFlow as Dt, DebugSwitches as E, FeatureDescription as Et, AnyComponent as F, SceneIdOf as Ft, Mut as G, State$4 as Gt, Entity as H, ClockSource as Ht, AnyComponentType as I, TypeTag as It, QueryTerm as J, Config$5 as Jt, Narrowed as K, types_d_exports$4 as Kt, AnyComponentValue as L, Descriptor as Lt, Explained as M, JournalEntry as Mt, State$2 as N, NodeTable as Nt, Point as O, Bookmark as Ot, types_d_exports$2 as P, RouteStep as Pt, ChangeHook as Q, State$5 as Qt, ComponentHandle as R, GuideOptions as Rt, Under as S, Require as Sn, exit as St, RenderStats as T, type as Tt, EntitySnapshot as U, Config$4 as Ut, ComponentValue as V, Api$4 as Vt, FrameDiff as W, FakeClock as Wt, SystemDefinition as X, Json as Xt, ResourceType as Y, Events$4 as Yt, TagType as Z, Root as Zt, PixiFilter as _, State$7 as _n, Position as _t, Config as a, Snapshot as an, NumericFields as at, PixiTexture as b, Config$8 as bn, types_d_exports$3 as bt, LoadBundles as c, RngState as cn, TrackOptions as ct, State as d, Events$5 as dn, Api$3 as dt, Patch as en, Ease as et, Tier as f, PauseReason as fn, BundlesOf as ft, Config$1 as g, Config$7 as gn, GameTypes as gt, Api$1 as h, Api$7 as hn, FeaturePlugin as ht, BundleSpec as i, SaveUnreadableError as in, MotionHandle as it, Config$2 as j, FlowState as jt, ViewportSize as k, DefineNode as kt, LoadReason as l, Api$6 as ln, TrackSegment as lt, types_d_exports as m, types_d_exports$6 as mn, EmitterIdOf as mt, AssetsIo as n, ProviderCall as nn, LayerSpec as nt, DefineBundles as o, StoreApi as on, ProjectionMotion as ot, Usage as p, State$6 as pn, Config$3 as pt, Owner as q, Api$5 as qt, BundleMap as r, SaveDoc as rn, Motion as rt, Events as s, RngApi as sn, ProjectionSpec as st, Api as t, PlayerStateProvider as tn, LayerSort as tt, Manifest as u, Config$6 as un, ViewHandle as ut, PixiModule as v, Time as vn, State$3 as vt, Captured as w, to as wt, State$1 as x, Events$6 as xn, defineFlow as xt, PixiSprite as y, types_d_exports$7 as yn, TextStylesOf as yt, ComponentSchema as z, Hint as zt };
|