emoemu 0.1.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 (213) hide show
  1. package/.claude/settings.local.json +77 -0
  2. package/.node-version +1 -0
  3. package/CLAUDE.md +435 -0
  4. package/README.md +404 -0
  5. package/TODO.md +655 -0
  6. package/dist/index.cjs +25108 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +25085 -0
  9. package/docs/building-libretro-cores-arm-mac.md +237 -0
  10. package/docs/config-file-format.md +488 -0
  11. package/docs/cores-trd.md +425 -0
  12. package/docs/headless-hardware-rendering-trd.md +676 -0
  13. package/docs/libretro-cores-trd.md +997 -0
  14. package/docs/mupen64-software-rendering-trd.md +751 -0
  15. package/docs/n64-support-trd.md +306 -0
  16. package/docs/native-rendering-trd.md +540 -0
  17. package/docs/native-ui-rendering-trd.md +1195 -0
  18. package/docs/netplay-trd.md +665 -0
  19. package/docs/retroarch-netplay-docs.md +277 -0
  20. package/docs/save-state-format.md +666 -0
  21. package/eslint.config.js +111 -0
  22. package/icon/icon.png +0 -0
  23. package/icon/icon.pxd +0 -0
  24. package/package.json +63 -0
  25. package/pnpm-workspace.yaml +10 -0
  26. package/src/Emulator/consts.ts +14 -0
  27. package/src/Emulator/index.ts +2496 -0
  28. package/src/Emulator/saveState/index.ts +155 -0
  29. package/src/Emulator/screenshot/index.ts +160 -0
  30. package/src/Emulator/terminalDimensions/index.ts +79 -0
  31. package/src/Emulator/types.ts +83 -0
  32. package/src/cli/commands/consts.ts +10 -0
  33. package/src/cli/commands/index.ts +462 -0
  34. package/src/cli/parseArgs/consts.ts +17 -0
  35. package/src/cli/parseArgs/index.ts +457 -0
  36. package/src/cli/parseArgs/types.ts +61 -0
  37. package/src/cli/runEmulator/index.ts +406 -0
  38. package/src/cli/runEmulator/types.ts +7 -0
  39. package/src/consts.ts +19 -0
  40. package/src/core/button/consts.ts +35 -0
  41. package/src/core/button/index.ts +123 -0
  42. package/src/core/core.ts +300 -0
  43. package/src/core/index.ts +19 -0
  44. package/src/cores/libretro/api/index.ts +334 -0
  45. package/src/cores/libretro/api/types.ts +148 -0
  46. package/src/cores/libretro/callbacks/consts.ts +41 -0
  47. package/src/cores/libretro/callbacks/index.ts +456 -0
  48. package/src/cores/libretro/consts.ts +45 -0
  49. package/src/cores/libretro/coreOptions/consts.ts +36 -0
  50. package/src/cores/libretro/coreOptions/index.ts +222 -0
  51. package/src/cores/libretro/environment/consts.ts +118 -0
  52. package/src/cores/libretro/environment/index.ts +1095 -0
  53. package/src/cores/libretro/index.ts +937 -0
  54. package/src/cores/libretro/loader/index.ts +496 -0
  55. package/src/cores/libretro/pixelFormat/consts.ts +43 -0
  56. package/src/cores/libretro/pixelFormat/index.ts +397 -0
  57. package/src/cores/libretro/types.ts +339 -0
  58. package/src/frontend/AudioManager/index.ts +420 -0
  59. package/src/frontend/SettingsManager/index.ts +250 -0
  60. package/src/frontend/config/index.ts +608 -0
  61. package/src/frontend/config/tests.ts +354 -0
  62. package/src/frontend/config/types.ts +36 -0
  63. package/src/frontend/consts.ts +114 -0
  64. package/src/frontend/coreBuilder/index.ts +644 -0
  65. package/src/frontend/coreBuilder/types.ts +15 -0
  66. package/src/frontend/coreDownloader/index.ts +620 -0
  67. package/src/frontend/coreDownloader/types.ts +17 -0
  68. package/src/frontend/corePreferences/index.ts +69 -0
  69. package/src/frontend/coreRegistry/index.ts +276 -0
  70. package/src/frontend/directoryCache/index.ts +75 -0
  71. package/src/frontend/index.ts +79 -0
  72. package/src/frontend/notifications/consts.ts +14 -0
  73. package/src/frontend/notifications/index.ts +250 -0
  74. package/src/frontend/playlist/consts.ts +168 -0
  75. package/src/frontend/playlist/index.ts +899 -0
  76. package/src/frontend/playlist/labelFormatter/consts.ts +15 -0
  77. package/src/frontend/playlist/labelFormatter/index.ts +155 -0
  78. package/src/frontend/playlist/labelFormatter/tests.ts +153 -0
  79. package/src/frontend/playlist/reader/index.ts +559 -0
  80. package/src/frontend/playlist/sync/index.ts +511 -0
  81. package/src/frontend/playlist/systemLookup/index.ts +233 -0
  82. package/src/frontend/playlist/utils/index.ts +50 -0
  83. package/src/frontend/romScanner/consts.ts +348 -0
  84. package/src/frontend/romScanner/index.ts +1957 -0
  85. package/src/frontend/saveServices/consts.ts +2 -0
  86. package/src/frontend/saveServices/index.ts +313 -0
  87. package/src/frontend/serviceProvider/index.ts +108 -0
  88. package/src/frontend/serviceProvider/types.ts +13 -0
  89. package/src/index.ts +428 -0
  90. package/src/input/Controller/consts.ts +50 -0
  91. package/src/input/Controller/index.ts +81 -0
  92. package/src/input/GamepadManager/consts.ts +22 -0
  93. package/src/input/GamepadManager/index.ts +418 -0
  94. package/src/input/InputManager/consts.ts +86 -0
  95. package/src/input/InputManager/index.ts +593 -0
  96. package/src/input/InputMapper/consts.ts +33 -0
  97. package/src/input/InputMapper/index.ts +436 -0
  98. package/src/input/consts.ts +410 -0
  99. package/src/input/gamepadProfiles/index.ts +1100 -0
  100. package/src/input/index.ts +38 -0
  101. package/src/input/inputUtils/index.ts +31 -0
  102. package/src/netplay/FrameBuffer/consts.ts +2 -0
  103. package/src/netplay/FrameBuffer/index.ts +364 -0
  104. package/src/netplay/FrameBuffer/tests.ts +286 -0
  105. package/src/netplay/InputBuffer/consts.ts +7 -0
  106. package/src/netplay/InputBuffer/index.ts +347 -0
  107. package/src/netplay/InputBuffer/tests.ts +166 -0
  108. package/src/netplay/NetplayClient/index.ts +976 -0
  109. package/src/netplay/NetplayConnection/index.ts +536 -0
  110. package/src/netplay/NetplayDiscovery/consts.ts +41 -0
  111. package/src/netplay/NetplayDiscovery/index.ts +525 -0
  112. package/src/netplay/NetplayServer/index.ts +1407 -0
  113. package/src/netplay/SyncManager/index.ts +984 -0
  114. package/src/netplay/SyncManager/tests.ts +419 -0
  115. package/src/netplay/consts.ts +371 -0
  116. package/src/netplay/crc32/consts.ts +14 -0
  117. package/src/netplay/crc32/index.ts +97 -0
  118. package/src/netplay/crc32/tests.ts +40 -0
  119. package/src/netplay/index.ts +41 -0
  120. package/src/netplay/netplayLogger/consts.ts +30 -0
  121. package/src/netplay/netplayLogger/index.ts +345 -0
  122. package/src/netplay/protocol/consts.ts +86 -0
  123. package/src/netplay/protocol/index.ts +1280 -0
  124. package/src/netplay/protocol/tests.ts +606 -0
  125. package/src/netplay/protocol/types.ts +20 -0
  126. package/src/netplay/types.ts +395 -0
  127. package/src/rendering/KittyRenderer/index.ts +616 -0
  128. package/src/rendering/NativeRenderer/index.ts +279 -0
  129. package/src/rendering/NativeRenderer/tests.ts +133 -0
  130. package/src/rendering/TerminalRenderer/index.ts +770 -0
  131. package/src/rendering/consts.ts +401 -0
  132. package/src/rendering/fonts/CozetteVector.ttf +0 -0
  133. package/src/rendering/index.ts +26 -0
  134. package/src/rendering/nativeUi/NativeWindowManager/index.ts +158 -0
  135. package/src/rendering/nativeUi/NativeWindowManager/tests.ts +81 -0
  136. package/src/rendering/nativeUi/consts.ts +6 -0
  137. package/src/rendering/nativeUi/index.ts +20 -0
  138. package/src/rendering/postProcessing/consts.ts +38 -0
  139. package/src/rendering/postProcessing/index.ts +923 -0
  140. package/src/rendering/shared/ansi/consts.ts +12 -0
  141. package/src/rendering/shared/ansi/index.ts +104 -0
  142. package/src/rendering/shared/consts.ts +2 -0
  143. package/src/rendering/shared/fitToTerminal/index.ts +67 -0
  144. package/src/ui/AddRomsPrompt/consts.ts +13 -0
  145. package/src/ui/AddRomsPrompt/index.tsx +781 -0
  146. package/src/ui/App/consts.ts +2 -0
  147. package/src/ui/App/index.tsx +456 -0
  148. package/src/ui/AppCapabilities/index.tsx +67 -0
  149. package/src/ui/ConfigContext/index.tsx +56 -0
  150. package/src/ui/CoreManager/consts.ts +11 -0
  151. package/src/ui/CoreManager/index.tsx +779 -0
  152. package/src/ui/CoreSelector/consts.ts +2 -0
  153. package/src/ui/CoreSelector/index.tsx +251 -0
  154. package/src/ui/DialogContainer/index.tsx +42 -0
  155. package/src/ui/DialogOptionsList/index.tsx +61 -0
  156. package/src/ui/DuplicateCrcPrompt/consts.ts +5 -0
  157. package/src/ui/DuplicateCrcPrompt/index.tsx +146 -0
  158. package/src/ui/GamepadContext/consts.ts +15 -0
  159. package/src/ui/GamepadContext/index.tsx +295 -0
  160. package/src/ui/NativeDialog/index.tsx +120 -0
  161. package/src/ui/NetplayDisconnectedDialog/index.tsx +93 -0
  162. package/src/ui/NetplayPauseMenu/consts.ts +2 -0
  163. package/src/ui/NetplayPauseMenu/index.tsx +97 -0
  164. package/src/ui/RomBrowser/NetplayPanel/consts.ts +24 -0
  165. package/src/ui/RomBrowser/NetplayPanel/index.tsx +520 -0
  166. package/src/ui/RomBrowser/SettingsPanel/index.tsx +478 -0
  167. package/src/ui/RomBrowser/consts.ts +61 -0
  168. package/src/ui/RomBrowser/index.tsx +1164 -0
  169. package/src/ui/RomBrowser/settingsConfig/index.ts +320 -0
  170. package/src/ui/RomBrowser/types.ts +67 -0
  171. package/src/ui/SaveStateDialog/consts.ts +2 -0
  172. package/src/ui/SaveStateDialog/index.tsx +225 -0
  173. package/src/ui/WarningDialog/index.tsx +113 -0
  174. package/src/ui/consts.ts +27 -0
  175. package/src/ui/hooks/useClearTerminal/consts.ts +2 -0
  176. package/src/ui/hooks/useClearTerminal/index.ts +37 -0
  177. package/src/ui/hooks/useDialogNavigation/index.ts +99 -0
  178. package/src/ui/hooks/useGamepad/consts.ts +21 -0
  179. package/src/ui/hooks/useGamepad/index.ts +194 -0
  180. package/src/ui/index.ts +27 -0
  181. package/src/utils/buffer/consts.ts +17 -0
  182. package/src/utils/buffer/index.ts +129 -0
  183. package/src/utils/color/consts.ts +58 -0
  184. package/src/utils/color/index.ts +183 -0
  185. package/src/utils/compression/consts.ts +50 -0
  186. package/src/utils/compression/index.ts +101 -0
  187. package/src/utils/consts.ts +2 -0
  188. package/src/utils/crc32/consts.ts +22 -0
  189. package/src/utils/crc32/index.ts +83 -0
  190. package/src/utils/ensureDirectory/index.ts +10 -0
  191. package/src/utils/format/consts.ts +8 -0
  192. package/src/utils/format/index.ts +53 -0
  193. package/src/utils/getErrorMessage/index.ts +10 -0
  194. package/src/utils/index.ts +113 -0
  195. package/src/utils/ini/index.ts +200 -0
  196. package/src/utils/kitty/consts.ts +13 -0
  197. package/src/utils/kitty/index.ts +181 -0
  198. package/src/utils/logger/consts.ts +35 -0
  199. package/src/utils/logger/index.ts +217 -0
  200. package/src/utils/paths/consts.ts +18 -0
  201. package/src/utils/paths/index.ts +151 -0
  202. package/src/utils/png/consts.ts +34 -0
  203. package/src/utils/png/index.ts +131 -0
  204. package/src/utils/readJsonFile/index.ts +16 -0
  205. package/src/utils/rotateLogFile/index.ts +44 -0
  206. package/src/utils/safeClose/index.ts +10 -0
  207. package/src/utils/terminal/consts.ts +8 -0
  208. package/src/utils/terminal/index.ts +102 -0
  209. package/src/utils/thumbnailRenderer/consts.ts +2 -0
  210. package/src/utils/thumbnailRenderer/index.ts +147 -0
  211. package/src/utils/typedError/index.ts +26 -0
  212. package/tsconfig.json +31 -0
  213. package/vitest.config.ts +13 -0
