@wasm-gaming/engine-specs 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +63 -54
  2. package/demo/app.html +227 -0
  3. package/demo/canvas-renderer.js +153 -0
  4. package/demo/components/asset-picker.html +137 -0
  5. package/demo/components/call-log.html +33 -0
  6. package/demo/components/engine-header.html +78 -0
  7. package/demo/components/lifecycle-bar.html +69 -0
  8. package/demo/components/property-table.html +119 -0
  9. package/demo/demo.css +26 -130
  10. package/demo/demo.sdk.js +405 -203
  11. package/demo/index.html +7 -56
  12. package/demo/tsconfig.json +17 -0
  13. package/dist/engine-specs.d.ts +99 -0
  14. package/dist/engine-specs.d.ts.map +1 -0
  15. package/dist/engine-specs.js +3 -0
  16. package/dist/engine-specs.js.map +1 -0
  17. package/package.json +7 -12
  18. package/src/engine-specs.ts +110 -0
  19. package/demo/README.md +0 -155
  20. package/demo/checksums.js +0 -101
  21. package/demo/components/drop-area.html +0 -171
  22. package/demo/components/esc-menu.html +0 -540
  23. package/demo/components/launcher.html +0 -220
  24. package/demo/components/sdk-info.html +0 -54
  25. package/demo/demo.js +0 -622
  26. package/demo/sdk.js +0 -19
  27. package/demo/storage.js +0 -88
  28. package/dist/engine.d.ts +0 -427
  29. package/dist/engine.d.ts.map +0 -1
  30. package/dist/engine.js +0 -83
  31. package/dist/engine.js.map +0 -1
  32. package/dist/index.d.ts +0 -13
  33. package/dist/index.d.ts.map +0 -1
  34. package/dist/index.js +0 -12
  35. package/dist/index.js.map +0 -1
  36. package/dist/manifest.d.ts +0 -69
  37. package/dist/manifest.d.ts.map +0 -1
  38. package/dist/manifest.js +0 -8
  39. package/dist/manifest.js.map +0 -1
  40. package/dist/validate.d.ts +0 -10
  41. package/dist/validate.d.ts.map +0 -1
  42. package/dist/validate.js +0 -79
  43. package/dist/validate.js.map +0 -1
  44. package/schema/engine-manifest.schema.json +0 -74
  45. package/src/engine.ts +0 -516
  46. package/src/index.ts +0 -55
  47. package/src/manifest.ts +0 -82
  48. package/src/validate.ts +0 -92
