@al8b/runtime 0.1.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.
Files changed (185) hide show
  1. package/README.md +87 -0
  2. package/dist/assets/constructors.d.mts +6 -0
  3. package/dist/assets/constructors.d.ts +6 -0
  4. package/dist/assets/constructors.js +40 -0
  5. package/dist/assets/constructors.js.map +1 -0
  6. package/dist/assets/constructors.mjs +12 -0
  7. package/dist/assets/constructors.mjs.map +1 -0
  8. package/dist/assets/index.d.mts +11 -0
  9. package/dist/assets/index.d.ts +11 -0
  10. package/dist/assets/index.js +276 -0
  11. package/dist/assets/index.js.map +1 -0
  12. package/dist/assets/index.mjs +247 -0
  13. package/dist/assets/index.mjs.map +1 -0
  14. package/dist/assets/loader.d.mts +83 -0
  15. package/dist/assets/loader.d.ts +83 -0
  16. package/dist/assets/loader.js +260 -0
  17. package/dist/assets/loader.js.map +1 -0
  18. package/dist/assets/loader.mjs +237 -0
  19. package/dist/assets/loader.mjs.map +1 -0
  20. package/dist/browser/index.js +16599 -0
  21. package/dist/browser/index.js.map +1 -0
  22. package/dist/browser/index.min.js +171 -0
  23. package/dist/constants.d.mts +16 -0
  24. package/dist/constants.d.ts +16 -0
  25. package/dist/constants.js +49 -0
  26. package/dist/constants.js.map +1 -0
  27. package/dist/constants.mjs +18 -0
  28. package/dist/constants.mjs.map +1 -0
  29. package/dist/core/api-factory.d.mts +63 -0
  30. package/dist/core/api-factory.d.ts +63 -0
  31. package/dist/core/api-factory.js +239 -0
  32. package/dist/core/api-factory.js.map +1 -0
  33. package/dist/core/api-factory.mjs +214 -0
  34. package/dist/core/api-factory.mjs.map +1 -0
  35. package/dist/core/assets-registry.d.mts +14 -0
  36. package/dist/core/assets-registry.d.ts +14 -0
  37. package/dist/core/assets-registry.js +64 -0
  38. package/dist/core/assets-registry.js.map +1 -0
  39. package/dist/core/assets-registry.mjs +41 -0
  40. package/dist/core/assets-registry.mjs.map +1 -0
  41. package/dist/core/controller.d.mts +109 -0
  42. package/dist/core/controller.d.ts +109 -0
  43. package/dist/core/controller.js +1782 -0
  44. package/dist/core/controller.js.map +1 -0
  45. package/dist/core/controller.mjs +1758 -0
  46. package/dist/core/controller.mjs.map +1 -0
  47. package/dist/core/debug-logger.d.mts +35 -0
  48. package/dist/core/debug-logger.d.ts +35 -0
  49. package/dist/core/debug-logger.js +177 -0
  50. package/dist/core/debug-logger.js.map +1 -0
  51. package/dist/core/debug-logger.mjs +154 -0
  52. package/dist/core/debug-logger.mjs.map +1 -0
  53. package/dist/core/error-handler.d.mts +25 -0
  54. package/dist/core/error-handler.d.ts +25 -0
  55. package/dist/core/error-handler.js +106 -0
  56. package/dist/core/error-handler.js.map +1 -0
  57. package/dist/core/error-handler.mjs +81 -0
  58. package/dist/core/error-handler.mjs.map +1 -0
  59. package/dist/core/index.d.mts +14 -0
  60. package/dist/core/index.d.ts +14 -0
  61. package/dist/core/index.js +1782 -0
  62. package/dist/core/index.js.map +1 -0
  63. package/dist/core/index.mjs +1757 -0
  64. package/dist/core/index.mjs.map +1 -0
  65. package/dist/hot-reload/index.d.mts +7 -0
  66. package/dist/hot-reload/index.d.ts +7 -0
  67. package/dist/hot-reload/index.js +103 -0
  68. package/dist/hot-reload/index.js.map +1 -0
  69. package/dist/hot-reload/index.mjs +78 -0
  70. package/dist/hot-reload/index.mjs.map +1 -0
  71. package/dist/hot-reload/updater.d.mts +33 -0
  72. package/dist/hot-reload/updater.d.ts +33 -0
  73. package/dist/hot-reload/updater.js +101 -0
  74. package/dist/hot-reload/updater.js.map +1 -0
  75. package/dist/hot-reload/updater.mjs +78 -0
  76. package/dist/hot-reload/updater.mjs.map +1 -0
  77. package/dist/index.d.mts +24 -0
  78. package/dist/index.d.ts +24 -0
  79. package/dist/index.js +1859 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/index.mjs +1817 -0
  82. package/dist/index.mjs.map +1 -0
  83. package/dist/input/index.d.mts +2 -0
  84. package/dist/input/index.d.ts +2 -0
  85. package/dist/input/index.js +79 -0
  86. package/dist/input/index.js.map +1 -0
  87. package/dist/input/index.mjs +54 -0
  88. package/dist/input/index.mjs.map +1 -0
  89. package/dist/input/manager.d.mts +37 -0
  90. package/dist/input/manager.d.ts +37 -0
  91. package/dist/input/manager.js +77 -0
  92. package/dist/input/manager.js.map +1 -0
  93. package/dist/input/manager.mjs +54 -0
  94. package/dist/input/manager.mjs.map +1 -0
  95. package/dist/loop/game-loop.d.mts +63 -0
  96. package/dist/loop/game-loop.d.ts +63 -0
  97. package/dist/loop/game-loop.js +156 -0
  98. package/dist/loop/game-loop.js.map +1 -0
  99. package/dist/loop/game-loop.mjs +131 -0
  100. package/dist/loop/game-loop.mjs.map +1 -0
  101. package/dist/loop/index.d.mts +1 -0
  102. package/dist/loop/index.d.ts +1 -0
  103. package/dist/loop/index.js +156 -0
  104. package/dist/loop/index.js.map +1 -0
  105. package/dist/loop/index.mjs +131 -0
  106. package/dist/loop/index.mjs.map +1 -0
  107. package/dist/storage/index.d.mts +1 -0
  108. package/dist/storage/index.d.ts +1 -0
  109. package/dist/storage/index.js +31 -0
  110. package/dist/storage/index.js.map +1 -0
  111. package/dist/storage/index.mjs +6 -0
  112. package/dist/storage/index.mjs.map +1 -0
  113. package/dist/system/api.d.mts +28 -0
  114. package/dist/system/api.d.ts +28 -0
  115. package/dist/system/api.js +126 -0
  116. package/dist/system/api.js.map +1 -0
  117. package/dist/system/api.mjs +101 -0
  118. package/dist/system/api.mjs.map +1 -0
  119. package/dist/system/index.d.mts +2 -0
  120. package/dist/system/index.d.ts +2 -0
  121. package/dist/system/index.js +126 -0
  122. package/dist/system/index.js.map +1 -0
  123. package/dist/system/index.mjs +101 -0
  124. package/dist/system/index.mjs.map +1 -0
  125. package/dist/types/assets.d.mts +43 -0
  126. package/dist/types/assets.d.ts +43 -0
  127. package/dist/types/assets.js +19 -0
  128. package/dist/types/assets.js.map +1 -0
  129. package/dist/types/assets.mjs +1 -0
  130. package/dist/types/assets.mjs.map +1 -0
  131. package/dist/types/bridge.d.mts +66 -0
  132. package/dist/types/bridge.d.ts +66 -0
  133. package/dist/types/bridge.js +19 -0
  134. package/dist/types/bridge.js.map +1 -0
  135. package/dist/types/bridge.mjs +1 -0
  136. package/dist/types/bridge.mjs.map +1 -0
  137. package/dist/types/index.d.mts +6 -0
  138. package/dist/types/index.d.ts +6 -0
  139. package/dist/types/index.js +19 -0
  140. package/dist/types/index.js.map +1 -0
  141. package/dist/types/index.mjs +1 -0
  142. package/dist/types/index.mjs.map +1 -0
  143. package/dist/types/runtime.d.mts +71 -0
  144. package/dist/types/runtime.d.ts +71 -0
  145. package/dist/types/runtime.js +19 -0
  146. package/dist/types/runtime.js.map +1 -0
  147. package/dist/types/runtime.mjs +1 -0
  148. package/dist/types/runtime.mjs.map +1 -0
  149. package/dist/types/vm.d.mts +1 -0
  150. package/dist/types/vm.d.ts +1 -0
  151. package/dist/types/vm.js +19 -0
  152. package/dist/types/vm.js.map +1 -0
  153. package/dist/types/vm.mjs +1 -0
  154. package/dist/types/vm.mjs.map +1 -0
  155. package/dist/utils/deep-clone.d.mts +14 -0
  156. package/dist/utils/deep-clone.d.ts +14 -0
  157. package/dist/utils/deep-clone.js +42 -0
  158. package/dist/utils/deep-clone.js.map +1 -0
  159. package/dist/utils/deep-clone.mjs +19 -0
  160. package/dist/utils/deep-clone.mjs.map +1 -0
  161. package/dist/utils/index.d.mts +3 -0
  162. package/dist/utils/index.d.ts +3 -0
  163. package/dist/utils/index.js +156 -0
  164. package/dist/utils/index.js.map +1 -0
  165. package/dist/utils/index.mjs +129 -0
  166. package/dist/utils/index.mjs.map +1 -0
  167. package/dist/utils/object-pool.d.mts +66 -0
  168. package/dist/utils/object-pool.d.ts +66 -0
  169. package/dist/utils/object-pool.js +113 -0
  170. package/dist/utils/object-pool.js.map +1 -0
  171. package/dist/utils/object-pool.mjs +90 -0
  172. package/dist/utils/object-pool.mjs.map +1 -0
  173. package/dist/utils/shallow-equal.d.mts +15 -0
  174. package/dist/utils/shallow-equal.d.ts +15 -0
  175. package/dist/utils/shallow-equal.js +53 -0
  176. package/dist/utils/shallow-equal.js.map +1 -0
  177. package/dist/utils/shallow-equal.mjs +30 -0
  178. package/dist/utils/shallow-equal.mjs.map +1 -0
  179. package/dist/vm/index.d.mts +1 -0
  180. package/dist/vm/index.d.ts +1 -0
  181. package/dist/vm/index.js +37 -0
  182. package/dist/vm/index.js.map +1 -0
  183. package/dist/vm/index.mjs +9 -0
  184. package/dist/vm/index.mjs.map +1 -0
  185. package/package.json +52 -0
