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