package/demo/storage.js DELETED
@@ -1,88 +0,0 @@
1
- /**
2
- * OPFS Asset Storage Helper for Engine Demo
3
- * Persists ROM and BIOS files in the Origin Private File System (OPFS) across page reloads.
4
- */
5
-
6
- const DIR_NAME = "wasm-gaming-demo";
7
-
8
- async function getStorageDir() {
9
- if (typeof navigator === "undefined" || !navigator.storage?.getDirectory) {
10
- return null;
11
- }
12
- try {
13
- const root = await navigator.storage.getDirectory();
14
- return await root.getDirectoryHandle(DIR_NAME, { create: true });
15
- } catch (err) {
16
- console.warn("OPFS storage not accessible:", err);
17
- return null;
18
- }
19
- }
20
-
21
- /**
22
- * Save an asset File into OPFS.
23
- */
24
- export async function saveStoredAsset(key, file) {
25
- const dir = await getStorageDir();
26
- if (!dir || !file) return;
27
- try {
28
- const fileHandle = await dir.getFileHandle(`${key}.bin`, { create: true });
29
- const writable = await fileHandle.createWritable();
30
- const bytes = await file.arrayBuffer();
31
- await writable.write(bytes);
32
- await writable.close();
33
-
34
- // Store file metadata
35
- const metaHandle = await dir.getFileHandle(`${key}.json`, { create: true });
36
- const metaWritable = await metaHandle.createWritable();
37
- await metaWritable.write(
38
- JSON.stringify({
39
- name: file.name,
40
- size: file.size || bytes.byteLength,
41
- type: file.type || "application/octet-stream",
42
- lastModified: file.lastModified || Date.now(),
43
- })
44
- );
45
- await metaWritable.close();
46
- } catch (err) {
47
- console.warn(`Failed to save ${key} in OPFS:`, err);
48
- }
49
- }
50
-
51
- /**
52
- * Retrieve a stored asset File from OPFS.
53
- */
54
- export async function loadStoredAsset(key) {
55
- const dir = await getStorageDir();
56
- if (!dir) return null;
57
- try {
58
- const fileHandle = await dir.getFileHandle(`${key}.bin`);
59
- const metaHandle = await dir.getFileHandle(`${key}.json`);
60
-
61
- const fileObj = await fileHandle.getFile();
62
- const metaText = await (await metaHandle.getFile()).text();
63
- const meta = JSON.parse(metaText);
64
-
65
- const bytes = await fileObj.arrayBuffer();
66
- return new File([bytes], meta.name, {
67
- type: meta.type,
68
- lastModified: meta.lastModified,
69
- });
70
- } catch {
71
- // File or meta doesn't exist yet
72
- return null;
73
- }
74
- }
75
-
76
- /**
77
- * Clear a stored asset from OPFS.
78
- */
79
- export async function clearStoredAsset(key) {
80
- const dir = await getStorageDir();
81
- if (!dir) return;
82
- try {
83
- await dir.removeEntry(`${key}.bin`);
84
- } catch {}
85
- try {
86
- await dir.removeEntry(`${key}.json`);
87
- } catch {}
88
- }
package/dist/engine.d.ts DELETED
@@ -1,427 +0,0 @@
1
- import type { EngineManifest, KeyMap, InputPreset } from './manifest.js';
2
- /**
3
- * Where a runtime asset comes from.
4
- *
5
- * The host owns its files; the SDK only ever reads them. The SDK picks the
6
- * strategy: an OPFS handle can be read on demand, a `showOpenFilePicker()` handle
7
- * has no sync access and gets copied. The host does not need to know which.
8
- *
9
- * A thunk is resolved by `boot()`, and only if the asset is actually needed.
10
- *
11
- * @category Setup
12
- */
13
- export type AssetSource = Uint8Array | ArrayBuffer | Blob
14
- /** A door to a file, read on demand. */
15
- | FileSystemFileHandle
16
- /** A URL the SDK downloads. The only source that makes the SDK touch the network. */
17
- | string
18
- /** Resolved lazily, by `boot()`, and only if the asset is needed. */
19
- | (() => AssetSource | Promise<AssetSource>);
20
- /**
21
- * The SDK's own folder: saved games, plus whatever internal state the engine
22
- * persists. Never assets, never player preferences.
23
- *
24
- * There is no `required` flag: if the engine needs persistence and cannot have it,
25
- * boot fails on its own with a typed {@link EngineError} that says *what* went
26
- * wrong. Without `storage()` nothing is persisted at all.
27
- *
28
- * @category Setup
29
- */
30
- export interface StorageSetup {
31
- /** Folder the SDK owns, e.g. `"rsdkv4/sonic-1"`. Chosen by the host. */
32
- namespace: string;
33
- }
34
- /** Live storage state, once the engine is up. @category Setup */
35
- export interface StorageInfo extends StorageSetup {
36
- /** `false` when the browser granted only best-effort storage — warn the player. */
37
- persistent: boolean;
38
- }
39
- /**
40
- * The JSON-safe half of {@link EngineSetup}.
41
- *
42
- * `structuredClone` (and therefore IndexedDB) round-trips the full setup, but
43
- * `JSON.stringify` silently turns a `FileSystemFileHandle`, an `ArrayBuffer` or a
44
- * `Blob` into `{}` and drops functions. Only this half survives JSON.
45
- *
46
- * @category Setup
47
- */
48
- export interface EngineSetupData<Config, Options> {
49
- /** Engine settings, applied at boot. */
50
- config?: Partial<Config>;
51
- /** Default session payload — see {@link EngineSDK.options}. */
52
- options?: Partial<Options>;
53
- storage?: StorageSetup;
54
- }
55
- /**
56
- * Everything the factory can be configured with, in one object.
57
- *
58
- * Accepted by the constructor, which is what makes "restore the last session"
59
- * a single call. Anything settable here is settable by chaining, and vice versa.
60
- *
61
- * @category Setup
62
- */
63
- export interface EngineSetup<Config, Options> extends EngineSetupData<Config, Options> {
64
- /** Runtime files keyed by `AssetSpec.key`. */
65
- assets?: Record<string, AssetSource>;
66
- /** Where the engine is seen. A container is scaled by the SDK; a canvas is yours. */
67
- mount?: HTMLElement;
68
- on?: Partial<EngineEventHandlers>;
69
- }
70
- /**
71
- * One setting, resolved: what is in effect, plus the JSON Schema vocabulary the
72
- * manifest declared for it. Enough to paint a settings screen without asking the
73
- * engine anything.
74
- *
75
- * There is no `source` field: the method you called *is* the source —
76
- * {@link EngineSDK.config} are engine keys, {@link EngineSDK.options} are session keys.
77
- *
78
- * @category Config & options
79
- */
80
- export interface ResolvedProperty<Value = unknown> {
81
- /** What is in effect — or what will be used, if there is no engine yet. */
82
- value: Value;
83
- /** What the manifest declares, when it declares it. */
84
- default?: Value;
85
- /**
86
- * Changing this needs a restart. It does **not** mean immutable: `player` is
87
- * `readOnly` in `options()` and changes fine through {@link EnginePlay.restart}.
88
- * A settings screen can grey it out instead of hiding it.
89
- */
90
- readOnly?: true;
91
- type?: string;
92
- enum?: readonly unknown[];
93
- enumNames?: readonly string[];
94
- minimum?: number;
95
- maximum?: number;
96
- title?: string;
97
- description?: string;
98
- }
99
- /** Every key of `T`, resolved. The return shape of all four getters. @category Config & options */
100
- export type ResolvedProperties<T> = {
101
- [K in keyof T]-?: ResolvedProperty<T[K]>;
102
- };
103
- /**
104
- * `ready` fires when `boot()` completes, `start` when a session opens. A
105
- * `sdk.start()` emits both, in that order; a `restart()` does not re-emit `ready`.
106
- *
107
- * @category Events
108
- */
109
- export interface EngineEventMap {
110
- /** Engine up, no session. */
111
- ready: void;
112
- /** A session opened, with the payload it opened with. */
113
- start: {
114
- payload: unknown;
115
- };
116
- /** `total` may be absent — guard before dividing. */
117
- progress: {
118
- key: string;
119
- loaded: number;
120
- total?: number;
121
- };
122
- frame: {
123
- fps: number;
124
- };
125
- pause: {
126
- owner: string;
127
- };
128
- resume: {
129
- owner: string;
130
- };
131
- error: {
132
- error: EngineError;
133
- };
134
- exit: void;
135
- }
136
- /** @category Events */
137
- export type EngineEventHandlers = {
138
- [K in keyof EngineEventMap]: (event: EngineEventMap[K]) => void;
139
- };
140
- /**
141
- * Why boot or a call failed.
142
- *
143
- * Storage checks (OPFS availability, `crossOriginIsolated`, sync access, quota) run
144
- * *before* the wasm is instantiated, so the failure arrives as a typed exception and
145
- * not as a console warning halfway through loading.
146
- *
147
- * @category Errors
148
- */
149
- export type EngineErrorCode =
150
- /** No storage backend at all. */
151
- 'storage-unavailable'
152
- /** The page needs COOP/COEP. */
153
- | 'not-isolated'
154
- /** Another tab holds the namespace. */
155
- | 'storage-locked' | 'quota-exceeded'
156
- /** A `required` asset was never handed over — see {@link EngineSDK.missingAssets}. */
157
- | 'asset-missing'
158
- /** The engine could not parse the file. Integrity checks against a catalogue are the host's job. */
159
- | 'asset-invalid' | 'unsupported-browser'
160
- /** A `readOnly` key was written on the live engine — use {@link EnginePlay.restart}. */
161
- | 'read-only'
162
- /** A setup method was called while an instance was alive. */
163
- | 'frozen'
164
- /** A method was called on a torn-down {@link EnginePlay}. */
165
- | 'destroyed';
166
- /**
167
- * Errors reach the host **both ways**: the promise rejects *and* `error` is
168
- * emitted, so a host that does not `await` is not left blind.
169
- *
170
- * @category Errors
171
- */
172
- export interface EngineError extends Error {
173
- code: EngineErrorCode;
174
- }
175
- /** One save slot, as a launcher would paint it. @category Saves */
176
- export interface SaveSlot {
177
- id: number;
178
- empty: boolean;
179
- /** Ready to show, e.g. `"Zone 1 · A · 3 lives"`. */
180
- label: string;
181
- /** Engine-specific extras the host may render. */
182
- meta: Record<string, unknown>;
183
- }
184
- /**
185
- * A promise of the live engine, hydrated with the engine's own methods.
186
- *
187
- * Every method of {@link EnginePlay} already returns a promise, so hydrating is
188
- * enough: the actions come back as another `PlayPromise` and keep chaining, the
189
- * queries come back as a plain promise and close.
190
- *
191
- * ```js
192
- * await play.pause(OWNER.menu).options({ spindash: true }).resume(OWNER.menu)
193
- * const slots = await play.pause(OWNER.menu).saves() // closes
194
- * ```
195
- *
196
- * Two normative rules:
197
- *
198
- * - A rejection aborts the rest of the chain — nothing downstream runs.
199
- * - It is a `Promise` subclass whose `Symbol.species` is `Promise`, so `then()`,
200
- * `catch()` and `finally()` return a **plain** promise. Calling `then()` closes
201
- * the chain, like a getter does.
202
- *
203
- * @category Lifecycle
204
- */
205
- export type PlayPromise<Play> = Promise<Play> & Play;
206
- /**
207
- * The live engine. Handed to the host by {@link EngineSDK.boot} or
208
- * {@link EngineSDK.start}; never constructed by the host.
209
- *
210
- * Sessions come and go on the same live engine: `boot()` gives you one with no
211
- * session at all, and each {@link EnginePlay.restart} opens another.
212
- *
213
- * The capability-gated members exist **if and only if** `manifest.capabilities`
214
- * says so, so `if (play.saveState)` and reading the manifest always agree.
215
- *
216
- * @category Lifecycle
217
- */
218
- export declare abstract class EnginePlay<Options = Record<string, unknown>, Config = Record<string, unknown>> {
219
- /**
220
- * Open a session, or reopen it.
221
- *
222
- * The payload is cumulative: there is one current payload, it starts as the
223
- * factory's, and every `start()`/`restart()` merges its own on top (flat merge,
224
- * one level). So `restart({ slot: 3 })` keeps the `player` you opened with, and
225
- * `restart()` with no argument is a power cycle that repeats the current payload.
226
- *
227
- * `null` is an explicit value, not "leave unchanged".
228
- */
229
- abstract restart(options?: Partial<Options>): PlayPromise<this>;
230
- /**
231
- * Freeze, on behalf of `owner`. Only the owner can resume, so an overlay cannot
232
- * resume what another one froze.
233
- */
234
- abstract pause(owner: string): PlayPromise<this>;
235
- /** Resume. Ignored when the pause belongs to somebody else. */
236
- abstract resume(owner: string): PlayPromise<this>;
237
- /**
238
- * Move the picture — reparents and refits. Throws on a different canvas: the GL
239
- * context was bound to a surface at boot.
240
- */
241
- abstract mount(target: HTMLElement): PlayPromise<this>;
242
- abstract setInput(map: InputPreset | KeyMap): PlayPromise<this>;
243
- /** Read the live engine settings: same keys as `sdk.config()`, current values. */
244
- abstract config(): Promise<ResolvedProperties<Config>>;
245
- /**
246
- * Apply engine settings to *this session only*. Never persisted — where
247
- * preferences live is the UI's call. Throws `read-only` on a `readOnly` key.
248
- */
249
- abstract config(values: Partial<Config>): PlayPromise<this>;
250
- /** Read the session options. Runtime discovery: their keys come from the loaded content. */
251
- abstract options(): Promise<ResolvedProperties<Options>>;
252
- /**
253
- * Write session options, as flat values. Throws `read-only` on a `readOnly` key —
254
- * those change through {@link EnginePlay.restart}.
255
- *
256
- * The getter shape (`{ volume: { value: 0.8 } }`) is not accepted: it would be
257
- * ambiguous the day a property is itself an object.
258
- */
259
- abstract options(values: Partial<Options>): PlayPromise<this>;
260
- /** Where saves are going, and whether the browser will really keep them. */
261
- abstract storage(): Promise<StorageInfo>;
262
- abstract on<K extends keyof EngineEventMap>(type: K, handler: (event: EngineEventMap[K]) => void): PlayPromise<this>;
263
- abstract off<K extends keyof EngineEventMap>(type: K, handler: (event: EngineEventMap[K]) => void): PlayPromise<this>;
264
- /**
265
- * Stop the loop, silence the audio, flush saves, release the canvas.
266
- *
267
- * **Closes the chain** — the factory stays frozen until this settles, which is
268
- * what stops a host from repointing `storage()` while the old engine is still
269
- * flushing into the old namespace.
270
- *
271
- * Idempotent: calling it again resolves and does nothing. The freeze is released
272
- * when it *settles*, not when it fulfils — a failed flush must not brick the
273
- * factory. Afterwards every method here throws `destroyed`, in-flight chains
274
- * included.
275
- */
276
- abstract destroy(): Promise<void>;
277
- /** Present when `capabilities.saves !== false`. */
278
- abstract saves?(): Promise<SaveSlot[]>;
279
- /** Present when `capabilities.saves !== false`. */
280
- abstract deleteSave?(id: number): PlayPromise<this>;
281
- /** Present when `capabilities.saveStates`. */
282
- abstract saveState?(): Promise<Uint8Array>;
283
- /** Present when `capabilities.saveStates`. */
284
- abstract loadState?(data: Uint8Array): PlayPromise<this>;
285
- /** Present when `capabilities.screenshots`. */
286
- abstract screenshot?(): Promise<Blob>;
287
- }
288
- /**
289
- * The factory every engine package exports a subclass of.
290
- *
291
- * Configured by chaining — all of it synchronous, because none of it crosses to
292
- * the engine; these methods record, they do not resolve. Then `boot()` or
293
- * `start()` produce the live {@link EnginePlay}.
294
- *
295
- * ```js
296
- * const play = await new MotorSDK()
297
- * .assets({ data: packHandle })
298
- * .config({ language: 4 })
299
- * .storage({ namespace: 'motor/game-1' })
300
- * .on('error', report)
301
- * .mount(document.querySelector('#stage'))
302
- * .start({ slot: 0, player: 1 })
303
- * ```
304
- *
305
- * Anything settable by chaining is settable in the constructor, and vice versa.
306
- *
307
- * Three boot verbs, two of them sugar for the third — and the equivalence is
308
- * literal, it is how the base class implements them:
309
- *
310
- * | | what it does | equals |
311
- * | --- | --- | --- |
312
- * | `boot()` | engine alive, no session | — |
313
- * | `play.restart(p)` | opens or reopens a session | — |
314
- * | `start(p)` | engine alive **and** session running | `boot().restart(p)` |
315
- *
316
- * `boot()` is idempotent, so a factory governs at most one live engine, and
317
- * `start(p)` on a live one is just `restart(p)`. Neither throws.
318
- *
319
- * @typeParam Config - the engine's own settings. Close it per engine; an open
320
- * `Record<string, unknown>` gives up the compile-time check that is the point.
321
- * @typeParam Options - the session payload, i.e. the writable keys of
322
- * {@link EnginePlay.options}.
323
- * @typeParam Play - the engine's own {@link EnginePlay} subclass, when it adds methods.
324
- *
325
- * @category Factory
326
- */
327
- export declare abstract class EngineSDK<Config = Record<string, unknown>, Options = Record<string, unknown>, Play extends EnginePlay<Options, Config> = EnginePlay<Options, Config>> {
328
- /**
329
- * Accepts the whole setup at once, which is what makes "restore the last session"
330
- * a single call. Only {@link EngineSetupData} survives `JSON` — use
331
- * `structuredClone`/IndexedDB for the rest.
332
- */
333
- constructor(setup?: EngineSetup<Config, Options>);
334
- /**
335
- * Register asset sources. **Always synchronous** — it records sources, it does
336
- * not resolve them, which is why a value may be a thunk.
337
- *
338
- * Throws `frozen` while an engine is alive.
339
- */
340
- abstract assets(sources: Record<string, AssetSource>): this;
341
- /**
342
- * Read the boot settings, without booting anything — enough to paint a
343
- * preferences screen. Synchronous: there is no engine to ask, only
344
- * `manifest.config` and whatever the chain put on top.
345
- *
346
- * With a live engine this still reads *what it booted with*;
347
- * {@link EnginePlay.config} reads how it is now. Both are correct at once.
348
- */
349
- abstract config(): ResolvedProperties<Config>;
350
- /** Set the boot settings. Throws `frozen` while an engine is alive. */
351
- abstract config(values: Partial<Config>): this;
352
- /**
353
- * Read the current session payload. Synchronous, and thinner than the other
354
- * getters — session keys are declared by the loaded content, so with no engine
355
- * there is no `enum` or `title` to give, only `value`.
356
- */
357
- abstract options(): ResolvedProperties<Options>;
358
- /**
359
- * Set the payload the next session will open with. Exactly the same as passing it
360
- * to `start()`.
361
- *
362
- * Unlike `assets()`, `config()` and `storage()` this does **not** freeze with a
363
- * live engine: it does not describe the engine, it describes the next session,
364
- * and opening another session is legal at any time. It changes nothing hot —
365
- * that is {@link EnginePlay.options}.
366
- */
367
- abstract options(payload: Partial<Options>): this;
368
- /** Point the SDK at its folder. Throws `frozen` while an engine is alive. */
369
- abstract storage(setup: StorageSetup): this;
370
- /**
371
- * Where the engine will be seen. A container is scaled by the SDK; pass your own
372
- * canvas and the scaling is yours — and only before booting.
373
- */
374
- abstract mount(target: HTMLElement): this;
375
- abstract on<K extends keyof EngineEventMap>(type: K, handler: (event: EngineEventMap[K]) => void): this;
376
- abstract off<K extends keyof EngineEventMap>(type: K, handler: (event: EngineEventMap[K]) => void): this;
377
- /**
378
- * The `required` manifest keys that have not been handed over, **or whose value
379
- * is not a valid {@link AssetSource}** — a `{}` left by a lost JSON round-trip
380
- * counts as absent, not as present.
381
- *
382
- * Synchronous: it reads the chain, not the disk. Whether the file exists in the
383
- * host's library is the host's question.
384
- */
385
- abstract missingAssets(): string[];
386
- /**
387
- * Engine alive, no session — where a launcher reads the saves and paints its
388
- * cards before committing to a game.
389
- *
390
- * Idempotent: with a live engine it returns *that* engine.
391
- */
392
- abstract boot(): PlayPromise<Play>;
393
- /** `boot()` and open a session. Literally `boot().restart(payload)`. */
394
- abstract start(payload?: Partial<Options>): PlayPromise<Play>;
395
- /**
396
- * Read the saves without a session.
397
- *
398
- * Present when `capabilities.saves !== false`. With `'storage'` it reads the
399
- * store in JS and does **not** boot; with `'engine'` it is sugar for
400
- * `boot().saves()` and does boot. Same rows either way.
401
- *
402
- * Every factory method that needs a live engine is sugar for
403
- * `boot().<method>()`; the capability only says whether that boot can be skipped.
404
- */
405
- abstract saves?(): Promise<SaveSlot[]>;
406
- /**
407
- * The only thing a concrete engine implements. Everything above is written once,
408
- * in the base class.
409
- */
410
- protected abstract create(setup: EngineSetup<Config, Options>): Promise<Play>;
411
- }
412
- /**
413
- * The static side of an engine package's export — the half `implements` cannot
414
- * check, since TypeScript has no abstract statics.
415
- *
416
- * ```ts
417
- * const _contract: EngineSDKClass<MotorConfig, MotorOptions, MotorPlay> = MotorSDK
418
- * ```
419
- *
420
- * @category Factory
421
- */
422
- export interface EngineSDKClass<Config = Record<string, unknown>, Options = Record<string, unknown>, Play extends EnginePlay<Options, Config> = EnginePlay<Options, Config>> {
423
- new (setup?: EngineSetup<Config, Options>): EngineSDK<Config, Options, Play>;
424
- /** What the engine knows about itself, with nothing instantiated. */
425
- readonly manifest: EngineManifest;
426
- }
427
- //# sourceMappingURL=engine.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/engine.js DELETED
@@ -1,83 +0,0 @@
1
- // Layer B — the imperative contract.
2
- //
3
- // Every engine subproject exports a class that extends `EngineSDK`: a factory the
4
- // host configures by chaining, and whose `boot()`/`start()` hand back an `EnginePlay`
5
- // — the live engine. The chain is written once, here, so twelve engines don't each
6
- // reinvent it; a concrete engine supplies only `create()`.
7
- //
8
- // Per-engine differences are absorbed by `manifest` (Layer A) and by closing the
9
- // `Config` / `Options` generics.
10
- //
11
- // Status: shapes only. Every member is `abstract`, so the classes exist at runtime but
12
- // implement nothing yet — the chain, the freeze, the payload merge and the idempotent
13
- // `boot()` land with the contract 1.0 release.
14
- // ---------------------------------------------------------------------------
15
- // EnginePlay — the live engine
16
- // ---------------------------------------------------------------------------
17
- /**
18
- * The live engine. Handed to the host by {@link EngineSDK.boot} or
19
- * {@link EngineSDK.start}; never constructed by the host.
20
- *
21
- * Sessions come and go on the same live engine: `boot()` gives you one with no
22
- * session at all, and each {@link EnginePlay.restart} opens another.
23
- *
24
- * The capability-gated members exist **if and only if** `manifest.capabilities`
25
- * says so, so `if (play.saveState)` and reading the manifest always agree.
26
- *
27
- * @category Lifecycle
28
- */
29
- export class EnginePlay {
30
- }
31
- // ---------------------------------------------------------------------------
32
- // EngineSDK — the factory
33
- // ---------------------------------------------------------------------------
34
- /**
35
- * The factory every engine package exports a subclass of.
36
- *
37
- * Configured by chaining — all of it synchronous, because none of it crosses to
38
- * the engine; these methods record, they do not resolve. Then `boot()` or
39
- * `start()` produce the live {@link EnginePlay}.
40
- *
41
- * ```js
42
- * const play = await new MotorSDK()
43
- * .assets({ data: packHandle })
44
- * .config({ language: 4 })
45
- * .storage({ namespace: 'motor/game-1' })
46
- * .on('error', report)
47
- * .mount(document.querySelector('#stage'))
48
- * .start({ slot: 0, player: 1 })
49
- * ```
50
- *
51
- * Anything settable by chaining is settable in the constructor, and vice versa.
52
- *
53
- * Three boot verbs, two of them sugar for the third — and the equivalence is
54
- * literal, it is how the base class implements them:
55
- *
56
- * | | what it does | equals |
57
- * | --- | --- | --- |
58
- * | `boot()` | engine alive, no session | — |
59
- * | `play.restart(p)` | opens or reopens a session | — |
60
- * | `start(p)` | engine alive **and** session running | `boot().restart(p)` |
61
- *
62
- * `boot()` is idempotent, so a factory governs at most one live engine, and
63
- * `start(p)` on a live one is just `restart(p)`. Neither throws.
64
- *
65
- * @typeParam Config - the engine's own settings. Close it per engine; an open
66
- * `Record<string, unknown>` gives up the compile-time check that is the point.
67
- * @typeParam Options - the session payload, i.e. the writable keys of
68
- * {@link EnginePlay.options}.
69
- * @typeParam Play - the engine's own {@link EnginePlay} subclass, when it adds methods.
70
- *
71
- * @category Factory
72
- */
73
- export class EngineSDK {
74
- /**
75
- * Accepts the whole setup at once, which is what makes "restore the last session"
76
- * a single call. Only {@link EngineSetupData} survives `JSON` — use
77
- * `structuredClone`/IndexedDB for the rest.
78
- */
79
- constructor(setup) {
80
- void setup; // shape only: the base class does not run yet
81
- }
82
- }
83
- //# sourceMappingURL=engine.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,EAAE;AACF,kFAAkF;AAClF,sFAAsF;AACtF,mFAAmF;AACnF,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,iCAAiC;AACjC,EAAE;AACF,uFAAuF;AACvF,sFAAsF;AACtF,+CAA+C;AAsO/C,8EAA8E;AAC9E,+BAA+B;AAC/B,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,OAAgB,UAAU;CAsF/B;AAED,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,OAAgB,SAAS;IAK7B;;;;OAIG;IACH,YAAY,KAAoC;QAC9C,KAAK,KAAK,CAAC,CAAC,8CAA8C;IAC5D,CAAC;CA8FF"}
package/dist/index.d.ts DELETED
@@ -1,13 +0,0 @@
1
- /**
2
- * @module
3
- * @document ../docs/CONTRACT-MAP.md
4
- * @document ../docs/USAGE.md
5
- */
6
- export type { EngineManifest, EngineArtifacts, AssetSpec, AssetValidation, VideoSpec, EngineCapabilities, KeyMap, InputPreset, JSONSchema, } from './manifest.js';
7
- export { EngineSDK, EnginePlay } from './engine.js';
8
- export type { EngineSDKClass, AssetSource, EngineSetup, EngineSetupData, StorageSetup, StorageInfo, ResolvedProperty, ResolvedProperties, PlayPromise, EngineEventMap, EngineEventHandlers, EngineError, EngineErrorCode, SaveSlot, } from './engine.js';
9
- export { validateManifest, assertManifest } from './validate.js';
10
- export type { ValidationResult } from './validate.js';
11
- /** The contract version. Bump on breaking changes to the shapes above. */
12
- export declare const CONTRACT_VERSION = "0.1.1";
13
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,YAAY,EACV,cAAc,EACd,eAAe,EACf,SAAS,EACT,eAAe,EACf,SAAS,EACT,kBAAkB,EAClB,MAAM,EACN,WAAW,EACX,UAAU,GACX,MAAM,eAAe,CAAC;AAIvB,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEpD,YAAY,EACV,cAAc,EAEd,WAAW,EACX,WAAW,EACX,eAAe,EACf,YAAY,EACZ,WAAW,EAEX,gBAAgB,EAChB,kBAAkB,EAElB,WAAW,EAEX,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,eAAe,EAEf,QAAQ,GACT,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjE,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,UAAU,CAAC"}
package/dist/index.js DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * @module
3
- * @document ../docs/CONTRACT-MAP.md
4
- * @document ../docs/USAGE.md
5
- */
6
- // The two base classes. Every member is abstract for now: the shapes are the 1.0
7
- // contract, the implementation lands with it.
8
- export { EngineSDK, EnginePlay } from './engine.js';
9
- export { validateManifest, assertManifest } from './validate.js';
10
- /** The contract version. Bump on breaking changes to the shapes above. */
11
- export const CONTRACT_VERSION = '0.1.1';
12
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoBH,iFAAiF;AACjF,8CAA8C;AAC9C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAwBpD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGjE,0EAA0E;AAC1E,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC"}