minimojs 1.0.0-alpha.3 → 1.0.0-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +85 -285
  2. package/dist/internal/AnimationSystem.js +345 -0
  3. package/dist/internal/AssetSystem.d.ts +1 -0
  4. package/dist/internal/AssetSystem.js +186 -0
  5. package/dist/internal/BackgroundSystem.d.ts +1 -0
  6. package/dist/internal/BackgroundSystem.js +26 -0
  7. package/dist/internal/CanvasSystem.d.ts +1 -0
  8. package/dist/internal/CanvasSystem.js +51 -0
  9. package/dist/internal/DesignNamingCheck.d.ts +1 -0
  10. package/dist/internal/DesignNamingCheck.js +7 -0
  11. package/dist/internal/ExplosionSystem.d.ts +1 -0
  12. package/dist/internal/ExplosionSystem.js +540 -0
  13. package/dist/internal/InputSystem.d.ts +1 -0
  14. package/dist/internal/InputSystem.js +292 -0
  15. package/dist/internal/LoopSystem.d.ts +1 -0
  16. package/dist/internal/LoopSystem.js +61 -0
  17. package/dist/internal/PhysicsSystem.d.ts +1 -0
  18. package/dist/internal/PhysicsSystem.js +174 -0
  19. package/dist/internal/RenderSystem.d.ts +1 -0
  20. package/dist/internal/RenderSystem.js +1021 -0
  21. package/dist/internal/SoundSystem.d.ts +1 -0
  22. package/dist/internal/SoundSystem.js +55 -0
  23. package/dist/internal/SpriteSystem.d.ts +1 -0
  24. package/dist/internal/SpriteSystem.js +32 -0
  25. package/dist/internal/TextSystem.d.ts +1 -0
  26. package/dist/internal/TextSystem.js +16 -0
  27. package/dist/internal/TimerSystem.d.ts +1 -0
  28. package/dist/internal/TimerSystem.js +43 -0
  29. package/dist/internal/TrailSystem.d.ts +1 -0
  30. package/dist/internal/TrailSystem.js +116 -0
  31. package/dist/internal/TransitionSystem.d.ts +1 -0
  32. package/dist/internal/TransitionSystem.js +74 -0
  33. package/dist/internal/arcade-racer/ArcadeRacerCollisionSystem.d.ts +1 -0
  34. package/dist/internal/arcade-racer/ArcadeRacerCollisionSystem.js +198 -0
  35. package/dist/internal/arcade-racer/ArcadeRacerLaneSystem.d.ts +1 -0
  36. package/dist/internal/arcade-racer/ArcadeRacerLaneSystem.js +120 -0
  37. package/dist/internal/arcade-racer/ArcadeRacerRenderSystem.d.ts +1 -0
  38. package/dist/internal/arcade-racer/ArcadeRacerRenderSystem.js +599 -0
  39. package/dist/internal/arcade-racer/ArcadeRacerRoadSprite.d.ts +1 -0
  40. package/dist/internal/arcade-racer/ArcadeRacerRoadSprite.js +13 -0
  41. package/dist/internal/arcade-racer/ArcadeRacerTrackSystem.d.ts +1 -0
  42. package/dist/internal/arcade-racer/ArcadeRacerTrackSystem.js +447 -0
  43. package/dist/minimo-ambient.d.ts +299 -0
  44. package/dist/minimo-ambient.js +1445 -0
  45. package/dist/minimo-arcaderacer.d.ts +1431 -0
  46. package/dist/minimo-arcaderacer.js +2060 -0
  47. package/dist/minimo-cards.d.ts +359 -0
  48. package/dist/minimo-cards.js +819 -0
  49. package/dist/minimo-format.d.ts +407 -0
  50. package/dist/minimo-format.js +446 -0
  51. package/dist/minimo.d.ts +1754 -225
  52. package/dist/minimo.js +2551 -761
  53. package/package.json +10 -3
  54. package/dist/animations.js +0 -30
  55. package/dist/audio.js +0 -17
  56. package/dist/game.js +0 -1105
  57. package/dist/input.js +0 -185
  58. package/dist/internal-types.js +0 -4
  59. package/dist/physics.js +0 -10
  60. package/dist/render.js +0 -75
  61. package/dist/sprite.js +0 -149
  62. package/dist/timers.js +0 -23
  63. /package/dist/{pointer-info.js → internal/AnimationSystem.d.ts} +0 -0
