@wasm-gaming/rsdkv4-wasm 0.2.0 → 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 (40) hide show
  1. package/README.md +159 -213
  2. package/dist/rsdkv4/app/app.css +160 -0
  3. package/dist/rsdkv4/app/app.js +521 -0
  4. package/dist/rsdkv4/app/assets/player-knuckles.svg +757 -0
  5. package/dist/rsdkv4/app/assets/player-sonic.svg +720 -0
  6. package/dist/rsdkv4/app/assets/player-sonictails.svg +1414 -0
  7. package/dist/rsdkv4/app/assets/player-tails.svg +979 -0
  8. package/dist/rsdkv4/app/assets/stages-sonic1/special.png +0 -0
  9. package/dist/rsdkv4/app/assets/stages-sonic1/zone-1.png +0 -0
  10. package/dist/rsdkv4/app/assets/stages-sonic1/zone-2.png +0 -0
  11. package/dist/rsdkv4/app/assets/stages-sonic1/zone-3.png +0 -0
  12. package/dist/rsdkv4/app/assets/stages-sonic1/zone-4.png +0 -0
  13. package/dist/rsdkv4/app/assets/stages-sonic1/zone-5.png +0 -0
  14. package/dist/rsdkv4/app/assets/stages-sonic1/zone-6.png +0 -0
  15. package/dist/rsdkv4/app/assets/stages-sonic1/zone-7.png +0 -0
  16. package/dist/rsdkv4/app/assets/stages-sonic2/special.png +0 -0
  17. package/dist/rsdkv4/app/assets/stages-sonic2/zone-1.png +0 -0
  18. package/dist/rsdkv4/app/assets/stages-sonic2/zone-10.png +0 -0
  19. package/dist/rsdkv4/app/assets/stages-sonic2/zone-2.png +0 -0
  20. package/dist/rsdkv4/app/assets/stages-sonic2/zone-3.png +0 -0
  21. package/dist/rsdkv4/app/assets/stages-sonic2/zone-4.png +0 -0
  22. package/dist/rsdkv4/app/assets/stages-sonic2/zone-5.png +0 -0
  23. package/dist/rsdkv4/app/assets/stages-sonic2/zone-6.png +0 -0
  24. package/dist/rsdkv4/app/assets/stages-sonic2/zone-7.png +0 -0
  25. package/dist/rsdkv4/app/assets/stages-sonic2/zone-8.png +0 -0
  26. package/dist/rsdkv4/app/assets/stages-sonic2/zone-9.png +0 -0
  27. package/dist/rsdkv4/engine-specs.js +382 -0
  28. package/dist/rsdkv4/rsdkv4.options.d.ts +13 -5
  29. package/dist/rsdkv4/rsdkv4.options.d.ts.map +1 -1
  30. package/dist/rsdkv4/rsdkv4.options.js +6 -1
  31. package/dist/rsdkv4/rsdkv4.options.js.map +1 -1
  32. package/dist/rsdkv4/rsdkv4.sdk.d.ts +82 -2
  33. package/dist/rsdkv4/rsdkv4.sdk.d.ts.map +1 -1
  34. package/dist/rsdkv4/rsdkv4.sdk.js +217 -26
  35. package/dist/rsdkv4/rsdkv4.sdk.js.map +1 -1
  36. package/package.json +7 -4
  37. package/src/app/app.css +160 -0
  38. package/src/app/app.js +521 -0
  39. package/src/rsdkv4.options.ts +14 -6
  40. package/src/rsdkv4.sdk.ts +283 -26
