wasmcart 0.2.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.
@@ -0,0 +1,111 @@
1
+ # wasmcart GL Surface Specification
2
+
3
+ ## Overview
4
+
5
+ The wasmcart GL surface is **WebGL2 / OpenGL ES 3.0**. This is the same on all hosts:
6
+ browser, Node.js, wasmcart-native, and RetroArch (libretro).
7
+
8
+ The host reports `GL_VERSION = "OpenGL ES 3.0 wasmcart"` regardless of the actual
9
+ driver. Real driver extensions pass through for engines that need them (e.g., Godot
10
+ texture format detection).
11
+
12
+ ## Shader Requirements
13
+
14
+ All shaders MUST use one of:
15
+ - `#version 100` (GLES 2.0 - works everywhere)
16
+ - `#version 300 es` (GLES 3.0 / WebGL2 - recommended)
17
+
18
+ Desktop GL version strings (`#version 110`, `#version 120`, `#version 330 core`)
19
+ are NOT supported. The host does NOT translate shaders.
20
+
21
+ ## Texture Formats
22
+
23
+ The host translates legacy/unsized formats to ES 3.0 equivalents:
24
+
25
+ | Legacy Format | ES 3.0 Internal Format | ES 3.0 Format |
26
+ |--------------|----------------------|--------------|
27
+ | `GL_LUMINANCE` | `GL_R8` | `GL_RED` |
28
+ | `GL_LUMINANCE_ALPHA` | `GL_RG8` | `GL_RG` |
29
+ | `GL_ALPHA` | `GL_R8` | `GL_RED` |
30
+ | `GL_RGB` (ubyte) | `GL_RGB8` | `GL_RGB` |
31
+ | `GL_RGBA` (ubyte) | `GL_RGBA8` | `GL_RGBA` |
32
+
33
+ Carts SHOULD use sized internal formats (`GL_RGBA8`, `GL_R8`, etc.) directly.
34
+ The host fixup exists for compatibility with legacy GL code.
35
+
36
+ ## Porting GL 1.x Games
37
+
38
+ Games written for OpenGL 1.x (fixed-function pipeline) need a renderer rewrite
39
+ to target ES 3.0. There are two approaches:
40
+
41
+ ### 1. Purpose-built gl_compat (recommended)
42
+
43
+ Write a small translation layer (~500-1000 lines) that replaces GL 1.x calls
44
+ with ES 3.0 shader-based equivalents. This is compiled into the cart.
45
+
46
+ Example: `chromium_bsu/gl_compat.cpp` - batch renderer with `#version 300 es`
47
+ shaders. Handles glBegin/glEnd, matrix stack, texture state, blending.
48
+
49
+ Advantages:
50
+ - No external dependencies
51
+ - Small binary size
52
+ - Only translates what the game actually uses
53
+ - Full ES 3.0 compliance
54
+
55
+ ### 2. gl4es (legacy compatibility)
56
+
57
+ Use [ptitSeb/gl4es](https://github.com/ptitSeb/gl4es) to translate GL 1.x → GLES 2.0.
58
+ gl4es is compiled to WASM as a static library and linked into the cart.
59
+
60
+ Current gl4es carts: Neverball, Neverputt, Extreme Tux Racer.
61
+
62
+ Limitations:
63
+ - gl4es only targets GLES 2.0, not ES 3.0
64
+ - Requires host-side format fixup and extension injection
65
+ - Large dependency (~150K lines)
66
+ - Shader version issues on Core 3.3 contexts (RetroArch desktop)
67
+
68
+ gl4es patches required for wasmcart:
69
+ - `hardext.c`: disable `#version 120` detection, force `esversion = 3`
70
+ - Build with `NO_LOADER=ON`, `NOEGL=ON`, `STATICLIB=ON`
71
+
72
+ ### Games that DON'T need translation
73
+
74
+ Games with native GLES 2.0/3.0 renderers work on all hosts with zero translation:
75
+ - Godot 4.x (GLES3 Compatibility renderer)
76
+ - ioquake3 renderergl2 (GLES2/3 path)
77
+ - GZDoom (GLES2 renderer)
78
+ - Any Emscripten/WebGL2 game
79
+ - SDL2 games using GLES backend
80
+
81
+ ## FBO Redirect
82
+
83
+ The host intercepts `glBindFramebuffer(GL_FRAMEBUFFER, 0)` and redirects to an
84
+ internal FBO with depth24+stencil8 attachments. This FBO is then blitted to the
85
+ display surface (EGL window, RetroArch hw_render FBO, or canvas backbuffer).
86
+
87
+ Carts should bind FBO 0 when they want to render to the "screen". The host
88
+ handles the actual presentation.
89
+
90
+ ## VAO Handling
91
+
92
+ On GLES 3.0, VAO 0 is the default VAO. On Core 3.3 (RetroArch desktop),
93
+ VAO 0 is invalid. The host creates an isolated cart VAO and redirects
94
+ `glBindVertexArray(0)` to it. The host also binds this VAO at frame start
95
+ for carts that don't use VAOs at all (gl4es).
96
+
97
+ ## Extension Passthrough
98
+
99
+ `GL_EXTENSIONS` returns real driver extensions. On Core 3.3 contexts,
100
+ the host also injects GLES-equivalent extension names (`GL_OES_*`, `GL_EXT_*`)
101
+ so GLES-targeting code (like gl4es) can detect features.
102
+
103
+ `GL_SHADING_LANGUAGE_VERSION` passes through real values so carts can
104
+ detect Core vs GLES contexts if needed.
105
+
106
+ ## Buffer Orphaning
107
+
108
+ The host applies buffer orphaning on `glBufferSubData` with offset 0:
109
+ `glBufferData(target, size, NULL, GL_STREAM_DRAW)` is called first to
110
+ avoid GPU sync stalls on mobile drivers (Mali, Adreno). This is transparent
111
+ to the cart.
package/docs/input.md ADDED
@@ -0,0 +1,102 @@
1
+ # Input - Gamepad, Pointer, Keyboard
2
+
3
+ ## Gamepad Input (Always Available)
4
+
5
+ Every wasmcart host normalizes controller input to the **W3C Standard Gamepad** layout before writing to `wc_pads[]`. The cart always sees the same button/axis mapping regardless of the physical controller.
6
+
7
+ ### Normalization Pipeline
8
+
9
+ ```
10
+ Physical controller (Xbox, PS5, 8BitDo, Switch Pro, etc.)
11
+
12
+ Host SDL / HID driver
13
+
14
+ SDL GameController mapping (gamecontrollerdb.txt - 2000+ controller definitions)
15
+
16
+ Normalized to W3C Standard Gamepad layout
17
+
18
+ Written to wc_pad_t[4] shared memory
19
+
20
+ Cart reads buttons/axes - every controller looks the same
21
+ ```
22
+
23
+ This means:
24
+ - An Xbox controller's A button = `WC_BTN_A` = W3C `buttons[0]`
25
+ - A PlayStation DualSense's X button = `WC_BTN_A` = W3C `buttons[0]`
26
+ - A Nintendo Pro Controller's B button = `WC_BTN_A` = W3C `buttons[0]`
27
+ - A virtual touchscreen overlay's south button = `WC_BTN_A` = W3C `buttons[0]`
28
+
29
+ The cart never needs to know what controller is physically connected.
30
+
31
+ ### wc_pad_t Layout (16 bytes per pad)
32
+
33
+ ```c
34
+ typedef struct {
35
+ uint16_t buttons; // Bitmask (WC_BTN_A, WC_BTN_B, etc.)
36
+ int16_t left_x; // Left stick X: -32768 to 32767
37
+ int16_t left_y; // Left stick Y: -32768 to 32767
38
+ int16_t right_x; // Right stick X: -32768 to 32767
39
+ int16_t right_y; // Right stick Y: -32768 to 32767
40
+ uint8_t left_trigger; // Left trigger: 0-255
41
+ uint8_t right_trigger; // Right trigger: 0-255
42
+ uint8_t connected; // 1 if controller is connected
43
+ uint8_t _pad[3]; // Alignment padding
44
+ } wc_pad_t;
45
+ ```
46
+
47
+ ### Button Mapping (W3C Standard Gamepad)
48
+
49
+ | Bit | Constant | W3C Index | Xbox | PlayStation | Nintendo |
50
+ |-----|----------|-----------|------|-------------|----------|
51
+ | 0 | `WC_BTN_A` | buttons[0] | A | Cross | B |
52
+ | 1 | `WC_BTN_B` | buttons[1] | B | Circle | A |
53
+ | 2 | `WC_BTN_X` | buttons[2] | X | Square | Y |
54
+ | 3 | `WC_BTN_Y` | buttons[3] | Y | Triangle | X |
55
+ | 4 | `WC_BTN_L` | buttons[4] | LB | L1 | L |
56
+ | 5 | `WC_BTN_R` | buttons[5] | RB | R1 | R |
57
+ | 6 | - | buttons[6] | LT (analog) | L2 (analog) | ZL |
58
+ | 7 | - | buttons[7] | RT (analog) | R2 (analog) | ZR |
59
+ | 8 | `WC_BTN_SELECT` | buttons[8] | Back/View | Share | - |
60
+ | 9 | `WC_BTN_START` | buttons[9] | Start/Menu | Options | + |
61
+ | 10 | `WC_BTN_UP` | buttons[12] | D-pad Up | D-pad Up | D-pad Up |
62
+ | 11 | `WC_BTN_DOWN` | buttons[13] | D-pad Down | D-pad Down | D-pad Down |
63
+ | 12 | `WC_BTN_LEFT` | buttons[14] | D-pad Left | D-pad Left | D-pad Left |
64
+ | 13 | `WC_BTN_RIGHT` | buttons[15] | D-pad Right | D-pad Right | D-pad Right |
65
+ | 14 | `WC_BTN_L3` | buttons[10] | LS Click | L3 | LS Click |
66
+ | 15 | `WC_BTN_R3` | buttons[11] | RS Click | R3 | RS Click |
67
+
68
+ Triggers (buttons[6]/[7]) are analog - use `left_trigger`/`right_trigger` (0-255) for analog values, or the button bit for digital pressed/not-pressed.
69
+
70
+ ### Axes
71
+
72
+ | Axis | W3C Index | Range |
73
+ |------|-----------|-------|
74
+ | Left Stick X | axes[0] | -32768 to 32767 (left to right) |
75
+ | Left Stick Y | axes[1] | -32768 to 32767 (up to down) |
76
+ | Right Stick X | axes[2] | -32768 to 32767 |
77
+ | Right Stick Y | axes[3] | -32768 to 32767 |
78
+
79
+ Note: W3C Gamepad API uses -1.0 to 1.0 floats. wasmcart uses int16. JS game runtimes (wasmcart-jsgame) convert: `axes[i] = pad.left_x / 32767.0`.
80
+
81
+ ### For JS Game Runtimes
82
+
83
+ When exposing `navigator.getGamepads()` to JavaScript games, the gamepad object **must** include `mapping: 'standard'`. Many game frameworks (Phaser, custom engines) filter gamepads by this property:
84
+
85
+ ```javascript
86
+ // Common pattern in game code:
87
+ gamepads = navigator.getGamepads().filter(gp => gp && gp.mapping === 'standard');
88
+ ```
89
+
90
+ Without `mapping: 'standard'`, the gamepad is invisible to the game even though input data is available.
91
+
92
+ ### 4-Player Support
93
+
94
+ `wc_pads[0]` through `wc_pads[3]` support up to 4 controllers. The host maps physical controllers to pad slots in connection order. `connected` field indicates which slots are active.
95
+
96
+ ## Pointer Input (Opt-In, ABI v3)
97
+
98
+ Unified mouse + multitouch. Cart declares `"pointer": true` in manifest. Host writes `wc_pointer_t[10]` state each frame.
99
+
100
+ ## Keyboard Input (Opt-In, ABI v3)
101
+
102
+ 256-bit key state bitmask using USB HID scancodes. Cart declares `"keyboard": true` in manifest. Host writes `uint8_t[32]` bitmask each frame.
@@ -0,0 +1,78 @@
1
+ # Networking - Future Design Notes
2
+
3
+ ## Current ABI
4
+
5
+ WebSocket client-only:
6
+ - `wc_ws_open(url, len)` - connect outbound
7
+ - `wc_ws_send` / `wc_ws_send_text` - send data
8
+ - `wc_ws_close` / `wc_ws_state` - lifecycle
9
+ - `wc_ws_on_open` / `wc_ws_on_message` / `wc_ws_on_close` - callbacks (host → cart)
10
+
11
+ Manifest allowlist controls which domains the cart can connect to.
12
+
13
+ ## Planned: Peer-to-Peer Multiplayer
14
+
15
+ ### Concept
16
+ One player "hosts", another "joins" - like classic LAN play. No cloud infrastructure required.
17
+
18
+ ### Cart-Side API
19
+ The cart uses the same WebSocket ABI with one addition:
20
+
21
+ - `wc_ws_listen(port)` - cart thinks it's hosting a WebSocket server
22
+ - `wc_ws_on_connect(conn_id)` - host notifies cart of new peer
23
+ - All data flows through existing `wc_ws_send` / `wc_ws_on_message`
24
+
25
+ The cart has NO idea what transport is underneath.
26
+
27
+ ### Host-Side Reality
28
+ The host implements "listen" as WebRTC data channels:
29
+
30
+ 1. Cart calls `wc_ws_listen` → host creates WebRTC peer connection
31
+ 2. Signaling handshake (SDP/ICE exchange) - could be:
32
+ - Local network discovery (mDNS/broadcast)
33
+ - QR code displayed on screen
34
+ - Simple signaling server (tiny, stateless)
35
+ 3. Data channel established → host calls `wc_ws_on_connect(conn_id)`
36
+ 4. All subsequent send/receive goes through the data channel
37
+ 5. Cart sees it as a normal WebSocket connection
38
+
39
+ ### Why WebRTC Under the Hood
40
+ - **NAT traversal** - works over the internet, not just LAN
41
+ - **Peer-to-peer** - no relay server needed (STUN is free, Google/Cloudflare run public ones)
42
+ - **Low latency** - UDP-based data channels, ideal for games
43
+ - **No infrastructure** - the user doesn't need to run servers
44
+ - **Browser-compatible** - browser wasmcart hosts get WebRTC for free
45
+
46
+ ### Why the Cart Doesn't Know
47
+ - Same ABI for LAN and internet play
48
+ - Cart code stays simple - just WebSocket send/receive
49
+ - Host can swap transport (WebRTC, actual WebSocket, Bluetooth, etc.) without cart changes
50
+ - Security - cart can't access raw WebRTC APIs (no fingerprinting, no media capture)
51
+
52
+ ### Signaling Options (Lightweight)
53
+ - **QR code** - host player's screen shows QR with SDP offer, joining player scans it
54
+ - **Local network** - mDNS/UDP broadcast discovers peers on same WiFi
55
+ - **Room codes** - 4-digit code, tiny stateless signaling relay matches peers
56
+ - **Manual IP** - old school, type in the IP (works on LAN without any service)
57
+
58
+ ### Example Flow
59
+ ```
60
+ Player 1 (host):
61
+ game calls wc_ws_listen(0)
62
+ host creates WebRTC peer, shows room code "ABCD" on screen
63
+ waits for peer...
64
+
65
+ Player 2 (join):
66
+ game calls wc_ws_open("room:ABCD")
67
+ host creates WebRTC peer, signals via room code
68
+ data channel established
69
+
70
+ Both carts:
71
+ wc_ws_on_connect(conn_id) fires
72
+ wc_ws_send(conn_id, game_packet, len) - just works
73
+ wc_ws_on_message(conn_id, data, len) - just works
74
+ ```
75
+
76
+ ## Planned: wc_fetch
77
+
78
+ See [fetch.md](fetch.md). Lower priority - games ship assets in .wasc, WebSocket covers real-time comms. Fetch is mainly for leaderboards/analytics which most non-browser games don't need.
@@ -0,0 +1,88 @@
1
+ # Porting Games to wasmcart
2
+
3
+ ## Target: ES 3.0 / WebGL2
4
+
5
+ wasmcart's GL surface is ES 3.0. All carts should use GLES 2.0 or 3.0
6
+ shaders. See [gl-surface.md](gl-surface.md) for the full spec.
7
+
8
+ ## Easiest Ports (zero GL translation needed)
9
+
10
+ | Source | Examples | Notes |
11
+ |--------|----------|-------|
12
+ | GLES 2.0/3.0 native | Godot, GZDoom | Engine handles GL, just wire up ABI |
13
+ | SDL2 + GLES | Many modern games | Use wasmcart SDL2 backend (sdl2_wc/) |
14
+ | Emscripten/WebGL2 | Browser games | Already WASM, add wasmcart ABI |
15
+ | ioquake3-based | OpenArena, Quake 3 | renderergl2 has GLES path |
16
+ | Canvas 2D / pixel buffer | Retro games | Use wc_gl_blit.h for GPU upload |
17
+
18
+ ## GL 1.x Games
19
+
20
+ Games using OpenGL 1.x (glBegin/glEnd, fixed-function lighting, display lists)
21
+ need a renderer rewrite. Two approaches:
22
+
23
+ ### Approach 1: Custom gl_compat (recommended)
24
+
25
+ Write a purpose-built ES 3.0 batch renderer for the specific game.
26
+ Only translate the GL 1.x calls the game actually uses.
27
+
28
+ **Example:** Chromium B.S.U. - `gl_compat.cpp` (~500 lines)
29
+ - Replaces immediate mode with VBO batching
30
+ - `#version 300 es` shaders
31
+ - Matrix stack, texture state, blending
32
+ - Zero external dependencies
33
+ - Compiled with `-include chromium_compat.h` (zero original files modified)
34
+
35
+ **Upstream value:** These renderers can be submitted as PRs to the original
36
+ game projects, benefiting the entire community.
37
+
38
+ ### Approach 2: gl4es (quick start, more complexity)
39
+
40
+ Use [gl4es](https://github.com/ptitSeb/gl4es) for automatic translation.
41
+ See [gl-surface.md](gl-surface.md#gl4es-legacy-compatibility) for details.
42
+
43
+ ## Porting Checklist
44
+
45
+ 1. **Build to WASM** - Emscripten with `-sWASM=1`
46
+ 2. **Export ABI** - `wc_get_info`, `wc_init`, `wc_render`
47
+ 3. **Set gpu_api** - `wc_info_t.gpu_api = 1` (GL) for all carts
48
+ 4. **Use ES 3.0 shaders** - `#version 100` or `#version 300 es`
49
+ 5. **Assets via .wasc** - Pack with `wasmcart-pack`, load via `wc_asset_size`/`wc_load_asset`
50
+ 6. **Audio** - Write to ring buffer, set sample rate + format flags
51
+ 7. **Input** - Read `wc_pad_t` array (Xbox/W3C button layout)
52
+ 8. **Test on all hosts** - Browser, Node.js, wasmcart-native, RetroArch
53
+
54
+ ## Shared Porting Libraries
55
+
56
+ Located at `wasmcart/porting/include/`:
57
+
58
+ | Header | Purpose |
59
+ |--------|---------|
60
+ | `wc_cart.h` | Buffer declarations, WC_FILL_INFO macro |
61
+ | `wc_gl.h` | Shader compile/link, VAO/VBO helpers |
62
+ | `wc_gl_blit.h` | Upload CPU pixels as GL texture (2D→GL) |
63
+ | `wc_fb.h` | 2D framebuffer drawing (fill_rect, blit) |
64
+ | `wc_math.h` | sin, cos, sqrt, atan2, clamp, lerp |
65
+ | `wc_mat4.h` | 4x4 column-major matrix ops |
66
+ | `wc_vec3.h` | 3D vector operations |
67
+ | `wc_pcm_mixer.h` | Multi-channel PCM audio mixer |
68
+ | `wc_sdl_stubs.h` | SDL2 type defs + no-op stubs |
69
+ | `stb_image.h` | Image loading (JPEG, PNG, BMP) |
70
+ | `audio_bridge.h/c` | SDL2_mixer → ring buffer bridge |
71
+ | `emstubs.c` | Emscripten runtime stubs |
72
+
73
+ ## SDL2 Games (Emscripten Backend)
74
+
75
+ Use the reusable `sdl2_wc/` backends for SDL2 games:
76
+
77
+ ```bash
78
+ # Compile: use SDL2 headers from Emscripten
79
+ -sUSE_SDL=2 # at compile time (headers only)
80
+
81
+ # Link: use wasmcart's SDL2 backend, not Emscripten's
82
+ -sUSE_SDL=0 # at link time
83
+ ```
84
+
85
+ The SDL2 backend provides video (GL surface), audio (ring buffer),
86
+ and input (gamepad) - all wired to the wasmcart ABI.
87
+
88
+ Validated on: Neverball ES, Neverputt ES, Celeste Classic ES, Flare ES.
@@ -0,0 +1,144 @@
1
+ /*
2
+ * wc_cart.h - Cart boilerplate macros for wasmcart
3
+ *
4
+ * Reduces the ~40 lines of buffer declarations + wc_get_info() that
5
+ * every cart copy-pastes. Include this AFTER wasmcart.h.
6
+ *
7
+ * USAGE (GL cart):
8
+ *
9
+ * #define WC_USE_GL
10
+ * #include "wasmcart.h"
11
+ * #include "wc_cart.h"
12
+ *
13
+ * // Define your resolution and audio settings:
14
+ * #define DEFAULT_WIDTH 640
15
+ * #define DEFAULT_HEIGHT 480
16
+ * #define MAX_WIDTH 1920
17
+ * #define MAX_HEIGHT 1080
18
+ * #define AUDIO_CAP 4096
19
+ *
20
+ * // Declare all cart buffers at once:
21
+ * WC_CART_BUFFERS;
22
+ *
23
+ * // In your wc_get_info():
24
+ * WC_EXPORT wc_info_t *wc_get_info(void) {
25
+ * WC_FILL_INFO(0);
26
+ * return &wc_info;
27
+ * }
28
+ *
29
+ * USAGE (2D framebuffer cart):
30
+ *
31
+ * #include "wasmcart.h"
32
+ * #include "wc_cart.h"
33
+ *
34
+ * #define DEFAULT_WIDTH 320
35
+ * #define DEFAULT_HEIGHT 240
36
+ * #define MAX_WIDTH 320
37
+ * #define MAX_HEIGHT 240
38
+ * #define AUDIO_CAP 4096
39
+ *
40
+ * WC_CART_BUFFERS;
41
+ *
42
+ * WC_EXPORT wc_info_t *wc_get_info(void) {
43
+ * WC_FILL_INFO(0);
44
+ * return &wc_info;
45
+ * }
46
+ *
47
+ * WHAT THIS PROVIDES:
48
+ * WC_CART_BUFFERS - declares all standard cart globals
49
+ * WC_FILL_INFO(f) - fills wc_info struct fields
50
+ * WC_EXPORT - shorthand for __attribute__((export_name(...)))
51
+ *
52
+ * You still write your own wc_init() and wc_render() - those have
53
+ * game-specific logic that can't be templated.
54
+ */
55
+
56
+ #ifndef WC_CART_H
57
+ #define WC_CART_H
58
+
59
+ #ifndef WASMCART_H
60
+ #error "Include wasmcart.h before wc_cart.h"
61
+ #endif
62
+
63
+ /* ── Export attribute shorthand ────────────────────────────────────── */
64
+
65
+ #ifndef WC_EXPORT
66
+ #define WC_EXPORT __attribute__((export_name("wc_get_info")))
67
+ #endif
68
+
69
+ /* Helper for other exports */
70
+ #define WC_EXPORT_INIT __attribute__((export_name("wc_init")))
71
+ #define WC_EXPORT_RENDER __attribute__((export_name("wc_render")))
72
+
73
+ /* ── Audio sample type ────────────────────────────────────────────── */
74
+
75
+ /*
76
+ * Default audio format is Float32 (WC_FLAG_AUDIO_F32 set automatically).
77
+ * Define WC_AUDIO_FORMAT_I16 before including this header to use int16
78
+ * ring buffers instead (legacy format).
79
+ */
80
+ #ifdef WC_AUDIO_FORMAT_I16
81
+ #define _WC_AUDIO_SAMPLE_T int16_t
82
+ #else
83
+ #define _WC_AUDIO_SAMPLE_T float
84
+ #endif
85
+
86
+ /* ── Standard cart buffer declarations ────────────────────────────── */
87
+
88
+ /*
89
+ * Declares all the standard globals every cart needs.
90
+ * Uses DEFAULT_WIDTH, DEFAULT_HEIGHT, MAX_WIDTH, MAX_HEIGHT, AUDIO_CAP
91
+ * which must be #defined before this macro.
92
+ *
93
+ * Names match the convention used across all existing carts.
94
+ */
95
+ #define WC_CART_BUFFERS \
96
+ static uint32_t wc_cur_width = DEFAULT_WIDTH; \
97
+ static uint32_t wc_cur_height = DEFAULT_HEIGHT; \
98
+ static uint32_t wc_framebuffer[MAX_WIDTH * MAX_HEIGHT]; \
99
+ static _WC_AUDIO_SAMPLE_T wc_audio_ring[AUDIO_CAP * 2]; \
100
+ static uint32_t wc_audio_write_cursor; \
101
+ static wc_pad_t wc_pads[4]; \
102
+ static wc_time_t wc_time; \
103
+ static wc_info_t wc_info; \
104
+ static wc_host_info_t wc_host_info; \
105
+ static wc_pointer_t wc_pointers[10]; \
106
+ static uint8_t wc_keys[32]
107
+
108
+ /* ── Fill info struct ─────────────────────────────────────────────── */
109
+
110
+ /*
111
+ * Fills the wc_info struct with standard field values.
112
+ * Call inside wc_get_info(). Pass flags like WC_FLAG_AUDIO_F32 or 0.
113
+ *
114
+ * For carts that don't use save data, save_ptr and save_size are set to 0.
115
+ * Override them after this macro if you need save support.
116
+ */
117
+ #define WC_FILL_INFO(_wc_flags) do { \
118
+ wc_info.version = WC_ABI_VERSION; \
119
+ wc_info.width = DEFAULT_WIDTH; \
120
+ wc_info.height = DEFAULT_HEIGHT; \
121
+ wc_info.fb_ptr = (uint32_t)(uintptr_t)wc_framebuffer; \
122
+ wc_info.audio_ptr = (uint32_t)(uintptr_t)wc_audio_ring; \
123
+ wc_info.audio_cap = AUDIO_CAP; \
124
+ wc_info.audio_write_ptr = (uint32_t)(uintptr_t)&wc_audio_write_cursor; \
125
+ wc_info.input_ptr = (uint32_t)(uintptr_t)wc_pads; \
126
+ wc_info.save_ptr = 0; \
127
+ wc_info.save_size = 0; \
128
+ wc_info.time_ptr = (uint32_t)(uintptr_t)&wc_time; \
129
+ wc_info.host_info_ptr = (uint32_t)(uintptr_t)&wc_host_info; \
130
+ wc_info.flags = (_wc_flags) \
131
+ _WC_FILL_INFO_AUDIO_FLAG; \
132
+ wc_info.audio_sample_rate = 0; /* 0 = let host decide */ \
133
+ wc_info.pointer_ptr = (uint32_t)(uintptr_t)wc_pointers; \
134
+ wc_info.keys_ptr = (uint32_t)(uintptr_t)wc_keys; \
135
+ } while (0)
136
+
137
+ /* Auto-set WC_FLAG_AUDIO_F32 unless cart opts into legacy I16 */
138
+ #ifdef WC_AUDIO_FORMAT_I16
139
+ #define _WC_FILL_INFO_AUDIO_FLAG
140
+ #else
141
+ #define _WC_FILL_INFO_AUDIO_FLAG | WC_FLAG_AUDIO_F32
142
+ #endif
143
+
144
+ #endif /* WC_CART_H */