@@ -0,0 +1,1757 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/core/controller.ts
5
+ import { AudioCore } from "@al8b/audio";
6
+ import { PlayerService } from "@al8b/player";
7
+ import { SceneManager } from "@al8b/scene";
8
+ import { Screen } from "@al8b/screen";
9
+ import { StatePlayer, TimeMachine } from "@al8b/time";
10
+ import { L8BVM } from "@al8b/vm";
11
+
12
+ // src/assets/constructors.ts
13
+ import { Sound } from "@al8b/audio";
14
+ import { TileMap } from "@al8b/map";
15
+ import { Image } from "@al8b/image";
16
+ import { Sprite } from "@al8b/sprites";
17
+
18
+ // src/assets/loader.ts
19
+ import { Sound as Sound2, Music } from "@al8b/audio";
20
+
21
+ // src/constants.ts
22
+ var DEFAULT_FPS = 60;
23
+ var DEFAULT_UPDATE_RATE = 60;
24
+ var FRAME_TIME_MS = 1e3 / DEFAULT_FPS;
25
+ var PAUSE_THRESHOLD_MS = 160;
26
+ var DEFAULT_BLOCK_SIZE = 16;
27
+ var LOADING_BAR_THROTTLE_MS = 16;
28
+ var ASSET_LOAD_TIMEOUT_MS = 3e4;
29
+
30
+ // src/assets/loader.ts
31
+ import { LoadMap } from "@al8b/map";
32
+ import { LoadSprite } from "@al8b/sprites";
33
+ function withTimeout(promise, ms) {
34
+ return Promise.race([
35
+ promise,
36
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Asset load timed out after ${ms}ms`)), ms))
37
+ ]);
38
+ }
39
+ __name(withTimeout, "withTimeout");
40
+ var AssetLoader = class {
41
+ static {
42
+ __name(this, "AssetLoader");
43
+ }
44
+ url;
45
+ resources;
46
+ collections;
47
+ loadingBarTime = null;
48
+ audioCore;
49
+ listener;
50
+ constructor(url, resources, audioCore, listener) {
51
+ this.url = url;
52
+ this.resources = resources;
53
+ this.audioCore = audioCore;
54
+ this.listener = listener;
55
+ this.collections = {
56
+ sprites: {},
57
+ maps: {},
58
+ sounds: {},
59
+ music: {},
60
+ assets: {}
61
+ };
62
+ }
63
+ /**
64
+ * Load all assets
65
+ */
66
+ async loadAll() {
67
+ await Promise.all([
68
+ this.loadSprites(),
69
+ this.loadMaps(),
70
+ this.loadSounds(),
71
+ this.loadMusic(),
72
+ this.loadGenericAssets()
73
+ ]);
74
+ return this.collections;
75
+ }
76
+ /**
77
+ * Load a set of callback-based assets (sprites or maps) with timeout and placeholder fallback.
78
+ */
79
+ async loadCallbackAssets(resources, urlPrefix, collectionKey, load, createPlaceholder) {
80
+ if (!resources) return;
81
+ const promises = resources.map((res) => new Promise((resolve) => {
82
+ const name = res.file.split(".")[0].replace(/-/g, "/");
83
+ const url = `${this.url}${urlPrefix}/${res.file}?v=${res.version || 0}`;
84
+ try {
85
+ const inner = new Promise((onReady) => {
86
+ this.collections[collectionKey][name] = load(url, res, onReady);
87
+ });
88
+ withTimeout(inner, ASSET_LOAD_TIMEOUT_MS).then(resolve).catch((err) => {
89
+ this.listener?.log?.(`[AssetLoader] Failed to load ${collectionKey.slice(0, -1)} "${name}": ${String(err)}`);
90
+ this.collections[collectionKey][name] = createPlaceholder(name, res);
91
+ resolve();
92
+ });
93
+ } catch (err) {
94
+ this.listener?.log?.(`[AssetLoader] Failed to load ${collectionKey.slice(0, -1)} "${name}": ${String(err)}`);
95
+ this.collections[collectionKey][name] = createPlaceholder(name, res);
96
+ resolve();
97
+ }
98
+ }));
99
+ await Promise.all(promises);
100
+ }
101
+ /**
102
+ * Load sprites
103
+ */
104
+ async loadSprites() {
105
+ await this.loadCallbackAssets(this.resources.images, "sprites", "sprites", (url, img, onReady) => LoadSprite(url, img.properties, onReady), (name, img) => ({
106
+ name,
107
+ ready: false,
108
+ frames: [],
109
+ fps: img.properties?.fps || 5,
110
+ width: 0,
111
+ height: 0
112
+ }));
113
+ }
114
+ /**
115
+ * Load maps
116
+ */
117
+ async loadMaps() {
118
+ await this.loadCallbackAssets(this.resources.maps, "maps", "maps", (url, _mapRes, onReady) => LoadMap(url, this.collections.sprites, onReady), (name) => ({
119
+ name,
120
+ ready: false,
121
+ width: 0,
122
+ height: 0,
123
+ block_width: DEFAULT_BLOCK_SIZE,
124
+ block_height: DEFAULT_BLOCK_SIZE,
125
+ data: []
126
+ }));
127
+ }
128
+ /**
129
+ * Load sounds
130
+ */
131
+ async loadSounds() {
132
+ if (!this.resources.sounds) return;
133
+ const promises = this.resources.sounds.map((sound) => {
134
+ return new Promise((resolve) => {
135
+ const name = sound.file.split(".")[0];
136
+ const url = `${this.url}sounds/${sound.file}?v=${sound.version || 0}`;
137
+ try {
138
+ const soundInstance = new Sound2(this.audioCore, url);
139
+ this.collections.sounds[name] = soundInstance;
140
+ resolve();
141
+ } catch (err) {
142
+ this.listener?.log?.(`[AssetLoader] Failed to load sound "${name}": ${String(err)}`);
143
+ this.collections.sounds[name] = new Sound2(this.audioCore, url);
144
+ resolve();
145
+ }
146
+ });
147
+ });
148
+ await Promise.all(promises);
149
+ }
150
+ /**
151
+ * Load music
152
+ */
153
+ async loadMusic() {
154
+ if (!this.resources.music) return;
155
+ const promises = this.resources.music.map((mus) => {
156
+ return new Promise((resolve) => {
157
+ const name = mus.file.split(".")[0];
158
+ const url = `${this.url}music/${mus.file}?v=${mus.version || 0}`;
159
+ try {
160
+ const musicInstance = new Music(this.audioCore, url);
161
+ this.collections.music[name] = musicInstance;
162
+ resolve();
163
+ } catch (err) {
164
+ this.listener?.log?.(`[AssetLoader] Failed to load music "${name}": ${String(err)}`);
165
+ this.collections.music[name] = new Music(this.audioCore, url);
166
+ resolve();
167
+ }
168
+ });
169
+ });
170
+ await Promise.all(promises);
171
+ }
172
+ /**
173
+ * Load generic assets
174
+ */
175
+ async loadGenericAssets() {
176
+ if (!this.resources.assets) return;
177
+ for (const asset of this.resources.assets) {
178
+ const name = asset.file.split(".")[0].replace(/-/g, "/");
179
+ this.collections.assets[name] = {
180
+ name,
181
+ file: asset.file,
182
+ version: asset.version
183
+ };
184
+ }
185
+ }
186
+ /**
187
+ * Check if all assets are ready
188
+ */
189
+ isReady() {
190
+ return this.countReady() === this.countTotal();
191
+ }
192
+ /**
193
+ * Get loading progress (0-1)
194
+ */
195
+ getProgress() {
196
+ const total = this.countTotal();
197
+ if (total === 0) return 1;
198
+ return this.countReady() / total;
199
+ }
200
+ /**
201
+ * Count total assets
202
+ */
203
+ countTotal() {
204
+ let count = 0;
205
+ count += Object.keys(this.collections.sprites).length;
206
+ count += Object.keys(this.collections.maps).length;
207
+ count += Object.keys(this.collections.sounds).length;
208
+ count += Object.keys(this.collections.music).length;
209
+ return count;
210
+ }
211
+ /**
212
+ * Count ready assets
213
+ */
214
+ countReady() {
215
+ let ready = 0;
216
+ for (const sprite of Object.values(this.collections.sprites)) {
217
+ if (sprite.ready) ready++;
218
+ }
219
+ for (const map of Object.values(this.collections.maps)) {
220
+ if (map.ready) ready++;
221
+ }
222
+ for (const sound of Object.values(this.collections.sounds)) {
223
+ if (sound.ready) ready++;
224
+ }
225
+ for (const mus of Object.values(this.collections.music)) {
226
+ if (mus.ready) ready++;
227
+ }
228
+ return ready;
229
+ }
230
+ /**
231
+ * Show loading bar on screen
232
+ */
233
+ showLoadingBar(screenInterface) {
234
+ if (this.loadingBarTime && Date.now() < this.loadingBarTime + LOADING_BAR_THROTTLE_MS) {
235
+ return;
236
+ }
237
+ this.loadingBarTime = Date.now();
238
+ const progress = this.getProgress();
239
+ screenInterface.clear("#000");
240
+ screenInterface.drawRect(0, 0, 100, 10, "#DDD");
241
+ screenInterface.fillRect(-(1 - progress) * 48, 0, progress * 96, 6, "#DDD");
242
+ }
243
+ /**
244
+ * Get loaded collections
245
+ */
246
+ getCollections() {
247
+ return this.collections;
248
+ }
249
+ };
250
+
251
+ // src/hot-reload/updater.ts
252
+ var SourceUpdater = class {
253
+ static {
254
+ __name(this, "SourceUpdater");
255
+ }
256
+ vm;
257
+ listener;
258
+ audio;
259
+ screen;
260
+ reportWarnings;
261
+ emitBridgeEvent;
262
+ updateMemory = {};
263
+ previousInit = null;
264
+ constructor(vm, listener, audio, screen, reportWarnings2, emitBridgeEvent) {
265
+ this.vm = vm;
266
+ this.listener = listener;
267
+ this.audio = audio;
268
+ this.screen = screen;
269
+ this.reportWarnings = reportWarnings2;
270
+ this.emitBridgeEvent = emitBridgeEvent;
271
+ }
272
+ /**
273
+ * Update source code (hot reload)
274
+ */
275
+ updateSource(file, src, reinit = false) {
276
+ if (!this.vm) return false;
277
+ if (src === this.updateMemory[file]) return false;
278
+ this.updateMemory[file] = src;
279
+ if (this.audio) {
280
+ this.audio.cancelBeeps();
281
+ }
282
+ if (this.screen) {
283
+ this.screen.clear();
284
+ }
285
+ try {
286
+ this.vm.run(src, 3e3, file);
287
+ if (this.emitBridgeEvent) {
288
+ this.emitBridgeEvent("compile_success", {
289
+ file
290
+ });
291
+ }
292
+ if (this.reportWarnings) {
293
+ this.reportWarnings();
294
+ }
295
+ if (this.vm.error_info) {
296
+ const err = Object.assign({}, this.vm.error_info);
297
+ err.type = "init";
298
+ err.file = file;
299
+ this.listener.reportError?.(err);
300
+ return false;
301
+ }
302
+ if (this.vm.runner?.getFunctionSource) {
303
+ const init = this.vm.runner.getFunctionSource("init");
304
+ if (init && init !== this.previousInit && reinit) {
305
+ this.previousInit = init;
306
+ this.vm.call("init");
307
+ if (this.vm.error_info) {
308
+ const err = Object.assign({}, this.vm.error_info);
309
+ err.type = "init";
310
+ this.listener.reportError?.(err);
311
+ }
312
+ }
313
+ }
314
+ return true;
315
+ } catch (err) {
316
+ err.file = file;
317
+ this.listener.reportError?.(err);
318
+ return false;
319
+ }
320
+ }
321
+ };
322
+
323
+ // src/input/manager.ts
324
+ import { GamepadInput, KeyboardInput, MouseInput, TouchInput } from "@al8b/input";
325
+ var InputManager = class {
326
+ static {
327
+ __name(this, "InputManager");
328
+ }
329
+ keyboard;
330
+ mouse;
331
+ touch;
332
+ gamepad;
333
+ constructor(canvas) {
334
+ this.keyboard = new KeyboardInput();
335
+ this.mouse = new MouseInput();
336
+ this.touch = new TouchInput(this.mouse);
337
+ this.gamepad = new GamepadInput();
338
+ if (canvas) {
339
+ this.attachCanvas(canvas);
340
+ }
341
+ }
342
+ /**
343
+ * Attach input systems to canvas
344
+ */
345
+ attachCanvas(canvas) {
346
+ this.mouse.setCanvas(canvas);
347
+ this.touch.setCanvas(canvas);
348
+ }
349
+ /**
350
+ * Update all input systems (call each frame)
351
+ */
352
+ update() {
353
+ this.keyboard.update();
354
+ this.mouse.update();
355
+ this.touch.update();
356
+ this.gamepad.update();
357
+ }
358
+ /**
359
+ * Get input states for game code
360
+ */
361
+ getStates() {
362
+ return {
363
+ keyboard: this.keyboard.state,
364
+ mouse: this.mouse.state,
365
+ touch: this.touch.state,
366
+ gamepad: this.gamepad.status
367
+ };
368
+ }
369
+ };
370
+
371
+ // src/loop/game-loop.ts
372
+ var GameLoop = class {
373
+ static {
374
+ __name(this, "GameLoop");
375
+ }
376
+ callbacks;
377
+ state;
378
+ stopped = false;
379
+ animationFrameId = null;
380
+ constructor(callbacks) {
381
+ this.callbacks = callbacks;
382
+ this.state = {
383
+ currentFrame: 0,
384
+ floatingFrame: 0,
385
+ dt: FRAME_TIME_MS,
386
+ lastTime: performance.now(),
387
+ fps: DEFAULT_FPS,
388
+ updateRate: DEFAULT_UPDATE_RATE
389
+ };
390
+ this.loop = this.loop.bind(this);
391
+ }
392
+ /**
393
+ * Start the game loop
394
+ */
395
+ start() {
396
+ this.stopped = false;
397
+ this.state.lastTime = performance.now();
398
+ this.state.currentFrame = 0;
399
+ this.state.floatingFrame = 0;
400
+ this.loop();
401
+ }
402
+ /**
403
+ * Stop the game loop
404
+ */
405
+ stop() {
406
+ this.stopped = true;
407
+ if (this.animationFrameId !== null) {
408
+ cancelAnimationFrame(this.animationFrameId);
409
+ this.animationFrameId = null;
410
+ }
411
+ }
412
+ /**
413
+ * Resume the game loop
414
+ */
415
+ resume() {
416
+ if (!this.stopped) return;
417
+ this.stopped = false;
418
+ this.state.lastTime = performance.now();
419
+ this.loop();
420
+ }
421
+ /**
422
+ * Main game loop
423
+ */
424
+ loop() {
425
+ if (this.stopped) return;
426
+ this.animationFrameId = requestAnimationFrame(this.loop);
427
+ const time = performance.now();
428
+ if (Math.abs(time - this.state.lastTime) > PAUSE_THRESHOLD_MS) {
429
+ this.state.lastTime = time - LOADING_BAR_THROTTLE_MS;
430
+ }
431
+ const dt = time - this.state.lastTime;
432
+ this.state.dt = this.state.dt * 0.9 + dt * 0.1;
433
+ this.state.lastTime = time;
434
+ const fps = Math.round(1e3 / this.state.dt);
435
+ this.state.fps = fps;
436
+ if (this.callbacks.setFPS) {
437
+ this.callbacks.setFPS(fps);
438
+ }
439
+ let updateRate = this.state.updateRate;
440
+ if (this.callbacks.getUpdateRate) {
441
+ const rate = this.callbacks.getUpdateRate();
442
+ if (rate != null && rate > 0 && Number.isFinite(rate)) {
443
+ updateRate = rate;
444
+ }
445
+ }
446
+ this.state.floatingFrame += this.state.dt * updateRate / 1e3;
447
+ let ds = Math.min(10, Math.round(this.state.floatingFrame - this.state.currentFrame));
448
+ if ((ds === 0 || ds === 2) && updateRate === DEFAULT_UPDATE_RATE && Math.abs(fps - DEFAULT_FPS) < 2) {
449
+ ds = 1;
450
+ this.state.floatingFrame = this.state.currentFrame + 1;
451
+ }
452
+ for (let i = 1; i <= ds; i++) {
453
+ this.callbacks.onUpdate();
454
+ if (i < ds && this.callbacks.onTick) {
455
+ this.callbacks.onTick();
456
+ }
457
+ }
458
+ this.state.currentFrame += ds;
459
+ this.callbacks.onDraw();
460
+ if (this.callbacks.onTick) {
461
+ this.callbacks.onTick();
462
+ }
463
+ if (ds > 0 && this.callbacks.onWatchStep) {
464
+ this.callbacks.onWatchStep();
465
+ }
466
+ }
467
+ /**
468
+ * Get current state
469
+ */
470
+ getState() {
471
+ return this.state;
472
+ }
473
+ /**
474
+ * Set update rate
475
+ */
476
+ setUpdateRate(rate) {
477
+ if (rate > 0 && Number.isFinite(rate)) {
478
+ this.state.updateRate = rate;
479
+ }
480
+ }
481
+ /**
482
+ * Get FPS
483
+ */
484
+ getFPS() {
485
+ return this.state.fps;
486
+ }
487
+ };
488
+
489
+ // src/system/api.ts
490
+ var System = class {
491
+ static {
492
+ __name(this, "System");
493
+ }
494
+ systemAPI;
495
+ constructor() {
496
+ this.systemAPI = {
497
+ // Time
498
+ get time() {
499
+ return Date.now();
500
+ },
501
+ // FPS
502
+ fps: DEFAULT_FPS,
503
+ // CPU load
504
+ cpu_load: 0,
505
+ // Update rate
506
+ update_rate: DEFAULT_UPDATE_RATE,
507
+ // Language
508
+ get language() {
509
+ return typeof navigator !== "undefined" ? navigator.language : "en";
510
+ },
511
+ // Input availability
512
+ inputs: {
513
+ get keyboard() {
514
+ return 1;
515
+ },
516
+ get mouse() {
517
+ return 1;
518
+ },
519
+ get touch() {
520
+ return typeof window !== "undefined" && "ontouchstart" in window ? 1 : 0;
521
+ },
522
+ get gamepad() {
523
+ return typeof navigator !== "undefined" && typeof navigator.getGamepads === "function" ? 1 : 0;
524
+ }
525
+ },
526
+ // Loading progress
527
+ loading: 0,
528
+ // Utility functions
529
+ prompt: /* @__PURE__ */ __name((text, callback) => {
530
+ if (typeof window !== "undefined") {
531
+ const result = window.prompt(text);
532
+ if (result !== null && callback) {
533
+ callback(result);
534
+ }
535
+ }
536
+ }, "prompt"),
537
+ say: /* @__PURE__ */ __name((text) => {
538
+ if (typeof window !== "undefined") {
539
+ window.alert(text);
540
+ }
541
+ }, "say"),
542
+ // File drop support
543
+ file: {
544
+ dropped: 0
545
+ },
546
+ // JavaScript interop (placeholder for future use)
547
+ javascript: {},
548
+ // Additional flags
549
+ disable_autofullscreen: 0,
550
+ preemptive: 1
551
+ };
552
+ }
553
+ /**
554
+ * Get system API for game code
555
+ */
556
+ getAPI() {
557
+ return this.systemAPI;
558
+ }
559
+ /**
560
+ * Update FPS
561
+ */
562
+ setFPS(fps) {
563
+ this.systemAPI.fps = fps;
564
+ }
565
+ /**
566
+ * Update CPU load
567
+ */
568
+ setCPULoad(load) {
569
+ this.systemAPI.cpu_load = load;
570
+ }
571
+ /**
572
+ * Update loading progress
573
+ */
574
+ setLoading(progress) {
575
+ this.systemAPI.loading = progress;
576
+ }
577
+ };
578
+
579
+ // src/core/debug-logger.ts
580
+ var DebugLogger = class {
581
+ static {
582
+ __name(this, "DebugLogger");
583
+ }
584
+ lastInputDebug;
585
+ lastScreenDebug;
586
+ /**
587
+ * Log input state changes (deduplication via shallow compare)
588
+ */
589
+ debugInputs(input, debug) {
590
+ if (!debug?.input) return;
591
+ const snapshot = this.createInputSnapshot(input, debug.input);
592
+ if (!snapshot) return;
593
+ if (this.lastInputDebug && shallowEqual(snapshot, this.lastInputDebug)) return;
594
+ this.lastInputDebug = snapshot;
595
+ console.debug("[@al8b/runtime][input]", snapshot);
596
+ }
597
+ /**
598
+ * Log screen dimension changes
599
+ */
600
+ debugScreen(screen, debug) {
601
+ if (!debug?.screen) return;
602
+ const canvas = screen.getCanvas();
603
+ const current = {
604
+ width: screen.width,
605
+ height: screen.height,
606
+ canvasWidth: canvas.width,
607
+ canvasHeight: canvas.height
608
+ };
609
+ if (this.lastScreenDebug && current.width === this.lastScreenDebug.width && current.height === this.lastScreenDebug.height && current.canvasWidth === this.lastScreenDebug.canvasWidth && current.canvasHeight === this.lastScreenDebug.canvasHeight) {
610
+ return;
611
+ }
612
+ this.lastScreenDebug = current;
613
+ console.debug("[@al8b/runtime][screen]", {
614
+ screen: {
615
+ width: screen.width,
616
+ height: screen.height
617
+ },
618
+ canvas: {
619
+ width: canvas.width,
620
+ height: canvas.height,
621
+ clientWidth: canvas.clientWidth,
622
+ clientHeight: canvas.clientHeight,
623
+ style: {
624
+ width: canvas.style.width,
625
+ height: canvas.style.height
626
+ }
627
+ }
628
+ });
629
+ }
630
+ /**
631
+ * Create a snapshot of current input state based on enabled channels
632
+ */
633
+ createInputSnapshot(input, setting) {
634
+ const channels = getEnabledInputChannels(setting);
635
+ if (channels.length === 0) return null;
636
+ const states = input.getStates();
637
+ const snapshot = {};
638
+ if (channels.includes("touch")) {
639
+ snapshot.touch = {
640
+ touching: states.touch.touching,
641
+ press: states.touch.press,
642
+ release: states.touch.release,
643
+ x: Number(states.touch.x?.toFixed?.(2) ?? states.touch.x),
644
+ y: Number(states.touch.y?.toFixed?.(2) ?? states.touch.y),
645
+ count: states.touch.touches?.length ?? 0
646
+ };
647
+ }
648
+ if (channels.includes("mouse")) {
649
+ snapshot.mouse = {
650
+ pressed: states.mouse.pressed,
651
+ left: states.mouse.left,
652
+ x: Number(states.mouse.x?.toFixed?.(2) ?? states.mouse.x),
653
+ y: Number(states.mouse.y?.toFixed?.(2) ?? states.mouse.y),
654
+ wheel: states.mouse.wheel
655
+ };
656
+ }
657
+ if (channels.includes("keyboard")) {
658
+ snapshot.keyboard = {
659
+ UP: states.keyboard.UP,
660
+ DOWN: states.keyboard.DOWN,
661
+ LEFT: states.keyboard.LEFT,
662
+ RIGHT: states.keyboard.RIGHT,
663
+ press: states.keyboard.press,
664
+ release: states.keyboard.release
665
+ };
666
+ }
667
+ if (channels.includes("gamepad")) {
668
+ snapshot.gamepad = {
669
+ count: input.gamepad.count,
670
+ A: states.gamepad.A,
671
+ B: states.gamepad.B,
672
+ UP: states.gamepad.UP,
673
+ DOWN: states.gamepad.DOWN,
674
+ LEFT: states.gamepad.LEFT,
675
+ RIGHT: states.gamepad.RIGHT
676
+ };
677
+ }
678
+ return Object.keys(snapshot).length === 0 ? null : snapshot;
679
+ }
680
+ };
681
+ function getEnabledInputChannels(setting) {
682
+ if (typeof setting === "boolean") {
683
+ return setting ? [
684
+ "keyboard",
685
+ "mouse",
686
+ "touch",
687
+ "gamepad"
688
+ ] : [];
689
+ }
690
+ const channels = [];
691
+ if (setting.keyboard) channels.push("keyboard");
692
+ if (setting.mouse) channels.push("mouse");
693
+ if (setting.touch) channels.push("touch");
694
+ if (setting.gamepad) channels.push("gamepad");
695
+ return channels;
696
+ }
697
+ __name(getEnabledInputChannels, "getEnabledInputChannels");
698
+ function shallowEqual(obj1, obj2) {
699
+ if (obj1 === obj2) return true;
700
+ if (!obj1 || !obj2 || typeof obj1 !== "object" || typeof obj2 !== "object") return false;
701
+ const keys1 = Object.keys(obj1);
702
+ const keys2 = Object.keys(obj2);
703
+ if (keys1.length !== keys2.length) return false;
704
+ for (const key of keys1) {
705
+ const val1 = obj1[key];
706
+ const val2 = obj2[key];
707
+ if (val1 === val2) continue;
708
+ if (val1 == null || val2 == null) {
709
+ if (val1 !== val2) return false;
710
+ continue;
711
+ }
712
+ if (typeof val1 === "object" && typeof val2 === "object") {
713
+ const keys1Nested = Object.keys(val1);
714
+ const keys2Nested = Object.keys(val2);
715
+ if (keys1Nested.length !== keys2Nested.length) return false;
716
+ for (const nestedKey of keys1Nested) {
717
+ if (val1[nestedKey] !== val2[nestedKey]) return false;
718
+ }
719
+ } else {
720
+ return false;
721
+ }
722
+ }
723
+ return true;
724
+ }
725
+ __name(shallowEqual, "shallowEqual");
726
+
727
+ // src/core/error-handler.ts
728
+ import { createDiagnostic, formatForBrowser } from "@al8b/diagnostics";
729
+ function formatRuntimeError(error) {
730
+ if (error.code || error.context || error.suggestions) {
731
+ return error;
732
+ }
733
+ const code = error.code || "E2005";
734
+ const diagnostic = createDiagnostic(code, {
735
+ file: error.file,
736
+ line: error.line,
737
+ column: error.column,
738
+ context: error.context,
739
+ suggestions: error.suggestions,
740
+ related: error.related,
741
+ stackTrace: error.stackTrace,
742
+ data: {
743
+ error: error.error || error.message
744
+ }
745
+ });
746
+ const formattedMessage = formatForBrowser(diagnostic);
747
+ return {
748
+ ...error,
749
+ ...diagnostic,
750
+ formatted: formattedMessage
751
+ };
752
+ }
753
+ __name(formatRuntimeError, "formatRuntimeError");
754
+ function reportError(listener, error) {
755
+ if (listener.reportError) {
756
+ const formatted = formatRuntimeError(error);
757
+ listener.reportError(formatted);
758
+ }
759
+ }
760
+ __name(reportError, "reportError");
761
+ function reportWarnings(vm, listener) {
762
+ if (!vm) return;
763
+ const warnings = vm.context?.warnings;
764
+ if (!warnings) return;
765
+ if (warnings.invoking_non_function) {
766
+ for (const value of Object.values(warnings.invoking_non_function)) {
767
+ const warning = value;
768
+ if (!warning.reported) {
769
+ warning.reported = true;
770
+ reportError(listener, {
771
+ error: "",
772
+ type: "non_function",
773
+ expression: warning.expression,
774
+ line: warning.line,
775
+ column: warning.column,
776
+ file: warning.file
777
+ });
778
+ }
779
+ }
780
+ }
781
+ if (warnings.using_undefined_variable) {
782
+ for (const value of Object.values(warnings.using_undefined_variable)) {
783
+ const warning = value;
784
+ if (!warning.reported) {
785
+ warning.reported = true;
786
+ reportError(listener, {
787
+ error: "",
788
+ type: "undefined_variable",
789
+ expression: warning.expression,
790
+ line: warning.line,
791
+ column: warning.column,
792
+ file: warning.file
793
+ });
794
+ }
795
+ }
796
+ }
797
+ }
798
+ __name(reportWarnings, "reportWarnings");
799
+
800
+ // src/core/assets-registry.ts
801
+ var RuntimeAssetsRegistry = class {
802
+ static {
803
+ __name(this, "RuntimeAssetsRegistry");
804
+ }
805
+ collections = {
806
+ sprites: {},
807
+ maps: {},
808
+ sounds: {},
809
+ music: {},
810
+ assets: {}
811
+ };
812
+ replace(collections) {
813
+ this.collections = collections;
814
+ }
815
+ getCollections() {
816
+ return this.collections;
817
+ }
818
+ get sprites() {
819
+ return this.collections.sprites;
820
+ }
821
+ get maps() {
822
+ return this.collections.maps;
823
+ }
824
+ get sounds() {
825
+ return this.collections.sounds;
826
+ }
827
+ get music() {
828
+ return this.collections.music;
829
+ }
830
+ get assets() {
831
+ return this.collections.assets;
832
+ }
833
+ };
834
+
835
+ // src/core/api-factory.ts
836
+ import { Palette } from "@al8b/palette";
837
+ import { Random, Routine } from "@al8b/vm";
838
+
839
+ // src/utils/object-pool.ts
840
+ var ObjectPool = class {
841
+ static {
842
+ __name(this, "ObjectPool");
843
+ }
844
+ pool = [];
845
+ factory;
846
+ reset;
847
+ maxSize;
848
+ /**
849
+ * Create a new object pool
850
+ *
851
+ * @param factory - Function to create new objects
852
+ * @param reset - Function to reset object state for reuse
853
+ * @param maxSize - Maximum pool size (default: 100)
854
+ */
855
+ constructor(factory, reset, maxSize = 100) {
856
+ this.factory = factory;
857
+ this.reset = reset;
858
+ this.maxSize = maxSize;
859
+ }
860
+ /**
861
+ * Acquire an object from the pool
862
+ *
863
+ * Returns a new object if pool is empty, otherwise reuses a pooled object.
864
+ *
865
+ * @returns Object from pool or newly created
866
+ */
867
+ acquire() {
868
+ if (this.pool.length > 0) {
869
+ return this.pool.pop();
870
+ }
871
+ return this.factory();
872
+ }
873
+ /**
874
+ * Release an object back to the pool
875
+ *
876
+ * Resets the object and adds it back to the pool for reuse.
877
+ * If pool is at max size, the object is discarded.
878
+ *
879
+ * @param obj - Object to release
880
+ */
881
+ release(obj) {
882
+ if (this.pool.length >= this.maxSize) {
883
+ return;
884
+ }
885
+ this.reset(obj);
886
+ this.pool.push(obj);
887
+ }
888
+ /**
889
+ * Clear all objects from the pool
890
+ */
891
+ clear() {
892
+ this.pool = [];
893
+ }
894
+ /**
895
+ * Get current pool size
896
+ *
897
+ * @returns Number of objects in pool
898
+ */
899
+ size() {
900
+ return this.pool.length;
901
+ }
902
+ /**
903
+ * Get maximum pool size
904
+ *
905
+ * @returns Maximum pool size
906
+ */
907
+ getMaxSize() {
908
+ return this.maxSize;
909
+ }
910
+ /**
911
+ * Set maximum pool size
912
+ *
913
+ * @param maxSize - New maximum pool size
914
+ */
915
+ setMaxSize(maxSize) {
916
+ this.maxSize = maxSize;
917
+ if (this.pool.length > maxSize) {
918
+ this.pool = this.pool.slice(0, maxSize);
919
+ }
920
+ }
921
+ };
922
+
923
+ // src/core/api-factory.ts
924
+ function createRuntimeMeta(context) {
925
+ return {
926
+ print: /* @__PURE__ */ __name((text) => {
927
+ const vm = context.getVM();
928
+ if ((typeof text === "object" || typeof text === "function") && vm) {
929
+ text = vm.toString(text);
930
+ }
931
+ if (context.listener.log) {
932
+ context.listener.log(String(text));
933
+ } else {
934
+ console.log(text);
935
+ }
936
+ }, "print")
937
+ };
938
+ }
939
+ __name(createRuntimeMeta, "createRuntimeMeta");
940
+ function createRuntimeGlobalApi(context) {
941
+ const inputStates = context.input.getStates();
942
+ const session = {
943
+ user: /* @__PURE__ */ __name(() => cloneValue(context.getSessionSnapshot()?.user ?? null), "user"),
944
+ player: /* @__PURE__ */ __name(() => cloneValue(context.getSessionSnapshot()?.player ?? null), "player"),
945
+ game: /* @__PURE__ */ __name(() => cloneValue(context.getSessionSnapshot()?.game ?? null), "game"),
946
+ room: /* @__PURE__ */ __name(() => cloneValue(context.getSessionSnapshot()?.room ?? null), "room")
947
+ };
948
+ const host = {
949
+ emit: /* @__PURE__ */ __name((name, payload) => {
950
+ context.sendHostEvent({
951
+ type: name,
952
+ payload,
953
+ source: "host"
954
+ });
955
+ }, "emit"),
956
+ request: /* @__PURE__ */ __name((name, payload, callback) => context.sendHostRequest(name, payload, callback), "request")
957
+ };
958
+ const memory = {
959
+ export: /* @__PURE__ */ __name(() => context.exportSnapshot(), "export"),
960
+ import: /* @__PURE__ */ __name((snapshot) => context.importSnapshot(snapshot), "import"),
961
+ reset: /* @__PURE__ */ __name((options) => context.resetRuntime(options), "reset"),
962
+ save: /* @__PURE__ */ __name((meta, callback) => context.saveSnapshot(meta, callback), "save"),
963
+ load: /* @__PURE__ */ __name((meta, callback) => context.loadSnapshot(meta, callback), "load")
964
+ };
965
+ return {
966
+ screen: context.screen.getInterface(),
967
+ audio: context.audio.getInterface(),
968
+ keyboard: inputStates.keyboard,
969
+ mouse: inputStates.mouse,
970
+ touch: inputStates.touch,
971
+ gamepad: inputStates.gamepad,
972
+ sprites: context.assets.sprites,
973
+ maps: context.assets.maps,
974
+ sounds: context.assets.sounds,
975
+ music: context.assets.music,
976
+ assets: context.assets.assets,
977
+ player: context.playerService.getInterface(),
978
+ host,
979
+ session,
980
+ memory,
981
+ system: context.system.getAPI(),
982
+ scene: /* @__PURE__ */ __name((name, definition) => {
983
+ const convertedDefinition = convertSceneDefinition(asSceneDefinition(definition), context.getVM(), context.listener);
984
+ context.sceneManager.registerScene(name, convertedDefinition);
985
+ }, "scene"),
986
+ route: /* @__PURE__ */ __name((path, sceneName) => context.sceneManager.registerRoute(path, sceneName), "route"),
987
+ router: context.sceneManager.router.getInterface(),
988
+ Image,
989
+ Sprite,
990
+ TileMap,
991
+ Sound,
992
+ Palette,
993
+ Random,
994
+ ObjectPool
995
+ };
996
+ }
997
+ __name(createRuntimeGlobalApi, "createRuntimeGlobalApi");
998
+ function convertSceneDefinition(definition, vm, listener) {
999
+ if (!vm?.runner?.main_thread?.processor) {
1000
+ listener.log?.("[RuntimeController] VM not ready for scene conversion. Scene functions may not work correctly.");
1001
+ return definition;
1002
+ }
1003
+ const processor = vm.runner.main_thread.processor;
1004
+ const context = vm.context;
1005
+ const converted = {};
1006
+ for (const [key, value] of Object.entries(definition)) {
1007
+ if (value instanceof Routine) {
1008
+ converted[key] = processor.routineAsFunction(value, context);
1009
+ continue;
1010
+ }
1011
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1012
+ converted[key] = convertSceneDefinition(value, vm, listener);
1013
+ continue;
1014
+ }
1015
+ converted[key] = value;
1016
+ }
1017
+ return converted;
1018
+ }
1019
+ __name(convertSceneDefinition, "convertSceneDefinition");
1020
+ function asSceneDefinition(definition) {
1021
+ if (!definition || typeof definition !== "object" || Array.isArray(definition)) {
1022
+ throw new Error("Scene definition must be an object.");
1023
+ }
1024
+ return definition;
1025
+ }
1026
+ __name(asSceneDefinition, "asSceneDefinition");
1027
+ function cloneValue(value) {
1028
+ if (value == null) {
1029
+ return value;
1030
+ }
1031
+ return JSON.parse(JSON.stringify(value));
1032
+ }
1033
+ __name(cloneValue, "cloneValue");
1034
+
1035
+ // src/core/controller.ts
1036
+ function createRuntime(options = {}) {
1037
+ return new RuntimeControllerImpl(options);
1038
+ }
1039
+ __name(createRuntime, "createRuntime");
1040
+ var RuntimeControllerImpl = class {
1041
+ static {
1042
+ __name(this, "RuntimeControllerImpl");
1043
+ }
1044
+ options;
1045
+ listener;
1046
+ assetRegistry = new RuntimeAssetsRegistry();
1047
+ assetLoader;
1048
+ debugLogger = new DebugLogger();
1049
+ DEBUG_UPDATE_FREQUENCY = 10;
1050
+ snapshotRestorer = new StatePlayer();
1051
+ bridgeUnsubscribe = null;
1052
+ sourceUpdater = null;
1053
+ gameLoop = null;
1054
+ frameCount = 0;
1055
+ lastUpdateRate = -1;
1056
+ isStopped = false;
1057
+ preserveStorageOnNextBoot;
1058
+ sessionSnapshot;
1059
+ screen;
1060
+ audio;
1061
+ input;
1062
+ system;
1063
+ playerService;
1064
+ sceneManager;
1065
+ vm = null;
1066
+ timeMachine = null;
1067
+ constructor(options = {}) {
1068
+ this.options = options;
1069
+ this.listener = options.listener || {};
1070
+ this.preserveStorageOnNextBoot = options.preserveStorage || false;
1071
+ this.sessionSnapshot = options.initialSession || null;
1072
+ this.screen = new Screen({
1073
+ runtime: this,
1074
+ canvas: options.canvas,
1075
+ width: options.width || 400,
1076
+ height: options.height || 400
1077
+ });
1078
+ this.audio = new AudioCore(this);
1079
+ this.input = new InputManager(this.screen.getCanvas());
1080
+ this.system = new System();
1081
+ this.playerService = new PlayerService({
1082
+ pause: /* @__PURE__ */ __name(() => this.stop(), "pause"),
1083
+ resume: /* @__PURE__ */ __name(() => this.resume(), "resume"),
1084
+ postMessage: /* @__PURE__ */ __name((message) => this.emitPlayerMessage(message), "postMessage"),
1085
+ getFps: /* @__PURE__ */ __name(() => this.system.getAPI().fps, "getFps"),
1086
+ getUpdateRate: /* @__PURE__ */ __name(() => this.system.getAPI().update_rate, "getUpdateRate"),
1087
+ setUpdateRate: /* @__PURE__ */ __name((rate) => {
1088
+ this.system.getAPI().update_rate = rate;
1089
+ }, "setUpdateRate")
1090
+ });
1091
+ this.sceneManager = new SceneManager();
1092
+ this.assetLoader = new AssetLoader(options.url || "", options.resources || {}, this.audio, this.listener);
1093
+ this.logStep("RuntimeController constructed", {
1094
+ width: this.screen.width,
1095
+ height: this.screen.height,
1096
+ resources: {
1097
+ images: options.resources?.images?.length ?? 0,
1098
+ sounds: options.resources?.sounds?.length ?? 0,
1099
+ music: options.resources?.music?.length ?? 0
1100
+ }
1101
+ });
1102
+ }
1103
+ get sprites() {
1104
+ return this.assetRegistry.sprites;
1105
+ }
1106
+ get maps() {
1107
+ return this.assetRegistry.maps;
1108
+ }
1109
+ get sounds() {
1110
+ return this.assetRegistry.sounds;
1111
+ }
1112
+ get music() {
1113
+ return this.assetRegistry.music;
1114
+ }
1115
+ get assets() {
1116
+ return this.assetRegistry.assets;
1117
+ }
1118
+ get stopped() {
1119
+ return this.isStopped;
1120
+ }
1121
+ getSession() {
1122
+ return this.sessionSnapshot ? cloneSnapshot(this.sessionSnapshot) : null;
1123
+ }
1124
+ async start() {
1125
+ this.logStep("startup: begin");
1126
+ await this.hydrateSession();
1127
+ this.ensureBridgeSubscription();
1128
+ this.logStep("startup: loading assets");
1129
+ await this.loadAssets();
1130
+ this.logStep("startup: assets loaded", {
1131
+ sprites: Object.keys(this.sprites).length,
1132
+ maps: Object.keys(this.maps).length,
1133
+ sounds: Object.keys(this.sounds).length,
1134
+ music: Object.keys(this.music).length,
1135
+ assets: Object.keys(this.assets).length
1136
+ });
1137
+ this.logStep("startup: waiting for asset readiness");
1138
+ await this.waitForAssetsReady();
1139
+ this.logStep("startup: assets ready");
1140
+ this.logStep("startup: initializing VM");
1141
+ this.initializeVM();
1142
+ this.logStep("startup: VM ready", {
1143
+ sourceFiles: Object.keys(this.options.sources || {}).length
1144
+ });
1145
+ this.logStep("startup: starting game loop");
1146
+ this.startGameLoop();
1147
+ this.isStopped = false;
1148
+ this.logStep("startup: completed");
1149
+ }
1150
+ stop() {
1151
+ this.logStep("lifecycle: stop requested");
1152
+ this.isStopped = true;
1153
+ this.gameLoop?.stop();
1154
+ this.audio.stopAll();
1155
+ }
1156
+ resume() {
1157
+ this.logStep("lifecycle: resume requested");
1158
+ this.isStopped = false;
1159
+ this.gameLoop?.resume();
1160
+ }
1161
+ async reset(options = {}) {
1162
+ this.logStep("lifecycle: reset requested", options);
1163
+ const preservedSnapshot = options.preserveSnapshot ? this.exportSnapshot() : null;
1164
+ const preserveSession = options.preserveSession ?? true;
1165
+ const preserveStorage = options.preserveStorage ?? this.options.preserveStorage ?? false;
1166
+ this.stop();
1167
+ this.teardownRuntimeState();
1168
+ if (!preserveSession) {
1169
+ this.sessionSnapshot = null;
1170
+ }
1171
+ this.preserveStorageOnNextBoot = preserveStorage;
1172
+ await this.start();
1173
+ if (preservedSnapshot) {
1174
+ await this.importSnapshot(preservedSnapshot);
1175
+ }
1176
+ }
1177
+ exportSnapshot() {
1178
+ const global = this.vm?.context?.global;
1179
+ const routerState = this.sceneManager.router.getState();
1180
+ return {
1181
+ version: 1,
1182
+ global: global ? serializeGlobalSnapshot(global) : {},
1183
+ session: this.getSession(),
1184
+ router: {
1185
+ path: routerState.path,
1186
+ sceneName: this.sceneManager.getCurrentSceneName()
1187
+ },
1188
+ system: {
1189
+ updateRate: this.system.getAPI().update_rate
1190
+ }
1191
+ };
1192
+ }
1193
+ async importSnapshot(snapshot) {
1194
+ if (!this.vm?.context?.global) {
1195
+ return;
1196
+ }
1197
+ this.snapshotRestorer.restoreState(this.vm.context.global, snapshot.global);
1198
+ this.system.getAPI().update_rate = snapshot.system.updateRate;
1199
+ this.updateGameLoopUpdateRate();
1200
+ if (snapshot.session) {
1201
+ this.sessionSnapshot = cloneSnapshot(snapshot.session);
1202
+ }
1203
+ if (snapshot.router.path) {
1204
+ this.sceneManager.router.replace(snapshot.router.path);
1205
+ } else if (snapshot.router.sceneName) {
1206
+ this.sceneManager.setActiveScene(snapshot.router.sceneName);
1207
+ }
1208
+ }
1209
+ updateSource(file, src, reinit = false) {
1210
+ if (!this.sourceUpdater) return false;
1211
+ return this.sourceUpdater.updateSource(file, src, reinit);
1212
+ }
1213
+ handleMessage(message) {
1214
+ if (!message) {
1215
+ return;
1216
+ }
1217
+ if (typeof message === "object" && "type" in message && typeof message.type === "string") {
1218
+ this.sendHostEvent(message);
1219
+ return;
1220
+ }
1221
+ if (message.name === "time_machine" && this.timeMachine) {
1222
+ this.timeMachine.messageReceived(message);
1223
+ }
1224
+ }
1225
+ sendHostEvent(event) {
1226
+ this.handleHostEvent(event);
1227
+ }
1228
+ getCanvas() {
1229
+ return this.screen.getCanvas();
1230
+ }
1231
+ async loadAssets() {
1232
+ const collections = await this.assetLoader.loadAll();
1233
+ this.assetRegistry.replace(collections);
1234
+ }
1235
+ async waitForAssetsReady() {
1236
+ return new Promise((resolve) => {
1237
+ const checkReady = /* @__PURE__ */ __name(() => {
1238
+ if (this.assetLoader.isReady()) {
1239
+ this.system.setLoading(100);
1240
+ resolve();
1241
+ return;
1242
+ }
1243
+ const progress = this.assetLoader.getProgress();
1244
+ this.system.setLoading(Math.floor(progress * 100));
1245
+ this.assetLoader.showLoadingBar(this.screen.getInterface());
1246
+ requestAnimationFrame(checkReady);
1247
+ }, "checkReady");
1248
+ checkReady();
1249
+ });
1250
+ }
1251
+ initializeVM() {
1252
+ this.logStep("vm: building meta/global APIs");
1253
+ const apiContext = {
1254
+ listener: this.listener,
1255
+ options: this.options,
1256
+ screen: this.screen,
1257
+ audio: this.audio,
1258
+ input: this.input,
1259
+ system: this.system,
1260
+ playerService: this.playerService,
1261
+ sceneManager: this.sceneManager,
1262
+ assets: this.assetRegistry,
1263
+ bridge: this.options.bridge,
1264
+ getVM: /* @__PURE__ */ __name(() => this.vm, "getVM"),
1265
+ getSessionSnapshot: /* @__PURE__ */ __name(() => this.getSession(), "getSessionSnapshot"),
1266
+ sendHostEvent: /* @__PURE__ */ __name((event) => this.emitBridgeEvent(event.type, event.payload), "sendHostEvent"),
1267
+ sendHostRequest: /* @__PURE__ */ __name((name, payload, callback) => this.sendBridgeRequest(name, payload, callback), "sendHostRequest"),
1268
+ exportSnapshot: /* @__PURE__ */ __name(() => this.exportSnapshot(), "exportSnapshot"),
1269
+ importSnapshot: /* @__PURE__ */ __name((snapshot) => this.importSnapshot(snapshot), "importSnapshot"),
1270
+ resetRuntime: /* @__PURE__ */ __name((options) => this.reset(options), "resetRuntime"),
1271
+ saveSnapshot: /* @__PURE__ */ __name((meta2, callback) => this.saveSnapshot(meta2, callback), "saveSnapshot"),
1272
+ loadSnapshot: /* @__PURE__ */ __name((meta2, callback) => this.loadSnapshot(meta2, callback), "loadSnapshot")
1273
+ };
1274
+ const meta = createRuntimeMeta(apiContext);
1275
+ const global = createRuntimeGlobalApi(apiContext);
1276
+ this.vm = new L8BVM(meta, global, this.options.namespace || "/l8b", this.preserveStorageOnNextBoot);
1277
+ this.sourceUpdater = new SourceUpdater(this.vm, this.listener, this.audio, this.screen, () => reportWarnings(this.vm, this.listener), (name, payload) => this.emitBridgeEvent(name, payload));
1278
+ this.timeMachine = new TimeMachine(this);
1279
+ this.timeMachine.onStatus((status) => {
1280
+ this.emitBridgeEvent("time_machine_status", {
1281
+ status
1282
+ });
1283
+ });
1284
+ this.loadPrograms();
1285
+ this.initializeScenesAndRouter();
1286
+ this.emitBridgeEvent("runtime.started", {});
1287
+ }
1288
+ loadPrograms() {
1289
+ if (!this.vm) {
1290
+ return;
1291
+ }
1292
+ const compiledRoutines = this.options.compiledRoutines || {};
1293
+ const sources = this.options.sources || {};
1294
+ if (Object.keys(compiledRoutines).length > 0) {
1295
+ this.logStep("vm: loading compiled routines", {
1296
+ files: Object.keys(compiledRoutines)
1297
+ });
1298
+ for (const [file, routine] of Object.entries(compiledRoutines)) {
1299
+ try {
1300
+ this.vm.loadRoutine(routine, file);
1301
+ } catch (err) {
1302
+ reportError(this.listener, {
1303
+ error: err.message || String(err),
1304
+ type: "compile",
1305
+ stack: err.stack,
1306
+ file
1307
+ });
1308
+ this.logStep("vm: routine load error", {
1309
+ file,
1310
+ message: err?.message || String(err)
1311
+ });
1312
+ }
1313
+ }
1314
+ } else if (Object.keys(sources).length > 0) {
1315
+ this.logStep("vm: executing sources", {
1316
+ files: Object.keys(sources)
1317
+ });
1318
+ for (const [file, src] of Object.entries(sources)) {
1319
+ this.sourceUpdater?.updateSource(file, src, false);
1320
+ }
1321
+ } else {
1322
+ this.logStep("vm: no sources or compiled routines provided");
1323
+ }
1324
+ try {
1325
+ this.vm.call("init");
1326
+ this.vm.runner.tick();
1327
+ this.logStep("vm: init() executed");
1328
+ } catch (err) {
1329
+ reportError(this.listener, {
1330
+ error: err.message || String(err),
1331
+ type: "init",
1332
+ stack: err.stack
1333
+ });
1334
+ this.logStep("vm: init() error", {
1335
+ message: err?.message || String(err)
1336
+ });
1337
+ }
1338
+ }
1339
+ initializeScenesAndRouter() {
1340
+ const registeredScenes = this.sceneManager.registry.getNames();
1341
+ this.logStep("router: initializing", {
1342
+ registeredScenes: registeredScenes.length,
1343
+ sceneNames: registeredScenes
1344
+ });
1345
+ this.sceneManager.router.init();
1346
+ const activeScene = this.sceneManager.hasActiveScene() ? this.sceneManager.getCurrentSceneName?.() || "unknown" : null;
1347
+ const routerState = this.sceneManager.router.getState();
1348
+ this.logStep("router: initialized", {
1349
+ activeScene: activeScene || "none",
1350
+ path: routerState.path,
1351
+ hasActiveScene: this.sceneManager.hasActiveScene()
1352
+ });
1353
+ }
1354
+ startGameLoop() {
1355
+ this.logStep("loop: creating game loop");
1356
+ this.gameLoop = new GameLoop({
1357
+ onUpdate: /* @__PURE__ */ __name(() => this.handleUpdate(), "onUpdate"),
1358
+ onDraw: /* @__PURE__ */ __name(() => this.handleDraw(), "onDraw"),
1359
+ onTick: /* @__PURE__ */ __name(() => this.handleTick(), "onTick"),
1360
+ onWatchStep: /* @__PURE__ */ __name(() => this.handleWatchStep(), "onWatchStep"),
1361
+ getUpdateRate: /* @__PURE__ */ __name(() => {
1362
+ if (!this.vm) return void 0;
1363
+ try {
1364
+ return this.vm.context?.global?.system?.update_rate;
1365
+ } catch {
1366
+ return void 0;
1367
+ }
1368
+ }, "getUpdateRate"),
1369
+ setFPS: /* @__PURE__ */ __name((fps) => {
1370
+ if (!this.vm) return;
1371
+ try {
1372
+ if (this.vm.context?.global?.system) {
1373
+ this.vm.context.global.system.fps = fps;
1374
+ }
1375
+ } catch {
1376
+ }
1377
+ }, "setFPS")
1378
+ });
1379
+ this.gameLoop.start();
1380
+ this.logStep("loop: started");
1381
+ }
1382
+ updateGameLoopUpdateRate() {
1383
+ if (!this.vm || !this.gameLoop) return;
1384
+ try {
1385
+ const updateRate = this.vm.context?.global?.system?.update_rate;
1386
+ const rate = updateRate != null && updateRate > 0 && Number.isFinite(updateRate) ? updateRate : 60;
1387
+ if (rate !== this.lastUpdateRate) {
1388
+ this.lastUpdateRate = rate;
1389
+ this.gameLoop.setUpdateRate(rate);
1390
+ }
1391
+ } catch {
1392
+ }
1393
+ }
1394
+ handleUpdate() {
1395
+ if (!this.vm) return;
1396
+ this.frameCount++;
1397
+ this.input.update();
1398
+ if (this.frameCount % this.DEBUG_UPDATE_FREQUENCY === 0) {
1399
+ this.debugLogger.debugInputs(this.input, this.options.debug);
1400
+ this.debugLogger.debugScreen(this.screen, this.options.debug);
1401
+ }
1402
+ if (this.gameLoop) {
1403
+ this.system.setFPS(this.gameLoop.getFPS());
1404
+ this.updateGameLoopUpdateRate();
1405
+ }
1406
+ try {
1407
+ if (this.sceneManager.hasActiveScene()) {
1408
+ this.sceneManager.update();
1409
+ } else {
1410
+ this.vm.call("update");
1411
+ this.vm.runner.tick();
1412
+ }
1413
+ if (this.vm.error_info) {
1414
+ const err = Object.assign({}, this.vm.error_info);
1415
+ err.type = "update";
1416
+ reportError(this.listener, err);
1417
+ }
1418
+ } catch (err) {
1419
+ reportError(this.listener, {
1420
+ error: err.message || String(err),
1421
+ type: "update",
1422
+ stack: err.stack
1423
+ });
1424
+ }
1425
+ }
1426
+ handleDraw() {
1427
+ if (!this.vm) return;
1428
+ try {
1429
+ this.screen.initDraw();
1430
+ this.screen.updateInterface();
1431
+ if (this.sceneManager.hasActiveScene()) {
1432
+ this.sceneManager.draw();
1433
+ } else {
1434
+ this.vm.call("draw");
1435
+ this.vm.runner.tick();
1436
+ }
1437
+ reportWarnings(this.vm, this.listener);
1438
+ if (this.vm.error_info) {
1439
+ const err = Object.assign({}, this.vm.error_info);
1440
+ err.type = "draw";
1441
+ reportError(this.listener, err);
1442
+ }
1443
+ } catch (err) {
1444
+ reportError(this.listener, {
1445
+ error: err.message || String(err),
1446
+ type: "draw",
1447
+ stack: err.stack
1448
+ });
1449
+ }
1450
+ this.timeMachine?.step();
1451
+ }
1452
+ handleTick() {
1453
+ if (this.vm?.runner) {
1454
+ this.vm.runner.tick?.();
1455
+ }
1456
+ }
1457
+ handleWatchStep() {
1458
+ this.timeMachine?.loopStep();
1459
+ }
1460
+ ensureBridgeSubscription() {
1461
+ if (this.bridgeUnsubscribe || !this.options.bridge?.subscribe) {
1462
+ return;
1463
+ }
1464
+ const maybeUnsubscribe = this.options.bridge.subscribe((event) => this.handleHostEvent(event));
1465
+ this.bridgeUnsubscribe = typeof maybeUnsubscribe === "function" ? maybeUnsubscribe : null;
1466
+ }
1467
+ handleHostEvent(event) {
1468
+ switch (event.type) {
1469
+ case "session.update":
1470
+ this.mergeSession(event.payload);
1471
+ break;
1472
+ case "runtime.reset":
1473
+ void this.reset(asRecord(event.payload));
1474
+ break;
1475
+ case "runtime.import_snapshot":
1476
+ if (isRuntimeSnapshot(event.payload)) {
1477
+ void this.importSnapshot(event.payload);
1478
+ }
1479
+ break;
1480
+ case "runtime.export_snapshot":
1481
+ this.emitBridgeEvent("runtime.snapshot", this.exportSnapshot());
1482
+ break;
1483
+ case "runtime.stop":
1484
+ case "runtime.pause":
1485
+ this.stop();
1486
+ break;
1487
+ case "runtime.resume":
1488
+ this.resume();
1489
+ break;
1490
+ case "time_machine":
1491
+ if (this.timeMachine && isRecord(event.payload)) {
1492
+ const command = event.payload.command;
1493
+ if (typeof command === "string") {
1494
+ this.timeMachine.messageReceived({
1495
+ name: "time_machine",
1496
+ command,
1497
+ position: typeof event.payload.position === "number" ? event.payload.position : void 0
1498
+ });
1499
+ }
1500
+ }
1501
+ break;
1502
+ }
1503
+ }
1504
+ emitPlayerMessage(message) {
1505
+ if (isRecord(message) && typeof message.type === "string") {
1506
+ this.emitBridgeEvent(message.type, message);
1507
+ } else {
1508
+ this.emitBridgeEvent("player.message", message);
1509
+ }
1510
+ }
1511
+ emitBridgeEvent(name, payload) {
1512
+ this.options.bridge?.emit?.(name, payload);
1513
+ }
1514
+ sendBridgeRequest(name, payload, callback) {
1515
+ const request = this.options.bridge?.request;
1516
+ if (!request) {
1517
+ callback?.({
1518
+ ok: false,
1519
+ error: `No runtime bridge request handler registered for "${name}"`
1520
+ });
1521
+ return null;
1522
+ }
1523
+ const requestId = createRequestId(name);
1524
+ try {
1525
+ const result = request(name, payload);
1526
+ if (isPromiseLike(result)) {
1527
+ void result.then((value) => callback?.(value)).catch((error) => callback?.({
1528
+ ok: false,
1529
+ error: error instanceof Error ? error.message : String(error)
1530
+ }));
1531
+ } else {
1532
+ callback?.(result);
1533
+ }
1534
+ return requestId;
1535
+ } catch (error) {
1536
+ callback?.({
1537
+ ok: false,
1538
+ error: error instanceof Error ? error.message : String(error)
1539
+ });
1540
+ return requestId;
1541
+ }
1542
+ }
1543
+ saveSnapshot(meta, callback) {
1544
+ const snapshot = this.exportSnapshot();
1545
+ const save = this.options.bridge?.saveSnapshot;
1546
+ if (!save) {
1547
+ const fallback = {
1548
+ ok: false,
1549
+ error: "No runtime bridge saveSnapshot handler registered",
1550
+ snapshot
1551
+ };
1552
+ callback?.(fallback);
1553
+ return fallback;
1554
+ }
1555
+ try {
1556
+ const result = save(snapshot, meta);
1557
+ if (isPromiseLike(result)) {
1558
+ void result.then(() => callback?.({
1559
+ ok: true,
1560
+ snapshot
1561
+ })).catch((error) => callback?.({
1562
+ ok: false,
1563
+ error: error instanceof Error ? error.message : String(error)
1564
+ }));
1565
+ return null;
1566
+ }
1567
+ callback?.({
1568
+ ok: true,
1569
+ snapshot
1570
+ });
1571
+ return {
1572
+ ok: true,
1573
+ snapshot
1574
+ };
1575
+ } catch (error) {
1576
+ const failure = {
1577
+ ok: false,
1578
+ error: error instanceof Error ? error.message : String(error)
1579
+ };
1580
+ callback?.(failure);
1581
+ return failure;
1582
+ }
1583
+ }
1584
+ loadSnapshot(meta, callback) {
1585
+ const load = this.options.bridge?.loadSnapshot;
1586
+ if (!load) {
1587
+ const fallback = {
1588
+ ok: false,
1589
+ error: "No runtime bridge loadSnapshot handler registered"
1590
+ };
1591
+ callback?.(fallback);
1592
+ return fallback;
1593
+ }
1594
+ try {
1595
+ const result = load(meta);
1596
+ if (isPromiseLike(result)) {
1597
+ void result.then(async (snapshot) => {
1598
+ if (snapshot) {
1599
+ await this.importSnapshot(snapshot);
1600
+ }
1601
+ callback?.({
1602
+ ok: true,
1603
+ snapshot
1604
+ });
1605
+ }).catch((error) => callback?.({
1606
+ ok: false,
1607
+ error: error instanceof Error ? error.message : String(error)
1608
+ }));
1609
+ return null;
1610
+ }
1611
+ if (result) {
1612
+ void this.importSnapshot(result);
1613
+ }
1614
+ callback?.({
1615
+ ok: true,
1616
+ snapshot: result
1617
+ });
1618
+ return result;
1619
+ } catch (error) {
1620
+ const failure = {
1621
+ ok: false,
1622
+ error: error instanceof Error ? error.message : String(error)
1623
+ };
1624
+ callback?.(failure);
1625
+ return failure;
1626
+ }
1627
+ }
1628
+ async hydrateSession() {
1629
+ if (this.sessionSnapshot) {
1630
+ return;
1631
+ }
1632
+ const getSession = this.options.bridge?.getSession;
1633
+ if (!getSession) {
1634
+ this.sessionSnapshot = this.options.initialSession || null;
1635
+ return;
1636
+ }
1637
+ try {
1638
+ const session = getSession();
1639
+ this.sessionSnapshot = isPromiseLike(session) ? await session : session;
1640
+ } catch {
1641
+ this.sessionSnapshot = this.options.initialSession || null;
1642
+ }
1643
+ }
1644
+ mergeSession(payload) {
1645
+ if (!isRecord(payload)) {
1646
+ return;
1647
+ }
1648
+ const current = this.sessionSnapshot || {};
1649
+ this.sessionSnapshot = {
1650
+ ...current,
1651
+ ...payload
1652
+ };
1653
+ }
1654
+ teardownRuntimeState() {
1655
+ this.gameLoop = null;
1656
+ this.sourceUpdater = null;
1657
+ this.vm = null;
1658
+ this.timeMachine = null;
1659
+ this.frameCount = 0;
1660
+ this.lastUpdateRate = -1;
1661
+ this.isStopped = false;
1662
+ this.sceneManager.registry.clear();
1663
+ this.sceneManager.routeManager.clear();
1664
+ }
1665
+ logStep(message, payload) {
1666
+ if (!this.options.debug?.lifecycle) return;
1667
+ const prefix = "[@al8b/runtime][lifecycle]";
1668
+ if (payload !== void 0) {
1669
+ console.info(`${prefix} ${message}`, payload);
1670
+ } else {
1671
+ console.info(`${prefix} ${message}`);
1672
+ }
1673
+ if (this.listener.log) {
1674
+ try {
1675
+ const serialized = payload === void 0 ? "" : ` ${JSON.stringify(payload)}`;
1676
+ this.listener.log(`${prefix} ${message}${serialized}`);
1677
+ } catch {
1678
+ this.listener.log(`${prefix} ${message}`);
1679
+ }
1680
+ }
1681
+ }
1682
+ };
1683
+ function serializeGlobalSnapshot(global) {
1684
+ const globalRecord = global;
1685
+ const excluded = [
1686
+ globalRecord.random,
1687
+ globalRecord.screen,
1688
+ globalRecord.audio,
1689
+ globalRecord.keyboard,
1690
+ globalRecord.mouse,
1691
+ globalRecord.touch,
1692
+ globalRecord.gamepad,
1693
+ globalRecord.system,
1694
+ globalRecord.storage,
1695
+ globalRecord.host,
1696
+ globalRecord.session,
1697
+ globalRecord.memory
1698
+ ].filter((value) => value != null);
1699
+ return deepCloneValue(globalRecord, excluded);
1700
+ }
1701
+ __name(serializeGlobalSnapshot, "serializeGlobalSnapshot");
1702
+ function deepCloneValue(value, excluded = []) {
1703
+ if (value == null) {
1704
+ return value;
1705
+ }
1706
+ if (excluded.includes(value)) {
1707
+ return null;
1708
+ }
1709
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1710
+ return value;
1711
+ }
1712
+ if (Array.isArray(value)) {
1713
+ return value.map((entry) => deepCloneValue(entry, excluded));
1714
+ }
1715
+ if (typeof value === "object") {
1716
+ const clone = {};
1717
+ for (const [key, entry] of Object.entries(value)) {
1718
+ clone[key] = deepCloneValue(entry, excluded);
1719
+ }
1720
+ return clone;
1721
+ }
1722
+ return null;
1723
+ }
1724
+ __name(deepCloneValue, "deepCloneValue");
1725
+ function cloneSnapshot(value) {
1726
+ return deepCloneValue(value);
1727
+ }
1728
+ __name(cloneSnapshot, "cloneSnapshot");
1729
+ function createRequestId(name) {
1730
+ return `${name}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
1731
+ }
1732
+ __name(createRequestId, "createRequestId");
1733
+ function isPromiseLike(value) {
1734
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
1735
+ }
1736
+ __name(isPromiseLike, "isPromiseLike");
1737
+ function isRecord(value) {
1738
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1739
+ }
1740
+ __name(isRecord, "isRecord");
1741
+ function asRecord(value) {
1742
+ return isRecord(value) ? value : void 0;
1743
+ }
1744
+ __name(asRecord, "asRecord");
1745
+ function isRuntimeSnapshot(value) {
1746
+ if (!isRecord(value)) return false;
1747
+ if (value.version !== 1) return false;
1748
+ if (!isRecord(value.global)) return false;
1749
+ if (!("router" in value)) return false;
1750
+ if (!("session" in value)) return false;
1751
+ return true;
1752
+ }
1753
+ __name(isRuntimeSnapshot, "isRuntimeSnapshot");
1754
+ export {
1755
+ createRuntime
1756
+ };
1757
+ //# sourceMappingURL=index.mjs.map