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,665 @@
1
+ # RetroArch Netplay Support - Technical Requirements Document
2
+
3
+ ## Overview
4
+
5
+ This document outlines the implementation plan for adding RetroArch-compatible netplay support to emoemu. The implementation will allow emoemu to act as both a netplay server (host) and client, enabling multiplayer gaming over the network with other emoemu instances and potentially RetroArch clients.
6
+
7
+ **Scope**: Libretro cores only. Native NES core is explicitly excluded from netplay support.
8
+
9
+ ## Implementation Status
10
+
11
+ | Milestone | Status | Description |
12
+ |-----------|--------|-------------|
13
+ | 1. Protocol Foundation | ✅ Complete | Protocol constants, command encoding/decoding, TCP connection wrapper |
14
+ | 2. Frame Buffer & State Management | ✅ Complete | CRC32, frame buffer ring, input buffer with prediction |
15
+ | 3. Sync Manager & Rollback | ✅ Complete | Rollback coordination, desync detection, input merging |
16
+ | 4. Server Implementation | ✅ Complete | NetplayServer class with handshake, input relay, client management |
17
+ | 5. Client Implementation | ✅ Complete | NetplayClient class with handshake, input exchange, rollback integration |
18
+ | 6. Emulator Integration | ✅ Complete | CLI arguments, status bar, notifications, ROM CRC validation |
19
+ | 7. Testing & Polish | ✅ Complete | 212 tests passing, documentation updated |
20
+ | 8. Documentation & Release | ✅ Complete | CLAUDE.md and README.md updated |
21
+
22
+ **Total Tests**: 212 passing (49 protocol + 57 frame buffer + 30 sync manager + 76 other)
23
+
24
+ ## Background
25
+
26
+ ### RetroArch Netplay Protocol
27
+
28
+ RetroArch's netplay uses a deterministic lockstep model with rollback:
29
+
30
+ - **Transport**: TCP on port 55435 (reliable, in-order delivery required)
31
+ - **Architecture**: Server is canonical for synchronization; supports up to 32 clients
32
+ - **Synchronization**: Input delay + rollback/replay when delayed input arrives
33
+ - **State Format**: Raw binary savestates from `retro_serialize()`
34
+
35
+ ### Key Protocol Concepts
36
+
37
+ 1. **Frame Buffer Ring**: Maintains history of frames with input and serialized state
38
+ 2. **Three Frame Pointers**:
39
+ - `self`: Current local execution frame
40
+ - `other`: Last perfectly synchronized frame
41
+ - `unread`: First frame with incomplete remote input
42
+ 3. **Rollback**: When late input arrives, rewind to `other`, replay with correct input
43
+
44
+ ### Command Protocol
45
+
46
+ Each command consists of:
47
+ - 32-bit command ID (network byte order)
48
+ - 32-bit payload size (network byte order)
49
+ - Variable payload
50
+
51
+ Key commands:
52
+ | Command | ID | Description |
53
+ |---------|-----|-------------|
54
+ | `INPUT` | 0x0003 | Per-frame input data (required every frame) |
55
+ | `NOINPUT` | 0x0004 | Server frame advance without input |
56
+ | `NICK` | 0x0020 | Nickname exchange |
57
+ | `PASSWORD` | 0x0021 | Authentication (SHA-256 hash) |
58
+ | `INFO` | 0x0022 | Core name, version, content CRC |
59
+ | `SYNC` | 0x0023 | Initial state synchronization |
60
+ | `MODE` | 0x0026 | Player mode changes (play/spectate) |
61
+ | `CRC` | 0x0040 | Frame hash for desync detection |
62
+ | `LOAD_SAVESTATE` | 0x0042 | State synchronization |
63
+ | `PAUSE` | 0x0043 | Pause notification |
64
+ | `RESUME` | 0x0044 | Resume notification |
65
+
66
+ ## Requirements
67
+
68
+ ### Functional Requirements
69
+
70
+ #### Server (Host) Mode
71
+ - FR-1: Accept incoming TCP connections on configurable port (default: 55435)
72
+ - FR-2: Perform handshake with clients (header, nick, password, info, sync)
73
+ - FR-3: Validate client compatibility (core name, core version, content CRC)
74
+ - FR-4: Send initial savestate to synchronize new clients
75
+ - FR-5: Relay input from all clients to all other clients
76
+ - FR-6: Periodically send CRC commands for desync detection
77
+ - FR-7: Handle client disconnection gracefully
78
+ - FR-8: Support optional password protection
79
+ - FR-9: Support spectator mode (receive state/input, no input sent)
80
+
81
+ #### Client Mode
82
+ - FR-10: Connect to server via hostname/IP and port
83
+ - FR-11: Perform handshake with server
84
+ - FR-12: Load initial savestate from server
85
+ - FR-13: Send local input every frame
86
+ - FR-14: Receive and apply remote input
87
+ - FR-15: Perform rollback/replay when input arrives late
88
+ - FR-16: Request savestate resync on desync detection
89
+ - FR-17: Support spectator mode
90
+
91
+ #### Input Handling
92
+ - FR-18: Buffer local input with configurable delay (0-16 frames)
93
+ - FR-19: Simulate remote input when not yet received (repeat last input)
94
+ - FR-20: Support all libretro input devices (joypad, analog)
95
+
96
+ #### State Management
97
+ - FR-21: Maintain ring buffer of recent frame states (configurable depth)
98
+ - FR-22: Serialize/deserialize state via libretro API
99
+ - FR-23: Compute CRC32 of serialized state for comparison
100
+ - FR-24: Support zlib compression for large state transfers
101
+
102
+ ### Non-Functional Requirements
103
+
104
+ - NFR-1: Latency: Support playable experience up to 200ms RTT
105
+ - NFR-2: Memory: State buffer should not exceed 256MB for typical cores
106
+ - NFR-3: CPU: Rollback should complete within frame budget (16ms at 60fps)
107
+ - NFR-4: Compatibility: Wire-compatible with RetroArch netplay protocol
108
+
109
+ ### Out of Scope
110
+
111
+ - Native NES core netplay support
112
+ - Lobby server integration (manual IP/hostname entry only)
113
+ - NAT traversal / hole punching
114
+ - Link-cable emulation (GB/GBA/PSP)
115
+ - Hardware-rendered cores (OpenGL/Vulkan)
116
+
117
+ ## Architecture
118
+
119
+ ### New Directory Structure
120
+
121
+ ```
122
+ src/
123
+ ├── netplay/
124
+ │ ├── index.ts # Module exports
125
+ │ ├── consts.ts # Protocol constants and command IDs
126
+ │ ├── types.ts # TypeScript interfaces
127
+ │ ├── protocol.ts # Command serialization/deserialization
128
+ │ ├── connection.ts # TCP connection wrapper
129
+ │ ├── server.ts # NetplayServer class
130
+ │ ├── client.ts # NetplayClient class
131
+ │ ├── frame-buffer.ts # Ring buffer for frame history
132
+ │ ├── input-buffer.ts # Input state management
133
+ │ └── sync-manager.ts # Rollback and replay logic
134
+ ```
135
+
136
+ ### Core Components
137
+
138
+ #### 1. Protocol Layer (`protocol.ts`)
139
+
140
+ Handles command encoding/decoding:
141
+
142
+ ```typescript
143
+ interface NetplayCommand {
144
+ cmd: NetplayCommandId;
145
+ payload: Buffer;
146
+ }
147
+
148
+ // Serialize command to wire format
149
+ const encodeCommand = (cmd: NetplayCommand): Buffer => { ... };
150
+
151
+ // Parse command from buffer (handles partial reads)
152
+ const decodeCommand = (buffer: Buffer): { command: NetplayCommand; bytesConsumed: number } | null => { ... };
153
+
154
+ // Specific command builders
155
+ const buildInputCommand = (frame: number, clientId: number, input: Uint32Array): Buffer => { ... };
156
+ const buildInfoCommand = (coreName: string, coreVersion: string, contentCrc: number): Buffer => { ... };
157
+ const buildSyncCommand = (frame: number, state: Buffer, players: number): Buffer => { ... };
158
+ // ... etc
159
+ ```
160
+
161
+ #### 2. Connection Manager (`connection.ts`)
162
+
163
+ Wraps TCP socket with buffered reads:
164
+
165
+ ```typescript
166
+ interface NetplayConnection {
167
+ readonly id: number;
168
+ readonly address: string;
169
+ readonly port: number;
170
+
171
+ send(command: NetplayCommand): Promise<void>;
172
+ receive(): AsyncGenerator<NetplayCommand>;
173
+ close(): void;
174
+
175
+ // Connection state
176
+ nickname: string;
177
+ clientNumber: number;
178
+ mode: 'playing' | 'spectating';
179
+ latency: number;
180
+ }
181
+ ```
182
+
183
+ #### 3. Frame Buffer (`frame-buffer.ts`)
184
+
185
+ Ring buffer storing frame history for rollback:
186
+
187
+ ```typescript
188
+ interface FrameState {
189
+ frameNumber: number;
190
+ serializedState: Buffer | null; // May be null if not yet captured
191
+ localInput: Uint32Array;
192
+ remoteInput: Map<number, Uint32Array>; // clientId -> input
193
+ crc: number | null;
194
+ }
195
+
196
+ interface FrameBuffer {
197
+ readonly capacity: number;
198
+
199
+ // Access frames
200
+ get(frameNumber: number): FrameState | null;
201
+ getCurrent(): FrameState;
202
+
203
+ // Frame management
204
+ advance(): FrameState; // Move to next frame
205
+ setLocalInput(input: Uint32Array): void;
206
+ setRemoteInput(clientId: number, frameNumber: number, input: Uint32Array): void;
207
+ setState(frameNumber: number, state: Buffer): void;
208
+
209
+ // Rollback support
210
+ findRollbackFrame(): number; // Earliest frame needing replay
211
+ getStateForFrame(frameNumber: number): Buffer | null;
212
+ }
213
+ ```
214
+
215
+ #### 4. Sync Manager (`sync-manager.ts`)
216
+
217
+ Coordinates rollback and replay:
218
+
219
+ ```typescript
220
+ interface SyncManager {
221
+ // Frame tracking
222
+ readonly selfFrame: number;
223
+ readonly otherFrame: number; // Last synced frame
224
+ readonly unreadFrame: number; // First frame with missing input
225
+
226
+ // State management
227
+ captureState(): void; // Serialize current core state
228
+ restoreState(frameNumber: number): void; // Load state for rollback
229
+
230
+ // Sync operations
231
+ needsRollback(): boolean;
232
+ performRollback(): void; // Rewind and replay
233
+
234
+ // Input
235
+ getInputForFrame(frameNumber: number, port: number): Uint32Array;
236
+ simulateInput(lastKnown: Uint32Array): Uint32Array; // Predict input
237
+
238
+ // Desync detection
239
+ computeFrameCrc(frameNumber: number): number;
240
+ checkDesync(remoteCrc: number, frameNumber: number): boolean;
241
+ }
242
+ ```
243
+
244
+ #### 5. Server (`server.ts`)
245
+
246
+ ```typescript
247
+ interface NetplayServerOptions {
248
+ port: number;
249
+ password?: string;
250
+ maxClients: number;
251
+ inputLatencyFrames: number;
252
+ }
253
+
254
+ interface NetplayServer {
255
+ // Lifecycle
256
+ start(core: LibretroCore, romPath: string): Promise<void>;
257
+ stop(): void;
258
+
259
+ // Client management
260
+ readonly clients: ReadonlyMap<number, NetplayConnection>;
261
+ kick(clientId: number, reason: string): void;
262
+
263
+ // Frame execution (called by emulator loop)
264
+ preFrame(): void; // Gather input, check for new connections
265
+ postFrame(): void; // Broadcast input, check sync
266
+
267
+ // Events
268
+ on(event: 'client-connected', handler: (client: NetplayConnection) => void): void;
269
+ on(event: 'client-disconnected', handler: (client: NetplayConnection) => void): void;
270
+ on(event: 'desync', handler: (clientId: number, frame: number) => void): void;
271
+ }
272
+ ```
273
+
274
+ #### 6. Client (`client.ts`)
275
+
276
+ ```typescript
277
+ interface NetplayClientOptions {
278
+ host: string;
279
+ port: number;
280
+ password?: string;
281
+ nickname: string;
282
+ inputLatencyFrames: number;
283
+ }
284
+
285
+ interface NetplayClient {
286
+ // Lifecycle
287
+ connect(core: LibretroCore): Promise<void>;
288
+ disconnect(): void;
289
+
290
+ // State
291
+ readonly connected: boolean;
292
+ readonly serverInfo: { coreName: string; coreVersion: string; contentCrc: number } | null;
293
+
294
+ // Frame execution
295
+ preFrame(): void; // Send input, receive remote input
296
+ postFrame(): void; // Handle sync, check for rollback
297
+
298
+ // Events
299
+ on(event: 'connected', handler: () => void): void;
300
+ on(event: 'disconnected', handler: (reason: string) => void): void;
301
+ on(event: 'desync', handler: (frame: number) => void): void;
302
+ on(event: 'rollback', handler: (frames: number) => void): void;
303
+ }
304
+ ```
305
+
306
+ ### Integration with Emulator
307
+
308
+ The `Emulator` class will be extended to support netplay:
309
+
310
+ ```typescript
311
+ // New methods in Emulator class
312
+ interface EmulatorNetplayMethods {
313
+ startNetplayServer(options: NetplayServerOptions): Promise<void>;
314
+ connectToNetplay(options: NetplayClientOptions): Promise<void>;
315
+ disconnectNetplay(): void;
316
+ isNetplayActive(): boolean;
317
+ }
318
+
319
+ // Modified run loop (pseudo-code)
320
+ const runFrameWithNetplay = (): void => {
321
+ if (this.netplay) {
322
+ this.netplay.preFrame(); // Gather/exchange input
323
+ }
324
+
325
+ if (this.syncManager?.needsRollback()) {
326
+ this.disableAudio(); // Prevent audio artifacts
327
+ this.syncManager.performRollback();
328
+ this.enableAudio();
329
+ }
330
+
331
+ this.syncInputToCore(); // Apply merged local+remote input
332
+ this.core.runFrame();
333
+
334
+ if (this.netplay) {
335
+ this.netplay.postFrame(); // Broadcast state, check sync
336
+ }
337
+ };
338
+ ```
339
+
340
+ ### CLI Interface
341
+
342
+ ```bash
343
+ # Host a netplay session
344
+ emoemu game.sfc --netplay-host [--netplay-port 55435] [--netplay-password secret]
345
+
346
+ # Connect to a netplay session
347
+ emoemu game.sfc --netplay-connect hostname[:port] [--netplay-password secret]
348
+
349
+ # Additional options
350
+ --netplay-spectate # Join as spectator (no input)
351
+ --netplay-nick "Player1" # Set nickname
352
+ --netplay-frames 2 # Input delay frames (0-16, default: 2)
353
+ ```
354
+
355
+ ## Implementation Plan
356
+
357
+ ### Milestone 1: Protocol Foundation (Week 1-2)
358
+
359
+ **Goal**: Implement core protocol primitives and basic TCP communication.
360
+
361
+ **Tasks**:
362
+ 1. Create `src/netplay/` directory structure
363
+ 2. Define protocol constants (`consts.ts`)
364
+ - Command IDs (INPUT, NOINPUT, NICK, PASSWORD, INFO, SYNC, etc.)
365
+ - Default port, timing constants, limits
366
+ 3. Implement command serialization/deserialization (`protocol.ts`)
367
+ - `encodeCommand()` / `decodeCommand()`
368
+ - Individual command builders (buildInputCommand, buildInfoCommand, etc.)
369
+ - Handle network byte order (big-endian)
370
+ 4. Implement TCP connection wrapper (`connection.ts`)
371
+ - Buffered reads for partial commands
372
+ - Async command iteration
373
+ - Connection state tracking
374
+ 5. Add TypeScript interfaces (`types.ts`)
375
+
376
+ **Deliverable**: Protocol layer that can encode/decode all netplay commands.
377
+
378
+ **Acceptance Criteria**:
379
+ - Unit tests for all command types
380
+ - Round-trip encode/decode produces identical data
381
+ - Handles partial TCP reads correctly
382
+
383
+ ### Milestone 2: Frame Buffer & State Management (Week 2-3)
384
+
385
+ **Goal**: Implement frame history tracking and state serialization.
386
+
387
+ **Tasks**:
388
+ 1. Implement frame buffer ring (`frame-buffer.ts`)
389
+ - Fixed-capacity ring buffer (default: 120 frames = 2 seconds at 60fps)
390
+ - Store serialized state, local input, remote input per frame
391
+ - Frame number wraparound handling
392
+ 2. Implement input buffer (`input-buffer.ts`)
393
+ - Track input per client per frame
394
+ - Input prediction (repeat last known input)
395
+ - Input delay queue
396
+ 3. Add CRC32 computation for state comparison
397
+ - Use existing CRC implementation or add lightweight one
398
+ 4. Implement state compression (optional, for large states)
399
+ - zlib compression for LOAD_SAVESTATE transfers
400
+ - Compression threshold (e.g., only compress if > 64KB)
401
+
402
+ **Deliverable**: Frame buffer that can track 2+ seconds of frame history.
403
+
404
+ **Acceptance Criteria**:
405
+ - Can store and retrieve frame states by number
406
+ - Handles buffer wraparound correctly
407
+ - CRC32 matches for identical states
408
+
409
+ ### Milestone 3: Sync Manager & Rollback (Week 3-4)
410
+
411
+ **Goal**: Implement deterministic rollback and replay.
412
+
413
+ **Tasks**:
414
+ 1. Implement sync manager (`sync-manager.ts`)
415
+ - Track self/other/unread frame pointers
416
+ - Detect when rollback is needed
417
+ - Coordinate state capture timing
418
+ 2. Implement rollback logic
419
+ - Restore state from frame buffer
420
+ - Replay frames with corrected input
421
+ - Re-capture states during replay
422
+ 3. Implement input merging
423
+ - Combine local + remote input for core
424
+ - Handle missing remote input (simulation)
425
+ 4. Add desync detection
426
+ - Periodic CRC comparison
427
+ - Trigger state resync on mismatch
428
+ 5. Handle audio during rollback
429
+ - Mute audio output during replay
430
+ - Resume audio after rollback complete
431
+
432
+ **Deliverable**: Sync manager that can rollback and replay frames.
433
+
434
+ **Acceptance Criteria**:
435
+ - Single-player rollback test: artificially delay input, verify correct replay
436
+ - State restoration produces identical CRC
437
+ - Audio doesn't glitch during rollback
438
+
439
+ ### Milestone 4: Server Implementation (Week 4-5)
440
+
441
+ **Goal**: Implement netplay server (host) functionality.
442
+
443
+ **Tasks**:
444
+ 1. Implement server class (`server.ts`)
445
+ - TCP server on configurable port
446
+ - Accept multiple client connections
447
+ - Track client state (nickname, player number, mode)
448
+ 2. Implement handshake flow (server side)
449
+ - Receive/verify connection header
450
+ - Exchange nicknames
451
+ - Validate password (if configured)
452
+ - Send INFO (core name, version, content CRC)
453
+ - Receive client INFO, validate compatibility
454
+ - Send SYNC with initial state
455
+ 3. Implement input relay
456
+ - Receive INPUT from clients
457
+ - Broadcast INPUT to all other clients
458
+ - Send server's own INPUT
459
+ 4. Implement periodic sync checks
460
+ - Send CRC command every N frames
461
+ - Handle desync (send LOAD_SAVESTATE)
462
+ 5. Handle client lifecycle
463
+ - New connections during gameplay
464
+ - Graceful disconnection
465
+ - Kick functionality
466
+
467
+ **Deliverable**: Functional netplay server.
468
+
469
+ **Acceptance Criteria**:
470
+ - Can accept connection from RetroArch client (handshake completes)
471
+ - Input reaches clients within expected latency
472
+ - New client receives valid initial state
473
+
474
+ ### Milestone 5: Client Implementation (Week 5-6)
475
+
476
+ **Goal**: Implement netplay client functionality.
477
+
478
+ **Tasks**:
479
+ 1. Implement client class (`client.ts`)
480
+ - TCP connection to server
481
+ - Connection state machine
482
+ - Reconnection logic (optional)
483
+ 2. Implement handshake flow (client side)
484
+ - Send connection header
485
+ - Exchange nicknames
486
+ - Send password (if required)
487
+ - Send INFO, receive server INFO
488
+ - Receive SYNC, load initial state
489
+ 3. Implement input exchange
490
+ - Send local INPUT every frame
491
+ - Receive and buffer remote INPUT
492
+ - Handle INPUT from other clients (via server relay)
493
+ 4. Integrate with sync manager
494
+ - Feed received input to frame buffer
495
+ - Trigger rollback when needed
496
+ 5. Handle connection issues
497
+ - Detect timeout / disconnect
498
+ - Attempt graceful recovery
499
+
500
+ **Deliverable**: Functional netplay client.
501
+
502
+ **Acceptance Criteria**:
503
+ - Can connect to RetroArch server (handshake completes)
504
+ - Gameplay syncs correctly (same visual state)
505
+ - Handles moderate packet delay gracefully
506
+
507
+ ### Milestone 6: Emulator Integration (Week 6-7)
508
+
509
+ **Goal**: Integrate netplay into main emulator loop.
510
+
511
+ **Tasks**:
512
+ 1. Modify `Emulator` class
513
+ - Add netplay server/client instance
514
+ - Inject preFrame/postFrame hooks
515
+ - Modify input handling for netplay
516
+ 2. Add CLI arguments
517
+ - `--netplay-host`, `--netplay-connect`
518
+ - `--netplay-port`, `--netplay-password`
519
+ - `--netplay-spectate`, `--netplay-nick`
520
+ - `--netplay-frames`
521
+ 3. Add status bar integration
522
+ - Show connection status
523
+ - Show ping/latency
524
+ - Show player count
525
+ 4. Add notifications
526
+ - Player connected/disconnected
527
+ - Desync detected/recovered
528
+ - Connection lost
529
+ 5. Handle ROM validation
530
+ - Compute content CRC on load
531
+ - Verify CRC matches server
532
+
533
+ **Deliverable**: Playable netplay from CLI.
534
+
535
+ **Acceptance Criteria**:
536
+ - Can host and connect via CLI
537
+ - Two emoemu instances can play together
538
+ - Status bar shows netplay info
539
+
540
+ ### Milestone 7: Testing & Polish (Week 7-8)
541
+
542
+ **Goal**: Comprehensive testing and edge case handling.
543
+
544
+ **Tasks**:
545
+ 1. Unit tests
546
+ - Protocol encoding/decoding
547
+ - Frame buffer operations
548
+ - Sync manager logic
549
+ 2. Integration tests
550
+ - Server/client handshake
551
+ - Input exchange
552
+ - Rollback scenarios
553
+ 3. Manual testing
554
+ - Various cores (SNES, Genesis, GBA)
555
+ - Different latency conditions
556
+ - Edge cases (mid-game connect, disconnect)
557
+ 4. Performance optimization
558
+ - Profile rollback performance
559
+ - Optimize state serialization
560
+ - Reduce memory allocations
561
+ 5. Documentation
562
+ - Update CLAUDE.md with netplay section
563
+ - Add netplay usage examples
564
+ - Document protocol compatibility notes
565
+
566
+ **Deliverable**: Production-ready netplay support.
567
+
568
+ **Acceptance Criteria**:
569
+ - All tests pass
570
+ - Playable with 100ms+ latency
571
+ - No memory leaks during extended sessions
572
+
573
+ ## Protocol Details
574
+
575
+ ### Connection Header
576
+
577
+ First 4 bytes exchanged by both parties:
578
+
579
+ ```
580
+ Offset Size Description
581
+ 0 4 Magic: "RANP" (0x52414E50) - RetroArch Netplay
582
+ ```
583
+
584
+ ### Handshake Sequence
585
+
586
+ ```
587
+ Client Server
588
+ | |
589
+ |-------- HEADER (RANP) ------->|
590
+ |<------- HEADER (RANP) --------|
591
+ | |
592
+ |-------- NICK (nickname) ----->|
593
+ |<------- NICK (nickname) ------|
594
+ | |
595
+ |-------- PASSWORD (hash) ----->| (if required)
596
+ | |
597
+ |<------- INFO (core info) -----|
598
+ |-------- INFO (core info) ---->|
599
+ | |
600
+ |<------- SYNC (state) ---------|
601
+ | |
602
+ |======= GAMEPLAY LOOP =========|
603
+ ```
604
+
605
+ ### INPUT Command Format
606
+
607
+ ```
608
+ Offset Size Description
609
+ 0 4 Frame number (uint32, network byte order)
610
+ 4 4 Client ID and flags:
611
+ - Bits 0-30: Client number
612
+ - Bit 31: Is server data flag
613
+ 8 4 Joypad input (RETRO_DEVICE_JOYPAD bitmask)
614
+ 12 4 Analog left (optional, if device supports)
615
+ 16 4 Analog right (optional, if device supports)
616
+ ```
617
+
618
+ ### INFO Command Format
619
+
620
+ ```
621
+ Offset Size Description
622
+ 0 32 Core name (null-terminated string)
623
+ 32 32 Core version (null-terminated string)
624
+ 64 4 Content CRC32 (uint32, network byte order)
625
+ ```
626
+
627
+ ### SYNC Command Format
628
+
629
+ ```
630
+ Offset Size Description
631
+ 0 4 Frame number (uint32)
632
+ 4 4 Flags:
633
+ - Bit 0: Paused
634
+ - Bits 1-31: Connected players bitmap
635
+ 8 4 Flip frame (for player swap)
636
+ 12 64 Controller devices (uint32[16])
637
+ 76 32 Client nickname
638
+ 108 var Serialized SRAM/state
639
+ ```
640
+
641
+ ## Risk Assessment
642
+
643
+ | Risk | Likelihood | Impact | Mitigation |
644
+ |------|------------|--------|------------|
645
+ | Protocol incompatibility with RetroArch | Medium | High | Test against multiple RA versions; document known working versions |
646
+ | Rollback performance too slow | Medium | Medium | Profile early; optimize state serialization; limit rollback depth |
647
+ | State size too large for some cores | Low | Medium | Implement compression; warn users about memory usage |
648
+ | Desync issues | High | Medium | Comprehensive CRC checking; detailed logging; state dumps for debugging |
649
+ | Network jitter causing poor experience | Medium | Medium | Tunable input delay; clear latency indicators |
650
+
651
+ ## Future Enhancements (Post-MVP)
652
+
653
+ 1. **Lobby Server Integration**: Announce sessions to libretro lobby
654
+ 2. **NAT Traversal**: UPnP port forwarding, STUN/TURN
655
+ 3. **Spectator Chat**: Text chat during spectating
656
+ 4. **Input Display**: Show inputs on screen for spectators
657
+ 5. **Replay Recording**: Save netplay sessions for replay
658
+ 6. **Native Core Support**: Extend to native NES core
659
+
660
+ ## References
661
+
662
+ - [RetroArch Netplay Documentation](https://docs.libretro.com/development/retroarch/netplay/)
663
+ - [RetroArch Netplay Source Code](https://github.com/libretro/RetroArch/tree/master/network/netplay)
664
+ - [Netplay FAQ](https://docs.libretro.com/guides/netplay-faq/)
665
+ - [netplay_private.h](https://github.com/libretro/RetroArch/blob/master/network/netplay/netplay_private.h)