@wasm-gaming/rsdkv4-wasm 0.1.2

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.
@@ -0,0 +1,4 @@
1
+ import type { EngineManifest } from '@wasm-gaming/engine-specs';
2
+ export declare const manifest: EngineManifest;
3
+ export default manifest;
4
+ //# sourceMappingURL=rsdkv4.manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsdkv4.manifest.d.ts","sourceRoot":"","sources":["../../src/rsdkv4.manifest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,eAAO,MAAM,QAAQ,EAAE,cAkCtB,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,39 @@
1
+ // The rsdkv4 EngineManifest — typed against the contract, so a drift from
2
+ // @wasm-gaming/engine-specs is a compile error here. `npm run build:manifest`
3
+ // serializes this to dist/manifest.json (the artifact CI attaches to a Release).
4
+ import { RSDKV4_OPTIONS_SCHEMA } from './rsdkv4.options.js';
5
+ export const manifest = {
6
+ id: 'rsdkv4',
7
+ version: '0.1.0',
8
+ name: 'Retro Software Development Kit v4',
9
+ artifacts: {
10
+ // Relative to the manifest (dist/manifest.json); the engine files live in
11
+ // the dist/rsdkv4/ subfolder.
12
+ wasm: 'rsdkv4/rsdkv4.wasm',
13
+ js: 'rsdkv4/rsdkv4.js',
14
+ },
15
+ assets: [
16
+ {
17
+ key: 'data',
18
+ // Lives under the OPFS-backed (persistent) working dir; the engine reads it
19
+ // via CWD (booted with the UsingCWD arg).
20
+ mountPath: '/data/Data.rsdk',
21
+ required: true,
22
+ accept: ['.rsdk'],
23
+ description: 'RSDKv4 game data pack. Differs per game (Sonic 1 vs Sonic 2); selects the game.',
24
+ },
25
+ {
26
+ key: 'settings',
27
+ mountPath: '/data/settings.ini',
28
+ required: false,
29
+ accept: ['.ini'],
30
+ description: 'Engine settings. Omitted → the SDK generates one from config.options.',
31
+ },
32
+ ],
33
+ input: 'rsdkv4',
34
+ video: { baseWidth: 424, baseHeight: 240, aspect: '16:9' },
35
+ options: RSDKV4_OPTIONS_SCHEMA,
36
+ capabilities: { saveStates: false, sram: false, coreSelectable: false },
37
+ };
38
+ export default manifest;
39
+ //# sourceMappingURL=rsdkv4.manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsdkv4.manifest.js","sourceRoot":"","sources":["../../src/rsdkv4.manifest.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8EAA8E;AAC9E,iFAAiF;AAGjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,EAAE,EAAE,QAAQ;IACZ,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,mCAAmC;IACzC,SAAS,EAAE;QACT,0EAA0E;QAC1E,8BAA8B;QAC9B,IAAI,EAAE,oBAAoB;QAC1B,EAAE,EAAE,kBAAkB;KACvB;IACD,MAAM,EAAE;QACN;YACE,GAAG,EAAE,MAAM;YACX,4EAA4E;YAC5E,0CAA0C;YAC1C,SAAS,EAAE,iBAAiB;YAC5B,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,CAAC,OAAO,CAAC;YACjB,WAAW,EACT,iFAAiF;SACpF;QACD;YACE,GAAG,EAAE,UAAU;YACf,SAAS,EAAE,oBAAoB;YAC/B,QAAQ,EAAE,KAAK;YACf,MAAM,EAAE,CAAC,MAAM,CAAC;YAChB,WAAW,EACT,uEAAuE;SAC1E;KACF;IACD,KAAK,EAAE,QAAQ;IACf,KAAK,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE;IAC1D,OAAO,EAAE,qBAAqB;IAC9B,YAAY,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE;CACxE,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { JSONSchema } from '@wasm-gaming/engine-specs';
2
+ export interface Rsdkv4Options {
3
+ /**
4
+ * RSDKv4's native in-canvas Dev Menu. Kept off by default: the launcher owns
5
+ * the debug/stage-select UI (via `instance.devMenu`), and leaving this on has
6
+ * historically produced a native menu-like screen at boot.
7
+ */
8
+ devMenu?: boolean;
9
+ /** RSDKv4 EngineDebugMode — enables the web devmenu embind bridge hooks. */
10
+ engineDebugMode?: boolean;
11
+ /** VSync the engine's SDL window. */
12
+ vsync?: boolean;
13
+ /** Boot directly into a stage: 0-based stage-list category. */
14
+ startingCategory?: number;
15
+ /** Boot directly into a stage: 0-based scene within the category. */
16
+ startingScene?: number;
17
+ /** Starting player/character index. */
18
+ startingPlayer?: number;
19
+ }
20
+ export declare const DEFAULT_RSDKV4_OPTIONS: Required<Rsdkv4Options>;
21
+ export declare const RSDKV4_OPTIONS_SCHEMA: JSONSchema;
22
+ //# sourceMappingURL=rsdkv4.options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsdkv4.options.d.ts","sourceRoot":"","sources":["../../src/rsdkv4.options.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qCAAqC;IACrC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,aAAa,CAO1D,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,UAoBnC,CAAC"}
@@ -0,0 +1,41 @@
1
+ // Engine-specific options for RSDKv4.
2
+ //
3
+ // This is the authoritative description of what `EngineConfig.options` accepts
4
+ // for this engine. It provides both:
5
+ // - `Rsdkv4Options` — the compile-time type (for TS hosts/launchers)
6
+ // - `RSDKV4_OPTIONS_SCHEMA` — the JSON Schema mirrored into manifest.json's
7
+ // `options` (for runtime host UI + validation)
8
+ // - `DEFAULT_RSDKV4_OPTIONS` — defaults the SDK falls back to.
9
+ //
10
+ // At runtime these options are serialized into RSDKv4's `settings.ini` (see the
11
+ // SDK's settings serializer) when the host does not pass an explicit `settings`
12
+ // asset.
13
+ export const DEFAULT_RSDKV4_OPTIONS = {
14
+ devMenu: false,
15
+ engineDebugMode: true,
16
+ vsync: true,
17
+ startingCategory: 0,
18
+ startingScene: 0,
19
+ startingPlayer: 0,
20
+ };
21
+ export const RSDKV4_OPTIONS_SCHEMA = {
22
+ type: 'object',
23
+ additionalProperties: false,
24
+ properties: {
25
+ devMenu: {
26
+ type: 'boolean',
27
+ default: false,
28
+ description: "RSDKv4's native in-canvas Dev Menu. The launcher provides its own overlay via instance.devMenu, so keep this off.",
29
+ },
30
+ engineDebugMode: {
31
+ type: 'boolean',
32
+ default: true,
33
+ description: 'Enables the web devmenu embind bridge (stage list / warp / pause).',
34
+ },
35
+ vsync: { type: 'boolean', default: true },
36
+ startingCategory: { type: 'integer', minimum: 0, default: 0 },
37
+ startingScene: { type: 'integer', minimum: 0, default: 0 },
38
+ startingPlayer: { type: 'integer', minimum: 0, default: 0 },
39
+ },
40
+ };
41
+ //# sourceMappingURL=rsdkv4.options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsdkv4.options.js","sourceRoot":"","sources":["../../src/rsdkv4.options.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,+EAA+E;AAC/E,qCAAqC;AACrC,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,iEAAiE;AACjE,EAAE;AACF,gFAAgF;AAChF,gFAAgF;AAChF,SAAS;AAuBT,MAAM,CAAC,MAAM,sBAAsB,GAA4B;IAC7D,OAAO,EAAE,KAAK;IACd,eAAe,EAAE,IAAI;IACrB,KAAK,EAAE,IAAI;IACX,gBAAgB,EAAE,CAAC;IACnB,aAAa,EAAE,CAAC;IAChB,cAAc,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAe;IAC/C,IAAI,EAAE,QAAQ;IACd,oBAAoB,EAAE,KAAK;IAC3B,UAAU,EAAE;QACV,OAAO,EAAE;YACP,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,KAAK;YACd,WAAW,EACT,mHAAmH;SACtH;QACD,eAAe,EAAE;YACf,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,oEAAoE;SAClF;QACD,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE;QACzC,gBAAgB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;QAC7D,aAAa,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;QAC1D,cAAc,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;KAC5D;CACF,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { EngineConfig, EngineInstance, AssetData } from '@wasm-gaming/engine-specs';
2
+ import { manifest } from './rsdkv4.manifest.js';
3
+ export { manifest };
4
+ /** RSDKv4-specific bridge for the launcher's debug/stage-select UI. */
5
+ export interface RsdkDevMenuBridge {
6
+ getStageList(): Array<{
7
+ name: string;
8
+ stages: Array<{
9
+ name: string;
10
+ }>;
11
+ }>;
12
+ loadStage(categoryIdx: number, stageIdx: number): void;
13
+ setPaused(paused: boolean): void;
14
+ }
15
+ export type Rsdkv4Instance = EngineInstance & {
16
+ devMenu: RsdkDevMenuBridge;
17
+ /** True when the working dir is OPFS-backed (persistent) rather than in-memory. */
18
+ persistent: boolean;
19
+ /** Relative storage namespace used under /data (e.g. "sonic1", "sonic2"). */
20
+ storageNamespace: string;
21
+ /** Remove persisted game files for this namespace only. */
22
+ purgeStorage(): {
23
+ data: boolean;
24
+ settings: boolean;
25
+ };
26
+ };
27
+ /**
28
+ * Extra (engine-specific) config on top of the contract's EngineConfig: lazy asset
29
+ * providers, invoked only on a cache miss — e.g. to skip a large Data.rsdk fetch
30
+ * when it's already persisted in OPFS.
31
+ */
32
+ export type Rsdkv4LoadConfig = EngineConfig & {
33
+ dataProvider?: () => Promise<AssetData> | AssetData;
34
+ settingsProvider?: () => Promise<AssetData> | AssetData;
35
+ /**
36
+ * Per-game storage folder under /data used for OPFS/WASMFS files.
37
+ * Examples: "sonic1", "sonic2", "my-pack/v1".
38
+ */
39
+ storageNamespace?: string;
40
+ };
41
+ /** Boot the RSDKv4 engine. */
42
+ export declare function load(config: Rsdkv4LoadConfig): Promise<Rsdkv4Instance>;
43
+ declare const _default: {
44
+ manifest: import("@wasm-gaming/engine-specs").EngineManifest;
45
+ load: typeof load;
46
+ };
47
+ export default _default;
48
+ //# sourceMappingURL=rsdkv4.sdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsdkv4.sdk.d.ts","sourceRoot":"","sources":["../../src/rsdkv4.sdk.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACzF,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGhD,OAAO,EAAE,QAAQ,EAAE,CAAC;AA+GpB,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,YAAY,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IACzE,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACvD,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG;IAC5C,OAAO,EAAE,iBAAiB,CAAC;IAC3B,mFAAmF;IACnF,UAAU,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,gBAAgB,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,YAAY,IAAI;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;CACtD,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG;IAC5C,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACpD,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACxD;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,8BAA8B;AAC9B,wBAAsB,IAAI,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,cAAc,CAAC,CA+H5E;;;;;AAED,wBAAkC"}
@@ -0,0 +1,257 @@
1
+ // @wasm-gaming/rsdkv4-wasm — SDK entry point.
2
+ //
3
+ // Conforms to the wasm-gaming engine contract (github.com/wasm-gaming/engine-specs):
4
+ // exports `manifest` (declarative) and `load(config)` (imperative).
5
+ //
6
+ // One game-agnostic rsdkv4.wasm runs both Sonic 1 and Sonic 2 — the difference is
7
+ // only which Data.rsdk the host hands us in `config.assets.data`, written into the
8
+ // filesystem at runtime (not baked with --preload-file).
9
+ //
10
+ // Filesystem: built with -sWASMFS (the modern Emscripten filesystem, not MEMFS).
11
+ // The game working dir `/data` is mounted on **OPFS** for persistence when the
12
+ // page is cross-origin isolated; otherwise it falls back to the WASMFS in-memory
13
+ // backend (transient, but the engine still boots). The backend is selectable via
14
+ // `config.persist` (see mountWorkingDir), and when the dir is OPFS-backed the SDK
15
+ // reuses an already-persisted Data.rsdk/settings.ini instead of re-fetching.
16
+ import { manifest } from './rsdkv4.manifest.js';
17
+ import { DEFAULT_RSDKV4_OPTIONS } from './rsdkv4.options.js';
18
+ export { manifest };
19
+ const WORK_ROOT = '/data';
20
+ const DEFAULT_STORAGE_NAMESPACE = 'default';
21
+ /** Serialize engine options into RSDKv4's settings.ini format. */
22
+ function buildSettingsIni(options = {}) {
23
+ const o = { ...DEFAULT_RSDKV4_OPTIONS, ...options };
24
+ return [
25
+ '[Dev]',
26
+ `EngineDebugMode=${o.engineDebugMode ? 'true' : 'false'}`,
27
+ `DevMenu=${o.devMenu ? 'true' : 'false'}`,
28
+ `StartingCategory=${o.startingCategory | 0}`,
29
+ `StartingScene=${o.startingScene | 0}`,
30
+ `StartingPlayer=${o.startingPlayer | 0}`,
31
+ '',
32
+ '[Window]',
33
+ `VSync=${o.vsync ? 'true' : 'false'}`,
34
+ '',
35
+ ].join('\n');
36
+ }
37
+ function toUint8(x) {
38
+ if (x == null)
39
+ return null;
40
+ if (typeof x === 'string')
41
+ return new TextEncoder().encode(x);
42
+ if (x instanceof Uint8Array)
43
+ return x;
44
+ if (x instanceof ArrayBuffer)
45
+ return new Uint8Array(x);
46
+ if (ArrayBuffer.isView(x))
47
+ return new Uint8Array(x.buffer, x.byteOffset, x.byteLength);
48
+ throw new TypeError('asset must be Uint8Array | ArrayBuffer | string');
49
+ }
50
+ /** Normalize a user-provided storage namespace into a safe relative path. */
51
+ function normalizeStorageNamespace(namespace) {
52
+ if (typeof namespace !== 'string' || !namespace.trim())
53
+ return DEFAULT_STORAGE_NAMESPACE;
54
+ const cleaned = namespace
55
+ .split('/')
56
+ .map((segment) => segment.trim())
57
+ .filter(Boolean)
58
+ .map((segment) => segment.replace(/[^A-Za-z0-9._-]/g, '_'))
59
+ .filter(Boolean)
60
+ .join('/');
61
+ return cleaned || DEFAULT_STORAGE_NAMESPACE;
62
+ }
63
+ /** Best-effort mkdir -p for the Emscripten FS layer. */
64
+ function ensureDir(Module, path) {
65
+ const parts = path.split('/').filter(Boolean);
66
+ let current = '';
67
+ for (const part of parts) {
68
+ current += `/${part}`;
69
+ try {
70
+ Module.FS.mkdir(current);
71
+ }
72
+ catch {
73
+ /* already exists */
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Mount the game working dir, honoring `persist`:
79
+ * 'opfs' → force an OPFS (persistent) mount; warn + fall back if unavailable.
80
+ * null → force the in-memory WASMFS backend (the MEMFS-equivalent fallback).
81
+ * undefined → auto: OPFS when the page is cross-origin isolated, else in-memory.
82
+ * ('idbfs' isn't supported under WASMFS and is treated as in-memory.)
83
+ *
84
+ * OPFS sync access needs a worker/pthread + cross-origin isolation, so 'auto' only
85
+ * attempts it when isolated (the single-threaded build would otherwise trap).
86
+ * Returns whether the resulting mount is persistent.
87
+ */
88
+ function mountWorkingDir(Module, persist) {
89
+ const isolated = typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated;
90
+ const attemptOpfs = persist === 'opfs' || (persist === undefined && isolated);
91
+ if (attemptOpfs && typeof Module.ccall === 'function') {
92
+ try {
93
+ const rc = Module.ccall('web_mount_opfs', 'number', ['string'], [WORK_ROOT]);
94
+ if (rc === 0)
95
+ return { persistent: true };
96
+ }
97
+ catch {
98
+ /* fall through to in-memory */
99
+ }
100
+ if (persist === 'opfs') {
101
+ console.warn('[rsdkv4] OPFS requested but unavailable — using in-memory WASMFS');
102
+ }
103
+ }
104
+ ensureDir(Module, WORK_ROOT);
105
+ return { persistent: false };
106
+ }
107
+ /** True if `path` exists in the (mounted) filesystem. */
108
+ function fileExists(Module, path) {
109
+ try {
110
+ Module.FS.stat(path);
111
+ return true;
112
+ }
113
+ catch {
114
+ return false;
115
+ }
116
+ }
117
+ /** Delete a file if present; returns true when something was removed. */
118
+ function deleteFileIfExists(Module, path) {
119
+ if (!fileExists(Module, path))
120
+ return false;
121
+ try {
122
+ Module.FS.unlink(path);
123
+ return true;
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ }
129
+ /** Boot the RSDKv4 engine. */
130
+ export async function load(config) {
131
+ const { canvas, assets, onEvent } = config;
132
+ const options = config.options;
133
+ if (!canvas)
134
+ throw new Error('rsdkv4: config.canvas is required');
135
+ // Emscripten's SDL2 port locates the canvas via document.querySelector('#canvas').
136
+ if (canvas.id !== 'canvas')
137
+ canvas.id = 'canvas';
138
+ const emit = (e) => {
139
+ try {
140
+ onEvent?.(e);
141
+ }
142
+ catch { /* host handler must not break us */ }
143
+ };
144
+ const jsUrl = config.jsUrl ?? new URL('./rsdkv4.js', import.meta.url).href;
145
+ const wasmUrl = config.wasmUrl ?? new URL('./rsdkv4.wasm', import.meta.url).href;
146
+ // RSDKv4 keyboard bindings are the shared input script's default preset.
147
+ if (typeof window !== 'undefined')
148
+ window.__gamepadKeyMap = manifest.input;
149
+ const mod = await import(/* @vite-ignore */ jsUrl);
150
+ const createRSDKv4 = mod.default;
151
+ const Module = await createRSDKv4({
152
+ canvas,
153
+ noInitialRun: true, // built with -sINVOKE_RUN=0; we mount data before main()
154
+ locateFile: (path) => (path.endsWith('.wasm') ? wasmUrl : path),
155
+ print: (...a) => console.log('[rsdkv4]', ...a),
156
+ printErr: (...a) => console.error('[rsdkv4]', ...a),
157
+ onAbort: (reason) => emit({ type: 'error', error: new Error(`rsdkv4 aborted: ${reason}`) }),
158
+ });
159
+ // Mount the working dir first, so we can see what's already persisted (OPFS)
160
+ // before deciding whether to pull assets.
161
+ const { persistent } = mountWorkingDir(Module, config.persist);
162
+ const storageNamespace = normalizeStorageNamespace(config.storageNamespace);
163
+ const workDir = `${WORK_ROOT}/${storageNamespace}`;
164
+ ensureDir(Module, workDir);
165
+ const dataPath = `${workDir}/Data.rsdk`;
166
+ const settingsPath = `${workDir}/settings.ini`;
167
+ // Data.rsdk — precedence: explicit asset > already persisted (skip fetch) > lazy provider.
168
+ let dataBytes = toUint8(assets?.data);
169
+ if (!dataBytes) {
170
+ if (fileExists(Module, dataPath)) {
171
+ console.log(`[rsdkv4] Data.rsdk already in persistent storage (${storageNamespace}) — skipping fetch`);
172
+ }
173
+ else if (config.dataProvider) {
174
+ dataBytes = toUint8(await config.dataProvider());
175
+ }
176
+ }
177
+ if (dataBytes) {
178
+ Module.FS.writeFile(dataPath, dataBytes);
179
+ }
180
+ else if (!fileExists(Module, dataPath)) {
181
+ throw new Error('rsdkv4: no Data.rsdk — provide assets.data, a dataProvider, or a persisted copy (OPFS)');
182
+ }
183
+ // settings.ini — explicit asset > persisted > lazy provider > generated from options.
184
+ let settingsBytes = toUint8(assets?.settings);
185
+ if (!settingsBytes && !fileExists(Module, settingsPath) && config.settingsProvider) {
186
+ settingsBytes = toUint8(await config.settingsProvider());
187
+ }
188
+ if (settingsBytes) {
189
+ Module.FS.writeFile(settingsPath, settingsBytes);
190
+ }
191
+ else if (!fileExists(Module, settingsPath)) {
192
+ Module.FS.writeFile(settingsPath, new TextEncoder().encode(buildSettingsIni(options)));
193
+ }
194
+ // else: keep the persisted settings.ini
195
+ Module.FS.chdir(workDir);
196
+ const setPaused = (paused) => {
197
+ if (typeof Module.web_devmenu_set_paused === 'function')
198
+ Module.web_devmenu_set_paused(paused);
199
+ };
200
+ const devMenu = {
201
+ getStageList() {
202
+ if (typeof Module.web_devmenu_get_stage_list !== 'function')
203
+ return [];
204
+ try {
205
+ return JSON.parse(Module.web_devmenu_get_stage_list());
206
+ }
207
+ catch (e) {
208
+ console.error('[rsdkv4] getStageList failed', e);
209
+ return [];
210
+ }
211
+ },
212
+ loadStage(categoryIdx, stageIdx) {
213
+ if (typeof Module.web_devmenu_load_stage === 'function') {
214
+ Module.web_devmenu_load_stage(categoryIdx | 0, stageIdx | 0);
215
+ }
216
+ },
217
+ setPaused,
218
+ };
219
+ // Run main() from the OPFS/WASMFS working dir. simulate_infinite_loop schedules
220
+ // the rAF loop and returns via a benign unwind Emscripten swallows.
221
+ Module.callMain(['UsingCWD']);
222
+ emit({ type: 'ready' });
223
+ return {
224
+ start() { },
225
+ pause() { setPaused(true); },
226
+ resume() { setPaused(false); },
227
+ reset() {
228
+ throw new Error('rsdkv4: reset() is not supported — destroy() and load() again');
229
+ },
230
+ setInput(preset) {
231
+ if (typeof window !== 'undefined') {
232
+ window.__gamepadKeyMap = preset ?? manifest.input;
233
+ }
234
+ },
235
+ destroy() {
236
+ try {
237
+ Module.pauseMainLoop?.();
238
+ }
239
+ catch { /* noop */ }
240
+ try {
241
+ setPaused(true);
242
+ }
243
+ catch { /* noop */ }
244
+ },
245
+ devMenu,
246
+ persistent,
247
+ storageNamespace,
248
+ purgeStorage() {
249
+ return {
250
+ data: deleteFileIfExists(Module, dataPath),
251
+ settings: deleteFileIfExists(Module, settingsPath),
252
+ };
253
+ },
254
+ };
255
+ }
256
+ export default { manifest, load };
257
+ //# sourceMappingURL=rsdkv4.sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsdkv4.sdk.js","sourceRoot":"","sources":["../../src/rsdkv4.sdk.ts"],"names":[],"mappings":"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"}
Binary file
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@wasm-gaming/rsdkv4-wasm",
3
+ "version": "0.1.2",
4
+ "description": "RSDKv4 (Retro Software Development Kit v4) compiled to WebAssembly, with a JS SDK conforming to the wasm-gaming engine contract. Runs Sonic 1 & Sonic 2 from a single WASM by loading Data.rsdk at runtime.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/wasm-gaming/rsdkv4-wasm.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/wasm-gaming/rsdkv4-wasm/issues"
11
+ },
12
+ "homepage": "https://github.com/wasm-gaming/rsdkv4-wasm#readme",
13
+ "type": "module",
14
+ "main": "./dist/rsdkv4/rsdkv4.sdk.js",
15
+ "types": "./dist/rsdkv4/rsdkv4.sdk.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/rsdkv4/rsdkv4.sdk.d.ts",
19
+ "import": "./dist/rsdkv4/rsdkv4.sdk.js"
20
+ },
21
+ "./options": {
22
+ "types": "./dist/rsdkv4/rsdkv4.options.d.ts",
23
+ "import": "./dist/rsdkv4/rsdkv4.options.js"
24
+ },
25
+ "./manifest": "./dist/manifest.json"
26
+ },
27
+ "files": [
28
+ "dist/rsdkv4/",
29
+ "dist/manifest.json",
30
+ "src/rsdkv4.manifest.ts",
31
+ "src/rsdkv4.options.ts",
32
+ "src/rsdkv4.sdk.ts",
33
+ "README.md"
34
+ ],
35
+ "comment:scripts": "Build logic lives in the Makefile, not here (make build / build-sdk / build-wasm / preview).",
36
+ "scripts": {},
37
+ "peerDependencies": {
38
+ "@wasm-gaming/engine-specs": "*"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "@wasm-gaming/engine-specs": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "devDependencies": {
46
+ "@wasm-gaming/engine-specs": "^0.1.0",
47
+ "typescript": "^5.5.0"
48
+ },
49
+ "engineContract": "https://github.com/wasm-gaming/engine-specs",
50
+ "license": "MIT"
51
+ }
@@ -0,0 +1,44 @@
1
+ // The rsdkv4 EngineManifest — typed against the contract, so a drift from
2
+ // @wasm-gaming/engine-specs is a compile error here. `npm run build:manifest`
3
+ // serializes this to dist/manifest.json (the artifact CI attaches to a Release).
4
+
5
+ import type { EngineManifest } from '@wasm-gaming/engine-specs';
6
+ import { RSDKV4_OPTIONS_SCHEMA } from './rsdkv4.options.js';
7
+
8
+ export const manifest: EngineManifest = {
9
+ id: 'rsdkv4',
10
+ version: '0.1.0',
11
+ name: 'Retro Software Development Kit v4',
12
+ artifacts: {
13
+ // Relative to the manifest (dist/manifest.json); the engine files live in
14
+ // the dist/rsdkv4/ subfolder.
15
+ wasm: 'rsdkv4/rsdkv4.wasm',
16
+ js: 'rsdkv4/rsdkv4.js',
17
+ },
18
+ assets: [
19
+ {
20
+ key: 'data',
21
+ // Lives under the OPFS-backed (persistent) working dir; the engine reads it
22
+ // via CWD (booted with the UsingCWD arg).
23
+ mountPath: '/data/Data.rsdk',
24
+ required: true,
25
+ accept: ['.rsdk'],
26
+ description:
27
+ 'RSDKv4 game data pack. Differs per game (Sonic 1 vs Sonic 2); selects the game.',
28
+ },
29
+ {
30
+ key: 'settings',
31
+ mountPath: '/data/settings.ini',
32
+ required: false,
33
+ accept: ['.ini'],
34
+ description:
35
+ 'Engine settings. Omitted → the SDK generates one from config.options.',
36
+ },
37
+ ],
38
+ input: 'rsdkv4',
39
+ video: { baseWidth: 424, baseHeight: 240, aspect: '16:9' },
40
+ options: RSDKV4_OPTIONS_SCHEMA,
41
+ capabilities: { saveStates: false, sram: false, coreSelectable: false },
42
+ };
43
+
44
+ export default manifest;
@@ -0,0 +1,64 @@
1
+ // Engine-specific options for RSDKv4.
2
+ //
3
+ // This is the authoritative description of what `EngineConfig.options` accepts
4
+ // for this engine. It provides both:
5
+ // - `Rsdkv4Options` — the compile-time type (for TS hosts/launchers)
6
+ // - `RSDKV4_OPTIONS_SCHEMA` — the JSON Schema mirrored into manifest.json's
7
+ // `options` (for runtime host UI + validation)
8
+ // - `DEFAULT_RSDKV4_OPTIONS` — defaults the SDK falls back to.
9
+ //
10
+ // At runtime these options are serialized into RSDKv4's `settings.ini` (see the
11
+ // SDK's settings serializer) when the host does not pass an explicit `settings`
12
+ // asset.
13
+
14
+ import type { JSONSchema } from '@wasm-gaming/engine-specs';
15
+
16
+ export interface Rsdkv4Options {
17
+ /**
18
+ * RSDKv4's native in-canvas Dev Menu. Kept off by default: the launcher owns
19
+ * the debug/stage-select UI (via `instance.devMenu`), and leaving this on has
20
+ * historically produced a native menu-like screen at boot.
21
+ */
22
+ devMenu?: boolean;
23
+ /** RSDKv4 EngineDebugMode — enables the web devmenu embind bridge hooks. */
24
+ engineDebugMode?: boolean;
25
+ /** VSync the engine's SDL window. */
26
+ vsync?: boolean;
27
+ /** Boot directly into a stage: 0-based stage-list category. */
28
+ startingCategory?: number;
29
+ /** Boot directly into a stage: 0-based scene within the category. */
30
+ startingScene?: number;
31
+ /** Starting player/character index. */
32
+ startingPlayer?: number;
33
+ }
34
+
35
+ export const DEFAULT_RSDKV4_OPTIONS: Required<Rsdkv4Options> = {
36
+ devMenu: false,
37
+ engineDebugMode: true,
38
+ vsync: true,
39
+ startingCategory: 0,
40
+ startingScene: 0,
41
+ startingPlayer: 0,
42
+ };
43
+
44
+ export const RSDKV4_OPTIONS_SCHEMA: JSONSchema = {
45
+ type: 'object',
46
+ additionalProperties: false,
47
+ properties: {
48
+ devMenu: {
49
+ type: 'boolean',
50
+ default: false,
51
+ description:
52
+ "RSDKv4's native in-canvas Dev Menu. The launcher provides its own overlay via instance.devMenu, so keep this off.",
53
+ },
54
+ engineDebugMode: {
55
+ type: 'boolean',
56
+ default: true,
57
+ description: 'Enables the web devmenu embind bridge (stage list / warp / pause).',
58
+ },
59
+ vsync: { type: 'boolean', default: true },
60
+ startingCategory: { type: 'integer', minimum: 0, default: 0 },
61
+ startingScene: { type: 'integer', minimum: 0, default: 0 },
62
+ startingPlayer: { type: 'integer', minimum: 0, default: 0 },
63
+ },
64
+ };