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