@@ -0,0 +1,540 @@
1
+ # Native Rendering Support - Technical Requirements Document
2
+
3
+ This document describes the requirements and implementation approach for adding a native-window rendering backend in emoemu, providing a native window alternative to terminal-based rendering.
4
+
5
+ > **Implementation note:** This design shipped via the [`ink-native`](https://www.npmjs.com/package/ink-native) package (bundled `fenster` window backend + embedded Cozette bitmap font) instead of the custom SDL2/koffi bindings originally proposed below - **zero system dependencies** are required (no SDL2 install). The CLI flag is `--native` and the config value is `video_driver = "native"`. `ink-native` owns the single native window (created via `createStreams()`); the game renderer writes each post-processed frame directly into ink-native's shared `Uint32Array` framebuffer (`0xAARRGGBB`, via `packColor`) and calls `renderer.present()`, while the Ink UI renders into the same window. The app hands the window off between UI and game with `window.pause()` / `window.resume()`. See [native-ui-rendering-trd.md](./native-ui-rendering-trd.md) for the UI side of this design.
6
+ >
7
+ > **Known limitations of the shipped implementation:** no runtime `setTitle` (ink-native exposes none - see "Status Bar / OSD" below, which is superseded), no runtime scale-factor change (`menu_scale_factor` maps to ink-native's `scaleFactor`, applied only at window creation), and no programmatic window resize (the game software-scales/letterboxes into the fixed window instead). The low-level `SDL_*` pseudocode throughout this document (SDL2 API surface, koffi bindings, `SdlRenderer` class) describes the pre-`ink-native` exploration and was not implemented as written - `ink-native` handles window/framebuffer management internally.
8
+
9
+ ## Overview
10
+
11
+ ### Goals
12
+
13
+ 1. **Native Window Rendering**: Render emulator output to a native window instead of the terminal
14
+ 2. **Performance**: Eliminate terminal I/O bottleneck that limits frame rates
15
+ 3. **Cross-Platform**: Support macOS, Linux, and Windows via the bundled `fenster` backend (via `ink-native`), with zero system dependencies
16
+ 4. **Feature Parity**: Support all post-processing effects available in terminal renderers
17
+ 5. **Seamless Integration**: Add "native" as a new video driver alongside kitty, terminal, ascii, emoji
18
+
19
+ ### Non-Goals
20
+
21
+ 1. Hardware-accelerated rendering (GPU shaders) - native rendering uses CPU/software rendering
22
+ 2. Multiple window support
23
+ 3. Fullscreen exclusive mode (windowed fullscreen is acceptable)
24
+ 4. Custom window chrome or UI elements beyond the game display
25
+
26
+ ### Background: Terminal I/O Bottleneck
27
+
28
+ Terminal rendering has inherent limitations:
29
+
30
+ | Issue | Impact |
31
+ |-------|--------|
32
+ | **I/O bandwidth** | Writing pixels as ANSI escape codes or PNG data is slower than direct framebuffer access |
33
+ | **Terminal parsing** | Terminal emulators must parse escape sequences, adding latency |
34
+ | **Redraw overhead** | Full-screen updates require significant data transfer |
35
+ | **Protocol limitations** | Even Kitty graphics protocol has per-frame overhead |
36
+
37
+ A native window bypasses all of these by rendering directly to a window's framebuffer.
38
+
39
+ ---
40
+
41
+ ## Architecture
42
+
43
+ ### Renderer Interface
44
+
45
+ All renderers implement the `Renderer` interface defined in `src/Emulator/types.ts`:
46
+
47
+ ```typescript
48
+ interface Renderer {
49
+ render(frameBuffer: Uint8Array): string;
50
+ renderRgb15?(frameBuffer: Uint16Array): string;
51
+ renderRgb24?(frameBuffer: Uint8Array): string;
52
+ clearScreen(): string;
53
+ hideCursor(): string;
54
+ showCursor(): string;
55
+ getStatusRow(): number;
56
+ moveCursorToRow(row: number): string;
57
+ setDimensions?(width: number, height: number): void;
58
+ }
59
+ ```
60
+
61
+ **Challenge**: The interface returns strings (ANSI escape sequences) written to stdout. SDL rendering doesn't output strings.
62
+
63
+ ### Proposed Solution: Dual-Mode Architecture
64
+
65
+ Modify the rendering pipeline to support both terminal-based and window-based renderers:
66
+
67
+ ```typescript
68
+ interface Renderer {
69
+ // Existing string-based methods (terminal renderers)
70
+ render(frameBuffer: Uint8Array): string;
71
+ renderRgb15?(frameBuffer: Uint16Array): string;
72
+ renderRgb24?(frameBuffer: Uint8Array): string;
73
+
74
+ // New: Direct rendering (window-based renderers)
75
+ renderDirect?(frameBuffer: Uint8Array): void;
76
+ renderRgb15Direct?(frameBuffer: Uint16Array): void;
77
+ renderRgb24Direct?(frameBuffer: Uint8Array): void;
78
+
79
+ // Renderer type indicator
80
+ readonly isWindowBased?: boolean;
81
+
82
+ // Terminal-specific (no-op for window renderers)
83
+ clearScreen(): string;
84
+ hideCursor(): string;
85
+ showCursor(): string;
86
+ getStatusRow(): number;
87
+ moveCursorToRow(row: number): string;
88
+
89
+ // Window-specific (optional)
90
+ setDimensions?(width: number, height: number): void;
91
+ destroy?(): void; // Cleanup SDL resources
92
+ }
93
+ ```
94
+
95
+ ### Emulator Integration
96
+
97
+ In `emulator.ts`, modify the render dispatch:
98
+
99
+ ```typescript
100
+ // In the render method
101
+ if (this.renderer.isWindowBased) {
102
+ // Direct rendering - no stdout write
103
+ if (colorFormat === 'rgb24' && this.renderer.renderRgb24Direct) {
104
+ this.renderer.renderRgb24Direct(framebuffer);
105
+ } else if (colorFormat === 'rgb15' && this.renderer.renderRgb15Direct) {
106
+ this.renderer.renderRgb15Direct(framebuffer);
107
+ } else if (this.renderer.renderDirect) {
108
+ this.renderer.renderDirect(framebuffer);
109
+ }
110
+ } else {
111
+ // Existing terminal-based rendering
112
+ const output = this.renderer.render(framebuffer);
113
+ process.stdout.write(output);
114
+ }
115
+ ```
116
+
117
+ ---
118
+
119
+ ## SDL Integration
120
+
121
+ > **Historical design exploration:** This section (Library Selection, SDL2 API Surface, `SdlRenderer` class) predates adopting `ink-native` and was not implemented as written - see the Implementation note at the top of this document. `ink-native` bundles its own `fenster` window backend; emoemu does not write any SDL2/koffi bindings.
122
+
123
+ ### Library Selection
124
+
125
+ **Recommended**: `@aspect-energy/node-sdl2` or `@aspect-energy/sdl-bindings`
126
+
127
+ | Library | Pros | Cons |
128
+ |---------|------|------|
129
+ | `node-sdl2` | Active maintenance, TypeScript support | May require native compilation |
130
+ | `sdl2-ffi` | Pure FFI via ffi-napi | Older, less maintained |
131
+ | Custom koffi bindings | Full control, consistent with libretro approach | More implementation work |
132
+
133
+ **Recommendation**: Use koffi (already used for libretro) to create SDL2 bindings. This maintains consistency and avoids adding new native dependencies.
134
+
135
+ ### SDL2 API Surface
136
+
137
+ Minimal SDL2 functions needed:
138
+
139
+ ```typescript
140
+ // Initialization
141
+ SDL_Init(SDL_INIT_VIDEO): number;
142
+ SDL_Quit(): void;
143
+
144
+ // Window
145
+ SDL_CreateWindow(title: string, x: number, y: number, w: number, h: number, flags: number): Pointer;
146
+ SDL_DestroyWindow(window: Pointer): void;
147
+ SDL_SetWindowTitle(window: Pointer, title: string): void;
148
+
149
+ // Renderer (SDL's 2D renderer, not to be confused with our Renderer interface)
150
+ SDL_CreateRenderer(window: Pointer, index: number, flags: number): Pointer;
151
+ SDL_DestroyRenderer(renderer: Pointer): void;
152
+ SDL_RenderClear(renderer: Pointer): number;
153
+ SDL_RenderPresent(renderer: Pointer): void;
154
+
155
+ // Texture
156
+ SDL_CreateTexture(renderer: Pointer, format: number, access: number, w: number, h: number): Pointer;
157
+ SDL_DestroyTexture(texture: Pointer): void;
158
+ SDL_UpdateTexture(texture: Pointer, rect: Pointer | null, pixels: Buffer, pitch: number): number;
159
+ SDL_RenderCopy(renderer: Pointer, texture: Pointer, src: Pointer | null, dst: Pointer | null): number;
160
+
161
+ // Events (for window close, resize)
162
+ SDL_PollEvent(event: Pointer): number;
163
+ ```
164
+
165
+ ### SdlRenderer Class
166
+
167
+ ```typescript
168
+ // src/rendering/sdl-renderer.ts
169
+
170
+ export interface SdlRendererOptions {
171
+ width: number;
172
+ height: number;
173
+ scale?: number;
174
+ title?: string;
175
+ // Post-processing options
176
+ gamma?: number;
177
+ scanlines?: number;
178
+ saturation?: number;
179
+ brightness?: number;
180
+ contrast?: number;
181
+ vignette?: number;
182
+ bloom?: number;
183
+ ntsc?: number;
184
+ curvature?: number;
185
+ chromaticAberration?: number;
186
+ }
187
+
188
+ export class SdlRenderer implements Renderer {
189
+ readonly isWindowBased = true;
190
+
191
+ private window: Pointer;
192
+ private sdlRenderer: Pointer;
193
+ private texture: Pointer;
194
+ private frameBuffer: Uint8Array;
195
+
196
+ constructor(options: SdlRendererOptions) {
197
+ // Initialize SDL
198
+ // Create window at scaled dimensions
199
+ // Create renderer and texture
200
+ }
201
+
202
+ renderDirect(frameBuffer: Uint8Array): void {
203
+ // Apply post-processing effects to frameBuffer
204
+ // Convert indexed color to RGB24
205
+ // Update texture with pixel data
206
+ // Render texture to window
207
+ // Present
208
+ }
209
+
210
+ renderRgb15Direct(frameBuffer: Uint16Array): void {
211
+ // Convert RGB15 to RGB24
212
+ // Apply effects
213
+ // Update and present
214
+ }
215
+
216
+ renderRgb24Direct(frameBuffer: Uint8Array): void {
217
+ // Apply effects directly
218
+ // Update and present
219
+ }
220
+
221
+ // Terminal methods return empty strings (no-op)
222
+ render(_frameBuffer: Uint8Array): string { return ''; }
223
+ clearScreen(): string { return ''; }
224
+ hideCursor(): string { return ''; }
225
+ showCursor(): string { return ''; }
226
+ getStatusRow(): number { return 0; }
227
+ moveCursorToRow(_row: number): string { return ''; }
228
+
229
+ destroy(): void {
230
+ // Clean up SDL resources
231
+ SDL_DestroyTexture(this.texture);
232
+ SDL_DestroyRenderer(this.sdlRenderer);
233
+ SDL_DestroyWindow(this.window);
234
+ SDL_Quit();
235
+ }
236
+ }
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Type System Changes (Shipped)
242
+
243
+ ### VideoDriver Type
244
+
245
+ **File**: `src/frontend/config/types.ts`
246
+
247
+ ```typescript
248
+ // Before
249
+ export type VideoDriver = "kitty" | "terminal" | "ascii" | "emoji";
250
+
251
+ // After (shipped)
252
+ export type VideoDriver = "native" | "kitty" | "terminal" | "ascii" | "emoji";
253
+ ```
254
+
255
+ ### RenderMode Type
256
+
257
+ **File**: `src/frontend/SettingsManager/index.ts`
258
+
259
+ ```typescript
260
+ // Before
261
+ export type RenderMode = 'kitty' | 'terminal' | 'ascii' | 'emoji';
262
+
263
+ // After (shipped)
264
+ export type RenderMode = 'native' | 'kitty' | 'terminal' | 'ascii' | 'emoji';
265
+ ```
266
+
267
+ ### Type Guards
268
+
269
+ **File**: `src/frontend/config/types.ts`
270
+
271
+ ```typescript
272
+ export const VIDEO_DRIVERS: readonly VideoDriver[] = ['native', 'kitty', 'terminal', 'ascii', 'emoji'];
273
+
274
+ export const isVideoDriver = (value: unknown): value is VideoDriver => {
275
+ return isString(value) && VIDEO_DRIVERS.includes(value as VideoDriver);
276
+ };
277
+ ```
278
+
279
+ ---
280
+
281
+ ## CLI Integration
282
+
283
+ ### New Flag (Shipped as `--native`)
284
+
285
+ **File**: `src/index.ts`
286
+
287
+ Added `--native` flag alongside existing video driver flags:
288
+
289
+ ```typescript
290
+ .option('--native', 'Use native window rendering (best performance)')
291
+ .option('--kitty', 'Use Kitty graphics protocol')
292
+ .option('--terminal', 'Use terminal character rendering')
293
+ .option('--ascii', 'Use colored ASCII character rendering')
294
+ .option('--emoji', 'Use emoji character rendering')
295
+ ```
296
+
297
+ ### Driver Selection Function
298
+
299
+ ```typescript
300
+ const videoDriverToRenderMode = (driver: VideoDriver | null): RenderMode | undefined => {
301
+ switch (driver) {
302
+ case null: return undefined;
303
+ case "native": return "native";
304
+ case "kitty": return "kitty";
305
+ case "terminal": return "terminal";
306
+ case "ascii": return "ascii";
307
+ case "emoji": return "emoji";
308
+ default: return "kitty";
309
+ }
310
+ };
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Configuration
316
+
317
+ ### Config File
318
+
319
+ **File**: `~/.config/emoemu/emoemu.cfg`
320
+
321
+ ```ini
322
+ video_driver = "native"
323
+ menu_scale_factor = 3
324
+ ```
325
+
326
+ ### Config Keys (Shipped)
327
+
328
+ Unlike the `sdl_scale` / `sdl_vsync` / `sdl_window_width` / `sdl_window_height` keys originally proposed below, no new config keys shipped. Window scale reuses the existing `menu_scale_factor` key (passed to `ink-native` as `scaleFactor`, applied only at window creation); `ink-native` has no VSync toggle or window-size override:
329
+
330
+ | Key | Type | Default | Description |
331
+ |-----|------|---------|-------------|
332
+ | `menu_scale_factor` | number | auto | Window scale, passed to `ink-native` as `scaleFactor`. Applies only at window creation. |
333
+
334
+ ---
335
+
336
+ ## Window Management
337
+
338
+ > **Historical design (SDL):** This section describes the original SDL-based window management design. The shipped implementation uses `ink-native` (fenster), which owns the window and has no VSync toggle (fixed `frameRate`, default 60), no runtime `setTitle`, and no programmatic window resize.
339
+
340
+ ### Initialization
341
+
342
+ 1. Calculate window size: `sourceWidth * scale` x `sourceHeight * scale`
343
+ 2. Create centered window with title "emoemu - {game_name}"
344
+ 3. Create SDL renderer with VSync if enabled
345
+ 4. Create streaming texture matching source dimensions
346
+
347
+ ### Event Handling
348
+
349
+ Poll SDL events in the main loop to handle:
350
+
351
+ | Event | Action |
352
+ |-------|--------|
353
+ | `SDL_QUIT` | Clean shutdown |
354
+ | `SDL_WINDOWEVENT_RESIZED` | Update scale/viewport |
355
+ | `SDL_WINDOWEVENT_FOCUS_LOST` | Optional: pause emulation |
356
+
357
+ ### Input Considerations
358
+
359
+ SDL can capture keyboard events. Two approaches:
360
+
361
+ 1. **Hybrid mode**: Continue using terminal keyboard input (Kitty protocol), SDL only for display
362
+ 2. **Full SDL mode**: Use SDL for both display and keyboard input
363
+
364
+ **Recommendation**: Start with hybrid mode (SDL display + terminal input) to minimize changes. Full SDL input can be added later.
365
+
366
+ ---
367
+
368
+ ## Post-Processing Effects
369
+
370
+ Reuse existing post-processing pipeline from `src/rendering/post-processing/`:
371
+
372
+ ```typescript
373
+ import { applyEffects, EffectOptions } from './post-processing';
374
+
375
+ // In the native renderer's RGB conversion path (NativeRenderer)
376
+ const processed = applyEffects(frameBuffer, width, height, {
377
+ gamma: this.gamma,
378
+ scanlines: this.scanlines,
379
+ saturation: this.saturation,
380
+ // ... other effects
381
+ });
382
+ ```
383
+
384
+ The effects operate on raw pixel data, so they work identically for native and terminal renderers.
385
+
386
+ ---
387
+
388
+ ## Status Bar / OSD
389
+
390
+ Terminal renderers display status information (FPS, core info) in terminal rows below the game display. For a native window:
391
+
392
+ > **Shipped reality:** `ink-native` exposes no runtime `setTitle`, so **Option C below is not available** - it was the original recommendation but could not be implemented. Options A and B remain the viable approaches for status/OSD in native mode.
393
+
394
+ ### Option A: Render to Texture
395
+
396
+ Draw status text directly on the native framebuffer using a bitmap font. Simple but limited styling.
397
+
398
+ ### Option B: Separate Terminal Output
399
+
400
+ Keep status/OSD in terminal while game renders to the native window. Users see:
401
+ - Native window with game display
402
+ - Terminal with status info, notifications
403
+
404
+ ### Option C: Window Title (not available - see note above)
405
+
406
+ Put key status info in window title: "emoemu - Super Mario Bros [60 FPS]". Not possible with `ink-native`, which has no runtime title-setting API.
407
+
408
+ ---
409
+
410
+ ## File Structure
411
+
412
+ ```
413
+ src/rendering/
414
+ ├── index.ts # Add SdlRenderer export
415
+ ├── sdl-renderer.ts # New: SDL renderer implementation
416
+ ├── sdl-bindings.ts # New: koffi SDL2 bindings
417
+ ├── renderer.ts # Existing: TerminalRenderer
418
+ ├── kitty-renderer.ts # Existing: KittyRenderer
419
+ └── ...
420
+ ```
421
+
422
+ ---
423
+
424
+ ## Implementation Phases
425
+
426
+ ### Phase 1: SDL Bindings
427
+
428
+ 1. Create `sdl-bindings.ts` with koffi FFI definitions
429
+ 2. Implement basic window creation and destruction
430
+ 3. Test standalone (outside emulator)
431
+
432
+ ### Phase 2: SdlRenderer Class
433
+
434
+ 1. Implement `SdlRenderer` with basic RGB24 rendering
435
+ 2. Add to renderer exports
436
+ 3. Integrate with emulator's renderer selection
437
+
438
+ ### Phase 3: Type System & CLI
439
+
440
+ 1. Update `VideoDriver` and `RenderMode` types
441
+ 2. Add `--native` CLI flag
442
+ 3. Update type guards
443
+ 4. Add config keys
444
+
445
+ ### Phase 4: Color Format Support
446
+
447
+ 1. Implement indexed color rendering (NES palette conversion)
448
+ 2. Implement RGB15 rendering (GBC, SNES)
449
+ 3. Test with various cores
450
+
451
+ ### Phase 5: Post-Processing
452
+
453
+ 1. Integrate existing effects pipeline
454
+ 2. Add SDL-specific effect options if needed
455
+ 3. Test all effects
456
+
457
+ ### Phase 6: Polish
458
+
459
+ 1. Window management (resize, close handling)
460
+ 2. Status display (window title or terminal)
461
+ 3. Documentation updates
462
+
463
+ ---
464
+
465
+ ## Testing Strategy
466
+
467
+ ### Manual Testing
468
+
469
+ | Test Case | Expected Result |
470
+ |-----------|-----------------|
471
+ | Launch with `--native` | Native window opens, game renders |
472
+ | NES ROM (indexed color) | Correct colors from palette |
473
+ | SNES ROM (RGB15) | Correct color conversion |
474
+ | N64 ROM (RGB24) | Direct rendering, good performance |
475
+ | Window close | Clean shutdown, no crash |
476
+ | Effects (CRT preset) | Visual effects applied |
477
+
478
+ ### Performance Testing
479
+
480
+ Compare frame rates between SDL and terminal modes:
481
+
482
+ | System | Terminal (Kitty) | SDL | Expected Improvement |
483
+ |--------|-----------------|-----|---------------------|
484
+ | NES | 60 FPS | 60 FPS | Equivalent (not bottlenecked) |
485
+ | N64 | 30-40 FPS | 50-60 FPS | ~50% improvement |
486
+
487
+ ### Compatibility Testing
488
+
489
+ Test on:
490
+ - macOS (Apple Silicon and Intel)
491
+ - Linux (X11 and Wayland)
492
+ - Windows (if supported)
493
+
494
+ ---
495
+
496
+ ## Dependencies
497
+
498
+ ### Runtime (Shipped)
499
+
500
+ `ink-native` bundles its `fenster` native window backend and the Cozette bitmap font directly in the npm package - **zero system dependencies**. No SDL2 install of any kind is required on any platform:
501
+
502
+ | Platform | Installation |
503
+ |----------|-------------|
504
+ | macOS | None - bundled with `ink-native` |
505
+ | Linux | None - bundled with `ink-native` |
506
+ | Windows | None - bundled with `ink-native` |
507
+
508
+ ### Build Dependencies
509
+
510
+ None - `ink-native` ships prebuilt/bundled, so there is no native compilation step (this matches the original goal of avoiding one, though the mechanism changed from koffi FFI to `ink-native`'s bundled backend).
511
+
512
+ ---
513
+
514
+ ## Limitations
515
+
516
+ 1. **No hardware acceleration**: software rendering is used; GPU shaders not available
517
+ 2. **Shared window with UI**: Game and Ink UI share a single native window (see [native-ui-rendering-trd.md](./native-ui-rendering-trd.md)), not integrated into the terminal
518
+ 3. **Input latency**: May have slightly different input characteristics than terminal mode
519
+ 4. **Status display**: Limited compared to terminal's flexible text output
520
+ 5. **No runtime `setTitle`**: `ink-native` exposes no API to change the window title after creation
521
+ 6. **No runtime scale-factor change**: `menu_scale_factor` (→ `scaleFactor`) only applies at window creation
522
+ 7. **No programmatic window resize**: the game letterboxes into the fixed window instead of resizing it
523
+
524
+ ---
525
+
526
+ ## Future Enhancements
527
+
528
+ 1. **SDL GPU renderer**: Use SDL's GPU-accelerated renderer for better scaling
529
+ 2. **Shader support**: Add shader passes for advanced effects
530
+ 3. **Full SDL input**: Use SDL for keyboard/gamepad input
531
+ 4. **Fullscreen mode**: Toggle between windowed and fullscreen
532
+ 5. **Multiple windows**: Support for debug views (VRAM, nametables)
533
+
534
+ ---
535
+
536
+ ## Resources
537
+
538
+ - [SDL2 Documentation](https://wiki.libsdl.org/SDL2/FrontPage)
539
+ - [SDL2 Rendering API](https://wiki.libretro.com/index.php/SDL2_Rendering)
540
+ - [koffi FFI Library](https://koffi.dev/)