@@ -0,0 +1,382 @@
1
+ // ---- Introspectable property definitions ----
2
+ /**
3
+ * The listener bookkeeping, shipped rather than described — the one piece of runtime
4
+ * code in this package.
5
+ *
6
+ * It is a **convenience, not a requirement**: the contract stays structural, so an
7
+ * engine that already owns an emitter satisfies {@link EngineSurface} without this.
8
+ * What it saves is the part everyone gets subtly wrong — iterating a copy, so a
9
+ * listener that unsubscribes mid-emit does not corrupt the loop; de-duplicating a
10
+ * listener registered twice; and keeping one throwing listener from silencing the
11
+ * rest.
12
+ *
13
+ * Hold it **privately** and hand the same instance to every carrier. That is what
14
+ * makes `sdk.on(…)` and `play.on(…)` one channel rather than two, lets a host
15
+ * subscribe before the engine exists, and keeps `emit` out of the public API — a host
16
+ * must not be able to forge an engine event.
17
+ */
18
+ export class EventEmitter {
19
+ #listeners = new Map();
20
+ /** @param listeners Registered immediately — what `EngineInit.on` carries. */
21
+ constructor(listeners) {
22
+ for (const type of Object.keys(listeners ?? {})) {
23
+ const listener = listeners?.[type];
24
+ if (listener)
25
+ this.on(type, listener);
26
+ }
27
+ }
28
+ /** Registering the same listener twice registers it once, as `EventTarget` does. */
29
+ on(type, listener) {
30
+ let set = this.#listeners.get(type);
31
+ if (!set)
32
+ this.#listeners.set(type, (set = new Set()));
33
+ set.add(listener);
34
+ return this;
35
+ }
36
+ /**
37
+ * The listener is required, as in `EventTarget`. A convenience `off(type)` dropping
38
+ * every listener for a type is a footgun on a page where two components subscribed
39
+ * independently: one unsubscribing would silence the other.
40
+ */
41
+ off(type, listener) {
42
+ const set = this.#listeners.get(type);
43
+ if (set?.delete(listener) && set.size === 0) {
44
+ this.#listeners.delete(type);
45
+ }
46
+ return this;
47
+ }
48
+ /**
49
+ * Takes the **detail**, and wraps it. The rest tuple is what lets an event with no
50
+ * detail be emitted as `emit('exit')` while still checking the ones that have one.
51
+ */
52
+ emit(type, ...[detail]) {
53
+ const set = this.#listeners.get(type);
54
+ if (!set)
55
+ return;
56
+ // Built once and frozen: every listener gets this same object, so a mutable one
57
+ // would let the first rewrite what the rest read.
58
+ const event = Object.freeze({ type, detail: detail });
59
+ // A copy: a listener may call `off()` — on itself or another — while this runs.
60
+ for (const listener of [...set]) {
61
+ try {
62
+ listener(event);
63
+ }
64
+ catch (thrown) {
65
+ // One bad listener must neither silence the rest nor vanish. Rethrowing on a
66
+ // fresh task puts it in front of `window.onerror` with its stack intact.
67
+ queueMicrotask(() => {
68
+ throw thrown;
69
+ });
70
+ }
71
+ }
72
+ }
73
+ }
74
+ /**
75
+ * Wrap a promise of a live engine in the chainable surface.
76
+ *
77
+ * Exported because it is the single most duplicated function in an engine, and an
78
+ * engine that does not extend {@link EnginePlayBase} still needs exactly this.
79
+ *
80
+ * `on` and `off` do **not** go through the chain: they reach the channel now, so a
81
+ * listener is live before the promise settles. Routing them through it would bind
82
+ * after the session opened and lose whatever was emitted while it was opening.
83
+ */
84
+ export function hydratePlay(inner, events) {
85
+ const chain = (step) => hydratePlay(inner.then(async (play) => {
86
+ await step(play);
87
+ return play;
88
+ }), events);
89
+ const hydrated = {
90
+ then: (onfulfilled, onrejected) => inner.then(onfulfilled, onrejected),
91
+ catch: (onrejected) => inner.catch(onrejected),
92
+ finally: (onfinally) => inner.finally(onfinally),
93
+ [Symbol.toStringTag]: 'PromisePlay',
94
+ config: ((patch) => patch === undefined
95
+ ? inner.then((play) => play.config())
96
+ : chain((play) => play.config(patch))),
97
+ options: ((patch) => patch === undefined
98
+ ? inner.then((play) => play.options())
99
+ : chain((play) => play.options(patch))),
100
+ assets: ((patch) => patch === undefined
101
+ ? inner.then((play) => play.assets())
102
+ : chain((play) => play.assets(patch))),
103
+ storage: ((patch) => patch === undefined
104
+ ? inner.then((play) => play.storage())
105
+ : chain((play) => play.storage(patch))),
106
+ mount: (target) => chain((play) => play.mount(target)),
107
+ on: (type, listener) => {
108
+ events.on(type, listener);
109
+ return hydrated;
110
+ },
111
+ off: (type, listener) => {
112
+ events.off(type, listener);
113
+ return hydrated;
114
+ },
115
+ restart: (options) => chain((play) => play.restart(options)),
116
+ destroy: () => inner.then((play) => play.destroy()),
117
+ };
118
+ return hydrated;
119
+ }
120
+ /**
121
+ * The live engine, with everything the contract prescribes already written: state,
122
+ * the cumulative merge, the descriptors, the chainable promise, and the channel.
123
+ *
124
+ * A subclass supplies what only it can know — {@link EnginePlayBase.open} and
125
+ * {@link EnginePlayBase.close}, optionally {@link EnginePlayBase.mounted} and
126
+ * {@link EnginePlayBase.patched} — and its properties as an {@link EngineSpec}.
127
+ *
128
+ * The spec arrives as a **constructor argument**, not an abstract field, and that is
129
+ * not a style choice: a subclass field initialiser runs *after* `super()`, so a
130
+ * `protected abstract spec` would still be `undefined` while this constructor seeded
131
+ * state from it. Passing it up makes the ordering trap impossible rather than merely
132
+ * avoided.
133
+ */
134
+ export class EnginePlayBase {
135
+ #spec;
136
+ #state;
137
+ #events;
138
+ #target;
139
+ /** The target {@link EnginePlayBase.mounted} has already been told about. */
140
+ #attached;
141
+ /**
142
+ * @param events The factory's channel. Omitted only when a play is built on its
143
+ * own; the factory always passes its own, which is what makes `sdk.on(…)` and
144
+ * `play.on(…)` one channel rather than two.
145
+ */
146
+ constructor(spec, init, events) {
147
+ this.#spec = spec;
148
+ this.#events = events ?? new EventEmitter(init?.on);
149
+ this.#state = {
150
+ config: seed(spec.config, init?.config),
151
+ options: seed(spec.options, init?.options),
152
+ assets: seed(spec.assets, init?.assets),
153
+ storage: seed(spec.storage, init?.storage),
154
+ };
155
+ // Recorded, not applied. `mounted()` is overridable, and a subclass's own fields
156
+ // are still uninitialised while this constructor runs — so the hook is deferred to
157
+ // the first `restart()`, by which point the subclass is whole.
158
+ this.#target = init?.mount;
159
+ }
160
+ /** The element is already recorded when this runs. */
161
+ mounted(target) {
162
+ void target;
163
+ }
164
+ /** A payload changed on a running engine — apply what is `live`, ignore the rest. */
165
+ patched(kind, patch) {
166
+ void kind;
167
+ void patch;
168
+ }
169
+ /**
170
+ * Emit one of this engine's own events.
171
+ *
172
+ * `protected`, so a typed host cannot forge an engine event — but be exact about
173
+ * what that buys: TypeScript erases `protected`, so this is a compile-time guard,
174
+ * not a capability boundary. Plain JavaScript can still reach it. Only the `#`
175
+ * fields below — the channel and the state — are unreachable at runtime.
176
+ */
177
+ emit(type, ...detail) {
178
+ this.#events.emit(type, ...detail);
179
+ }
180
+ /** Where the picture is, for a subclass that needs the element after `mounted()`. */
181
+ get target() {
182
+ return this.#target;
183
+ }
184
+ config(patch) {
185
+ // `arguments.length`, not `patch === undefined`: `config(undefined)` is a write of
186
+ // nothing, not a read.
187
+ if (arguments.length === 0)
188
+ return Promise.resolve(this.#describe('config'));
189
+ return this.#patch('config', patch);
190
+ }
191
+ options(patch) {
192
+ if (arguments.length === 0)
193
+ return Promise.resolve(this.#describe('options'));
194
+ return this.#patch('options', patch);
195
+ }
196
+ assets(patch) {
197
+ if (arguments.length === 0)
198
+ return Promise.resolve(this.#describe('assets'));
199
+ return this.#patch('assets', patch);
200
+ }
201
+ storage(patch) {
202
+ if (arguments.length === 0)
203
+ return Promise.resolve(this.#describe('storage'));
204
+ return this.#patch('storage', patch);
205
+ }
206
+ mount(target) {
207
+ this.#target = target;
208
+ return this.#run(() => this.#attach());
209
+ }
210
+ on(type, listener) {
211
+ this.#events.on(type, listener);
212
+ return hydratePlay(Promise.resolve(this), this.#events);
213
+ }
214
+ off(type, listener) {
215
+ this.#events.off(type, listener);
216
+ return hydratePlay(Promise.resolve(this), this.#events);
217
+ }
218
+ restart(options) {
219
+ return this.#run(async () => {
220
+ if (options)
221
+ Object.assign(this.#state.options, options);
222
+ // A target supplied to the constructor lands here, which is what makes
223
+ // `new Sdk({ mount: el }).start()` and `new Sdk().mount(el).start()` the same
224
+ // call — the rule the contract states for everything on the surface.
225
+ await this.#attach();
226
+ const opened = { ...this.#state.options };
227
+ await this.open(opened);
228
+ this.#emitCore('start', opened);
229
+ });
230
+ }
231
+ async destroy() {
232
+ await this.#guard(async () => {
233
+ await this.close();
234
+ this.#target = undefined;
235
+ this.#attached = undefined;
236
+ this.#emitCore('exit', undefined);
237
+ });
238
+ }
239
+ /** Tell the engine where it is, once per distinct target. Reparenting is legal. */
240
+ async #attach() {
241
+ if (!this.#target || this.#target === this.#attached)
242
+ return;
243
+ this.#attached = this.#target;
244
+ await this.mounted(this.#target);
245
+ }
246
+ // ---- Shared machinery ----
247
+ /** One getter's answer: the spec, with the current value written into each entry. */
248
+ #describe(kind) {
249
+ const spec = this.#spec[kind];
250
+ const state = this.#state[kind];
251
+ const out = {};
252
+ for (const key of Object.keys(spec)) {
253
+ out[key] = { ...spec[key], value: state[key] };
254
+ }
255
+ return out;
256
+ }
257
+ /** Cumulative, merged flat, one level — `undefined` is a value, not "leave alone". */
258
+ #patch(kind, patch) {
259
+ Object.assign(this.#state[kind], patch);
260
+ return this.#run(async () => {
261
+ await this.patched(kind, patch ?? {});
262
+ });
263
+ }
264
+ /** A step, as a link in the chain — and as an `error` event if it throws. */
265
+ #run(step) {
266
+ return hydratePlay(this.#guard(step), this.#events);
267
+ }
268
+ /**
269
+ * The rule that errors reach a host **both ways**: the promise rejects *and*
270
+ * `error` is emitted. Written once here rather than at every throw site.
271
+ */
272
+ async #guard(step) {
273
+ try {
274
+ await step();
275
+ return this;
276
+ }
277
+ catch (thrown) {
278
+ const error = thrown instanceof Error ? thrown : new Error(String(thrown));
279
+ this.#emitCore('error', error);
280
+ throw error;
281
+ }
282
+ }
283
+ /**
284
+ * The core events, emitted from here rather than by a subclass.
285
+ *
286
+ * The cast is the one thing TypeScript will not check here: `emit` takes a rest
287
+ * tuple that is conditional on the detail type, and that conditional stays
288
+ * unresolved while `P` is still a type parameter. It is sound — `EventsOf<P>`
289
+ * always contains `CoreEvents<P>` by construction — and confining it to this one
290
+ * method is why the three call sites above read plainly.
291
+ */
292
+ #emitCore(type, detail) {
293
+ // Cast the channel, not the method: pulling `emit` out into a variable would call
294
+ // it unbound, and it reads its listeners off `this`.
295
+ const events = this.#events;
296
+ events.emit(type, detail);
297
+ }
298
+ }
299
+ /** Starting state: every `default` the spec declares, then whatever `init` overrides. */
300
+ function seed(spec, patch) {
301
+ const state = {};
302
+ for (const [key, property] of Object.entries(spec)) {
303
+ if (property.default !== undefined)
304
+ state[key] = property.default;
305
+ }
306
+ return Object.assign(state, patch);
307
+ }
308
+ /**
309
+ * The factory, with everything the contract prescribes already written: the
310
+ * cumulative init, the synchronous chain, the channel that outlives every session,
311
+ * and one live engine per factory.
312
+ *
313
+ * A subclass supplies its static identity and {@link EngineSDKBase.createPlay}.
314
+ */
315
+ export class EngineSDKBase {
316
+ #init;
317
+ #play;
318
+ /**
319
+ * Created before any engine exists and handed to every carrier downstream. That is
320
+ * what lets a host subscribe to an error thrown while the first session opens.
321
+ */
322
+ events;
323
+ constructor(init) {
324
+ this.#init = { ...init };
325
+ this.events = new EventEmitter(init?.on);
326
+ }
327
+ config(patch) {
328
+ if (arguments.length === 0)
329
+ return this.#pending().config();
330
+ return this.#patch('config', patch);
331
+ }
332
+ options(patch) {
333
+ if (arguments.length === 0)
334
+ return this.#pending().options();
335
+ return this.#patch('options', patch);
336
+ }
337
+ assets(patch) {
338
+ if (arguments.length === 0)
339
+ return this.#pending().assets();
340
+ return this.#patch('assets', patch);
341
+ }
342
+ storage(patch) {
343
+ if (arguments.length === 0)
344
+ return this.#pending().storage();
345
+ return this.#patch('storage', patch);
346
+ }
347
+ /** Recorded now, applied when the engine exists — same call as `new Sdk({ mount })`. */
348
+ mount(target) {
349
+ this.#init.mount = target;
350
+ this.#play?.mount(target);
351
+ return this;
352
+ }
353
+ on(type, listener) {
354
+ this.events.on(type, listener);
355
+ return this;
356
+ }
357
+ off(type, listener) {
358
+ this.events.off(type, listener);
359
+ return this;
360
+ }
361
+ /**
362
+ * The only way in. On a live engine it reopens the session rather than building a
363
+ * second one: a factory governs at most one engine.
364
+ */
365
+ start(options) {
366
+ this.#play ??= this.createPlay(this.#init, this.events);
367
+ return this.#play.restart(options);
368
+ }
369
+ /** Answers a read before there is an engine: the settings the next one will use. */
370
+ #pending() {
371
+ return this.#play ?? this.createPlay(this.#init, this.events);
372
+ }
373
+ #patch(kind, patch) {
374
+ this.#init[kind] = { ...this.#init[kind], ...patch };
375
+ // A live engine gets it too, so the factory does not silently describe a state
376
+ // the engine is not in.
377
+ if (this.#play && patch)
378
+ this.#play[kind](patch);
379
+ return this;
380
+ }
381
+ }
382
+ //# sourceMappingURL=engine-specs.js.map
@@ -160,6 +160,11 @@ export interface Rsdkv4InputDefaults {
160
160
  /**
161
161
  * The engine's own default bindings. See {@link Rsdkv4InputDefaults}.
162
162
  *
163
+ * Directions are the arrow keys. This build's `ProcessInput()` also reads `KeyW`,
164
+ * `KeyA`, `KeyS` and `KeyD` as a second set of them, and takes those three scancodes
165
+ * off `x`, `y` and `z` to do it — so the table below is what a *claiming* host should
166
+ * bind, and the engine under SDL polling answers to both.
167
+ *
163
168
  * This replaces the old `RSDKV4_KEYMAP`, which nothing read and which was wrong in
164
169
  * three ways for want of ever being checked against the engine: it listed 9 of the
165
170
  * 14 buttons, and bound `select` to `ShiftLeft` where the engine's default is `Tab`.
@@ -283,12 +288,15 @@ export interface Rsdkv4Assets {
283
288
  export interface Rsdkv4Storage {
284
289
  /**
285
290
  * Per-game folder for the pack, the settings and the save file, so two games do not
286
- * overwrite each other. Examples: `"sonic1"`, `"rsdkv4/Sonic2"`.
291
+ * overwrite each other. Examples: `"sonic-1"`, `"sonic-2/profile-a"`.
287
292
  *
288
- * It is a path under the engine's working root *and* the OPFS directory the SDK
289
- * mirrors save data into, which is what lets a host read `<namespace>/SData.bin`
290
- * itself. Anything that is not a usable relative path normalises to `"default"` —
291
- * including the object you get from interpolating a game record by mistake.
293
+ * Relative to the engine's own folder: the SDK always prefixes `rsdkv4/`, so the
294
+ * files land in `rsdkv4/<namespace>/` — in WASMFS (`/data/rsdkv4/<namespace>`) and in
295
+ * OPFS alike — and no namespace can reach another engine's files. A host reads the
296
+ * resolved path from `play.storagePath`. Segments are cleaned (`.`/`..` dropped,
297
+ * odd characters → `_`); anything unusable normalises to `"default"`, including the
298
+ * object you get from interpolating a game record by mistake. A leading `rsdkv4/`
299
+ * (the old convention) is stripped with a warning.
292
300
  *
293
301
  * @defaultValue `"default"`
294
302
  */
@@ -1 +1 @@
1
- {"version":3,"file":"rsdkv4.options.d.ts","sourceRoot":"","sources":["../../src/rsdkv4.options.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rsdkv4.options.d.ts","sourceRoot":"","sources":["../../src/rsdkv4.options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5E,+CAA+C;AAC/C,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,eAAe,GAAG,MAAM,CAAC;AAElF;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;AAE/F;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC9B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,qGAejB,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACvC;;;;;;;;;;;;OAYG;IACH,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,EAAE,mBAiC1B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B;;;;;;OAMG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,uFAAuF;AACvF,MAAM,WAAW,YAAY;IAC3B,WAAW;IACX,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,KAAK,EAAE,OAAO,CAAC;IACf,wFAAwF;IACxF,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAe,SAAQ,cAAc;IACpD,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,EAAE,UAAU,CAAC,cAAc,CA6ElD,CAAC"}
@@ -51,6 +51,11 @@ export const RSDKV4_BUTTONS = [
51
51
  /**
52
52
  * The engine's own default bindings. See {@link Rsdkv4InputDefaults}.
53
53
  *
54
+ * Directions are the arrow keys. This build's `ProcessInput()` also reads `KeyW`,
55
+ * `KeyA`, `KeyS` and `KeyD` as a second set of them, and takes those three scancodes
56
+ * off `x`, `y` and `z` to do it — so the table below is what a *claiming* host should
57
+ * bind, and the engine under SDL polling answers to both.
58
+ *
54
59
  * This replaces the old `RSDKV4_KEYMAP`, which nothing read and which was wrong in
55
60
  * three ways for want of ever being checked against the engine: it listed 9 of the
56
61
  * 14 buttons, and bound `select` to `ShiftLeft` where the engine's default is `Tab`.
@@ -168,7 +173,7 @@ export const RSDKV4_SPEC = {
168
173
  namespace: {
169
174
  default: 'default',
170
175
  live: false,
171
- description: "Folder for this game's pack, settings and save file.",
176
+ description: "Folder for this game's pack, settings and save file, inside the engine's own rsdkv4/ folder (OPFS rsdkv4/<namespace>/).",
172
177
  },
173
178
  persist: {
174
179
  enum: ['opfs', null],
@@ -1 +1 @@
1
- {"version":3,"file":"rsdkv4.options.js","sourceRoot":"","sources":["../../src/rsdkv4.options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAwFH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI;IACJ,MAAM;IACN,MAAM;IACN,OAAO;IACP,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,OAAO;IACP,QAAQ;CACA,CAAC;AAgDX;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAwB;IAC/C,QAAQ,EAAE;QACR,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,YAAY;QACnB,CAAC,EAAE,MAAM;QACT,CAAC,EAAE,MAAM;QACT,CAAC,EAAE,MAAM;QACT,CAAC,EAAE,MAAM;QACT,CAAC,EAAE,MAAM;QACT,CAAC,EAAE,MAAM;QACT,CAAC,EAAE,MAAM;QACT,CAAC,EAAE,MAAM;QACT,KAAK,EAAE,OAAO;QACd,MAAM,EAAE,KAAK;KACd;IACD,OAAO,EAAE;QACP,EAAE,EAAE,EAAE;QACN,IAAI,EAAE,EAAE;QACR,IAAI,EAAE,EAAE;QACR,KAAK,EAAE,EAAE;QACT,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,IAAI;KACb;CACF,CAAC;AA4LF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,WAAW,GAA+B;IACrD,MAAM,EAAE;QACN,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,0BAA0B,EAAE;QAC9E,OAAO,EAAE;YACP,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,KAAK;YACX,WAAW,EAAE,6EAA6E;SAC3F;QACD,eAAe,EAAE;YACf,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,KAAK;YACX,WAAW,EAAE,6EAA6E;SAC3F;QACD,SAAS,EAAE;YACT,OAAO,EAAE,QAAQ;YACjB,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;YACxB,IAAI,EAAE,KAAK;YACX,WAAW,EACT,sFAAsF;SACzF;QACD,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,+CAA+C,EAAE;QACpF,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,sCAAsC,EAAE;KAC9E;IACD,OAAO,EAAE;QACP,gFAAgF;QAChF,sCAAsC;QACtC,MAAM,EAAE;YACN,IAAI,EAAE,KAAK;YACX,WAAW,EAAE,4EAA4E;SAC1F;QACD,IAAI,EAAE;YACJ,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACxB,IAAI,EAAE,KAAK;YACX,WAAW,EAAE,gDAAgD;SAC9D;QACD,KAAK,EAAE;YACL,IAAI,EAAE,KAAK;YACX,WAAW,EAAE,mEAAmE;SACjF;QACD,6EAA6E;QAC7E,kEAAkE;QAClE,EAAE;QACF,6EAA6E;QAC7E,8EAA8E;QAC9E,wEAAwE;QACxE,2EAA2E;QAC3E,yCAAyC;QACzC,OAAO,EAAE;YACP,IAAI,EAAE,IAAI;YACV,WAAW,EACT,gMAAgM;SACnM;KACF;IACD,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,YAAY,EAAE,CAAC,OAAO,CAAC;YACvB,IAAI,EAAE,KAAK;YACX,WAAW,EAAE,iEAAiE;SAC/E;QACD,QAAQ,EAAE;YACR,YAAY,EAAE,CAAC,MAAM,CAAC;YACtB,IAAI,EAAE,KAAK;YACX,WAAW,EAAE,mEAAmE;SACjF;KACF;IACD,OAAO,EAAE;QACP,SAAS,EAAE;YACT,OAAO,EAAE,SAAS;YAClB,IAAI,EAAE,KAAK;YACX,WAAW,EAAE,sDAAsD;SACpE;QACD,OAAO,EAAE;YACP,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC;YACpB,IAAI,EAAE,KAAK;YACX,WAAW,EAAE,qEAAqE;SACnF;KACF;CACF,CAAC"}
1
+ {"version":3,"file":"rsdkv4.options.js","sourceRoot":"","sources":["../../src/rsdkv4.options.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@
7
7
  * const play = await new Rsdkv4SDK()
8
8
  * .mount(document.querySelector('#stage'))
9
9
  * .assets({ data: () => library.readBytes('Sonic2') })
10
- * .storage({ namespace: 'rsdkv4/Sonic2' })
10
+ * .storage({ namespace: 'sonic-2' }) // → OPFS rsdkv4/sonic-2/
11
11
  * .config({ startMenu: 'host' })
12
12
  * .on('saves', ({ detail }) => paintSlots(detail))
13
13
  * .start({ slot: 0 });
@@ -54,6 +54,32 @@ export declare const RSDKV4_VIDEO: {
54
54
  readonly baseHeight: 240;
55
55
  readonly aspect: "16:9";
56
56
  };
57
+ /**
58
+ * Where an engine keeps one namespace: `<engineId>/<namespace>`, relative to the OPFS
59
+ * root (which is WASMFS `/data`). Written with nothing rsdkv4-specific in it, so it can
60
+ * move verbatim into engine-specs.
61
+ *
62
+ * The host's namespace is cleaned segment by segment: blanks dropped, `.` and `..`
63
+ * dropped (they used to survive, and `'../x'` walked out of the work root), anything
64
+ * outside `[A-Za-z0-9._-]` turned into `_`. Nothing usable left means `'default'`.
65
+ * A namespace that already starts with the engine id has it stripped once, so a host
66
+ * written against the old convention (`'rsdkv4/Sonic1'`) lands where it always did.
67
+ */
68
+ export declare function storagePath(engineId: string, namespace: unknown): {
69
+ namespace: string;
70
+ path: string;
71
+ prefixed: boolean;
72
+ };
73
+ /**
74
+ * A screen of RSDKv4's own in-canvas Dev Menu (Debug.cpp), as {@link
75
+ * RsdkDevMenuBridge.open} names them.
76
+ *
77
+ * `'categories'` is the engine's `SELECT A STAGE LIST` — PRESENTATION / REGULAR /
78
+ * SPECIAL / BONUS — and `'stages'` the scene list under one of them. Those two are the
79
+ * only native way to reach the BONUS list: the packs' own menu scenes (Sonic 1 and 2's
80
+ * `LEVEL SELECT` and `STAGE MENU`) can load a regular stage and nothing else.
81
+ */
82
+ export type RsdkDevMenuScreen = 'main' | 'player' | 'categories' | 'stages';
57
83
  /** RSDKv4-specific bridge for a host's debug/stage-select UI. */
58
84
  export interface RsdkDevMenuBridge {
59
85
  getStageList(): Array<{
@@ -64,6 +90,26 @@ export interface RsdkDevMenuBridge {
64
90
  }>;
65
91
  loadStage(categoryIdx: number, stageIdx: number): void;
66
92
  setPaused(paused: boolean): void;
93
+ /**
94
+ * Whether the loaded wasm carries {@link open}. False means an engine built before
95
+ * the bridge existed — a host should hide the button rather than offer a dead one.
96
+ */
97
+ readonly canOpen: boolean;
98
+ /**
99
+ * Open the engine's own Dev Menu at `screen`, with `category` selecting the stage
100
+ * list for `'categories'` (preselected) and `'stages'` (listed). Categories are the
101
+ * engine's own indices, the ones {@link getStageList} is ordered by — 2 is BONUS.
102
+ *
103
+ * This is what Escape does natively: the request is handed to the engine thread
104
+ * (`ENGINE_INITDEVMENU`) and the menu comes up on its next frame, so **the running
105
+ * stage is torn down** — `initDevMenu()` clears graphics and animation data. Save
106
+ * files are untouched; the current run is not.
107
+ *
108
+ * The menu is driven by the engine's own input and does not run while the SDK holds
109
+ * a pause, so resume before calling. Returns false when the engine cannot be reached
110
+ * or the wasm predates the bridge ({@link canOpen}).
111
+ */
112
+ open(screen?: RsdkDevMenuScreen, category?: number): boolean;
67
113
  }
68
114
  /**
69
115
  * A row of the engine's GAME OPTIONS screen (differs between Sonic 1 and 2), as the
@@ -128,6 +174,34 @@ export interface RsdkInputBridge {
128
174
  release(): PromisePlay<Rsdkv4Payloads>;
129
175
  /** Whether a claim is held. The `options()` descriptor deliberately will not say. */
130
176
  readonly claimed: boolean;
177
+ /**
178
+ * What the engine has held **right now**, as the bitmask a claim writes: bit 0 is
179
+ * `up`, bit 13 is `select`, in {@link RSDKV4_BUTTONS} order.
180
+ *
181
+ * This is the read-back to the claim's write, and it answers the one question no
182
+ * amount of listening on the page can: *did the button reach the engine?* A key
183
+ * that fires `keydown` in the browser and never lights a bit here died between SDL
184
+ * and `ProcessInput()`; one that lights the bit and changes nothing on screen was
185
+ * received and the game chose not to act on it. Those two are otherwise the same
186
+ * silence, and telling them apart is the difference between a real input bug and
187
+ * Sonic simply refusing to roll while a direction is held.
188
+ *
189
+ * Works whether or not a claim is held — under SDL polling it is the keyboard and
190
+ * the pad as the engine read them. `0` when the engine is not up.
191
+ *
192
+ * Two things it is not. It is `hold`, not `press`: a button reads the same on the
193
+ * frame it goes down and on the hundredth frame it stays down. And it is the
194
+ * engine's view *after* the B→A fold, so in-stage the jump key `x` lights `a` and
195
+ * `b` never lights at all — which is the fold working, not a lost button.
196
+ */
197
+ readonly mask: number;
198
+ /**
199
+ * {@link mask}, spelled as the fourteen named booleans — the same shape a claim
200
+ * hands over, so a host can diff what it sent against what arrived.
201
+ *
202
+ * A fresh object on every read; it is a snapshot, not a live view.
203
+ */
204
+ readonly held: Rsdkv4Buttons;
131
205
  }
132
206
  /**
133
207
  * What `start()` and `restart()` hand back: the contract's chainable promise, narrowed
@@ -209,6 +283,12 @@ export declare class Rsdkv4Play extends EnginePlayBase<Rsdkv4Payloads> {
209
283
  get persistent(): boolean;
210
284
  /** The normalised {@link Rsdkv4Storage.namespace} this session actually used. */
211
285
  get namespace(): string;
286
+ /**
287
+ * Where this session's files live, relative to the OPFS root: `rsdkv4/<namespace>`.
288
+ * The path a host walks with `navigator.storage.getDirectory()` to stage a
289
+ * `Data.rsdk` before booting, or to read `SData.bin` after.
290
+ */
291
+ get storagePath(): string;
212
292
  /**
213
293
  * Input ownership, named. `play.input.claim(buttons)` and `play.input.release()`
214
294
  * are exactly `options({ buttons })` and `options({ buttons: null })` — same state,
@@ -229,7 +309,7 @@ export declare class Rsdkv4Play extends EnginePlayBase<Rsdkv4Payloads> {
229
309
  export declare class Rsdkv4SDK extends EngineSDKBase<Rsdkv4Payloads> {
230
310
  static readonly id = "rsdkv4";
231
311
  /** Kept in step with package.json by `node scripts/sync-version.mjs`. */
232
- static readonly version = "0.2.0";
312
+ static readonly version = "0.3.0";
233
313
  static readonly name = "Retro Software Development Kit v4";
234
314
  static readonly description = "RSDKv4 compiled to WebAssembly via Emscripten. One game-agnostic WASM binary runs both Sonic 1 and Sonic 2 \u2014 the difference is only which Data.rsdk the host supplies at runtime. Ships none of the game: the player provides their own Data.rsdk, and it never leaves their browser.";
235
315
  protected createPlay(init: EngineInit<Rsdkv4Payloads>, events: EventEmitter<Rsdkv4Events_>): Rsdkv4Play;
@@ -1 +1 @@
1
- {"version":3,"file":"rsdkv4.sdk.d.ts","sourceRoot":"","sources":["../../src/rsdkv4.sdk.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rsdkv4.sdk.d.ts","sourceRoot":"","sources":["../../src/rsdkv4.sdk.ts"],"names":[],"mappings":"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"}