@@ -0,0 +1,345 @@
1
+ /** @internal */
2
+ export class AnimationSystem {
3
+ constructor() {
4
+ /** @internal */
5
+ this._animations = [];
6
+ /** @internal */
7
+ this._deformAnimations = [];
8
+ /** @internal */
9
+ this._motionAnimations = [];
10
+ /** @internal */
11
+ this._shakeAnimations = [];
12
+ /** @internal */
13
+ this._blinkAnimations = [];
14
+ /** @internal */
15
+ this._flickerAnimations = [];
16
+ /** @internal */
17
+ this._renderDataSprites = new Set();
18
+ }
19
+ update(dtMs) {
20
+ const animations = this._animations;
21
+ const toRemove = [];
22
+ for (let i = 0; i < animations.length; i++) {
23
+ const anim = animations[i];
24
+ anim.elapsed += dtMs;
25
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
26
+ anim.sprite[anim.property] = anim.from + (anim.to - anim.from) * t;
27
+ if (t >= 1) {
28
+ toRemove.push(i);
29
+ anim.onComplete?.();
30
+ }
31
+ }
32
+ for (let i = toRemove.length - 1; i >= 0; i--) {
33
+ animations.splice(toRemove[i], 1);
34
+ }
35
+ const deformAnimations = this._deformAnimations;
36
+ const deformToRemove = [];
37
+ for (let i = 0; i < deformAnimations.length; i++) {
38
+ const anim = deformAnimations[i];
39
+ anim.elapsed += dtMs;
40
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
41
+ if (t >= 1) {
42
+ deformToRemove.push(i);
43
+ anim.onComplete?.();
44
+ }
45
+ }
46
+ for (let i = deformToRemove.length - 1; i >= 0; i--) {
47
+ deformAnimations.splice(deformToRemove[i], 1);
48
+ }
49
+ const motionAnimations = this._motionAnimations;
50
+ const motionToRemove = [];
51
+ for (let i = 0; i < motionAnimations.length; i++) {
52
+ const anim = motionAnimations[i];
53
+ anim.elapsed += dtMs;
54
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
55
+ if (anim.kind === "arc") {
56
+ anim.sprite.x = anim.fromX + (anim.toX - anim.fromX) * t;
57
+ anim.sprite.y =
58
+ anim.fromY +
59
+ (anim.toY - anim.fromY) * t -
60
+ anim.magnitude * 4 * t * (1 - t);
61
+ }
62
+ else if (anim.kind === "bounce") {
63
+ anim.sprite.x = anim.fromX;
64
+ anim.sprite.y = anim.fromY - anim.magnitude * 4 * t * (1 - t);
65
+ }
66
+ else {
67
+ anim.sprite.x = anim.fromX;
68
+ anim.sprite.y = anim.fromY - Math.sin(t * Math.PI) * anim.magnitude;
69
+ }
70
+ if (t >= 1) {
71
+ motionToRemove.push(i);
72
+ anim.onComplete?.();
73
+ }
74
+ }
75
+ for (let i = motionToRemove.length - 1; i >= 0; i--) {
76
+ motionAnimations.splice(motionToRemove[i], 1);
77
+ }
78
+ this.rebuildRenderData(dtMs);
79
+ }
80
+ animateAlpha(sprite, to, durationMs, onComplete) {
81
+ const animations = this._animations.filter((a) => !(a.sprite === sprite && a.property === "alpha"));
82
+ animations.push({
83
+ sprite,
84
+ property: "alpha",
85
+ from: sprite.alpha,
86
+ to,
87
+ durationMs,
88
+ elapsed: 0,
89
+ onComplete,
90
+ });
91
+ this._animations = animations;
92
+ }
93
+ animateRotation(sprite, to, durationMs, onComplete) {
94
+ const animations = this._animations.filter((a) => !(a.sprite === sprite && a.property === "rotation"));
95
+ animations.push({
96
+ sprite,
97
+ property: "rotation",
98
+ from: sprite.rotation,
99
+ to,
100
+ durationMs,
101
+ elapsed: 0,
102
+ onComplete,
103
+ });
104
+ this._animations = animations;
105
+ }
106
+ animateDeform(sprite, toScaleX, toScaleY, pivotX, pivotY, durationMs, onComplete) {
107
+ const current = sprite._renderData;
108
+ const fromScaleX = current?.scaleX ?? 1;
109
+ const fromScaleY = current?.scaleY ?? 1;
110
+ const deformAnimations = this._deformAnimations.filter((a) => a.sprite !== sprite);
111
+ deformAnimations.push({
112
+ sprite,
113
+ fromScaleX,
114
+ fromScaleY,
115
+ toScaleX: this.sanitizeScale(toScaleX),
116
+ toScaleY: this.sanitizeScale(toScaleY),
117
+ pivotX: this.sanitizePivot(pivotX),
118
+ pivotY: this.sanitizePivot(pivotY),
119
+ durationMs,
120
+ elapsed: 0,
121
+ onComplete,
122
+ });
123
+ this._deformAnimations = deformAnimations;
124
+ }
125
+ animateShake(sprite, intensity, durationMs, onComplete) {
126
+ const animations = this._shakeAnimations.filter((a) => a.sprite !== sprite);
127
+ animations.push({
128
+ sprite,
129
+ intensity: this.sanitizeNonNegative(intensity),
130
+ durationMs: this.sanitizeDuration(durationMs),
131
+ elapsed: 0,
132
+ onComplete,
133
+ });
134
+ this._shakeAnimations = animations;
135
+ }
136
+ animateBounce(sprite, height, durationMs, onComplete) {
137
+ this.replaceMotionAnimation({
138
+ sprite,
139
+ kind: "bounce",
140
+ fromX: sprite.x,
141
+ fromY: sprite.y,
142
+ toX: sprite.x,
143
+ toY: sprite.y,
144
+ magnitude: this.sanitizeNonNegative(height),
145
+ durationMs: this.sanitizeDuration(durationMs),
146
+ elapsed: 0,
147
+ onComplete,
148
+ });
149
+ }
150
+ animateFloat(sprite, distance, durationMs, onComplete) {
151
+ this.replaceMotionAnimation({
152
+ sprite,
153
+ kind: "float",
154
+ fromX: sprite.x,
155
+ fromY: sprite.y,
156
+ toX: sprite.x,
157
+ toY: sprite.y,
158
+ magnitude: this.sanitizeNonNegative(distance),
159
+ durationMs: this.sanitizeDuration(durationMs),
160
+ elapsed: 0,
161
+ onComplete,
162
+ });
163
+ }
164
+ animateArc(sprite, toX, toY, arcHeight, durationMs, onComplete) {
165
+ this.replaceMotionAnimation({
166
+ sprite,
167
+ kind: "arc",
168
+ fromX: sprite.x,
169
+ fromY: sprite.y,
170
+ toX,
171
+ toY,
172
+ magnitude: this.sanitizeNonNegative(arcHeight),
173
+ durationMs: this.sanitizeDuration(durationMs),
174
+ elapsed: 0,
175
+ onComplete,
176
+ });
177
+ }
178
+ animateBlink(sprite, times, durationMs, onComplete) {
179
+ const animations = this._blinkAnimations.filter((a) => a.sprite !== sprite);
180
+ animations.push({
181
+ sprite,
182
+ times: Math.max(1, Math.round(Number.isFinite(times) ? times : 1)),
183
+ durationMs: this.sanitizeDuration(durationMs),
184
+ elapsed: 0,
185
+ onComplete,
186
+ });
187
+ this._blinkAnimations = animations;
188
+ }
189
+ animateFlicker(sprite, durationMs, onComplete) {
190
+ const animations = this._flickerAnimations.filter((a) => a.sprite !== sprite);
191
+ animations.push({
192
+ sprite,
193
+ durationMs: this.sanitizeDuration(durationMs),
194
+ elapsed: 0,
195
+ onComplete,
196
+ });
197
+ this._flickerAnimations = animations;
198
+ }
199
+ clearSpriteAnimations(sprite) {
200
+ this._animations = this._animations.filter((a) => a.sprite !== sprite);
201
+ this._deformAnimations = this._deformAnimations.filter((a) => a.sprite !== sprite);
202
+ this._motionAnimations = this._motionAnimations.filter((a) => a.sprite !== sprite);
203
+ this._shakeAnimations = this._shakeAnimations.filter((a) => a.sprite !== sprite);
204
+ this._blinkAnimations = this._blinkAnimations.filter((a) => a.sprite !== sprite);
205
+ this._flickerAnimations = this._flickerAnimations.filter((a) => a.sprite !== sprite);
206
+ this._renderDataSprites.delete(sprite);
207
+ sprite._renderData = null;
208
+ }
209
+ clearAll() {
210
+ for (const sprite of this._renderDataSprites) {
211
+ sprite._renderData = null;
212
+ }
213
+ this._animations = [];
214
+ this._deformAnimations = [];
215
+ this._motionAnimations = [];
216
+ this._shakeAnimations = [];
217
+ this._blinkAnimations = [];
218
+ this._flickerAnimations = [];
219
+ this._renderDataSprites.clear();
220
+ }
221
+ /** @internal */
222
+ sanitizeScale(value) {
223
+ if (!Number.isFinite(value))
224
+ return 1;
225
+ return Math.max(0, value);
226
+ }
227
+ /** @internal */
228
+ sanitizeDuration(value) {
229
+ if (!Number.isFinite(value))
230
+ return 1;
231
+ return Math.max(1, value);
232
+ }
233
+ /** @internal */
234
+ sanitizeNonNegative(value) {
235
+ if (!Number.isFinite(value))
236
+ return 0;
237
+ return Math.max(0, value);
238
+ }
239
+ /** @internal */
240
+ sanitizePivot(value) {
241
+ if (!Number.isFinite(value))
242
+ return 0.5;
243
+ return Math.max(0, Math.min(1, value));
244
+ }
245
+ /** @internal */
246
+ replaceMotionAnimation(entry) {
247
+ const animations = this._motionAnimations.filter((a) => a.sprite !== entry.sprite);
248
+ animations.push(entry);
249
+ this._motionAnimations = animations;
250
+ }
251
+ /** @internal */
252
+ rebuildRenderData(dtMs) {
253
+ const next = new Map();
254
+ const nextSprites = new Set();
255
+ const upsert = (sprite) => {
256
+ let state = next.get(sprite);
257
+ if (!state) {
258
+ state = {
259
+ scaleX: 1,
260
+ scaleY: 1,
261
+ pivotX: 0.5,
262
+ pivotY: 0.5,
263
+ offsetX: 0,
264
+ offsetY: 0,
265
+ alphaMultiplier: 1,
266
+ };
267
+ next.set(sprite, state);
268
+ nextSprites.add(sprite);
269
+ }
270
+ return state;
271
+ };
272
+ for (const anim of this._deformAnimations) {
273
+ const state = upsert(anim.sprite);
274
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
275
+ state.scaleX = anim.fromScaleX + (anim.toScaleX - anim.fromScaleX) * t;
276
+ state.scaleY = anim.fromScaleY + (anim.toScaleY - anim.fromScaleY) * t;
277
+ state.pivotX = anim.pivotX;
278
+ state.pivotY = anim.pivotY;
279
+ }
280
+ const shakeToRemove = [];
281
+ for (let i = 0; i < this._shakeAnimations.length; i++) {
282
+ const anim = this._shakeAnimations[i];
283
+ anim.elapsed += dtMs;
284
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
285
+ const decay = 1 - t;
286
+ const state = upsert(anim.sprite);
287
+ state.offsetX += (Math.random() * 2 - 1) * anim.intensity * decay;
288
+ state.offsetY += (Math.random() * 2 - 1) * anim.intensity * decay;
289
+ if (t >= 1) {
290
+ shakeToRemove.push(i);
291
+ anim.onComplete?.();
292
+ }
293
+ }
294
+ for (let i = shakeToRemove.length - 1; i >= 0; i--) {
295
+ this._shakeAnimations.splice(shakeToRemove[i], 1);
296
+ }
297
+ const blinkToRemove = [];
298
+ for (let i = 0; i < this._blinkAnimations.length; i++) {
299
+ const anim = this._blinkAnimations[i];
300
+ anim.elapsed += dtMs;
301
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
302
+ const state = upsert(anim.sprite);
303
+ const step = Math.floor(t * anim.times * 2);
304
+ state.alphaMultiplier *= step % 2 === 0 ? 1 : 0;
305
+ if (t >= 1) {
306
+ blinkToRemove.push(i);
307
+ anim.onComplete?.();
308
+ }
309
+ }
310
+ for (let i = blinkToRemove.length - 1; i >= 0; i--) {
311
+ this._blinkAnimations.splice(blinkToRemove[i], 1);
312
+ }
313
+ const flickerToRemove = [];
314
+ for (let i = 0; i < this._flickerAnimations.length; i++) {
315
+ const anim = this._flickerAnimations[i];
316
+ anim.elapsed += dtMs;
317
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
318
+ const state = upsert(anim.sprite);
319
+ state.alphaMultiplier *= 0.25 + Math.random() * 0.75;
320
+ if (t >= 1) {
321
+ flickerToRemove.push(i);
322
+ anim.onComplete?.();
323
+ }
324
+ }
325
+ for (let i = flickerToRemove.length - 1; i >= 0; i--) {
326
+ this._flickerAnimations.splice(flickerToRemove[i], 1);
327
+ }
328
+ for (const sprite of this._renderDataSprites) {
329
+ if (!nextSprites.has(sprite)) {
330
+ sprite._renderData = null;
331
+ }
332
+ }
333
+ for (const [sprite, state] of next.entries()) {
334
+ const isDefault = state.scaleX === 1 &&
335
+ state.scaleY === 1 &&
336
+ state.pivotX === 0.5 &&
337
+ state.pivotY === 0.5 &&
338
+ state.offsetX === 0 &&
339
+ state.offsetY === 0 &&
340
+ state.alphaMultiplier === 1;
341
+ sprite._renderData = isDefault ? null : state;
342
+ }
343
+ this._renderDataSprites = nextSprites;
344
+ }
345
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,186 @@
1
+ /** @internal */
2
+ export class AssetSystem {
3
+ constructor() {
4
+ /** @internal */
5
+ this._queuedImages = new Map();
6
+ /** @internal */
7
+ this._loadedImages = new Map();
8
+ /** @internal */
9
+ this._imageSources = new Map();
10
+ /** @internal */
11
+ this._imageVersions = new Map();
12
+ /**
13
+ * The asset file that declared each key, as the object passed to
14
+ * `loadAssets`. Keys are unique in the whole game, so a key belongs to
15
+ * exactly one asset file, or to `loadImage` and `createTexture`.
16
+ *
17
+ * @internal
18
+ */
19
+ this._assetFileByKey = new Map();
20
+ /** @internal */
21
+ this._registeredAssetFiles = new WeakSet();
22
+ }
23
+ queueImage(key, src) {
24
+ const safeKey = key.trim();
25
+ const safeSrc = src.trim();
26
+ if (safeKey.length === 0) {
27
+ throw new Error("MinimoJS: Image keys must be non-empty strings.");
28
+ }
29
+ if (safeSrc.length === 0) {
30
+ throw new Error("MinimoJS: Image sources must be non-empty strings.");
31
+ }
32
+ this.refuseAssetFileKey(safeKey, "loadImage()");
33
+ this.enqueueImage(safeKey, safeSrc);
34
+ }
35
+ /**
36
+ * Registers a parsed asset file and queues its images. `source` is the
37
+ * object the game passed, which identifies the file, so loading the same
38
+ * file again, as a scene's `onPreload()` does on every entry, only queues
39
+ * images that are not loaded.
40
+ *
41
+ * Every key is checked before anything is queued, so a refused file
42
+ * registers nothing. Returns `true` the first time a file is registered.
43
+ */
44
+ registerAssetFile(file, source) {
45
+ for (const entry of file.assets) {
46
+ const owner = this._assetFileByKey.get(entry.key);
47
+ if (owner === undefined) {
48
+ if (this._imageSources.has(entry.key) || this._loadedImages.has(entry.key)) {
49
+ throw new Error(`MinimoJS: Asset key "${entry.key}" is already registered with loadImage() or createTexture(). Keys must be unique in the whole game.`);
50
+ }
51
+ }
52
+ else if (owner !== source) {
53
+ throw new Error(`MinimoJS: Asset key "${entry.key}" is declared by two asset files. Keys must be unique in the whole game.`);
54
+ }
55
+ }
56
+ for (const entry of file.assets) {
57
+ this._assetFileByKey.set(entry.key, source);
58
+ if (entry.type === "image") {
59
+ this.enqueueImage(entry.key, entry.src.trim());
60
+ }
61
+ }
62
+ const isFirstRegistration = !this._registeredAssetFiles.has(source);
63
+ this._registeredAssetFiles.add(source);
64
+ return isFirstRegistration;
65
+ }
66
+ /** @internal */
67
+ enqueueImage(safeKey, safeSrc) {
68
+ const existingSrc = this._imageSources.get(safeKey);
69
+ if (existingSrc !== undefined && existingSrc !== safeSrc) {
70
+ throw new Error(`MinimoJS: Image key "${safeKey}" is already registered with a different source.`);
71
+ }
72
+ this._imageSources.set(safeKey, safeSrc);
73
+ this._queuedImages.set(safeKey, safeSrc);
74
+ }
75
+ createTexture(key, width, height, painter) {
76
+ const safeKey = key.trim();
77
+ if (safeKey.length === 0) {
78
+ throw new Error("MinimoJS: Texture keys must be non-empty strings.");
79
+ }
80
+ if (typeof painter !== "function") {
81
+ throw new Error("MinimoJS: createTexture() requires a painter function.");
82
+ }
83
+ this.refuseAssetFileKey(safeKey, "createTexture()");
84
+ const canvas = document.createElement("canvas");
85
+ canvas.width = Math.max(1, Math.round(Number.isFinite(width) ? width : 1));
86
+ canvas.height = Math.max(1, Math.round(Number.isFinite(height) ? height : 1));
87
+ const ctx = canvas.getContext("2d");
88
+ if (!ctx) {
89
+ throw new Error("MinimoJS: Could not acquire a texture rendering context.");
90
+ }
91
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
92
+ painter(ctx, canvas);
93
+ this._queuedImages.delete(safeKey);
94
+ this._imageSources.delete(safeKey);
95
+ this._loadedImages.set(safeKey, canvas);
96
+ this.bumpImageVersion(safeKey);
97
+ return canvas;
98
+ }
99
+ getImage(key) {
100
+ return this._loadedImages.get(key);
101
+ }
102
+ getImageVersion(key) {
103
+ return this._imageVersions.get(key) ?? 0;
104
+ }
105
+ hasImage(key) {
106
+ return this._loadedImages.has(key);
107
+ }
108
+ getQueuedImageCount() {
109
+ let total = 0;
110
+ for (const key of this._queuedImages.keys()) {
111
+ if (!this._loadedImages.has(key)) {
112
+ total++;
113
+ }
114
+ }
115
+ return total;
116
+ }
117
+ /**
118
+ * Loads every queued image that is not loaded yet, settling all of them
119
+ * before returning instead of stopping at the first failure.
120
+ *
121
+ * Loaded images leave the queue; failed ones stay in it, so a retry loads
122
+ * only them. Returns the keys that failed, empty when all loaded.
123
+ */
124
+ async loadQueuedImages(onProgress) {
125
+ const entries = [...this._queuedImages.entries()].filter(([key]) => !this._loadedImages.has(key));
126
+ const total = entries.length;
127
+ let settled = 0;
128
+ const failed = [];
129
+ onProgress?.(0, total, null);
130
+ if (total === 0) {
131
+ this._queuedImages.clear();
132
+ return failed;
133
+ }
134
+ await Promise.all(entries.map(async ([key, src]) => {
135
+ try {
136
+ const image = await this.loadImage(src);
137
+ this._loadedImages.set(key, image);
138
+ this.bumpImageVersion(key);
139
+ this._queuedImages.delete(key);
140
+ }
141
+ catch {
142
+ failed.push(key);
143
+ }
144
+ settled += 1;
145
+ onProgress?.(settled, total, key);
146
+ }));
147
+ for (const key of [...this._queuedImages.keys()]) {
148
+ if (this._loadedImages.has(key)) {
149
+ this._queuedImages.delete(key);
150
+ }
151
+ }
152
+ return failed;
153
+ }
154
+ /** Returns the source registered for an image key, if any. */
155
+ getImageSource(key) {
156
+ return this._imageSources.get(key);
157
+ }
158
+ /**
159
+ * Drops queued images that were not loaded, so a later load does not pick
160
+ * them up. Their keys may be registered again.
161
+ */
162
+ discardQueuedImages() {
163
+ this._queuedImages.clear();
164
+ }
165
+ /** @internal */
166
+ refuseAssetFileKey(safeKey, caller) {
167
+ if (this._assetFileByKey.has(safeKey)) {
168
+ throw new Error(`MinimoJS: ${caller} cannot use the key "${safeKey}", which an asset file declares. Keys must be unique in the whole game.`);
169
+ }
170
+ }
171
+ /** @internal */
172
+ loadImage(src) {
173
+ return new Promise((resolve, reject) => {
174
+ const image = new Image();
175
+ image.onload = () => resolve(image);
176
+ image.onerror = () => {
177
+ reject(new Error(`MinimoJS: Failed to load image "${src}".`));
178
+ };
179
+ image.src = src;
180
+ });
181
+ }
182
+ /** @internal */
183
+ bumpImageVersion(key) {
184
+ this._imageVersions.set(key, (this._imageVersions.get(key) ?? 0) + 1);
185
+ }
186
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ /** @internal */
2
+ export class BackgroundSystem {
3
+ constructor() {
4
+ /** @internal */
5
+ this._layers = [];
6
+ }
7
+ add(layer) {
8
+ this._layers.push(layer);
9
+ return layer;
10
+ }
11
+ destroyLayer(layer) {
12
+ const index = this._layers.indexOf(layer);
13
+ if (index !== -1) {
14
+ this._layers.splice(index, 1);
15
+ }
16
+ }
17
+ getLayers() {
18
+ return [...this._layers];
19
+ }
20
+ getMutableLayers() {
21
+ return this._layers;
22
+ }
23
+ clearAll() {
24
+ this._layers = [];
25
+ }
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ /** @internal */
2
+ export class CanvasSystem {
3
+ constructor(/** @internal */ canvas) {
4
+ this.canvas = canvas;
5
+ /** @internal */
6
+ this.onResize = () => this.applyResponsiveCanvasLayout();
7
+ /** @internal */
8
+ this.onViewportChange = () => this.applyResponsiveCanvasLayout();
9
+ }
10
+ initialize() {
11
+ const mountCanvas = () => {
12
+ if (document.body && !this.canvas.isConnected) {
13
+ document.body.appendChild(this.canvas);
14
+ }
15
+ this.applyResponsiveCanvasLayout();
16
+ };
17
+ if (document.body) {
18
+ mountCanvas();
19
+ }
20
+ else {
21
+ window.addEventListener("DOMContentLoaded", mountCanvas, { once: true });
22
+ }
23
+ window.addEventListener("resize", this.onResize);
24
+ window.visualViewport?.addEventListener("resize", this.onViewportChange);
25
+ window.visualViewport?.addEventListener("scroll", this.onViewportChange);
26
+ }
27
+ /** @internal */
28
+ applyResponsiveCanvasLayout() {
29
+ if (!document.body)
30
+ return;
31
+ const viewportW = Math.max(1, Math.floor(window.visualViewport?.width ??
32
+ window.innerWidth ??
33
+ document.documentElement.clientWidth ??
34
+ 1));
35
+ const viewportH = Math.max(1, Math.floor(window.visualViewport?.height ??
36
+ window.innerHeight ??
37
+ document.documentElement.clientHeight ??
38
+ 1));
39
+ document.body.style.margin = "0";
40
+ document.body.style.height = `${viewportH}px`;
41
+ document.body.style.minHeight = `${viewportH}px`;
42
+ document.body.style.display = "grid";
43
+ document.body.style.placeItems = "center";
44
+ document.body.style.touchAction = "manipulation";
45
+ document.body.style.overflow = "hidden";
46
+ const scale = Math.min(viewportW / this.canvas.width, viewportH / this.canvas.height);
47
+ const safeScale = Number.isFinite(scale) && scale > 0 ? scale : 1;
48
+ this.canvas.style.width = `${Math.floor(this.canvas.width * safeScale)}px`;
49
+ this.canvas.style.height = `${Math.floor(this.canvas.height * safeScale)}px`;
50
+ }
51
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ // A compile-time check, with no runtime code, that every property of a design
2
+ // object, except `id` and `type`, exists on its runtime class under the same
3
+ // name and with a compatible type (X-002). Designs use the runtime's names so
4
+ // agents learn one vocabulary for code and files. The build fails when a
5
+ // property is misnamed; a property that must be named differently would be
6
+ // declared here as an explicit exception.
7
+ export {};
@@ -0,0 +1 @@
1
+ export {};