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,419 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { SyncManager, createSyncManager } from '.';
3
+ import { DEFAULT_FRAME_BUFFER_SIZE } from '..';
4
+
5
+ describe('SyncManager', () => {
6
+ let syncManager: SyncManager;
7
+
8
+ beforeEach(() => {
9
+ syncManager = createSyncManager({
10
+ localClientId: 0,
11
+ inputDelayFrames: 0,
12
+ });
13
+ });
14
+
15
+ describe('initialization', () => {
16
+ it('should create with default config', () => {
17
+ expect(syncManager.selfFrame).toBe(-1);
18
+ expect(syncManager.otherFrame).toBe(-1);
19
+ expect(syncManager.unreadFrame).toBeNull();
20
+ expect(syncManager.inRollback).toBe(false);
21
+ });
22
+
23
+ it('should initialize at specific frame', () => {
24
+ const initialState = Buffer.from([1, 2, 3, 4]);
25
+ syncManager.initialize(100, initialState);
26
+
27
+ expect(syncManager.selfFrame).toBe(100);
28
+ expect(syncManager.otherFrame).toBe(100);
29
+
30
+ const frameBuffer = syncManager.getFrameBuffer();
31
+ expect(frameBuffer.getState(100)).toEqual(initialState);
32
+ });
33
+
34
+ it('should use configured frame buffer size', () => {
35
+ const customManager = createSyncManager({
36
+ localClientId: 0,
37
+ frameBufferSize: 64,
38
+ });
39
+
40
+ const frameBuffer = customManager.getFrameBuffer();
41
+ expect(frameBuffer.capacity).toBe(64);
42
+ });
43
+
44
+ it('should use default frame buffer size', () => {
45
+ const frameBuffer = syncManager.getFrameBuffer();
46
+ expect(frameBuffer.capacity).toBe(DEFAULT_FRAME_BUFFER_SIZE);
47
+ });
48
+ });
49
+
50
+ describe('remote client management', () => {
51
+ it('should add remote clients', () => {
52
+ syncManager.addRemoteClient(1);
53
+ syncManager.addRemoteClient(2);
54
+
55
+ const remoteIds = syncManager.getRemoteClientIds();
56
+ expect(remoteIds).toContain(1);
57
+ expect(remoteIds).toContain(2);
58
+ expect(remoteIds).not.toContain(0); // Local client
59
+ });
60
+
61
+ it('should assign device indices automatically', () => {
62
+ // Local client has device 0
63
+ syncManager.addRemoteClient(1); // Should get device 1
64
+ syncManager.addRemoteClient(2); // Should get device 2
65
+
66
+ expect(syncManager.getRemoteClientIds()).toHaveLength(2);
67
+ });
68
+
69
+ it('should assign specific device indices', () => {
70
+ syncManager.addRemoteClient(1, [2, 3]);
71
+ expect(syncManager.getRemoteClientIds()).toContain(1);
72
+ });
73
+
74
+ it('should remove remote clients', () => {
75
+ syncManager.addRemoteClient(1);
76
+ syncManager.removeRemoteClient(1);
77
+
78
+ expect(syncManager.getRemoteClientIds()).not.toContain(1);
79
+ });
80
+ });
81
+
82
+ describe('frame advancement', () => {
83
+ beforeEach(() => {
84
+ syncManager.initialize(0);
85
+ });
86
+
87
+ it('should advance frame on preFrame', () => {
88
+ const result = syncManager.preFrame([0]);
89
+ expect(result).not.toBeNull();
90
+ expect(result!.shouldStall).toBe(false);
91
+ expect(syncManager.selfFrame).toBe(1);
92
+ });
93
+
94
+ it('should return merged input', () => {
95
+ const result = syncManager.preFrame([0xff]);
96
+ expect(result).not.toBeNull();
97
+ expect(result!.input).toBeDefined();
98
+ expect(Array.isArray(result!.input)).toBe(true);
99
+ });
100
+
101
+ it('should store state on postFrame', () => {
102
+ syncManager.preFrame([0]);
103
+ const state = Buffer.from([1, 2, 3, 4, 5]);
104
+ syncManager.postFrame(state);
105
+
106
+ const frameBuffer = syncManager.getFrameBuffer();
107
+ expect(frameBuffer.getState(1)).toEqual(state);
108
+ });
109
+
110
+ it('should compute CRC on postFrame', () => {
111
+ syncManager.preFrame([0]);
112
+ syncManager.postFrame(Buffer.from([1, 2, 3]));
113
+
114
+ expect(syncManager.getCurrentCrc()).not.toBeNull();
115
+ });
116
+ });
117
+
118
+ describe('remote input', () => {
119
+ beforeEach(() => {
120
+ syncManager.initialize(0);
121
+ syncManager.addRemoteClient(1);
122
+ });
123
+
124
+ it('should receive remote input and trigger rollback for past frames', () => {
125
+ syncManager.preFrame([0]);
126
+ syncManager.postFrame(Buffer.from([1]));
127
+
128
+ // Receiving input for a frame we've already run triggers rollback
129
+ const needsRollback = syncManager.receiveRemoteInput(1, 1, [0xaa, 0xbb, 0xcc]);
130
+ expect(needsRollback).toBe(true);
131
+ });
132
+
133
+ it('should receive remote input without rollback for future frames', () => {
134
+ syncManager.preFrame([0]);
135
+ syncManager.postFrame(Buffer.from([1]));
136
+
137
+ // Receiving input for a future frame doesn't trigger rollback
138
+ const needsRollback = syncManager.receiveRemoteInput(1, 5, [0xaa, 0xbb, 0xcc]);
139
+ expect(needsRollback).toBe(false);
140
+ });
141
+
142
+ it('should detect rollback needed for late input', () => {
143
+ // Advance a few frames with simulated remote input
144
+ for (let i = 0; i < 3; i++) {
145
+ syncManager.preFrame([i]);
146
+ syncManager.simulateRemoteInput(1);
147
+ syncManager.postFrame(Buffer.from([i]));
148
+ }
149
+
150
+ // Now receive real input for frame 1 (late)
151
+ const needsRollback = syncManager.receiveRemoteInput(1, 1, [0xff]);
152
+ expect(needsRollback).toBe(true);
153
+ });
154
+
155
+ });
156
+
157
+ describe('rollback', () => {
158
+ beforeEach(() => {
159
+ // Provide initial state so we can rollback to frame 0
160
+ syncManager.initialize(0, Buffer.from([0, 0, 0, 0]));
161
+ syncManager.addRemoteClient(1);
162
+ });
163
+
164
+ it('should emit rollback events', () => {
165
+ const rollbackStartFn = vi.fn();
166
+ const rollbackEndFn = vi.fn();
167
+ const runFrameFn = vi.fn();
168
+ const restoreStateFn = vi.fn();
169
+
170
+ syncManager.on('rollback-start', rollbackStartFn);
171
+ syncManager.on('rollback-end', rollbackEndFn);
172
+ syncManager.on('run-frame', runFrameFn);
173
+ syncManager.on('restore-state', restoreStateFn);
174
+
175
+ // Advance frames with simulated input
176
+ for (let i = 0; i < 5; i++) {
177
+ syncManager.preFrame([i]);
178
+ syncManager.simulateRemoteInput(1);
179
+ syncManager.postFrame(Buffer.from([i, i, i, i]));
180
+ }
181
+
182
+ // Receive late input for frame 2
183
+ syncManager.receiveRemoteInput(1, 2, [0xff]);
184
+
185
+ // Perform rollback
186
+ const didRollback = syncManager.performRollbackIfNeeded();
187
+
188
+ expect(didRollback).toBe(true);
189
+ expect(rollbackStartFn).toHaveBeenCalled();
190
+ expect(rollbackEndFn).toHaveBeenCalled();
191
+ expect(restoreStateFn).toHaveBeenCalled();
192
+ expect(runFrameFn).toHaveBeenCalled();
193
+ });
194
+
195
+ it('should not rollback when not needed', () => {
196
+ syncManager.preFrame([0]);
197
+ syncManager.postFrame(Buffer.from([0]));
198
+
199
+ const didRollback = syncManager.performRollbackIfNeeded();
200
+ expect(didRollback).toBe(false);
201
+ });
202
+
203
+ it('should update rollback statistics', () => {
204
+ // Advance frames
205
+ for (let i = 0; i < 5; i++) {
206
+ syncManager.preFrame([i]);
207
+ syncManager.simulateRemoteInput(1);
208
+ syncManager.postFrame(Buffer.from([i, i, i, i]));
209
+ }
210
+
211
+ // Trigger rollback
212
+ syncManager.receiveRemoteInput(1, 2, [0xff]);
213
+ syncManager.performRollbackIfNeeded();
214
+
215
+ const stats = syncManager.statistics;
216
+ expect(stats.rollbackCount).toBe(1);
217
+ expect(stats.totalFramesReplayed).toBeGreaterThan(0);
218
+ });
219
+
220
+ it('should set inRollback flag during rollback', () => {
221
+ let wasInRollback = false;
222
+
223
+ syncManager.on('run-frame', () => {
224
+ wasInRollback = syncManager.inRollback;
225
+ });
226
+
227
+ // Advance frames
228
+ for (let i = 0; i < 3; i++) {
229
+ syncManager.preFrame([i]);
230
+ syncManager.simulateRemoteInput(1);
231
+ syncManager.postFrame(Buffer.from([i, i, i, i]));
232
+ }
233
+
234
+ // Trigger rollback
235
+ syncManager.receiveRemoteInput(1, 1, [0xff]);
236
+ syncManager.performRollbackIfNeeded();
237
+
238
+ expect(wasInRollback).toBe(true);
239
+ expect(syncManager.inRollback).toBe(false); // Should be false after rollback
240
+ });
241
+ });
242
+
243
+ describe('stalling', () => {
244
+ beforeEach(() => {
245
+ syncManager = createSyncManager({
246
+ localClientId: 0,
247
+ maxFramesBehind: 5, // Use smaller value for testing
248
+ });
249
+ syncManager.initialize(0);
250
+ syncManager.addRemoteClient(1);
251
+ });
252
+
253
+ it('should stall when too far ahead', () => {
254
+ // Advance frames with simulated input (no real remote input)
255
+ for (let i = 0; i < 6; i++) {
256
+ const result = syncManager.preFrame([i]);
257
+ if (result && !result.shouldStall) {
258
+ syncManager.simulateRemoteInput(1);
259
+ syncManager.postFrame(Buffer.from([i]));
260
+ }
261
+ }
262
+
263
+ // Try to advance one more frame
264
+ const result = syncManager.preFrame([99]);
265
+ expect(result).not.toBeNull();
266
+ expect(result!.shouldStall).toBe(true);
267
+ });
268
+
269
+ it('should not stall with real input', () => {
270
+ // Provide real remote input for each frame
271
+ for (let i = 0; i < 10; i++) {
272
+ const result = syncManager.preFrame([i]);
273
+ if (result && !result.shouldStall) {
274
+ syncManager.receiveRemoteInput(1, i + 1, [i]);
275
+ syncManager.postFrame(Buffer.from([i]));
276
+ } else {
277
+ break;
278
+ }
279
+ }
280
+
281
+ // Should be able to advance
282
+ const result = syncManager.preFrame([10]);
283
+ expect(result).not.toBeNull();
284
+ expect(result!.shouldStall).toBe(false);
285
+ });
286
+ });
287
+
288
+ describe('CRC verification', () => {
289
+ beforeEach(() => {
290
+ syncManager = createSyncManager({
291
+ localClientId: 0,
292
+ crcCheckInterval: 5, // Check every 5 frames
293
+ });
294
+ syncManager.initialize(0);
295
+ });
296
+
297
+ it('should determine when to send CRC', () => {
298
+ expect(syncManager.shouldSendCrc()).toBe(true); // Frame 0
299
+
300
+ syncManager.preFrame([0]);
301
+ syncManager.postFrame(Buffer.from([1]));
302
+ expect(syncManager.shouldSendCrc()).toBe(false); // Frame 1
303
+
304
+ for (let i = 0; i < 4; i++) {
305
+ syncManager.preFrame([i]);
306
+ syncManager.postFrame(Buffer.from([i]));
307
+ }
308
+ expect(syncManager.shouldSendCrc()).toBe(true); // Frame 5
309
+ });
310
+
311
+ it('should emit desync on CRC mismatch', () => {
312
+ const desyncFn = vi.fn();
313
+ syncManager.on('desync', desyncFn);
314
+
315
+ syncManager.preFrame([0]);
316
+ syncManager.postFrame(Buffer.from([1, 2, 3, 4]));
317
+
318
+ const localCrc = syncManager.getCurrentCrc();
319
+ expect(localCrc).not.toBeNull();
320
+
321
+ // Receive different CRC from remote
322
+ syncManager.receiveCrcCheck(1, 0x12345678);
323
+
324
+ // Force CRC check (would normally happen in postFrame when other frame advances)
325
+ syncManager.addRemoteClient(1);
326
+ syncManager.receiveRemoteInput(1, 1, [0]); // Mark frame 1 as synced
327
+
328
+ // Advance and check
329
+ syncManager.preFrame([1]);
330
+ syncManager.postFrame(Buffer.from([5, 6, 7, 8]));
331
+
332
+ expect(desyncFn).toHaveBeenCalled();
333
+ });
334
+
335
+ it('should track desync history', () => {
336
+ syncManager.preFrame([0]);
337
+ syncManager.postFrame(Buffer.from([1, 2, 3, 4]));
338
+
339
+ syncManager.addRemoteClient(1);
340
+ syncManager.receiveRemoteInput(1, 1, [0]);
341
+
342
+ // Trigger desync
343
+ syncManager.receiveCrcCheck(1, 0x12345678);
344
+
345
+ syncManager.preFrame([1]);
346
+ syncManager.postFrame(Buffer.from([5, 6, 7, 8]));
347
+
348
+ const history = syncManager.getDesyncHistory();
349
+ expect(history.length).toBeGreaterThan(0);
350
+ expect(history[0].frameNumber).toBe(1);
351
+ expect(history[0].remoteCrc).toBe(0x12345678);
352
+ });
353
+
354
+ it('should update desync statistics', () => {
355
+ syncManager.preFrame([0]);
356
+ syncManager.postFrame(Buffer.from([1, 2, 3, 4]));
357
+
358
+ syncManager.addRemoteClient(1);
359
+ syncManager.receiveRemoteInput(1, 1, [0]);
360
+ syncManager.receiveCrcCheck(1, 0xdeadbeef);
361
+
362
+ syncManager.preFrame([1]);
363
+ syncManager.postFrame(Buffer.from([5, 6, 7, 8]));
364
+
365
+ expect(syncManager.statistics.desyncCount).toBe(1);
366
+ });
367
+ });
368
+
369
+ describe('input delay', () => {
370
+ it('should apply input delay', () => {
371
+ const delayedManager = createSyncManager({
372
+ localClientId: 0,
373
+ inputDelayFrames: 2,
374
+ });
375
+ delayedManager.initialize(0);
376
+
377
+ expect(delayedManager.inputDelayFrames).toBe(2);
378
+ });
379
+ });
380
+
381
+ describe('local input for frame', () => {
382
+ beforeEach(() => {
383
+ syncManager.initialize(0);
384
+ });
385
+
386
+ it('should return local input for frame', () => {
387
+ syncManager.preFrame([0xaa, 0xbb, 0xcc]);
388
+ syncManager.postFrame(Buffer.from([1]));
389
+
390
+ const input = syncManager.getLocalInputForFrame(1);
391
+ expect(input).not.toBeNull();
392
+ });
393
+
394
+ it('should return null for unknown frame', () => {
395
+ const input = syncManager.getLocalInputForFrame(999);
396
+ expect(input).toBeNull();
397
+ });
398
+ });
399
+
400
+ describe('reset', () => {
401
+ it('should reset all state', () => {
402
+ syncManager.initialize(0);
403
+ syncManager.addRemoteClient(1);
404
+
405
+ for (let i = 0; i < 5; i++) {
406
+ syncManager.preFrame([i]);
407
+ syncManager.postFrame(Buffer.from([i]));
408
+ }
409
+
410
+ syncManager.reset();
411
+
412
+ expect(syncManager.selfFrame).toBe(-1);
413
+ expect(syncManager.otherFrame).toBe(-1);
414
+ expect(syncManager.unreadFrame).toBeNull();
415
+ expect(syncManager.getRemoteClientIds()).toHaveLength(0);
416
+ expect(syncManager.statistics.rollbackCount).toBe(0);
417
+ });
418
+ });
419
+ });