@yagejs/core 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.
package/dist/index.js ADDED
@@ -0,0 +1,2943 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
5
+ var __typeError = (msg) => {
6
+ throw TypeError(msg);
7
+ };
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+ var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
11
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
12
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
13
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
14
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
15
+ var __runInitializers = (array, flags, self, value) => {
16
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
17
+ return value;
18
+ };
19
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
20
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
21
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
22
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
23
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
24
+ return __privateGet(this, extra);
25
+ }, set [name](x) {
26
+ return __privateSet(this, extra, x);
27
+ } }, name));
28
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
29
+ for (var i = decorators.length - 1; i >= 0; i--) {
30
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
31
+ if (k) {
32
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
33
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
34
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
35
+ }
36
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
37
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
38
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
39
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
40
+ }
41
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
42
+ };
43
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
44
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
45
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
46
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
47
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
48
+
49
+ // src/types.ts
50
+ var Phase = /* @__PURE__ */ ((Phase2) => {
51
+ Phase2["EarlyUpdate"] = "earlyUpdate";
52
+ Phase2["FixedUpdate"] = "fixedUpdate";
53
+ Phase2["Update"] = "update";
54
+ Phase2["LateUpdate"] = "lateUpdate";
55
+ Phase2["Render"] = "render";
56
+ Phase2["EndOfFrame"] = "endOfFrame";
57
+ return Phase2;
58
+ })(Phase || {});
59
+
60
+ // src/Vec2.ts
61
+ var EPSILON = 1e-6;
62
+ var Vec2 = class _Vec2 {
63
+ constructor(x, y) {
64
+ this.x = x;
65
+ this.y = y;
66
+ }
67
+ x;
68
+ y;
69
+ static {
70
+ __name(this, "Vec2");
71
+ }
72
+ /** The zero vector (0, 0). */
73
+ static ZERO = new _Vec2(0, 0);
74
+ /** The one vector (1, 1). */
75
+ static ONE = new _Vec2(1, 1);
76
+ /** Up direction (0, -1) — screen coordinates. */
77
+ static UP = new _Vec2(0, -1);
78
+ /** Down direction (0, 1) — screen coordinates. */
79
+ static DOWN = new _Vec2(0, 1);
80
+ /** Left direction (-1, 0). */
81
+ static LEFT = new _Vec2(-1, 0);
82
+ /** Right direction (1, 0). */
83
+ static RIGHT = new _Vec2(1, 0);
84
+ /** Add another vector. */
85
+ add(other) {
86
+ return new _Vec2(this.x + other.x, this.y + other.y);
87
+ }
88
+ /** Subtract another vector. */
89
+ sub(other) {
90
+ return new _Vec2(this.x - other.x, this.y - other.y);
91
+ }
92
+ /** Scale by a scalar. */
93
+ scale(scalar) {
94
+ return new _Vec2(this.x * scalar, this.y * scalar);
95
+ }
96
+ /** Component-wise multiply with another vector. */
97
+ multiply(other) {
98
+ return new _Vec2(this.x * other.x, this.y * other.y);
99
+ }
100
+ /** Dot product with another vector. */
101
+ dot(other) {
102
+ return this.x * other.x + this.y * other.y;
103
+ }
104
+ /** Cross product (z-component of the 3D cross product). */
105
+ cross(other) {
106
+ return this.x * other.y - this.y * other.x;
107
+ }
108
+ /** Magnitude of this vector. */
109
+ length() {
110
+ return Math.sqrt(this.x * this.x + this.y * this.y);
111
+ }
112
+ /** Squared magnitude (avoids sqrt). */
113
+ lengthSq() {
114
+ return this.x * this.x + this.y * this.y;
115
+ }
116
+ /** Return a unit vector in the same direction. Returns ZERO for zero-length vectors. */
117
+ normalize() {
118
+ const len = this.length();
119
+ if (len < EPSILON) return _Vec2.ZERO;
120
+ return new _Vec2(this.x / len, this.y / len);
121
+ }
122
+ /** Euclidean distance to another vector. */
123
+ distance(other) {
124
+ const dx = this.x - other.x;
125
+ const dy = this.y - other.y;
126
+ return Math.sqrt(dx * dx + dy * dy);
127
+ }
128
+ /** Squared distance to another vector (avoids sqrt). */
129
+ distanceSq(other) {
130
+ const dx = this.x - other.x;
131
+ const dy = this.y - other.y;
132
+ return dx * dx + dy * dy;
133
+ }
134
+ /** Linear interpolation toward another vector. */
135
+ lerp(other, t) {
136
+ return new _Vec2(
137
+ this.x + (other.x - this.x) * t,
138
+ this.y + (other.y - this.y) * t
139
+ );
140
+ }
141
+ /** Angle of this vector in radians (atan2). */
142
+ angle() {
143
+ return Math.atan2(this.y, this.x);
144
+ }
145
+ /** Rotate this vector by radians. */
146
+ rotate(radians) {
147
+ const cos = Math.cos(radians);
148
+ const sin = Math.sin(radians);
149
+ return new _Vec2(this.x * cos - this.y * sin, this.x * sin + this.y * cos);
150
+ }
151
+ /** Check equality with optional epsilon tolerance. */
152
+ equals(other, epsilon = EPSILON) {
153
+ return Math.abs(this.x - other.x) < epsilon && Math.abs(this.y - other.y) < epsilon;
154
+ }
155
+ /** String representation. */
156
+ toString() {
157
+ return `Vec2(${this.x}, ${this.y})`;
158
+ }
159
+ /** Create a unit vector from an angle in radians, optionally scaled. */
160
+ static fromAngle(radians, length = 1) {
161
+ return new _Vec2(Math.cos(radians) * length, Math.sin(radians) * length);
162
+ }
163
+ /** Euclidean distance between two vectors. */
164
+ static distance(a, b) {
165
+ const dx = a.x - b.x;
166
+ const dy = a.y - b.y;
167
+ return Math.sqrt(dx * dx + dy * dy);
168
+ }
169
+ /** Linear interpolation between two vectors. */
170
+ static lerp(a, b, t) {
171
+ return new _Vec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
172
+ }
173
+ };
174
+
175
+ // src/MathUtils.ts
176
+ var MathUtils = {
177
+ /** Linear interpolation between a and b. */
178
+ lerp(a, b, t) {
179
+ return a + (b - a) * t;
180
+ },
181
+ /** Clamp a value between min and max. */
182
+ clamp(value, min, max) {
183
+ return Math.max(min, Math.min(max, value));
184
+ },
185
+ /** Remap a value from one range to another. */
186
+ remap(value, inMin, inMax, outMin, outMax) {
187
+ const t = (value - inMin) / (inMax - inMin);
188
+ return outMin + (outMax - outMin) * t;
189
+ },
190
+ /** Random float in [min, max). */
191
+ randomRange(min, max) {
192
+ return min + Math.random() * (max - min);
193
+ },
194
+ /** Random integer in [min, max] (inclusive). */
195
+ randomInt(min, max) {
196
+ return Math.floor(min + Math.random() * (max - min + 1));
197
+ },
198
+ /** Convert degrees to radians. */
199
+ degToRad(degrees) {
200
+ return degrees * Math.PI / 180;
201
+ },
202
+ /** Convert radians to degrees. */
203
+ radToDeg(radians) {
204
+ return radians * 180 / Math.PI;
205
+ },
206
+ /** Move current toward target by at most step. */
207
+ approach(current, target, step) {
208
+ if (current < target) {
209
+ return Math.min(current + step, target);
210
+ }
211
+ return Math.max(current - step, target);
212
+ },
213
+ /** Wrap value into the range [min, max). */
214
+ wrap(value, min, max) {
215
+ const range = max - min;
216
+ return ((value - min) % range + range) % range + min;
217
+ }
218
+ };
219
+
220
+ // src/EventBus.ts
221
+ var EventBus = class {
222
+ static {
223
+ __name(this, "EventBus");
224
+ }
225
+ handlers = /* @__PURE__ */ new Map();
226
+ /** Subscribe to an event. Returns an unsubscribe function. */
227
+ on(event, handler) {
228
+ let list = this.handlers.get(event);
229
+ if (!list) {
230
+ list = [];
231
+ this.handlers.set(event, list);
232
+ }
233
+ list.push(handler);
234
+ return () => {
235
+ const arr = this.handlers.get(event);
236
+ if (arr) {
237
+ const idx = arr.indexOf(handler);
238
+ if (idx !== -1) arr.splice(idx, 1);
239
+ }
240
+ };
241
+ }
242
+ /** Subscribe to an event, auto-unsubscribe after first emission. */
243
+ once(event, handler) {
244
+ const unsub = this.on(event, (data) => {
245
+ unsub();
246
+ handler(data);
247
+ });
248
+ return unsub;
249
+ }
250
+ /** Emit an event. Handlers are called synchronously in registration order. */
251
+ emit(event, data) {
252
+ const list = this.handlers.get(event);
253
+ if (!list) return;
254
+ const snapshot = [...list];
255
+ for (const handler of snapshot) {
256
+ handler(data);
257
+ }
258
+ }
259
+ /** Remove all handlers for an event, or all handlers if no event specified. */
260
+ clear(event) {
261
+ if (event !== void 0) {
262
+ this.handlers.delete(event);
263
+ } else {
264
+ this.handlers.clear();
265
+ }
266
+ }
267
+ };
268
+
269
+ // src/Logger.ts
270
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
271
+ LogLevel2[LogLevel2["Debug"] = 0] = "Debug";
272
+ LogLevel2[LogLevel2["Info"] = 1] = "Info";
273
+ LogLevel2[LogLevel2["Warn"] = 2] = "Warn";
274
+ LogLevel2[LogLevel2["Error"] = 3] = "Error";
275
+ LogLevel2[LogLevel2["None"] = 4] = "None";
276
+ return LogLevel2;
277
+ })(LogLevel || {});
278
+ var LEVEL_LABELS = {
279
+ [0 /* Debug */]: "DEBUG",
280
+ [1 /* Info */]: "INFO",
281
+ [2 /* Warn */]: "WARN",
282
+ [3 /* Error */]: "ERROR",
283
+ [4 /* None */]: "NONE"
284
+ };
285
+ var Logger = class {
286
+ static {
287
+ __name(this, "Logger");
288
+ }
289
+ level;
290
+ categories;
291
+ bufferSize;
292
+ output;
293
+ buffer;
294
+ writeIndex = 0;
295
+ count = 0;
296
+ currentFrame = 0;
297
+ constructor(config) {
298
+ this.level = config?.level ?? 1 /* Info */;
299
+ this.categories = new Set(config?.categories ?? []);
300
+ this.bufferSize = config?.bufferSize ?? 500;
301
+ this.output = config?.output;
302
+ this.buffer = new Array(this.bufferSize);
303
+ }
304
+ /** Set the current frame number (incremented externally by the game loop). */
305
+ setFrame(frame) {
306
+ this.currentFrame = frame;
307
+ }
308
+ /** Log a debug message. */
309
+ debug(category, message, data) {
310
+ this.log(0 /* Debug */, category, message, data);
311
+ }
312
+ /** Log an info message. */
313
+ info(category, message, data) {
314
+ this.log(1 /* Info */, category, message, data);
315
+ }
316
+ /** Log a warning message. */
317
+ warn(category, message, data) {
318
+ this.log(2 /* Warn */, category, message, data);
319
+ }
320
+ /** Log an error message. */
321
+ error(category, message, data) {
322
+ this.log(3 /* Error */, category, message, data);
323
+ }
324
+ /** Get recent log entries from the ring buffer. */
325
+ getRecent(count) {
326
+ const available = Math.min(this.count, this.bufferSize);
327
+ const n = count !== void 0 ? Math.min(count, available) : available;
328
+ const result = [];
329
+ for (let i = 0; i < n; i++) {
330
+ const idx = (this.writeIndex - n + i + this.bufferSize) % this.bufferSize;
331
+ const entry = this.buffer[idx];
332
+ if (entry) result.push(entry);
333
+ }
334
+ return result;
335
+ }
336
+ /** Format recent logs as structured text for agent consumption. */
337
+ formatRecentLogs(count) {
338
+ return this.getRecent(count).map((e) => {
339
+ const levelStr = LEVEL_LABELS[e.level] ?? "UNKNOWN";
340
+ const dataStr = e.data !== void 0 ? ` ${JSON.stringify(e.data)}` : "";
341
+ return `[${levelStr}][${e.category}] f${e.frame} ${e.message}${dataStr}`;
342
+ }).join("\n");
343
+ }
344
+ /** Clear the ring buffer. */
345
+ clear() {
346
+ this.writeIndex = 0;
347
+ this.count = 0;
348
+ this.buffer.fill(void 0);
349
+ }
350
+ log(level, category, message, data) {
351
+ if (level < this.level) return;
352
+ if (this.categories.size > 0 && !this.categories.has(category)) return;
353
+ const entry = {
354
+ level,
355
+ category,
356
+ message,
357
+ data,
358
+ timestamp: Date.now(),
359
+ frame: this.currentFrame
360
+ };
361
+ this.buffer[this.writeIndex] = entry;
362
+ this.writeIndex = (this.writeIndex + 1) % this.bufferSize;
363
+ this.count++;
364
+ this.output?.(entry);
365
+ }
366
+ };
367
+
368
+ // src/EngineContext.ts
369
+ var ServiceKey = class {
370
+ constructor(id) {
371
+ this.id = id;
372
+ }
373
+ id;
374
+ static {
375
+ __name(this, "ServiceKey");
376
+ }
377
+ };
378
+ var EngineContext = class {
379
+ static {
380
+ __name(this, "EngineContext");
381
+ }
382
+ services = /* @__PURE__ */ new Map();
383
+ /** Register a service. Throws if the key is already registered. */
384
+ register(key, service) {
385
+ if (this.services.has(key.id)) {
386
+ throw new Error(`Service "${key.id}" is already registered.`);
387
+ }
388
+ this.services.set(key.id, service);
389
+ }
390
+ /** Resolve a service. Throws if not registered. */
391
+ resolve(key) {
392
+ if (!this.services.has(key.id)) {
393
+ throw new Error(`Service "${key.id}" is not registered.`);
394
+ }
395
+ return this.services.get(key.id);
396
+ }
397
+ /** Resolve a service, returning undefined if not registered. */
398
+ tryResolve(key) {
399
+ return this.services.get(key.id);
400
+ }
401
+ /** Remove a registered service. No-op if not registered. */
402
+ unregister(key) {
403
+ this.services.delete(key.id);
404
+ }
405
+ /** Check if a service is registered. */
406
+ has(key) {
407
+ return this.services.has(key.id);
408
+ }
409
+ };
410
+ var EngineKey = new ServiceKey("engine");
411
+ var EventBusKey = new ServiceKey("eventBus");
412
+ var SceneManagerKey = new ServiceKey("sceneManager");
413
+ var LoggerKey = new ServiceKey("logger");
414
+ var InspectorKey = new ServiceKey("inspector");
415
+ var QueryCacheKey = new ServiceKey("queryCache");
416
+ var ErrorBoundaryKey = new ServiceKey("errorBoundary");
417
+ var GameLoopKey = new ServiceKey("gameLoop");
418
+ var SystemSchedulerKey = new ServiceKey("systemScheduler");
419
+ var ProcessSystemKey = new ServiceKey("processSystem");
420
+ var AssetManagerKey = new ServiceKey("assetManager");
421
+
422
+ // src/EventToken.ts
423
+ var EventToken = class {
424
+ constructor(name) {
425
+ this.name = name;
426
+ }
427
+ name;
428
+ static {
429
+ __name(this, "EventToken");
430
+ }
431
+ };
432
+ function defineEvent(name) {
433
+ return new EventToken(name);
434
+ }
435
+ __name(defineEvent, "defineEvent");
436
+
437
+ // src/AssetHandle.ts
438
+ var AssetHandle = class {
439
+ constructor(type, path) {
440
+ this.type = type;
441
+ this.path = path;
442
+ }
443
+ type;
444
+ path;
445
+ static {
446
+ __name(this, "AssetHandle");
447
+ }
448
+ };
449
+
450
+ // src/AssetManager.ts
451
+ var AssetManager = class {
452
+ static {
453
+ __name(this, "AssetManager");
454
+ }
455
+ loaders = /* @__PURE__ */ new Map();
456
+ cache = /* @__PURE__ */ new Map();
457
+ /** Register a loader for a given asset type. Called by plugins during install(). */
458
+ registerLoader(type, loader) {
459
+ this.loaders.set(type, loader);
460
+ }
461
+ /** Retrieve a loaded asset. Throws if not loaded. */
462
+ get(handle) {
463
+ const key = this.key(handle);
464
+ const asset = this.cache.get(key);
465
+ if (asset === void 0) {
466
+ throw new Error(`Asset not loaded: "${handle.path}" (type: ${handle.type})`);
467
+ }
468
+ return asset;
469
+ }
470
+ /** Check if an asset is loaded. */
471
+ has(handle) {
472
+ return this.cache.has(this.key(handle));
473
+ }
474
+ /**
475
+ * Load all assets, skipping already-cached ones.
476
+ * Reports progress via optional callback (0→1).
477
+ */
478
+ async loadAll(handles, onProgress) {
479
+ const toLoad = handles.filter((h) => !this.cache.has(this.key(h)));
480
+ if (toLoad.length === 0) {
481
+ onProgress?.(1);
482
+ return;
483
+ }
484
+ let done = 0;
485
+ onProgress?.(0);
486
+ await Promise.all(
487
+ toLoad.map(async (handle) => {
488
+ const loader = this.loaders.get(handle.type);
489
+ if (!loader) {
490
+ throw new Error(
491
+ `No loader registered for asset type "${handle.type}". Missing plugin?`
492
+ );
493
+ }
494
+ const asset = await loader.load(handle.path);
495
+ this.cache.set(this.key(handle), asset);
496
+ onProgress?.(++done / toLoad.length);
497
+ })
498
+ );
499
+ }
500
+ /** Unload a single asset and remove from cache. */
501
+ unload(handle) {
502
+ const key = this.key(handle);
503
+ const asset = this.cache.get(key);
504
+ if (asset === void 0) return;
505
+ const loader = this.loaders.get(handle.type);
506
+ loader?.unload?.(handle.path, asset);
507
+ this.cache.delete(key);
508
+ }
509
+ /** Unload all cached assets. */
510
+ clear() {
511
+ for (const [key, asset] of this.cache) {
512
+ const [type, ...pathParts] = key.split(":");
513
+ const path = pathParts.join(":");
514
+ this.loaders.get(type)?.unload?.(path, asset);
515
+ }
516
+ this.cache.clear();
517
+ }
518
+ key(handle) {
519
+ return `${handle.type}:${handle.path}`;
520
+ }
521
+ };
522
+
523
+ // src/Blueprint.ts
524
+ function defineBlueprint(name, build) {
525
+ return { name, build };
526
+ }
527
+ __name(defineBlueprint, "defineBlueprint");
528
+
529
+ // src/Trait.ts
530
+ var TRAITS_KEY = /* @__PURE__ */ Symbol("TRAITS_KEY");
531
+ var TraitToken = class {
532
+ constructor(name) {
533
+ this.name = name;
534
+ this.symbol = /* @__PURE__ */ Symbol(`Trait:${name}`);
535
+ }
536
+ name;
537
+ static {
538
+ __name(this, "TraitToken");
539
+ }
540
+ symbol;
541
+ };
542
+ function defineTrait(name) {
543
+ return new TraitToken(name);
544
+ }
545
+ __name(defineTrait, "defineTrait");
546
+ function trait(token) {
547
+ return (target) => {
548
+ const traitSymbols = new Set(target[TRAITS_KEY] ?? []);
549
+ traitSymbols.add(token.symbol);
550
+ target[TRAITS_KEY] = traitSymbols;
551
+ return target;
552
+ };
553
+ }
554
+ __name(trait, "trait");
555
+
556
+ // src/Serializable.ts
557
+ var SERIALIZABLE_KEY = /* @__PURE__ */ Symbol("SERIALIZABLE");
558
+ var registry = /* @__PURE__ */ new Map();
559
+ var SerializableRegistry = {
560
+ /** Look up a class by its type string. */
561
+ get(type) {
562
+ return registry.get(type);
563
+ },
564
+ /** Check if a type string is registered. */
565
+ has(type) {
566
+ return registry.has(type);
567
+ },
568
+ /** Iterate all registered [type, class] entries. */
569
+ entries() {
570
+ return registry.entries();
571
+ },
572
+ /** Remove a type from the registry. */
573
+ delete(type) {
574
+ return registry.delete(type);
575
+ }
576
+ };
577
+ function isSerializable(instance) {
578
+ return SERIALIZABLE_KEY in instance.constructor;
579
+ }
580
+ __name(isSerializable, "isSerializable");
581
+ function getSerializableType(target) {
582
+ const ctor = typeof target === "function" ? target : target.constructor;
583
+ return ctor[SERIALIZABLE_KEY];
584
+ }
585
+ __name(getSerializableType, "getSerializableType");
586
+ function serializable(targetOrConfig) {
587
+ if (typeof targetOrConfig === "function") {
588
+ const target = targetOrConfig;
589
+ const type = target.name;
590
+ target[SERIALIZABLE_KEY] = type;
591
+ registry.set(type, target);
592
+ return target;
593
+ }
594
+ const config = targetOrConfig;
595
+ return (target) => {
596
+ target[SERIALIZABLE_KEY] = config.type;
597
+ registry.set(config.type, target);
598
+ return target;
599
+ };
600
+ }
601
+ __name(serializable, "serializable");
602
+
603
+ // src/EntityFilter.ts
604
+ function filterEntities(entities, filter) {
605
+ const result = [];
606
+ for (const entity of entities) {
607
+ if (entity.isDestroyed) continue;
608
+ if (filter.name !== void 0 && entity.name !== filter.name) continue;
609
+ if (filter.tags) {
610
+ let allMatch = true;
611
+ for (const tag of filter.tags) {
612
+ if (!entity.tags.has(tag)) {
613
+ allMatch = false;
614
+ break;
615
+ }
616
+ }
617
+ if (!allMatch) continue;
618
+ }
619
+ if (filter.trait && !entity.hasTrait(filter.trait)) continue;
620
+ if (filter.filter && !filter.filter(entity)) continue;
621
+ result.push(entity);
622
+ }
623
+ return result;
624
+ }
625
+ __name(filterEntities, "filterEntities");
626
+
627
+ // src/Component.ts
628
+ var Component = class {
629
+ static {
630
+ __name(this, "Component");
631
+ }
632
+ /**
633
+ * Back-reference to the owning entity. Set by the engine when the component
634
+ * is added to an entity. Do not set manually.
635
+ */
636
+ entity;
637
+ /** Whether this component is active. Disabled components are skipped by ComponentUpdateSystem. */
638
+ enabled = true;
639
+ _serviceCache;
640
+ _cleanups;
641
+ /**
642
+ * Access the entity's scene. Throws if the entity is not in a scene.
643
+ * Prefer this over `this.entity.scene!` in component methods.
644
+ */
645
+ get scene() {
646
+ const scene = this.entity.scene;
647
+ if (!scene) {
648
+ throw new Error(
649
+ "Cannot access scene: entity is not attached to a scene."
650
+ );
651
+ }
652
+ return scene;
653
+ }
654
+ /**
655
+ * Access the EngineContext from the entity's scene.
656
+ * Throws if the entity is not in a scene.
657
+ */
658
+ get context() {
659
+ return this.scene.context;
660
+ }
661
+ /** Resolve a service by key, cached after first lookup. */
662
+ use(key) {
663
+ this._serviceCache ??= /* @__PURE__ */ new Map();
664
+ let value = this._serviceCache.get(key.id);
665
+ if (value === void 0) {
666
+ value = this.context.resolve(key);
667
+ this._serviceCache.set(key.id, value);
668
+ }
669
+ return value;
670
+ }
671
+ /**
672
+ * Lazy proxy-based service resolution. Can be used at field-declaration time:
673
+ * ```ts
674
+ * readonly camera = this.service(CameraKey);
675
+ * ```
676
+ * The actual resolution is deferred until first property access.
677
+ */
678
+ service(key) {
679
+ let resolved;
680
+ return new Proxy({}, {
681
+ get: /* @__PURE__ */ __name((_target, prop) => {
682
+ resolved ??= this.use(key);
683
+ const value = resolved[prop];
684
+ return typeof value === "function" ? value.bind(resolved) : value;
685
+ }, "get"),
686
+ set: /* @__PURE__ */ __name((_target, prop, value) => {
687
+ resolved ??= this.use(key);
688
+ resolved[prop] = value;
689
+ return true;
690
+ }, "set")
691
+ });
692
+ }
693
+ /**
694
+ * Lazy proxy-based sibling component resolution. Can be used at field-declaration time:
695
+ * ```ts
696
+ * readonly anim = this.sibling(AnimatedSpriteComponent);
697
+ * ```
698
+ * The actual resolution is deferred until first property access.
699
+ */
700
+ sibling(cls) {
701
+ let resolved;
702
+ return new Proxy({}, {
703
+ get: /* @__PURE__ */ __name((_target, prop) => {
704
+ resolved ??= this.entity.get(cls);
705
+ const value = resolved[prop];
706
+ return typeof value === "function" ? value.bind(resolved) : value;
707
+ }, "get"),
708
+ set: /* @__PURE__ */ __name((_target, prop, value) => {
709
+ resolved ??= this.entity.get(cls);
710
+ resolved[prop] = value;
711
+ return true;
712
+ }, "set")
713
+ });
714
+ }
715
+ /** Subscribe to events on any entity, auto-unsubscribe on removal. */
716
+ listen(entity, token, handler) {
717
+ const unsub = entity.on(token, handler);
718
+ this.addCleanup(unsub);
719
+ }
720
+ /** Subscribe to scene-level bubbled events, auto-unsubscribe on removal. */
721
+ listenScene(token, handler) {
722
+ const unsub = this.scene.on(token, handler);
723
+ this.addCleanup(unsub);
724
+ }
725
+ /** Register a cleanup function to run when this component is removed or destroyed. */
726
+ addCleanup(fn) {
727
+ this._cleanups ??= [];
728
+ this._cleanups.push(fn);
729
+ }
730
+ /**
731
+ * Run and clear all registered cleanups.
732
+ * Called by Entity.remove() and Entity._performDestroy() before onRemove/onDestroy.
733
+ * @internal
734
+ */
735
+ _runCleanups() {
736
+ if (this._cleanups) {
737
+ for (const fn of this._cleanups) {
738
+ fn();
739
+ }
740
+ this._cleanups.length = 0;
741
+ }
742
+ }
743
+ };
744
+
745
+ // src/Transform.ts
746
+ var _Transform_decorators, _init, _a;
747
+ _Transform_decorators = [serializable];
748
+ var _Transform = class _Transform extends (_a = Component) {
749
+ static {
750
+ __name(this, "Transform");
751
+ }
752
+ // Private backing fields
753
+ _position;
754
+ _rotation;
755
+ _scale;
756
+ _worldPosition;
757
+ _worldRotation;
758
+ _worldScale;
759
+ _dirty = false;
760
+ constructor(options) {
761
+ super();
762
+ this._position = options?.position ? new Vec2(options.position.x, options.position.y) : Vec2.ZERO;
763
+ this._rotation = options?.rotation ?? 0;
764
+ this._scale = options?.scale ? new Vec2(options.scale.x, options.scale.y) : Vec2.ONE;
765
+ this._worldPosition = this._position;
766
+ this._worldRotation = this._rotation;
767
+ this._worldScale = this._scale;
768
+ }
769
+ /** Local position (relative to parent, or world if no parent). */
770
+ get position() {
771
+ return this._position;
772
+ }
773
+ set position(v) {
774
+ this._position = v;
775
+ this._markDirty();
776
+ }
777
+ /** Local rotation in radians. */
778
+ get rotation() {
779
+ return this._rotation;
780
+ }
781
+ set rotation(v) {
782
+ this._rotation = v;
783
+ this._markDirty();
784
+ }
785
+ /** Local scale factor. */
786
+ get scale() {
787
+ return this._scale;
788
+ }
789
+ set scale(v) {
790
+ this._scale = v;
791
+ this._markDirty();
792
+ }
793
+ /** Computed world position. Recomputed lazily when dirty. */
794
+ get worldPosition() {
795
+ if (this._dirty) this._recompute();
796
+ return this._worldPosition;
797
+ }
798
+ /** Set position in world space. Back-computes the local position from the parent chain. */
799
+ set worldPosition(v) {
800
+ const pt = this.entity?.parent?.tryGet(_Transform);
801
+ if (!pt) {
802
+ this._position = v;
803
+ } else {
804
+ const delta = v.sub(pt.worldPosition).rotate(-pt.worldRotation);
805
+ const ps = pt.worldScale;
806
+ this._position = new Vec2(delta.x / ps.x, delta.y / ps.y);
807
+ }
808
+ this._markDirty();
809
+ }
810
+ /** Computed world rotation. Recomputed lazily when dirty. */
811
+ get worldRotation() {
812
+ if (this._dirty) this._recompute();
813
+ return this._worldRotation;
814
+ }
815
+ /** Set rotation in world space. Back-computes the local rotation from the parent chain. */
816
+ set worldRotation(v) {
817
+ const pt = this.entity?.parent?.tryGet(_Transform);
818
+ if (!pt) {
819
+ this._rotation = v;
820
+ } else {
821
+ this._rotation = v - pt.worldRotation;
822
+ }
823
+ this._markDirty();
824
+ }
825
+ /** Computed world scale. Recomputed lazily when dirty. */
826
+ get worldScale() {
827
+ if (this._dirty) this._recompute();
828
+ return this._worldScale;
829
+ }
830
+ /** Set position directly. */
831
+ setPosition(x, y) {
832
+ this._position = new Vec2(x, y);
833
+ this._markDirty();
834
+ }
835
+ /** Translate by an offset. */
836
+ translate(dx, dy) {
837
+ this._position = new Vec2(this._position.x + dx, this._position.y + dy);
838
+ this._markDirty();
839
+ }
840
+ /** Set rotation in radians. */
841
+ setRotation(radians) {
842
+ this._rotation = radians;
843
+ this._markDirty();
844
+ }
845
+ /** Rotate by a delta in radians. */
846
+ rotate(deltaRadians) {
847
+ this._rotation += deltaRadians;
848
+ this._markDirty();
849
+ }
850
+ /** Set scale. */
851
+ setScale(x, y) {
852
+ this._scale = new Vec2(x, y);
853
+ this._markDirty();
854
+ }
855
+ /**
856
+ * Mark this transform and all descendant transforms as dirty.
857
+ * @internal
858
+ */
859
+ _markDirty() {
860
+ if (this._dirty) return;
861
+ this._dirty = true;
862
+ for (const child of this.entity?.children.values() ?? []) {
863
+ child.tryGet(_Transform)?._markDirty();
864
+ }
865
+ }
866
+ _recompute() {
867
+ this._dirty = false;
868
+ const pt = this.entity?.parent?.tryGet(_Transform);
869
+ if (!pt) {
870
+ this._worldPosition = this._position;
871
+ this._worldRotation = this._rotation;
872
+ this._worldScale = this._scale;
873
+ return;
874
+ }
875
+ const rotatedLocal = this._position.multiply(pt.worldScale).rotate(pt.worldRotation);
876
+ this._worldPosition = pt.worldPosition.add(rotatedLocal);
877
+ this._worldRotation = pt.worldRotation + this._rotation;
878
+ this._worldScale = pt.worldScale.multiply(this._scale);
879
+ }
880
+ // ---- Save/load support ----
881
+ serialize() {
882
+ return {
883
+ position: { x: this._position.x, y: this._position.y },
884
+ rotation: this._rotation,
885
+ scale: { x: this._scale.x, y: this._scale.y }
886
+ };
887
+ }
888
+ static fromSnapshot(data) {
889
+ return new _Transform({
890
+ position: { x: data.position.x, y: data.position.y },
891
+ rotation: data.rotation,
892
+ scale: { x: data.scale.x, y: data.scale.y }
893
+ });
894
+ }
895
+ };
896
+ _init = __decoratorStart(_a);
897
+ _Transform = __decorateElement(_init, 0, "Transform", _Transform_decorators, _Transform);
898
+ __runInitializers(_init, 1, _Transform);
899
+ var Transform = _Transform;
900
+
901
+ // src/Entity.ts
902
+ var nextEntityId = 1;
903
+ var EMPTY_CHILDREN = /* @__PURE__ */ new Map();
904
+ function _resetEntityIdCounter() {
905
+ nextEntityId = 1;
906
+ }
907
+ __name(_resetEntityIdCounter, "_resetEntityIdCounter");
908
+ var Entity = class {
909
+ static {
910
+ __name(this, "Entity");
911
+ }
912
+ static [TRAITS_KEY] = /* @__PURE__ */ new Set();
913
+ /** Unique auto-incrementing ID. */
914
+ id;
915
+ /** Display name for debugging. */
916
+ name;
917
+ /** Tags for group queries. */
918
+ tags;
919
+ components = /* @__PURE__ */ new Map();
920
+ _destroyed = false;
921
+ _scene = null;
922
+ callbacks = null;
923
+ _eventHandlers;
924
+ _parent = null;
925
+ _children = null;
926
+ constructor(name, tags) {
927
+ this.id = nextEntityId++;
928
+ this.name = name ?? new.target.name ?? "Entity";
929
+ this.tags = new Set(tags);
930
+ }
931
+ /** The scene this entity belongs to, or null. */
932
+ get scene() {
933
+ return this._scene;
934
+ }
935
+ /** True if destroy() has been called. */
936
+ get isDestroyed() {
937
+ return this._destroyed;
938
+ }
939
+ /** The parent entity, or null if this is a root entity. */
940
+ get parent() {
941
+ return this._parent;
942
+ }
943
+ /** Named children as a read-only map. Empty map if no children. */
944
+ get children() {
945
+ return this._children ?? EMPTY_CHILDREN;
946
+ }
947
+ /** Add a named child entity. Auto-adds to parent's scene if not already in one. */
948
+ addChild(name, child) {
949
+ if (child === this) {
950
+ throw new Error(`Entity "${this.name}" cannot be a child of itself.`);
951
+ }
952
+ if (child._parent) {
953
+ throw new Error(
954
+ `Entity "${child.name}" already has a parent ("${child._parent.name}"). Remove it first.`
955
+ );
956
+ }
957
+ this._children ??= /* @__PURE__ */ new Map();
958
+ if (this._children.has(name)) {
959
+ throw new Error(
960
+ `Entity "${this.name}" already has a child named "${name}".`
961
+ );
962
+ }
963
+ child._parent = this;
964
+ this._children.set(name, child);
965
+ child.tryGet(Transform)?._markDirty();
966
+ if (this._scene && !child._scene) {
967
+ this._scene._addExistingEntity(child);
968
+ }
969
+ }
970
+ /** Remove a named child. Returns the detached entity. */
971
+ removeChild(name) {
972
+ const child = this._children?.get(name);
973
+ if (!child) {
974
+ throw new Error(`Entity "${this.name}" has no child named "${name}".`);
975
+ }
976
+ child._parent = null;
977
+ this._children.delete(name);
978
+ child.tryGet(Transform)?._markDirty();
979
+ return child;
980
+ }
981
+ /** Get a child by name. Throws if not found. */
982
+ getChild(name) {
983
+ const child = this._children?.get(name);
984
+ if (!child) {
985
+ throw new Error(`Entity "${this.name}" has no child named "${name}".`);
986
+ }
987
+ return child;
988
+ }
989
+ /** Get a child by name, or undefined if not found. */
990
+ tryGetChild(name) {
991
+ return this._children?.get(name);
992
+ }
993
+ /** Add a component instance. Returns the component for chaining. */
994
+ add(component) {
995
+ const cls = component.constructor;
996
+ if (this.components.has(cls)) {
997
+ throw new Error(
998
+ `Entity "${this.name}" already has component ${cls.name}.`
999
+ );
1000
+ }
1001
+ component.entity = this;
1002
+ this.components.set(cls, component);
1003
+ component.onAdd?.();
1004
+ this.callbacks?.onComponentAdded(this, cls);
1005
+ return component;
1006
+ }
1007
+ /** Get a component by class. Throws if not found. */
1008
+ get(cls) {
1009
+ const comp = this.components.get(cls);
1010
+ if (!comp) {
1011
+ throw new Error(
1012
+ `Entity "${this.name}" does not have component ${cls.name}.`
1013
+ );
1014
+ }
1015
+ return comp;
1016
+ }
1017
+ /** Get a component by class, or undefined if not found. */
1018
+ tryGet(cls) {
1019
+ return this.components.get(cls);
1020
+ }
1021
+ /** Check if entity has a component of the given class. */
1022
+ has(cls) {
1023
+ return this.components.has(cls);
1024
+ }
1025
+ /** Remove a component by class. */
1026
+ remove(cls) {
1027
+ const comp = this.components.get(cls);
1028
+ if (!comp) return;
1029
+ comp._runCleanups();
1030
+ comp.onRemove?.();
1031
+ comp.onDestroy?.();
1032
+ this.components.delete(cls);
1033
+ this.callbacks?.onComponentRemoved(this, cls);
1034
+ }
1035
+ /** Subscribe to a typed event on this entity. Returns an unsubscribe function. */
1036
+ on(token, handler) {
1037
+ this._eventHandlers ??= /* @__PURE__ */ new Map();
1038
+ let handlers = this._eventHandlers.get(token.name);
1039
+ if (!handlers) {
1040
+ handlers = /* @__PURE__ */ new Set();
1041
+ this._eventHandlers.set(token.name, handlers);
1042
+ }
1043
+ handlers.add(handler);
1044
+ return () => {
1045
+ handlers.delete(handler);
1046
+ };
1047
+ }
1048
+ emit(token, data) {
1049
+ if (this._destroyed) return;
1050
+ const handlers = this._eventHandlers?.get(token.name);
1051
+ if (handlers) {
1052
+ for (const handler of [...handlers]) {
1053
+ handler(data);
1054
+ }
1055
+ }
1056
+ this._scene?._onEntityEvent(token.name, data, this);
1057
+ }
1058
+ /** Get all components as an iterable. */
1059
+ getAll() {
1060
+ return this.components.values();
1061
+ }
1062
+ /** Mark for deferred destruction. Actual cleanup happens at end of frame. */
1063
+ destroy() {
1064
+ if (this._destroyed) return;
1065
+ this._destroyed = true;
1066
+ if (this._children) {
1067
+ for (const child of this._children.values()) {
1068
+ child.destroy();
1069
+ }
1070
+ }
1071
+ this._scene?._queueDestroy(this);
1072
+ }
1073
+ /**
1074
+ * Internal: perform actual destruction — remove all components and clear state.
1075
+ * Called by Scene during endOfFrame flush.
1076
+ * @internal
1077
+ */
1078
+ _performDestroy() {
1079
+ if (this._parent?._children) {
1080
+ for (const [name, child] of this._parent._children) {
1081
+ if (child === this) {
1082
+ this._parent._children.delete(name);
1083
+ break;
1084
+ }
1085
+ }
1086
+ }
1087
+ this._parent = null;
1088
+ this._children?.clear();
1089
+ for (const [cls, comp] of this.components) {
1090
+ comp._runCleanups();
1091
+ comp.onRemove?.();
1092
+ comp.onDestroy?.();
1093
+ this.callbacks?.onComponentRemoved(this, cls);
1094
+ }
1095
+ this.components.clear();
1096
+ this._eventHandlers?.clear();
1097
+ }
1098
+ /** Check if this entity's class implements a given trait. Acts as a type guard. */
1099
+ hasTrait(token) {
1100
+ let ctor = this.constructor;
1101
+ while (ctor) {
1102
+ const traits = ctor[TRAITS_KEY];
1103
+ if (traits?.has(token.symbol)) return true;
1104
+ ctor = Object.getPrototypeOf(ctor);
1105
+ }
1106
+ return false;
1107
+ }
1108
+ /**
1109
+ * Internal: set the scene and callbacks. Called by Scene.spawn().
1110
+ * @internal
1111
+ */
1112
+ _setScene(scene, callbacks) {
1113
+ this._scene = scene;
1114
+ this.callbacks = callbacks;
1115
+ }
1116
+ };
1117
+
1118
+ // src/QueryCache.ts
1119
+ var QueryResult = class {
1120
+ static {
1121
+ __name(this, "QueryResult");
1122
+ }
1123
+ /** @internal */
1124
+ _entities = /* @__PURE__ */ new Set();
1125
+ /** @internal */
1126
+ _filter;
1127
+ /** @internal */
1128
+ constructor(filter) {
1129
+ this._filter = filter;
1130
+ }
1131
+ /** Iterate matching entities. */
1132
+ [Symbol.iterator]() {
1133
+ return this._entities[Symbol.iterator]();
1134
+ }
1135
+ /** Number of matching entities. */
1136
+ get size() {
1137
+ return this._entities.size;
1138
+ }
1139
+ /** Get the first match (useful for singleton queries). */
1140
+ get first() {
1141
+ for (const e of this._entities) return e;
1142
+ return void 0;
1143
+ }
1144
+ /** Convert to array (allocates). */
1145
+ toArray() {
1146
+ return [...this._entities];
1147
+ }
1148
+ };
1149
+ var QueryCache = class {
1150
+ static {
1151
+ __name(this, "QueryCache");
1152
+ }
1153
+ queries = [];
1154
+ /** Register a query. Returns a stable reference to a live result set. */
1155
+ register(filter) {
1156
+ const result = new QueryResult(filter);
1157
+ this.queries.push(result);
1158
+ return result;
1159
+ }
1160
+ /** Called by Entity when a component is added. */
1161
+ onComponentAdded(entity) {
1162
+ for (const q of this.queries) {
1163
+ if (this.matches(entity, q._filter)) {
1164
+ q._entities.add(entity);
1165
+ }
1166
+ }
1167
+ }
1168
+ /** Called by Entity when a component is removed. */
1169
+ onComponentRemoved(entity) {
1170
+ for (const q of this.queries) {
1171
+ if (!this.matches(entity, q._filter)) {
1172
+ q._entities.delete(entity);
1173
+ }
1174
+ }
1175
+ }
1176
+ /** Called when an entity is destroyed. */
1177
+ onEntityDestroyed(entity) {
1178
+ for (const q of this.queries) {
1179
+ q._entities.delete(entity);
1180
+ }
1181
+ }
1182
+ matches(entity, filter) {
1183
+ for (const cls of filter) {
1184
+ if (!entity.has(cls)) return false;
1185
+ }
1186
+ return true;
1187
+ }
1188
+ };
1189
+
1190
+ // src/System.ts
1191
+ var System = class {
1192
+ static {
1193
+ __name(this, "System");
1194
+ }
1195
+ /** Execution priority within the phase. Lower = earlier. Default: 0. */
1196
+ priority = 0;
1197
+ /** Whether this system is active. */
1198
+ enabled = true;
1199
+ /** Reference to the engine context, set on registration. */
1200
+ context;
1201
+ _serviceCache;
1202
+ /**
1203
+ * Set the engine context. Called by Engine during startup.
1204
+ * @internal
1205
+ */
1206
+ _setContext(context) {
1207
+ this.context = context;
1208
+ }
1209
+ /** Resolve a service by key, cached after first lookup. */
1210
+ use(key) {
1211
+ this._serviceCache ??= /* @__PURE__ */ new Map();
1212
+ let value = this._serviceCache.get(key.id);
1213
+ if (value === void 0) {
1214
+ value = this.context.resolve(key);
1215
+ this._serviceCache.set(key.id, value);
1216
+ }
1217
+ return value;
1218
+ }
1219
+ };
1220
+
1221
+ // src/SystemScheduler.ts
1222
+ var SystemScheduler = class {
1223
+ static {
1224
+ __name(this, "SystemScheduler");
1225
+ }
1226
+ phases = /* @__PURE__ */ new Map();
1227
+ errorBoundary = null;
1228
+ /** Set the error boundary for wrapping system execution. */
1229
+ setErrorBoundary(boundary) {
1230
+ this.errorBoundary = boundary;
1231
+ }
1232
+ /** Register a system. Sorted by priority within its phase. */
1233
+ add(system) {
1234
+ let list = this.phases.get(system.phase);
1235
+ if (!list) {
1236
+ list = [];
1237
+ this.phases.set(system.phase, list);
1238
+ }
1239
+ list.push(system);
1240
+ list.sort((a, b) => a.priority - b.priority);
1241
+ }
1242
+ /** Remove a system. */
1243
+ remove(system) {
1244
+ const list = this.phases.get(system.phase);
1245
+ if (!list) return;
1246
+ const idx = list.indexOf(system);
1247
+ if (idx !== -1) list.splice(idx, 1);
1248
+ }
1249
+ /** Run all enabled systems in a given phase. Wraps each in ErrorBoundary if available. */
1250
+ run(phase, dt) {
1251
+ const list = this.phases.get(phase);
1252
+ if (!list) return;
1253
+ for (const system of list) {
1254
+ if (!system.enabled) continue;
1255
+ if (this.errorBoundary) {
1256
+ this.errorBoundary.wrapSystem(system, () => system.update(dt));
1257
+ } else {
1258
+ system.update(dt);
1259
+ }
1260
+ }
1261
+ }
1262
+ /** Get all systems registered for a phase. */
1263
+ getSystems(phase) {
1264
+ return this.phases.get(phase) ?? [];
1265
+ }
1266
+ /** Get all systems across all phases. */
1267
+ getAllSystems() {
1268
+ const all = [];
1269
+ for (const list of this.phases.values()) {
1270
+ all.push(...list);
1271
+ }
1272
+ return all;
1273
+ }
1274
+ };
1275
+
1276
+ // src/ComponentUpdateSystem.ts
1277
+ var BaseComponentUpdateSystem = class extends System {
1278
+ static {
1279
+ __name(this, "BaseComponentUpdateSystem");
1280
+ }
1281
+ /** High priority so game logic runs after engine systems like physics. */
1282
+ priority = 1e3;
1283
+ sceneManager;
1284
+ errorBoundary;
1285
+ onRegister(context) {
1286
+ this.sceneManager = context.resolve(SceneManagerKey);
1287
+ this.errorBoundary = context.resolve(ErrorBoundaryKey);
1288
+ }
1289
+ };
1290
+ var ComponentFixedUpdateSystem = class extends BaseComponentUpdateSystem {
1291
+ static {
1292
+ __name(this, "ComponentFixedUpdateSystem");
1293
+ }
1294
+ phase = "fixedUpdate" /* FixedUpdate */;
1295
+ update(dt) {
1296
+ for (const scene of this.sceneManager.activeScenes) {
1297
+ const sceneDt = dt * scene.timeScale;
1298
+ for (const entity of scene.getEntities()) {
1299
+ if (entity.isDestroyed) continue;
1300
+ for (const component of entity.getAll()) {
1301
+ if (!component.enabled || !component.fixedUpdate) continue;
1302
+ const fixedUpdate = component.fixedUpdate;
1303
+ this.errorBoundary.wrapComponent(
1304
+ component,
1305
+ () => fixedUpdate.call(component, sceneDt)
1306
+ );
1307
+ }
1308
+ }
1309
+ }
1310
+ }
1311
+ };
1312
+ var ComponentUpdateSystem = class extends BaseComponentUpdateSystem {
1313
+ static {
1314
+ __name(this, "ComponentUpdateSystem");
1315
+ }
1316
+ phase = "update" /* Update */;
1317
+ update(dt) {
1318
+ for (const scene of this.sceneManager.activeScenes) {
1319
+ const sceneDt = dt * scene.timeScale;
1320
+ for (const entity of scene.getEntities()) {
1321
+ if (entity.isDestroyed) continue;
1322
+ for (const component of entity.getAll()) {
1323
+ if (!component.enabled || !component.update) continue;
1324
+ const update = component.update;
1325
+ this.errorBoundary.wrapComponent(
1326
+ component,
1327
+ () => update.call(component, sceneDt)
1328
+ );
1329
+ }
1330
+ }
1331
+ }
1332
+ }
1333
+ };
1334
+
1335
+ // src/ErrorBoundary.ts
1336
+ var ErrorBoundary = class {
1337
+ static {
1338
+ __name(this, "ErrorBoundary");
1339
+ }
1340
+ logger;
1341
+ disabledSystems = [];
1342
+ disabledComponents = [];
1343
+ constructor(logger) {
1344
+ this.logger = logger;
1345
+ }
1346
+ /** Wrap a system update call. On throw, disables the system. */
1347
+ wrapSystem(system, fn) {
1348
+ try {
1349
+ fn();
1350
+ } catch (err) {
1351
+ const message = err instanceof Error ? err.message : String(err);
1352
+ system.enabled = false;
1353
+ this.disabledSystems.push({ system, error: message });
1354
+ this.logger.error(
1355
+ "core",
1356
+ `System ${system.constructor.name} threw and was disabled`,
1357
+ { error: message }
1358
+ );
1359
+ }
1360
+ }
1361
+ /** Wrap a component lifecycle or update call. On throw, disables the component. */
1362
+ wrapComponent(component, fn) {
1363
+ try {
1364
+ fn();
1365
+ } catch (err) {
1366
+ const message = err instanceof Error ? err.message : String(err);
1367
+ component.enabled = false;
1368
+ this.disabledComponents.push({ component, error: message });
1369
+ const entityName = component.entity?.name ?? "unknown";
1370
+ this.logger.error(
1371
+ "core",
1372
+ `Component ${component.constructor.name} on entity "${entityName}" threw and was disabled`,
1373
+ { error: message }
1374
+ );
1375
+ }
1376
+ }
1377
+ /** Get all disabled systems and components for inspection. */
1378
+ getDisabled() {
1379
+ return {
1380
+ systems: this.disabledSystems,
1381
+ components: this.disabledComponents
1382
+ };
1383
+ }
1384
+ };
1385
+
1386
+ // src/GameLoop.ts
1387
+ var GameLoop = class {
1388
+ static {
1389
+ __name(this, "GameLoop");
1390
+ }
1391
+ /** Fixed timestep in ms. */
1392
+ fixedTimestep;
1393
+ /** Max fixed steps per frame. */
1394
+ maxFixedStepsPerFrame;
1395
+ accumulator = 0;
1396
+ running = false;
1397
+ callbacks = null;
1398
+ tickerUnsubscribe = null;
1399
+ rafId = null;
1400
+ lastTime = 0;
1401
+ _frameCount = 0;
1402
+ constructor(config) {
1403
+ this.fixedTimestep = config?.fixedTimestep ?? 1e3 / 60;
1404
+ this.maxFixedStepsPerFrame = config?.maxFixedStepsPerFrame ?? 5;
1405
+ }
1406
+ /** Current frame count. */
1407
+ get frameCount() {
1408
+ return this._frameCount;
1409
+ }
1410
+ /** Whether the loop is running. */
1411
+ get isRunning() {
1412
+ return this.running;
1413
+ }
1414
+ /** Ratio of accumulated time to fixed timestep, for physics interpolation. */
1415
+ get interpolationAlpha() {
1416
+ return this.accumulator / this.fixedTimestep;
1417
+ }
1418
+ /** Provide the callbacks that the loop invokes each frame. */
1419
+ setCallbacks(callbacks) {
1420
+ this.callbacks = callbacks;
1421
+ }
1422
+ /**
1423
+ * Attach an external ticker (e.g., PixiJS Ticker).
1424
+ * The ticker calls `tick(dt)` every frame.
1425
+ * If no ticker is attached, the loop uses requestAnimationFrame.
1426
+ */
1427
+ attachTicker(subscribe) {
1428
+ this.tickerUnsubscribe = subscribe((dt) => this.tick(dt));
1429
+ }
1430
+ /** Start the loop. */
1431
+ start() {
1432
+ if (this.running) return;
1433
+ this.running = true;
1434
+ this._frameCount = 0;
1435
+ this.accumulator = 0;
1436
+ if (!this.tickerUnsubscribe && typeof requestAnimationFrame !== "undefined") {
1437
+ this.lastTime = performance.now();
1438
+ const loop = /* @__PURE__ */ __name((now) => {
1439
+ if (!this.running) return;
1440
+ const dt = now - this.lastTime;
1441
+ this.lastTime = now;
1442
+ this.tick(dt);
1443
+ this.rafId = requestAnimationFrame(loop);
1444
+ }, "loop");
1445
+ this.rafId = requestAnimationFrame(loop);
1446
+ }
1447
+ }
1448
+ /** Stop the loop. */
1449
+ stop() {
1450
+ this.running = false;
1451
+ if (this.rafId !== null && typeof cancelAnimationFrame !== "undefined") {
1452
+ cancelAnimationFrame(this.rafId);
1453
+ this.rafId = null;
1454
+ }
1455
+ if (this.tickerUnsubscribe) {
1456
+ this.tickerUnsubscribe();
1457
+ this.tickerUnsubscribe = null;
1458
+ }
1459
+ }
1460
+ /** Process one frame with the given dt in milliseconds. */
1461
+ tick(dtMs) {
1462
+ if (!this.callbacks) return;
1463
+ this._frameCount++;
1464
+ this.callbacks.earlyUpdate(dtMs);
1465
+ this.accumulator += dtMs;
1466
+ let steps = 0;
1467
+ while (this.accumulator >= this.fixedTimestep && steps < this.maxFixedStepsPerFrame) {
1468
+ this.callbacks.fixedUpdate(this.fixedTimestep);
1469
+ this.accumulator -= this.fixedTimestep;
1470
+ steps++;
1471
+ }
1472
+ this.callbacks.update(dtMs);
1473
+ this.callbacks.lateUpdate(dtMs);
1474
+ this.callbacks.render(dtMs);
1475
+ this.callbacks.endOfFrame(dtMs);
1476
+ }
1477
+ };
1478
+
1479
+ // src/Scene.ts
1480
+ var Scene = class {
1481
+ static {
1482
+ __name(this, "Scene");
1483
+ }
1484
+ /** Whether scenes below this one in the stack should be paused. Default: true. */
1485
+ pauseBelow = true;
1486
+ /** Whether scenes below this one should still render. Default: false. */
1487
+ transparentBelow = false;
1488
+ /** Asset handles to load before onEnter(). Override in subclasses. */
1489
+ preload;
1490
+ /** Manual pause flag. Set by game code to pause this scene regardless of stack position. */
1491
+ paused = false;
1492
+ /** Time scale multiplier for this scene. 1.0 = normal, 0.5 = half speed. Default: 1. */
1493
+ timeScale = 1;
1494
+ entities = /* @__PURE__ */ new Set();
1495
+ destroyQueue = [];
1496
+ _context;
1497
+ entityCallbacks;
1498
+ queryCache;
1499
+ bus;
1500
+ _entityEventHandlers;
1501
+ /** Access the EngineContext. */
1502
+ get context() {
1503
+ return this._context;
1504
+ }
1505
+ /** Whether this scene is effectively paused (manual pause or paused by stack). */
1506
+ get isPaused() {
1507
+ if (this.paused) return true;
1508
+ const sm = this._context?.tryResolve(SceneManagerKey);
1509
+ if (!sm) return false;
1510
+ const stack = sm.all;
1511
+ const idx = stack.indexOf(this);
1512
+ if (idx === -1) return false;
1513
+ for (let i = idx + 1; i < stack.length; i++) {
1514
+ if (stack[i].pauseBelow) return true;
1515
+ }
1516
+ return false;
1517
+ }
1518
+ /** Convenience accessor for the AssetManager. */
1519
+ get assets() {
1520
+ return this._context.resolve(AssetManagerKey);
1521
+ }
1522
+ /**
1523
+ * Lazy proxy-based service resolution. Can be used at field-declaration time:
1524
+ * ```ts
1525
+ * readonly layers = this.service(RenderLayerManagerKey);
1526
+ * ```
1527
+ * The actual resolution is deferred until first property access.
1528
+ */
1529
+ service(key) {
1530
+ let resolved;
1531
+ return new Proxy({}, {
1532
+ get: /* @__PURE__ */ __name((_target, prop) => {
1533
+ resolved ??= this._context.resolve(key);
1534
+ const value = resolved[prop];
1535
+ return typeof value === "function" ? value.bind(resolved) : value;
1536
+ }, "get"),
1537
+ set: /* @__PURE__ */ __name((_target, prop, value) => {
1538
+ resolved ??= this._context.resolve(key);
1539
+ resolved[prop] = value;
1540
+ return true;
1541
+ }, "set")
1542
+ });
1543
+ }
1544
+ spawn(nameOrBlueprintOrClass, params) {
1545
+ if (typeof nameOrBlueprintOrClass === "function") {
1546
+ const entity2 = new nameOrBlueprintOrClass();
1547
+ entity2._setScene(this, this.entityCallbacks);
1548
+ this.entities.add(entity2);
1549
+ this.bus?.emit("entity:created", { entity: entity2 });
1550
+ entity2.setup?.(params);
1551
+ return entity2;
1552
+ }
1553
+ const isBlueprint = typeof nameOrBlueprintOrClass === "object" && nameOrBlueprintOrClass !== null && "build" in nameOrBlueprintOrClass;
1554
+ const name = isBlueprint ? nameOrBlueprintOrClass.name : nameOrBlueprintOrClass;
1555
+ const entity = new Entity(name);
1556
+ entity._setScene(this, this.entityCallbacks);
1557
+ this.entities.add(entity);
1558
+ this.bus?.emit("entity:created", { entity });
1559
+ if (isBlueprint) {
1560
+ nameOrBlueprintOrClass.build(entity, params);
1561
+ }
1562
+ return entity;
1563
+ }
1564
+ /**
1565
+ * Add an existing entity to this scene (used by Entity.addChild for auto-scene-membership).
1566
+ * @internal
1567
+ */
1568
+ _addExistingEntity(entity) {
1569
+ entity._setScene(this, this.entityCallbacks);
1570
+ this.entities.add(entity);
1571
+ this.bus?.emit("entity:created", { entity });
1572
+ if (!entity.getAll()[Symbol.iterator]().next().done) {
1573
+ this.queryCache?.onComponentAdded(entity);
1574
+ }
1575
+ }
1576
+ /** Mark an entity for destruction. Deferred to endOfFrame flush. */
1577
+ destroyEntity(entity) {
1578
+ entity.destroy();
1579
+ }
1580
+ /**
1581
+ * Add an entity to the destroy queue. Called by Entity.destroy().
1582
+ * @internal
1583
+ */
1584
+ _queueDestroy(entity) {
1585
+ this.destroyQueue.push(entity);
1586
+ }
1587
+ /** Get all active entities. */
1588
+ getEntities() {
1589
+ return this.entities;
1590
+ }
1591
+ /** Find entity by name (first match). */
1592
+ findEntity(name) {
1593
+ for (const e of this.entities) {
1594
+ if (e.name === name && !e.isDestroyed) return e;
1595
+ }
1596
+ return void 0;
1597
+ }
1598
+ /** Find entities by tag. */
1599
+ findEntitiesByTag(tag) {
1600
+ const result = [];
1601
+ for (const e of this.entities) {
1602
+ if (e.tags.has(tag) && !e.isDestroyed) result.push(e);
1603
+ }
1604
+ return result;
1605
+ }
1606
+ findEntities(filter) {
1607
+ if (!filter) {
1608
+ const result = [];
1609
+ for (const e of this.entities) {
1610
+ if (!e.isDestroyed) result.push(e);
1611
+ }
1612
+ return result;
1613
+ }
1614
+ return filterEntities(this.entities, filter);
1615
+ }
1616
+ /** Subscribe to bubbled entity events at the scene level. Handler receives (data, emittingEntity). */
1617
+ on(token, handler) {
1618
+ this._entityEventHandlers ??= /* @__PURE__ */ new Map();
1619
+ let handlers = this._entityEventHandlers.get(token.name);
1620
+ if (!handlers) {
1621
+ handlers = /* @__PURE__ */ new Set();
1622
+ this._entityEventHandlers.set(token.name, handlers);
1623
+ }
1624
+ handlers.add(handler);
1625
+ return () => {
1626
+ handlers.delete(handler);
1627
+ };
1628
+ }
1629
+ /**
1630
+ * Called by Entity.emit() for bubbling entity events to the scene.
1631
+ * @internal
1632
+ */
1633
+ _onEntityEvent(eventName, data, entity) {
1634
+ const handlers = this._entityEventHandlers?.get(eventName);
1635
+ if (handlers) {
1636
+ for (const handler of [...handlers]) {
1637
+ handler(data, entity);
1638
+ }
1639
+ }
1640
+ }
1641
+ // ---- Internal methods ----
1642
+ /**
1643
+ * Set the engine context. Called by SceneManager when the scene is pushed.
1644
+ * @internal
1645
+ */
1646
+ _setContext(context) {
1647
+ this._context = context;
1648
+ this.queryCache = context.tryResolve(QueryCacheKey);
1649
+ this.bus = context.tryResolve(EventBusKey);
1650
+ this.entityCallbacks = {
1651
+ onComponentAdded: /* @__PURE__ */ __name((entity, cls) => {
1652
+ this.queryCache?.onComponentAdded(entity);
1653
+ this.bus?.emit("component:added", {
1654
+ entity,
1655
+ component: entity.get(cls)
1656
+ });
1657
+ }, "onComponentAdded"),
1658
+ onComponentRemoved: /* @__PURE__ */ __name((entity, cls) => {
1659
+ this.queryCache?.onComponentRemoved(entity);
1660
+ this.bus?.emit("component:removed", { entity, componentClass: cls });
1661
+ }, "onComponentRemoved")
1662
+ };
1663
+ }
1664
+ /**
1665
+ * Flush the destroy queue — destroy pending entities.
1666
+ * Called by the engine during the endOfFrame phase.
1667
+ * @internal
1668
+ */
1669
+ _flushDestroyQueue() {
1670
+ for (const entity of this.destroyQueue) {
1671
+ entity._performDestroy();
1672
+ this.queryCache?.onEntityDestroyed(entity);
1673
+ this.entities.delete(entity);
1674
+ this.bus?.emit("entity:destroyed", { entity });
1675
+ }
1676
+ this.destroyQueue.length = 0;
1677
+ }
1678
+ /**
1679
+ * Destroy all entities — used during scene exit.
1680
+ * @internal
1681
+ */
1682
+ _destroyAllEntities() {
1683
+ for (const entity of this.entities) {
1684
+ entity._performDestroy();
1685
+ this.queryCache?.onEntityDestroyed(entity);
1686
+ }
1687
+ this.entities.clear();
1688
+ this.destroyQueue.length = 0;
1689
+ this._entityEventHandlers?.clear();
1690
+ }
1691
+ };
1692
+
1693
+ // src/SceneManager.ts
1694
+ var SceneManager = class {
1695
+ static {
1696
+ __name(this, "SceneManager");
1697
+ }
1698
+ stack = [];
1699
+ _context;
1700
+ bus;
1701
+ assetManager;
1702
+ /**
1703
+ * Set the engine context.
1704
+ * @internal
1705
+ */
1706
+ _setContext(context) {
1707
+ this._context = context;
1708
+ this.bus = context.tryResolve(EventBusKey);
1709
+ this.assetManager = context.tryResolve(AssetManagerKey);
1710
+ }
1711
+ /** The topmost (active) scene. */
1712
+ get active() {
1713
+ return this.stack[this.stack.length - 1];
1714
+ }
1715
+ /** All scenes in the stack, bottom to top. */
1716
+ get all() {
1717
+ return this.stack;
1718
+ }
1719
+ /** All non-paused scenes in the stack, bottom to top. */
1720
+ get activeScenes() {
1721
+ return this.stack.filter((s) => !s.isPaused);
1722
+ }
1723
+ /**
1724
+ * Push a scene onto the stack. Scenes below may receive onPause().
1725
+ * If the scene declares a `preload` array, assets are loaded before onEnter().
1726
+ * Await the returned promise when using preloaded scenes.
1727
+ */
1728
+ push(scene) {
1729
+ const wasPaused = new Map(this.stack.map((s) => [s, s.isPaused]));
1730
+ scene._setContext(this._context);
1731
+ this.stack.push(scene);
1732
+ this._firePauseTransitions(wasPaused);
1733
+ if (scene.preload?.length && this.assetManager) {
1734
+ return this.assetManager.loadAll(scene.preload, scene.onProgress?.bind(scene)).then(() => {
1735
+ scene.onEnter?.();
1736
+ this.bus?.emit("scene:pushed", { scene });
1737
+ });
1738
+ }
1739
+ scene.onEnter?.();
1740
+ this.bus?.emit("scene:pushed", { scene });
1741
+ return Promise.resolve();
1742
+ }
1743
+ /** Pop the top scene. Scenes below may receive onResume(). */
1744
+ pop() {
1745
+ const wasPaused = new Map(this.stack.map((s) => [s, s.isPaused]));
1746
+ const removed = this.stack.pop();
1747
+ if (!removed) return void 0;
1748
+ removed.onExit?.();
1749
+ removed._destroyAllEntities();
1750
+ this._fireResumeTransitions(wasPaused);
1751
+ this.bus?.emit("scene:popped", { scene: removed });
1752
+ return removed;
1753
+ }
1754
+ /**
1755
+ * Replace the top scene. Old scene receives onExit().
1756
+ * New scene receives onEnter() (after preload, if declared).
1757
+ */
1758
+ replace(scene) {
1759
+ const wasPaused = new Map(this.stack.map((s) => [s, s.isPaused]));
1760
+ const old = this.stack.pop();
1761
+ if (old) {
1762
+ old.onExit?.();
1763
+ old._destroyAllEntities();
1764
+ }
1765
+ scene._setContext(this._context);
1766
+ this.stack.push(scene);
1767
+ this._firePauseTransitions(wasPaused);
1768
+ this._fireResumeTransitions(wasPaused);
1769
+ if (scene.preload?.length && this.assetManager) {
1770
+ return this.assetManager.loadAll(scene.preload, scene.onProgress?.bind(scene)).then(() => {
1771
+ scene.onEnter?.();
1772
+ if (old) {
1773
+ this.bus?.emit("scene:replaced", {
1774
+ oldScene: old,
1775
+ newScene: scene
1776
+ });
1777
+ } else {
1778
+ this.bus?.emit("scene:pushed", { scene });
1779
+ }
1780
+ });
1781
+ }
1782
+ scene.onEnter?.();
1783
+ if (old) {
1784
+ this.bus?.emit("scene:replaced", { oldScene: old, newScene: scene });
1785
+ } else {
1786
+ this.bus?.emit("scene:pushed", { scene });
1787
+ }
1788
+ return Promise.resolve();
1789
+ }
1790
+ /** Clear all scenes. Each receives onExit() from top to bottom. */
1791
+ clear() {
1792
+ while (this.stack.length > 0) {
1793
+ const scene = this.stack.pop();
1794
+ if (!scene) break;
1795
+ scene.onExit?.();
1796
+ scene._destroyAllEntities();
1797
+ this.bus?.emit("scene:popped", { scene });
1798
+ }
1799
+ }
1800
+ /**
1801
+ * Flush destroy queues for all active scenes.
1802
+ * Called by the engine during endOfFrame.
1803
+ * @internal
1804
+ */
1805
+ _flushDestroyQueues() {
1806
+ for (const scene of this.stack) {
1807
+ scene._flushDestroyQueue();
1808
+ }
1809
+ }
1810
+ /** Fire onPause() for scenes that transitioned from not-paused to paused. */
1811
+ _firePauseTransitions(wasPaused) {
1812
+ for (const s of this.stack) {
1813
+ const was = wasPaused.get(s) ?? false;
1814
+ if (s.isPaused && !was) {
1815
+ s.onPause?.();
1816
+ }
1817
+ }
1818
+ }
1819
+ /** Fire onResume() for scenes that transitioned from paused to not-paused. */
1820
+ _fireResumeTransitions(wasPaused) {
1821
+ for (const s of this.stack) {
1822
+ const was = wasPaused.get(s) ?? false;
1823
+ if (!s.isPaused && was) {
1824
+ s.onResume?.();
1825
+ }
1826
+ }
1827
+ }
1828
+ };
1829
+
1830
+ // src/Process.ts
1831
+ var Process = class _Process {
1832
+ static {
1833
+ __name(this, "Process");
1834
+ }
1835
+ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
1836
+ updateFn;
1837
+ onCompleteFn;
1838
+ duration;
1839
+ loop;
1840
+ /** Tags for filtering/grouping. */
1841
+ tags;
1842
+ elapsed = 0;
1843
+ _completed = false;
1844
+ _paused = false;
1845
+ _cancelled = false;
1846
+ resolvePromise;
1847
+ /** Create a timer that fires `onComplete` after `duration` ms. */
1848
+ static delay(duration, onComplete, tags) {
1849
+ const opts = { duration };
1850
+ if (onComplete !== void 0) opts.onComplete = onComplete;
1851
+ if (tags !== void 0) opts.tags = tags;
1852
+ return new _Process(opts);
1853
+ }
1854
+ constructor(options) {
1855
+ this.updateFn = options.update ?? (() => {
1856
+ });
1857
+ this.onCompleteFn = options.onComplete;
1858
+ this.duration = options.duration;
1859
+ this.loop = options.loop ?? false;
1860
+ this.tags = options.tags ?? [];
1861
+ }
1862
+ /** Whether the process has completed. */
1863
+ get completed() {
1864
+ return this._completed;
1865
+ }
1866
+ /** Whether the process is paused. */
1867
+ get paused() {
1868
+ return this._paused;
1869
+ }
1870
+ /** Pause the process. */
1871
+ pause() {
1872
+ this._paused = true;
1873
+ }
1874
+ /** Resume the process. */
1875
+ resume() {
1876
+ this._paused = false;
1877
+ }
1878
+ /** Cancel the process. */
1879
+ cancel() {
1880
+ this._cancelled = true;
1881
+ this._completed = true;
1882
+ this.resolvePromise?.();
1883
+ }
1884
+ /** Returns a promise that resolves when the process completes or is cancelled. */
1885
+ toPromise() {
1886
+ if (this._completed) return Promise.resolve();
1887
+ return new Promise((resolve) => {
1888
+ this.resolvePromise = resolve;
1889
+ });
1890
+ }
1891
+ /**
1892
+ * Advance the process by dt milliseconds.
1893
+ * @internal
1894
+ */
1895
+ _update(dt) {
1896
+ if (this._completed || this._paused || this._cancelled) return;
1897
+ this.elapsed += dt;
1898
+ if (this.duration !== void 0 && this.elapsed >= this.duration) {
1899
+ const result2 = this.updateFn(dt, this.elapsed);
1900
+ if (this.loop && result2 !== true) {
1901
+ this.elapsed = this.elapsed % this.duration;
1902
+ return;
1903
+ }
1904
+ this.complete();
1905
+ return;
1906
+ }
1907
+ const result = this.updateFn(dt, this.elapsed);
1908
+ if (result === true) {
1909
+ if (this.loop) {
1910
+ this.elapsed = 0;
1911
+ return;
1912
+ }
1913
+ this.complete();
1914
+ }
1915
+ }
1916
+ /**
1917
+ * Reset the process to its initial state so it can be re-run.
1918
+ * @internal Used by Sequence for loop/repeat with direct instances.
1919
+ */
1920
+ _reset() {
1921
+ this.elapsed = 0;
1922
+ this._completed = false;
1923
+ this._paused = false;
1924
+ this._cancelled = false;
1925
+ delete this.resolvePromise;
1926
+ }
1927
+ complete() {
1928
+ this._completed = true;
1929
+ this.onCompleteFn?.();
1930
+ this.resolvePromise?.();
1931
+ }
1932
+ };
1933
+ var easeLinear = /* @__PURE__ */ __name((t) => t, "easeLinear");
1934
+ var easeInQuad = /* @__PURE__ */ __name((t) => t * t, "easeInQuad");
1935
+ var easeOutQuad = /* @__PURE__ */ __name((t) => t * (2 - t), "easeOutQuad");
1936
+ var easeInOutQuad = /* @__PURE__ */ __name((t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t, "easeInOutQuad");
1937
+ var easeOutBounce = /* @__PURE__ */ __name((t) => {
1938
+ if (t < 1 / 2.75) {
1939
+ return 7.5625 * t * t;
1940
+ } else if (t < 2 / 2.75) {
1941
+ const t2 = t - 1.5 / 2.75;
1942
+ return 7.5625 * t2 * t2 + 0.75;
1943
+ } else if (t < 2.5 / 2.75) {
1944
+ const t2 = t - 2.25 / 2.75;
1945
+ return 7.5625 * t2 * t2 + 0.9375;
1946
+ } else {
1947
+ const t2 = t - 2.625 / 2.75;
1948
+ return 7.5625 * t2 * t2 + 0.984375;
1949
+ }
1950
+ }, "easeOutBounce");
1951
+
1952
+ // src/Tween.ts
1953
+ var Tween = {
1954
+ /** Tween a numeric property on a target object. */
1955
+ to(target, property, to, duration, easing = easeLinear) {
1956
+ const from = target[property] ?? 0;
1957
+ return new Process({
1958
+ duration,
1959
+ update: /* @__PURE__ */ __name((_dt, elapsed) => {
1960
+ const t = Math.min(elapsed / duration, 1);
1961
+ target[property] = from + (to - from) * easing(t);
1962
+ }, "update")
1963
+ });
1964
+ },
1965
+ /** Tween using a custom setter. */
1966
+ custom(setter, from, to, duration, easing = easeLinear) {
1967
+ return new Process({
1968
+ duration,
1969
+ update: /* @__PURE__ */ __name((_dt, elapsed) => {
1970
+ const t = Math.min(elapsed / duration, 1);
1971
+ setter(from + (to - from) * easing(t));
1972
+ }, "update")
1973
+ });
1974
+ },
1975
+ /** Tween a Vec2 value. */
1976
+ vec2(setter, from, to, duration, easing = easeLinear) {
1977
+ return new Process({
1978
+ duration,
1979
+ update: /* @__PURE__ */ __name((_dt, elapsed) => {
1980
+ const t = Math.min(elapsed / duration, 1);
1981
+ const e = easing(t);
1982
+ setter(
1983
+ new Vec2(from.x + (to.x - from.x) * e, from.y + (to.y - from.y) * e)
1984
+ );
1985
+ }, "update")
1986
+ });
1987
+ }
1988
+ };
1989
+
1990
+ // src/interpolate.ts
1991
+ function interpolate(from, to, t, easing) {
1992
+ const e = easing ? easing(t) : t;
1993
+ if (typeof from === "number") {
1994
+ return from + (to - from) * e;
1995
+ }
1996
+ const a = from;
1997
+ const b = to;
1998
+ return new Vec2(a.x + (b.x - a.x) * e, a.y + (b.y - a.y) * e);
1999
+ }
2000
+ __name(interpolate, "interpolate");
2001
+
2002
+ // src/KeyframeTrack.ts
2003
+ function createKeyframeTrack(options) {
2004
+ const {
2005
+ keyframes,
2006
+ setter,
2007
+ speed = 1,
2008
+ easing: defaultEasing = easeLinear,
2009
+ onComplete
2010
+ } = options;
2011
+ const duration = options.duration ?? keyframes[keyframes.length - 1].time;
2012
+ if (duration <= 0) {
2013
+ throw new Error("createKeyframeTrack: duration must be > 0");
2014
+ }
2015
+ const loop = options.loop ?? false;
2016
+ let internalElapsed = 0;
2017
+ const firedEvents = /* @__PURE__ */ new Set();
2018
+ const processOpts = {
2019
+ update(dt) {
2020
+ internalElapsed += dt * speed;
2021
+ if (internalElapsed >= duration) {
2022
+ if (loop) {
2023
+ for (let i = 0; i < keyframes.length; i++) {
2024
+ if (keyframes[i].event && !firedEvents.has(i)) {
2025
+ keyframes[i].event();
2026
+ }
2027
+ }
2028
+ internalElapsed = internalElapsed % duration;
2029
+ firedEvents.clear();
2030
+ return;
2031
+ } else {
2032
+ setter(keyframes[keyframes.length - 1].data);
2033
+ for (let i = 0; i < keyframes.length; i++) {
2034
+ if (!firedEvents.has(i) && keyframes[i].event) {
2035
+ keyframes[i].event();
2036
+ }
2037
+ }
2038
+ return true;
2039
+ }
2040
+ }
2041
+ for (let i = 0; i < keyframes.length; i++) {
2042
+ if (!firedEvents.has(i) && keyframes[i].event && internalElapsed >= keyframes[i].time) {
2043
+ firedEvents.add(i);
2044
+ keyframes[i].event();
2045
+ }
2046
+ }
2047
+ let segIdx = 0;
2048
+ for (let i = 0; i < keyframes.length - 1; i++) {
2049
+ if (internalElapsed >= keyframes[i].time) {
2050
+ segIdx = i;
2051
+ }
2052
+ }
2053
+ const kfA = keyframes[segIdx];
2054
+ const kfB = keyframes[segIdx + 1];
2055
+ const segDuration = kfB.time - kfA.time;
2056
+ const segT = segDuration > 0 ? Math.min((internalElapsed - kfA.time) / segDuration, 1) : 1;
2057
+ const segEasing = kfA.easing ?? defaultEasing;
2058
+ setter(interpolate(kfA.data, kfB.data, segT, segEasing));
2059
+ }
2060
+ };
2061
+ if (onComplete) processOpts.onComplete = onComplete;
2062
+ return new Process(processOpts);
2063
+ }
2064
+ __name(createKeyframeTrack, "createKeyframeTrack");
2065
+
2066
+ // src/ProcessSlot.ts
2067
+ var ProcessSlot = class {
2068
+ static {
2069
+ __name(this, "ProcessSlot");
2070
+ }
2071
+ config;
2072
+ _elapsed = 0;
2073
+ _completed = true;
2074
+ _paused = false;
2075
+ /** Tags for filtering/grouping. */
2076
+ tags;
2077
+ constructor(config = {}) {
2078
+ this.config = config;
2079
+ this.tags = config.tags ?? [];
2080
+ }
2081
+ /** Whether the slot has completed (starts true). */
2082
+ get completed() {
2083
+ return this._completed;
2084
+ }
2085
+ /** Whether the slot is actively running (not completed and not paused). */
2086
+ get running() {
2087
+ return !this._completed && !this._paused;
2088
+ }
2089
+ /** Milliseconds elapsed since start. */
2090
+ get elapsed() {
2091
+ return this._elapsed;
2092
+ }
2093
+ /** Progress ratio 0..1 (elapsed / duration). 0 if no duration set. */
2094
+ get ratio() {
2095
+ const d = this.config.duration;
2096
+ if (d === void 0 || d <= 0) return 0;
2097
+ return Math.min(this._elapsed / d, 1);
2098
+ }
2099
+ /** Start the slot. No-op if already running (use restart() to force). */
2100
+ start(overrides) {
2101
+ if (!this._completed) return this;
2102
+ this._elapsed = 0;
2103
+ this._completed = false;
2104
+ this._paused = false;
2105
+ if (overrides) {
2106
+ this.config = { ...this.config, ...overrides };
2107
+ if (overrides.tags) {
2108
+ this.tags = overrides.tags;
2109
+ }
2110
+ }
2111
+ return this;
2112
+ }
2113
+ /** Cancel if running, then start fresh. Always restarts. */
2114
+ restart(overrides) {
2115
+ if (!this._completed) {
2116
+ this.config.cleanup?.();
2117
+ this._completed = true;
2118
+ }
2119
+ this._completed = true;
2120
+ return this.start(overrides);
2121
+ }
2122
+ /** Cancel the slot. Calls cleanup if running. */
2123
+ cancel() {
2124
+ if (this._completed) return;
2125
+ this.config.cleanup?.();
2126
+ this._completed = true;
2127
+ }
2128
+ /** Pause the slot. */
2129
+ pause() {
2130
+ if (!this._completed) this._paused = true;
2131
+ }
2132
+ /** Resume the slot. */
2133
+ resume() {
2134
+ this._paused = false;
2135
+ }
2136
+ /** Set/override the onComplete callback. Chainable. */
2137
+ onComplete(fn) {
2138
+ this.config = { ...this.config, onComplete: fn };
2139
+ return this;
2140
+ }
2141
+ /**
2142
+ * Advance the slot by dt milliseconds.
2143
+ * @internal — called by ProcessComponent
2144
+ */
2145
+ _tick(dt) {
2146
+ if (this._completed || this._paused) return;
2147
+ this._elapsed += dt;
2148
+ const result = this.config.update?.(dt, this._elapsed);
2149
+ const duration = this.config.duration;
2150
+ if (duration !== void 0 && this._elapsed >= duration) {
2151
+ if (this.config.loop && result !== true) {
2152
+ this._elapsed = this._elapsed % duration;
2153
+ return;
2154
+ }
2155
+ this._complete();
2156
+ return;
2157
+ }
2158
+ if (result === true) {
2159
+ if (this.config.loop) {
2160
+ this._elapsed = 0;
2161
+ return;
2162
+ }
2163
+ this._complete();
2164
+ }
2165
+ }
2166
+ _complete() {
2167
+ this._completed = true;
2168
+ try {
2169
+ this.config.onComplete?.();
2170
+ } finally {
2171
+ this.config.cleanup?.();
2172
+ }
2173
+ }
2174
+ };
2175
+
2176
+ // src/ProcessComponent.ts
2177
+ var ProcessComponent = class extends Component {
2178
+ static {
2179
+ __name(this, "ProcessComponent");
2180
+ }
2181
+ processes = /* @__PURE__ */ new Set();
2182
+ slots = /* @__PURE__ */ new Set();
2183
+ /**
2184
+ * Run a one-off process (tween, sequence, delay).
2185
+ * Optionally apply tags for cancel-by-tag.
2186
+ */
2187
+ run(process, options) {
2188
+ if (options?.tags?.length) {
2189
+ process.tags = [
2190
+ ...process.tags,
2191
+ ...options.tags
2192
+ ];
2193
+ }
2194
+ this.processes.add(process);
2195
+ return process;
2196
+ }
2197
+ /** Create a reusable, restartable process slot. */
2198
+ slot(config) {
2199
+ const s = new ProcessSlot(config);
2200
+ this.slots.add(s);
2201
+ return s;
2202
+ }
2203
+ /** Cancel all processes and slots, or only those matching a tag. */
2204
+ cancel(tag) {
2205
+ for (const p of this.processes) {
2206
+ if (tag === void 0 || p.tags.includes(tag)) {
2207
+ p.cancel();
2208
+ this.processes.delete(p);
2209
+ }
2210
+ }
2211
+ for (const s of this.slots) {
2212
+ if (tag === void 0 || s.tags.includes(tag)) {
2213
+ s.cancel();
2214
+ }
2215
+ }
2216
+ }
2217
+ /** Number of active (non-completed) processes and slots. */
2218
+ get count() {
2219
+ let n = 0;
2220
+ for (const p of this.processes) {
2221
+ if (!p.completed) n++;
2222
+ }
2223
+ for (const s of this.slots) {
2224
+ if (!s.completed) n++;
2225
+ }
2226
+ return n;
2227
+ }
2228
+ /**
2229
+ * Advance all processes and slots by dt milliseconds and remove completed one-offs.
2230
+ * @internal — called by ProcessSystem
2231
+ */
2232
+ _tick(dt) {
2233
+ for (const p of this.processes) {
2234
+ p._update(dt);
2235
+ if (p.completed) {
2236
+ this.processes.delete(p);
2237
+ }
2238
+ }
2239
+ for (const s of this.slots) {
2240
+ s._tick(dt);
2241
+ }
2242
+ }
2243
+ /** Cancel all processes and slots on entity destroy. */
2244
+ onDestroy() {
2245
+ this.cancel();
2246
+ }
2247
+ };
2248
+
2249
+ // src/KeyframeAnimator.ts
2250
+ var KeyframeAnimator = class extends Component {
2251
+ static {
2252
+ __name(this, "KeyframeAnimator");
2253
+ }
2254
+ defs;
2255
+ active = /* @__PURE__ */ new Map();
2256
+ pc = this.sibling(ProcessComponent);
2257
+ constructor(animations) {
2258
+ super();
2259
+ this.defs = animations;
2260
+ }
2261
+ /** Start (or restart) a named animation. */
2262
+ play(name) {
2263
+ const def = this.defs[name];
2264
+ if (!def) return;
2265
+ if (this.active.has(name)) {
2266
+ this.stopInternal(name, false);
2267
+ }
2268
+ def.onEnter?.();
2269
+ const opts = {
2270
+ keyframes: def.keyframes,
2271
+ setter: def.setter,
2272
+ onComplete: /* @__PURE__ */ __name(() => {
2273
+ this.active.delete(name);
2274
+ def.onExit?.(true);
2275
+ }, "onComplete")
2276
+ };
2277
+ if (def.loop !== void 0) opts.loop = def.loop;
2278
+ if (def.speed !== void 0) opts.speed = def.speed;
2279
+ if (def.duration !== void 0) opts.duration = def.duration;
2280
+ if (def.easing !== void 0) opts.easing = def.easing;
2281
+ const process = createKeyframeTrack(opts);
2282
+ this.active.set(name, process);
2283
+ this.pc.run(process);
2284
+ }
2285
+ /** Stop a named animation. */
2286
+ stop(name) {
2287
+ this.stopInternal(name, false);
2288
+ }
2289
+ /** Stop all playing animations. */
2290
+ stopAll() {
2291
+ for (const name of [...this.active.keys()]) {
2292
+ this.stopInternal(name, false);
2293
+ }
2294
+ }
2295
+ /** Whether a named animation is currently playing. */
2296
+ isPlaying(name) {
2297
+ return this.active.has(name);
2298
+ }
2299
+ onDestroy() {
2300
+ this.stopAll();
2301
+ }
2302
+ stopInternal(name, complete) {
2303
+ const process = this.active.get(name);
2304
+ if (!process) return;
2305
+ process.cancel();
2306
+ this.active.delete(name);
2307
+ this.defs[name]?.onExit?.(complete);
2308
+ }
2309
+ };
2310
+
2311
+ // src/Sequence.ts
2312
+ var Sequence = class {
2313
+ static {
2314
+ __name(this, "Sequence");
2315
+ }
2316
+ steps = [];
2317
+ _loop = false;
2318
+ _repeatCount;
2319
+ /** Add a step (Process or factory function). */
2320
+ then(step) {
2321
+ this.steps.push({
2322
+ type: "single",
2323
+ factories: [typeof step === "function" ? step : wrapInstance(step)]
2324
+ });
2325
+ return this;
2326
+ }
2327
+ /** Add a delay in ms. */
2328
+ wait(ms) {
2329
+ this.steps.push({
2330
+ type: "single",
2331
+ factories: [
2332
+ () => new Process({
2333
+ duration: ms,
2334
+ update: /* @__PURE__ */ __name(() => {
2335
+ }, "update")
2336
+ })
2337
+ ]
2338
+ });
2339
+ return this;
2340
+ }
2341
+ /** Add an instant callback. */
2342
+ call(fn) {
2343
+ this.steps.push({
2344
+ type: "single",
2345
+ factories: [
2346
+ () => new Process({
2347
+ update: /* @__PURE__ */ __name(() => {
2348
+ fn();
2349
+ return true;
2350
+ }, "update")
2351
+ })
2352
+ ]
2353
+ });
2354
+ return this;
2355
+ }
2356
+ /** Run steps in parallel (all must complete before sequence continues). */
2357
+ parallel(...steps) {
2358
+ this.steps.push({
2359
+ type: "parallel",
2360
+ factories: steps.map(
2361
+ (s) => typeof s === "function" ? s : wrapInstance(s)
2362
+ )
2363
+ });
2364
+ return this;
2365
+ }
2366
+ /** Loop the sequence indefinitely. */
2367
+ loop() {
2368
+ this._loop = true;
2369
+ return this;
2370
+ }
2371
+ /** Repeat the sequence a fixed number of times (1 = play once, 2 = play twice, etc.). */
2372
+ repeat(times) {
2373
+ this._repeatCount = times;
2374
+ return this;
2375
+ }
2376
+ /**
2377
+ * Build the sequence into a Process without registering with a scene.
2378
+ * Exposed for unit testing.
2379
+ * @internal
2380
+ */
2381
+ _build() {
2382
+ const steps = this.steps;
2383
+ const looping = this._loop;
2384
+ const repeatCount = this._repeatCount;
2385
+ let stepIndex = 0;
2386
+ let active = [];
2387
+ let iteration = 1;
2388
+ return new Process({
2389
+ update: /* @__PURE__ */ __name((dt) => {
2390
+ if (active.length === 0 && stepIndex < steps.length) {
2391
+ const step = steps[stepIndex];
2392
+ if (!step) return true;
2393
+ active = step.factories.map((f) => f());
2394
+ }
2395
+ for (const proc of active) {
2396
+ proc._update(dt);
2397
+ }
2398
+ if (active.every((p) => p.completed)) {
2399
+ active = [];
2400
+ stepIndex++;
2401
+ if (stepIndex >= steps.length) {
2402
+ if (looping) {
2403
+ stepIndex = 0;
2404
+ return false;
2405
+ }
2406
+ if (repeatCount !== void 0 && iteration < repeatCount) {
2407
+ iteration++;
2408
+ stepIndex = 0;
2409
+ return false;
2410
+ }
2411
+ return true;
2412
+ }
2413
+ }
2414
+ return false;
2415
+ }, "update")
2416
+ });
2417
+ }
2418
+ /** Build and start the sequence. Returns the wrapping Process. */
2419
+ start() {
2420
+ return this._build();
2421
+ }
2422
+ };
2423
+ function wrapInstance(proc) {
2424
+ return () => {
2425
+ proc._reset();
2426
+ return proc;
2427
+ };
2428
+ }
2429
+ __name(wrapInstance, "wrapInstance");
2430
+
2431
+ // src/TimerEntity.ts
2432
+ var TimerEntity = class extends Entity {
2433
+ static {
2434
+ __name(this, "TimerEntity");
2435
+ }
2436
+ pc;
2437
+ setup() {
2438
+ this.pc = this.add(new ProcessComponent());
2439
+ }
2440
+ run(process, options) {
2441
+ return this.pc.run(process, options);
2442
+ }
2443
+ slot(config) {
2444
+ return this.pc.slot(config);
2445
+ }
2446
+ cancel(tag) {
2447
+ this.pc.cancel(tag);
2448
+ }
2449
+ };
2450
+
2451
+ // src/ProcessSystem.ts
2452
+ var ProcessSystem = class extends System {
2453
+ static {
2454
+ __name(this, "ProcessSystem");
2455
+ }
2456
+ phase = "update" /* Update */;
2457
+ priority = 500;
2458
+ /** Global time scale multiplier. Stacks multiplicatively with per-scene timeScale. */
2459
+ timeScale = 1;
2460
+ sceneManager;
2461
+ sceneProcesses = /* @__PURE__ */ new Set();
2462
+ onRegister(context) {
2463
+ this.sceneManager = context.resolve(SceneManagerKey);
2464
+ }
2465
+ /** Add a scene-level process (not tied to any entity). */
2466
+ add(process) {
2467
+ this.sceneProcesses.add(process);
2468
+ return process;
2469
+ }
2470
+ /** Cancel scene-level processes, optionally by tag. */
2471
+ cancel(tag) {
2472
+ for (const p of this.sceneProcesses) {
2473
+ if (tag === void 0 || p.tags.includes(tag)) {
2474
+ p.cancel();
2475
+ }
2476
+ }
2477
+ if (tag === void 0) {
2478
+ this.sceneProcesses.clear();
2479
+ }
2480
+ }
2481
+ update(dt) {
2482
+ const globalScaledDt = dt * this.timeScale;
2483
+ for (const p of this.sceneProcesses) {
2484
+ p._update(globalScaledDt);
2485
+ if (p.completed) {
2486
+ this.sceneProcesses.delete(p);
2487
+ }
2488
+ }
2489
+ for (const scene of this.sceneManager.activeScenes) {
2490
+ const effectiveDt = globalScaledDt * scene.timeScale;
2491
+ for (const entity of scene.getEntities()) {
2492
+ if (entity.isDestroyed) continue;
2493
+ const pc = entity.tryGet(ProcessComponent);
2494
+ if (!pc) continue;
2495
+ pc._tick(effectiveDt);
2496
+ }
2497
+ }
2498
+ }
2499
+ };
2500
+
2501
+ // src/Inspector.ts
2502
+ var Inspector = class {
2503
+ static {
2504
+ __name(this, "Inspector");
2505
+ }
2506
+ engine;
2507
+ constructor(engine) {
2508
+ this.engine = engine;
2509
+ }
2510
+ /** Full state snapshot (serializable). */
2511
+ snapshot() {
2512
+ return {
2513
+ frameCount: this.engine.loop.frameCount,
2514
+ sceneStack: this.getSceneStack(),
2515
+ entityCount: this.countEntities(),
2516
+ systemCount: this.getSystems().length,
2517
+ errors: this.getErrors()
2518
+ };
2519
+ }
2520
+ /** Find entity by name in the active scene. */
2521
+ getEntityByName(name) {
2522
+ const entity = this.findActiveEntity(name);
2523
+ if (!entity) return void 0;
2524
+ return this.entityToSnapshot(entity);
2525
+ }
2526
+ /** Get entity position (from Transform component). */
2527
+ getEntityPosition(name) {
2528
+ const entity = this.findActiveEntity(name);
2529
+ if (!entity) return void 0;
2530
+ const transform = this.getTransform(entity);
2531
+ if (!transform) return void 0;
2532
+ return { x: transform.position.x, y: transform.position.y };
2533
+ }
2534
+ /** Check if an entity has a component by class name string. */
2535
+ hasComponent(entityName, componentClass) {
2536
+ return this.findComponentByName(entityName, componentClass) !== void 0;
2537
+ }
2538
+ /** Get component data (serializable subset) by class name string. */
2539
+ getComponentData(entityName, componentClass) {
2540
+ const comp = this.findComponentByName(entityName, componentClass);
2541
+ if (!comp) return void 0;
2542
+ return this.serializeComponent(comp);
2543
+ }
2544
+ /** Get all entities in the active scene as snapshots. */
2545
+ getEntities() {
2546
+ const scene = this.engine.scenes.active;
2547
+ if (!scene) return [];
2548
+ const result = [];
2549
+ for (const entity of scene.getEntities()) {
2550
+ if (!entity.isDestroyed) {
2551
+ result.push(this.entityToSnapshot(entity));
2552
+ }
2553
+ }
2554
+ return result;
2555
+ }
2556
+ /** Get scene stack info. */
2557
+ getSceneStack() {
2558
+ return this.engine.scenes.all.map((scene) => ({
2559
+ name: scene.name,
2560
+ entityCount: scene.getEntities().size,
2561
+ paused: scene.isPaused
2562
+ }));
2563
+ }
2564
+ /** Get active system info. */
2565
+ getSystems() {
2566
+ const scheduler = this.engine.context.tryResolve(SystemSchedulerKey);
2567
+ if (!scheduler) return [];
2568
+ return scheduler.getAllSystems().map((sys) => ({
2569
+ name: sys.constructor.name,
2570
+ phase: sys.phase,
2571
+ priority: sys.priority,
2572
+ enabled: sys.enabled
2573
+ }));
2574
+ }
2575
+ /** Get disabled components/systems from error boundary. */
2576
+ getErrors() {
2577
+ const boundary = this.engine.context.tryResolve(ErrorBoundaryKey);
2578
+ if (!boundary) return { disabledSystems: [], disabledComponents: [] };
2579
+ const disabled = boundary.getDisabled();
2580
+ return {
2581
+ disabledSystems: disabled.systems.map(
2582
+ (s) => s.system.constructor.name
2583
+ ),
2584
+ disabledComponents: disabled.components.map((c) => ({
2585
+ entity: c.component.entity?.name ?? "unknown",
2586
+ component: c.component.constructor.name,
2587
+ error: c.error
2588
+ }))
2589
+ };
2590
+ }
2591
+ findActiveEntity(name) {
2592
+ return this.engine.scenes.active?.findEntity(name);
2593
+ }
2594
+ findComponentByName(entityName, componentClass) {
2595
+ const entity = this.findActiveEntity(entityName);
2596
+ if (!entity) return void 0;
2597
+ for (const comp of entity.getAll()) {
2598
+ if (comp.constructor.name === componentClass) return comp;
2599
+ }
2600
+ return void 0;
2601
+ }
2602
+ entityToSnapshot(entity) {
2603
+ const transform = this.getTransform(entity);
2604
+ const snapshot = {
2605
+ id: entity.id,
2606
+ name: entity.name,
2607
+ tags: [...entity.tags],
2608
+ components: [...entity.getAll()].map((c) => c.constructor.name)
2609
+ };
2610
+ if (transform) {
2611
+ snapshot.position = {
2612
+ x: transform.position.x,
2613
+ y: transform.position.y
2614
+ };
2615
+ }
2616
+ return snapshot;
2617
+ }
2618
+ getTransform(entity) {
2619
+ return entity.has(Transform) ? entity.get(Transform) : void 0;
2620
+ }
2621
+ serializeComponent(comp) {
2622
+ const result = {};
2623
+ for (const key of Object.getOwnPropertyNames(comp)) {
2624
+ if (key === "entity") continue;
2625
+ const value = comp[key];
2626
+ if (typeof value !== "function") {
2627
+ result[key] = value;
2628
+ }
2629
+ }
2630
+ return result;
2631
+ }
2632
+ countEntities() {
2633
+ let count = 0;
2634
+ for (const scene of this.engine.scenes.all) {
2635
+ count += scene.getEntities().size;
2636
+ }
2637
+ return count;
2638
+ }
2639
+ };
2640
+
2641
+ // src/Engine.ts
2642
+ var Engine = class {
2643
+ static {
2644
+ __name(this, "Engine");
2645
+ }
2646
+ /** The dependency injection container. */
2647
+ context;
2648
+ /** The scene manager. */
2649
+ scenes;
2650
+ /** The event bus. */
2651
+ events;
2652
+ /** The game loop. */
2653
+ loop;
2654
+ /** The logger. */
2655
+ logger;
2656
+ /** The inspector (debug queries). */
2657
+ inspector;
2658
+ scheduler;
2659
+ errorBoundary;
2660
+ queryCache;
2661
+ /** The asset manager. */
2662
+ assets;
2663
+ plugins = /* @__PURE__ */ new Map();
2664
+ sortedPlugins = [];
2665
+ started = false;
2666
+ debug;
2667
+ constructor(config) {
2668
+ this.debug = config?.debug ?? false;
2669
+ this.context = new EngineContext();
2670
+ this.events = new EventBus();
2671
+ this.logger = new Logger(config?.logger);
2672
+ this.queryCache = new QueryCache();
2673
+ this.errorBoundary = new ErrorBoundary(this.logger);
2674
+ this.loop = new GameLoop(config);
2675
+ this.scenes = new SceneManager();
2676
+ this.scheduler = new SystemScheduler();
2677
+ this.inspector = new Inspector(this);
2678
+ this.assets = new AssetManager();
2679
+ this.scheduler.setErrorBoundary(this.errorBoundary);
2680
+ this.context.register(EngineKey, this);
2681
+ this.context.register(EventBusKey, this.events);
2682
+ this.context.register(SceneManagerKey, this.scenes);
2683
+ this.context.register(LoggerKey, this.logger);
2684
+ this.context.register(QueryCacheKey, this.queryCache);
2685
+ this.context.register(ErrorBoundaryKey, this.errorBoundary);
2686
+ this.context.register(GameLoopKey, this.loop);
2687
+ this.context.register(InspectorKey, this.inspector);
2688
+ this.context.register(SystemSchedulerKey, this.scheduler);
2689
+ this.context.register(AssetManagerKey, this.assets);
2690
+ this.scenes._setContext(this.context);
2691
+ this.registerBuiltInSystems();
2692
+ this.loop.setCallbacks({
2693
+ earlyUpdate: /* @__PURE__ */ __name((dt) => {
2694
+ this.logger.setFrame(this.loop.frameCount);
2695
+ this.scheduler.run("earlyUpdate" /* EarlyUpdate */, dt);
2696
+ }, "earlyUpdate"),
2697
+ fixedUpdate: /* @__PURE__ */ __name((dt) => this.scheduler.run("fixedUpdate" /* FixedUpdate */, dt), "fixedUpdate"),
2698
+ update: /* @__PURE__ */ __name((dt) => this.scheduler.run("update" /* Update */, dt), "update"),
2699
+ lateUpdate: /* @__PURE__ */ __name((dt) => this.scheduler.run("lateUpdate" /* LateUpdate */, dt), "lateUpdate"),
2700
+ render: /* @__PURE__ */ __name((dt) => this.scheduler.run("render" /* Render */, dt), "render"),
2701
+ endOfFrame: /* @__PURE__ */ __name((dt) => {
2702
+ this.scheduler.run("endOfFrame" /* EndOfFrame */, dt);
2703
+ this.scenes._flushDestroyQueues();
2704
+ }, "endOfFrame")
2705
+ });
2706
+ }
2707
+ /** Register a plugin. Must be called before start(). */
2708
+ use(plugin) {
2709
+ if (this.started) {
2710
+ throw new Error("Cannot register plugins after engine has started.");
2711
+ }
2712
+ if (this.plugins.has(plugin.name)) {
2713
+ throw new Error(`Plugin "${plugin.name}" is already registered.`);
2714
+ }
2715
+ this.plugins.set(plugin.name, plugin);
2716
+ return this;
2717
+ }
2718
+ /** Start the engine. Installs plugins in topological order, starts the game loop. */
2719
+ async start() {
2720
+ if (this.started) return;
2721
+ this.started = true;
2722
+ this.sortedPlugins = this.topologicalSort();
2723
+ const sorted = this.sortedPlugins;
2724
+ for (const plugin of sorted) {
2725
+ await plugin.install?.(this.context);
2726
+ }
2727
+ for (const plugin of sorted) {
2728
+ plugin.registerSystems?.(this.scheduler);
2729
+ }
2730
+ for (const sys of this.scheduler.getAllSystems()) {
2731
+ sys._setContext(this.context);
2732
+ sys.onRegister?.(this.context);
2733
+ }
2734
+ this.loop.start();
2735
+ if (this.debug && typeof globalThis !== "undefined") {
2736
+ globalThis["__yage__"] = {
2737
+ inspector: this.inspector,
2738
+ logger: this.logger
2739
+ };
2740
+ }
2741
+ for (const plugin of sorted) {
2742
+ plugin.onStart?.();
2743
+ }
2744
+ this.events.emit("engine:started", void 0);
2745
+ }
2746
+ /** Stop the engine. Destroys all scenes, plugins, and the game loop. */
2747
+ destroy() {
2748
+ this.events.emit("engine:stopped", void 0);
2749
+ this.loop.stop();
2750
+ this.scenes.clear();
2751
+ const allSystems = this.scheduler.getAllSystems();
2752
+ for (let i = allSystems.length - 1; i >= 0; i--) {
2753
+ allSystems[i].onUnregister?.();
2754
+ }
2755
+ for (let i = this.sortedPlugins.length - 1; i >= 0; i--) {
2756
+ const plugin = this.sortedPlugins[i];
2757
+ if (plugin) plugin.onDestroy?.();
2758
+ }
2759
+ if (this.debug && typeof globalThis !== "undefined" && "__yage__" in globalThis) {
2760
+ delete globalThis["__yage__"];
2761
+ }
2762
+ this.events.clear();
2763
+ this.started = false;
2764
+ }
2765
+ registerBuiltInSystems() {
2766
+ const fixedUpdate = new ComponentFixedUpdateSystem();
2767
+ const update = new ComponentUpdateSystem();
2768
+ const processSystem = new ProcessSystem();
2769
+ this.scheduler.add(fixedUpdate);
2770
+ this.scheduler.add(update);
2771
+ this.scheduler.add(processSystem);
2772
+ this.context.register(ProcessSystemKey, processSystem);
2773
+ }
2774
+ /**
2775
+ * Topological sort of plugins using Kahn's algorithm.
2776
+ * Errors on missing dependencies, circular dependencies, and duplicates.
2777
+ */
2778
+ topologicalSort() {
2779
+ const plugins = [...this.plugins.values()];
2780
+ const nameMap = /* @__PURE__ */ new Map();
2781
+ const inDegree = /* @__PURE__ */ new Map();
2782
+ const edges = /* @__PURE__ */ new Map();
2783
+ for (const p of plugins) {
2784
+ nameMap.set(p.name, p);
2785
+ inDegree.set(p.name, 0);
2786
+ edges.set(p.name, []);
2787
+ }
2788
+ for (const p of plugins) {
2789
+ for (const dep of p.dependencies ?? []) {
2790
+ if (!nameMap.has(dep)) {
2791
+ throw new Error(
2792
+ `Plugin "${p.name}" depends on "${dep}", which is not registered.`
2793
+ );
2794
+ }
2795
+ const depEdges = edges.get(dep);
2796
+ if (depEdges) depEdges.push(p.name);
2797
+ inDegree.set(p.name, (inDegree.get(p.name) ?? 0) + 1);
2798
+ }
2799
+ }
2800
+ const queue = [];
2801
+ for (const [name, degree] of inDegree) {
2802
+ if (degree === 0) queue.push(name);
2803
+ }
2804
+ const result = [];
2805
+ while (queue.length > 0) {
2806
+ const name = queue.shift();
2807
+ if (name === void 0) break;
2808
+ const plugin = nameMap.get(name);
2809
+ if (!plugin) continue;
2810
+ result.push(plugin);
2811
+ for (const dependent of edges.get(name) ?? []) {
2812
+ const newDegree = (inDegree.get(dependent) ?? 0) - 1;
2813
+ inDegree.set(dependent, newDegree);
2814
+ if (newDegree === 0) queue.push(dependent);
2815
+ }
2816
+ }
2817
+ if (result.length !== plugins.length) {
2818
+ throw new Error("Circular dependency detected among plugins.");
2819
+ }
2820
+ return result;
2821
+ }
2822
+ };
2823
+
2824
+ // src/test-utils.ts
2825
+ var _TestScene = class extends Scene {
2826
+ static {
2827
+ __name(this, "_TestScene");
2828
+ }
2829
+ name;
2830
+ constructor(name) {
2831
+ super();
2832
+ this.name = name;
2833
+ }
2834
+ };
2835
+ async function createTestEngine(config) {
2836
+ _resetEntityIdCounter();
2837
+ const engine = new Engine(config);
2838
+ await engine.start();
2839
+ return engine;
2840
+ }
2841
+ __name(createTestEngine, "createTestEngine");
2842
+ function createMockScene(name = "mock-scene") {
2843
+ _resetEntityIdCounter();
2844
+ const ctx = new EngineContext();
2845
+ const queryCache = new QueryCache();
2846
+ const bus = new EventBus();
2847
+ const logger = new Logger({ level: 0 /* Debug */ });
2848
+ const boundary = new ErrorBoundary(logger);
2849
+ ctx.register(QueryCacheKey, queryCache);
2850
+ ctx.register(EventBusKey, bus);
2851
+ ctx.register(ErrorBoundaryKey, boundary);
2852
+ const scene = new _TestScene(name);
2853
+ scene._setContext(ctx);
2854
+ return { scene, context: ctx };
2855
+ }
2856
+ __name(createMockScene, "createMockScene");
2857
+ function createMockEntity(name = "mock-entity") {
2858
+ const { scene, context } = createMockScene();
2859
+ const entity = scene.spawn(name);
2860
+ return { entity, scene, context };
2861
+ }
2862
+ __name(createMockEntity, "createMockEntity");
2863
+ function advanceFrames(engine, n, dtMs = 1e3 / 60) {
2864
+ for (let i = 0; i < n; i++) {
2865
+ engine.loop.tick(dtMs);
2866
+ }
2867
+ }
2868
+ __name(advanceFrames, "advanceFrames");
2869
+
2870
+ // src/index.ts
2871
+ var VERSION = "0.0.0";
2872
+ export {
2873
+ AssetHandle,
2874
+ AssetManager,
2875
+ AssetManagerKey,
2876
+ Component,
2877
+ ComponentFixedUpdateSystem,
2878
+ ComponentUpdateSystem,
2879
+ Engine,
2880
+ EngineContext,
2881
+ EngineKey,
2882
+ Entity,
2883
+ ErrorBoundary,
2884
+ ErrorBoundaryKey,
2885
+ EventBus,
2886
+ EventBusKey,
2887
+ EventToken,
2888
+ GameLoop,
2889
+ GameLoopKey,
2890
+ Inspector,
2891
+ InspectorKey,
2892
+ KeyframeAnimator,
2893
+ LogLevel,
2894
+ Logger,
2895
+ LoggerKey,
2896
+ MathUtils,
2897
+ Phase,
2898
+ Process,
2899
+ ProcessComponent,
2900
+ ProcessSlot,
2901
+ ProcessSystem,
2902
+ ProcessSystemKey,
2903
+ QueryCache,
2904
+ QueryCacheKey,
2905
+ QueryResult,
2906
+ SERIALIZABLE_KEY,
2907
+ Scene,
2908
+ SceneManager,
2909
+ SceneManagerKey,
2910
+ Sequence,
2911
+ SerializableRegistry,
2912
+ ServiceKey,
2913
+ System,
2914
+ SystemScheduler,
2915
+ SystemSchedulerKey,
2916
+ TimerEntity,
2917
+ TraitToken,
2918
+ Transform,
2919
+ Tween,
2920
+ VERSION,
2921
+ Vec2,
2922
+ _resetEntityIdCounter,
2923
+ advanceFrames,
2924
+ createKeyframeTrack,
2925
+ createMockEntity,
2926
+ createMockScene,
2927
+ createTestEngine,
2928
+ defineBlueprint,
2929
+ defineEvent,
2930
+ defineTrait,
2931
+ easeInOutQuad,
2932
+ easeInQuad,
2933
+ easeLinear,
2934
+ easeOutBounce,
2935
+ easeOutQuad,
2936
+ filterEntities,
2937
+ getSerializableType,
2938
+ interpolate,
2939
+ isSerializable,
2940
+ serializable,
2941
+ trait
2942
+ };
2943
+ //# sourceMappingURL=index.js.map