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