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.
- package/.claude/settings.local.json +77 -0
- package/.node-version +1 -0
- package/CLAUDE.md +435 -0
- package/README.md +404 -0
- package/TODO.md +655 -0
- package/dist/index.cjs +25108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25085 -0
- package/docs/building-libretro-cores-arm-mac.md +237 -0
- package/docs/config-file-format.md +488 -0
- package/docs/cores-trd.md +425 -0
- package/docs/headless-hardware-rendering-trd.md +676 -0
- package/docs/libretro-cores-trd.md +997 -0
- package/docs/mupen64-software-rendering-trd.md +751 -0
- package/docs/n64-support-trd.md +306 -0
- package/docs/native-rendering-trd.md +540 -0
- package/docs/native-ui-rendering-trd.md +1195 -0
- package/docs/netplay-trd.md +665 -0
- package/docs/retroarch-netplay-docs.md +277 -0
- package/docs/save-state-format.md +666 -0
- package/eslint.config.js +111 -0
- package/icon/icon.png +0 -0
- package/icon/icon.pxd +0 -0
- package/package.json +63 -0
- package/pnpm-workspace.yaml +10 -0
- package/src/Emulator/consts.ts +14 -0
- package/src/Emulator/index.ts +2496 -0
- package/src/Emulator/saveState/index.ts +155 -0
- package/src/Emulator/screenshot/index.ts +160 -0
- package/src/Emulator/terminalDimensions/index.ts +79 -0
- package/src/Emulator/types.ts +83 -0
- package/src/cli/commands/consts.ts +10 -0
- package/src/cli/commands/index.ts +462 -0
- package/src/cli/parseArgs/consts.ts +17 -0
- package/src/cli/parseArgs/index.ts +457 -0
- package/src/cli/parseArgs/types.ts +61 -0
- package/src/cli/runEmulator/index.ts +406 -0
- package/src/cli/runEmulator/types.ts +7 -0
- package/src/consts.ts +19 -0
- package/src/core/button/consts.ts +35 -0
- package/src/core/button/index.ts +123 -0
- package/src/core/core.ts +300 -0
- package/src/core/index.ts +19 -0
- package/src/cores/libretro/api/index.ts +334 -0
- package/src/cores/libretro/api/types.ts +148 -0
- package/src/cores/libretro/callbacks/consts.ts +41 -0
- package/src/cores/libretro/callbacks/index.ts +456 -0
- package/src/cores/libretro/consts.ts +45 -0
- package/src/cores/libretro/coreOptions/consts.ts +36 -0
- package/src/cores/libretro/coreOptions/index.ts +222 -0
- package/src/cores/libretro/environment/consts.ts +118 -0
- package/src/cores/libretro/environment/index.ts +1095 -0
- package/src/cores/libretro/index.ts +937 -0
- package/src/cores/libretro/loader/index.ts +496 -0
- package/src/cores/libretro/pixelFormat/consts.ts +43 -0
- package/src/cores/libretro/pixelFormat/index.ts +397 -0
- package/src/cores/libretro/types.ts +339 -0
- package/src/frontend/AudioManager/index.ts +420 -0
- package/src/frontend/SettingsManager/index.ts +250 -0
- package/src/frontend/config/index.ts +608 -0
- package/src/frontend/config/tests.ts +354 -0
- package/src/frontend/config/types.ts +36 -0
- package/src/frontend/consts.ts +114 -0
- package/src/frontend/coreBuilder/index.ts +644 -0
- package/src/frontend/coreBuilder/types.ts +15 -0
- package/src/frontend/coreDownloader/index.ts +620 -0
- package/src/frontend/coreDownloader/types.ts +17 -0
- package/src/frontend/corePreferences/index.ts +69 -0
- package/src/frontend/coreRegistry/index.ts +276 -0
- package/src/frontend/directoryCache/index.ts +75 -0
- package/src/frontend/index.ts +79 -0
- package/src/frontend/notifications/consts.ts +14 -0
- package/src/frontend/notifications/index.ts +250 -0
- package/src/frontend/playlist/consts.ts +168 -0
- package/src/frontend/playlist/index.ts +899 -0
- package/src/frontend/playlist/labelFormatter/consts.ts +15 -0
- package/src/frontend/playlist/labelFormatter/index.ts +155 -0
- package/src/frontend/playlist/labelFormatter/tests.ts +153 -0
- package/src/frontend/playlist/reader/index.ts +559 -0
- package/src/frontend/playlist/sync/index.ts +511 -0
- package/src/frontend/playlist/systemLookup/index.ts +233 -0
- package/src/frontend/playlist/utils/index.ts +50 -0
- package/src/frontend/romScanner/consts.ts +348 -0
- package/src/frontend/romScanner/index.ts +1957 -0
- package/src/frontend/saveServices/consts.ts +2 -0
- package/src/frontend/saveServices/index.ts +313 -0
- package/src/frontend/serviceProvider/index.ts +108 -0
- package/src/frontend/serviceProvider/types.ts +13 -0
- package/src/index.ts +428 -0
- package/src/input/Controller/consts.ts +50 -0
- package/src/input/Controller/index.ts +81 -0
- package/src/input/GamepadManager/consts.ts +22 -0
- package/src/input/GamepadManager/index.ts +418 -0
- package/src/input/InputManager/consts.ts +86 -0
- package/src/input/InputManager/index.ts +593 -0
- package/src/input/InputMapper/consts.ts +33 -0
- package/src/input/InputMapper/index.ts +436 -0
- package/src/input/consts.ts +410 -0
- package/src/input/gamepadProfiles/index.ts +1100 -0
- package/src/input/index.ts +38 -0
- package/src/input/inputUtils/index.ts +31 -0
- package/src/netplay/FrameBuffer/consts.ts +2 -0
- package/src/netplay/FrameBuffer/index.ts +364 -0
- package/src/netplay/FrameBuffer/tests.ts +286 -0
- package/src/netplay/InputBuffer/consts.ts +7 -0
- package/src/netplay/InputBuffer/index.ts +347 -0
- package/src/netplay/InputBuffer/tests.ts +166 -0
- package/src/netplay/NetplayClient/index.ts +976 -0
- package/src/netplay/NetplayConnection/index.ts +536 -0
- package/src/netplay/NetplayDiscovery/consts.ts +41 -0
- package/src/netplay/NetplayDiscovery/index.ts +525 -0
- package/src/netplay/NetplayServer/index.ts +1407 -0
- package/src/netplay/SyncManager/index.ts +984 -0
- package/src/netplay/SyncManager/tests.ts +419 -0
- package/src/netplay/consts.ts +371 -0
- package/src/netplay/crc32/consts.ts +14 -0
- package/src/netplay/crc32/index.ts +97 -0
- package/src/netplay/crc32/tests.ts +40 -0
- package/src/netplay/index.ts +41 -0
- package/src/netplay/netplayLogger/consts.ts +30 -0
- package/src/netplay/netplayLogger/index.ts +345 -0
- package/src/netplay/protocol/consts.ts +86 -0
- package/src/netplay/protocol/index.ts +1280 -0
- package/src/netplay/protocol/tests.ts +606 -0
- package/src/netplay/protocol/types.ts +20 -0
- package/src/netplay/types.ts +395 -0
- package/src/rendering/KittyRenderer/index.ts +616 -0
- package/src/rendering/NativeRenderer/index.ts +279 -0
- package/src/rendering/NativeRenderer/tests.ts +133 -0
- package/src/rendering/TerminalRenderer/index.ts +770 -0
- package/src/rendering/consts.ts +401 -0
- package/src/rendering/fonts/CozetteVector.ttf +0 -0
- package/src/rendering/index.ts +26 -0
- package/src/rendering/nativeUi/NativeWindowManager/index.ts +158 -0
- package/src/rendering/nativeUi/NativeWindowManager/tests.ts +81 -0
- package/src/rendering/nativeUi/consts.ts +6 -0
- package/src/rendering/nativeUi/index.ts +20 -0
- package/src/rendering/postProcessing/consts.ts +38 -0
- package/src/rendering/postProcessing/index.ts +923 -0
- package/src/rendering/shared/ansi/consts.ts +12 -0
- package/src/rendering/shared/ansi/index.ts +104 -0
- package/src/rendering/shared/consts.ts +2 -0
- package/src/rendering/shared/fitToTerminal/index.ts +67 -0
- package/src/ui/AddRomsPrompt/consts.ts +13 -0
- package/src/ui/AddRomsPrompt/index.tsx +781 -0
- package/src/ui/App/consts.ts +2 -0
- package/src/ui/App/index.tsx +456 -0
- package/src/ui/AppCapabilities/index.tsx +67 -0
- package/src/ui/ConfigContext/index.tsx +56 -0
- package/src/ui/CoreManager/consts.ts +11 -0
- package/src/ui/CoreManager/index.tsx +779 -0
- package/src/ui/CoreSelector/consts.ts +2 -0
- package/src/ui/CoreSelector/index.tsx +251 -0
- package/src/ui/DialogContainer/index.tsx +42 -0
- package/src/ui/DialogOptionsList/index.tsx +61 -0
- package/src/ui/DuplicateCrcPrompt/consts.ts +5 -0
- package/src/ui/DuplicateCrcPrompt/index.tsx +146 -0
- package/src/ui/GamepadContext/consts.ts +15 -0
- package/src/ui/GamepadContext/index.tsx +295 -0
- package/src/ui/NativeDialog/index.tsx +120 -0
- package/src/ui/NetplayDisconnectedDialog/index.tsx +93 -0
- package/src/ui/NetplayPauseMenu/consts.ts +2 -0
- package/src/ui/NetplayPauseMenu/index.tsx +97 -0
- package/src/ui/RomBrowser/NetplayPanel/consts.ts +24 -0
- package/src/ui/RomBrowser/NetplayPanel/index.tsx +520 -0
- package/src/ui/RomBrowser/SettingsPanel/index.tsx +478 -0
- package/src/ui/RomBrowser/consts.ts +61 -0
- package/src/ui/RomBrowser/index.tsx +1164 -0
- package/src/ui/RomBrowser/settingsConfig/index.ts +320 -0
- package/src/ui/RomBrowser/types.ts +67 -0
- package/src/ui/SaveStateDialog/consts.ts +2 -0
- package/src/ui/SaveStateDialog/index.tsx +225 -0
- package/src/ui/WarningDialog/index.tsx +113 -0
- package/src/ui/consts.ts +27 -0
- package/src/ui/hooks/useClearTerminal/consts.ts +2 -0
- package/src/ui/hooks/useClearTerminal/index.ts +37 -0
- package/src/ui/hooks/useDialogNavigation/index.ts +99 -0
- package/src/ui/hooks/useGamepad/consts.ts +21 -0
- package/src/ui/hooks/useGamepad/index.ts +194 -0
- package/src/ui/index.ts +27 -0
- package/src/utils/buffer/consts.ts +17 -0
- package/src/utils/buffer/index.ts +129 -0
- package/src/utils/color/consts.ts +58 -0
- package/src/utils/color/index.ts +183 -0
- package/src/utils/compression/consts.ts +50 -0
- package/src/utils/compression/index.ts +101 -0
- package/src/utils/consts.ts +2 -0
- package/src/utils/crc32/consts.ts +22 -0
- package/src/utils/crc32/index.ts +83 -0
- package/src/utils/ensureDirectory/index.ts +10 -0
- package/src/utils/format/consts.ts +8 -0
- package/src/utils/format/index.ts +53 -0
- package/src/utils/getErrorMessage/index.ts +10 -0
- package/src/utils/index.ts +113 -0
- package/src/utils/ini/index.ts +200 -0
- package/src/utils/kitty/consts.ts +13 -0
- package/src/utils/kitty/index.ts +181 -0
- package/src/utils/logger/consts.ts +35 -0
- package/src/utils/logger/index.ts +217 -0
- package/src/utils/paths/consts.ts +18 -0
- package/src/utils/paths/index.ts +151 -0
- package/src/utils/png/consts.ts +34 -0
- package/src/utils/png/index.ts +131 -0
- package/src/utils/readJsonFile/index.ts +16 -0
- package/src/utils/rotateLogFile/index.ts +44 -0
- package/src/utils/safeClose/index.ts +10 -0
- package/src/utils/terminal/consts.ts +8 -0
- package/src/utils/terminal/index.ts +102 -0
- package/src/utils/thumbnailRenderer/consts.ts +2 -0
- package/src/utils/thumbnailRenderer/index.ts +147 -0
- package/src/utils/typedError/index.ts +26 -0
- package/tsconfig.json +31 -0
- package/vitest.config.ts +13 -0
package/TODO.md
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
# emoemu Development TODO
|
|
2
|
+
|
|
3
|
+
## Milestone 1: Core Emulation (MVP)
|
|
4
|
+
|
|
5
|
+
### CPU
|
|
6
|
+
- [x] Implement all 151 official 6502 opcodes
|
|
7
|
+
- [x] Implement all 13 addressing modes
|
|
8
|
+
- [x] Cycle-accurate timing
|
|
9
|
+
- [x] Interrupt handling (NMI, IRQ, Reset)
|
|
10
|
+
- [ ] Unofficial/illegal opcodes (for better compatibility)
|
|
11
|
+
- [ ] Decimal mode (low priority - unused by NES)
|
|
12
|
+
|
|
13
|
+
### PPU - Background
|
|
14
|
+
- [x] Nametable rendering
|
|
15
|
+
- [x] Pattern table decoding
|
|
16
|
+
- [x] Attribute table palette selection
|
|
17
|
+
- [x] Horizontal scrolling (coarse + fine X)
|
|
18
|
+
- [x] Vertical scrolling (coarse + fine Y)
|
|
19
|
+
- [x] Nametable mirroring (H, V, single-screen)
|
|
20
|
+
- [ ] Mid-frame scroll changes
|
|
21
|
+
- [ ] Fine scroll timing accuracy
|
|
22
|
+
|
|
23
|
+
### PPU - Sprites
|
|
24
|
+
- [x] OAM (Object Attribute Memory) rendering
|
|
25
|
+
- [x] 8x8 sprite support
|
|
26
|
+
- [x] 8x16 sprite support
|
|
27
|
+
- [x] Sprite priority (front/behind background)
|
|
28
|
+
- [x] Sprite 0 hit detection
|
|
29
|
+
- [x] 8 sprites per scanline limit
|
|
30
|
+
- [x] Sprite overflow flag
|
|
31
|
+
|
|
32
|
+
### Memory
|
|
33
|
+
- [x] 2KB internal RAM with mirroring
|
|
34
|
+
- [x] PPU register access ($2000-$2007)
|
|
35
|
+
- [x] OAM DMA ($4014)
|
|
36
|
+
- [x] Controller ports ($4016-$4017)
|
|
37
|
+
- [x] APU registers ($4000-$4017)
|
|
38
|
+
|
|
39
|
+
### Cartridge
|
|
40
|
+
- [x] iNES file format parsing
|
|
41
|
+
- [x] PRG-ROM loading
|
|
42
|
+
- [x] CHR-ROM loading
|
|
43
|
+
- [x] CHR-RAM support
|
|
44
|
+
- [ ] NES 2.0 format support
|
|
45
|
+
- [ ] Battery-backed save RAM persistence
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Milestone 2: Mapper Support
|
|
50
|
+
|
|
51
|
+
### Implemented
|
|
52
|
+
- [x] Mapper 0 (NROM) - No banking
|
|
53
|
+
- [x] Mapper 1 (MMC1) - Bank switching + mirroring control
|
|
54
|
+
- [x] Mapper 2 (UxROM) - PRG bank switching
|
|
55
|
+
- [x] Mapper 3 (CNROM) - CHR bank switching
|
|
56
|
+
- [x] Mapper 4 (MMC3) - Scanline counter, IRQ
|
|
57
|
+
- [x] Mapper 7 (AxROM) - Single-screen mirroring
|
|
58
|
+
- [x] Mapper 9 (MMC2) - Punch-Out tile-based CHR switching
|
|
59
|
+
|
|
60
|
+
### Extended Mappers
|
|
61
|
+
- [ ] Mapper 11 (Color Dreams)
|
|
62
|
+
- [ ] Mapper 66 (GxROM)
|
|
63
|
+
- [ ] Mapper 71 (Camerica)
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Milestone 3: Audio (APU)
|
|
68
|
+
|
|
69
|
+
### Channels
|
|
70
|
+
- [x] Pulse 1 channel
|
|
71
|
+
- [x] Pulse 2 channel
|
|
72
|
+
- [x] Triangle channel
|
|
73
|
+
- [x] Noise channel
|
|
74
|
+
- [x] DMC (Delta Modulation Channel)
|
|
75
|
+
|
|
76
|
+
### APU Features
|
|
77
|
+
- [x] Frame counter / sequencer
|
|
78
|
+
- [x] Length counters
|
|
79
|
+
- [x] Envelope generators
|
|
80
|
+
- [x] Sweep units (pulse channels)
|
|
81
|
+
- [x] Linear counter (triangle)
|
|
82
|
+
- [x] Audio output mixing (NESDev wiki formula)
|
|
83
|
+
|
|
84
|
+
### Audio Output
|
|
85
|
+
- [x] Audio via audify/RtAudio (22050 Hz stereo)
|
|
86
|
+
- [x] Optional audio disable (--no-audio flag)
|
|
87
|
+
- [x] Audio sync with emulation (cycle-based sampling)
|
|
88
|
+
- [x] Sample buffering for fixed-size frame output
|
|
89
|
+
|
|
90
|
+
### Audio Architecture Improvements
|
|
91
|
+
- [x] Use `frameOutputCallback` for flow control (replace manual timing sync)
|
|
92
|
+
- [x] Leverage RtAudio's internal queue (write smaller chunks more frequently)
|
|
93
|
+
- [x] Fixed-size ring buffer (prevent unbounded memory growth)
|
|
94
|
+
- [x] Higher sample rate (44100 Hz for better quality)
|
|
95
|
+
- [x] Error callback for graceful error recovery
|
|
96
|
+
- [x] Smaller frame size (10ms for lower latency)
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Milestone 4: Input & Controls
|
|
101
|
+
|
|
102
|
+
### NES Controller Behavior
|
|
103
|
+
The NES controller uses a 4021 shift register that captures the state of all 8 buttons each frame when polled. Buttons can be held continuously (not rapid presses) and multiple buttons can be pressed simultaneously.
|
|
104
|
+
|
|
105
|
+
**Platform Note:** Input uses Kitty keyboard protocol for true keydown/keyup events. Requires Kitty terminal or a terminal that supports the Kitty keyboard protocol.
|
|
106
|
+
|
|
107
|
+
- [x] Shift register emulation ($4016, $4017)
|
|
108
|
+
- [x] Button state capture on strobe
|
|
109
|
+
- [x] Sequential bit reading
|
|
110
|
+
- [x] True button hold support (using Kitty keyboard protocol)
|
|
111
|
+
- [x] Simultaneous multi-button input
|
|
112
|
+
- [x] Per-frame state polling accuracy (InputManager.update() called each frame)
|
|
113
|
+
|
|
114
|
+
### Keyboard Input
|
|
115
|
+
- [x] Basic keyboard input
|
|
116
|
+
- [x] D-pad mapping (WASD + arrows)
|
|
117
|
+
- [x] A/B button mapping
|
|
118
|
+
- [x] Start/Select mapping
|
|
119
|
+
- [x] Proper key-down/key-up handling (Kitty keyboard protocol)
|
|
120
|
+
- [x] Simultaneous button press support
|
|
121
|
+
- [x] Controller 1 keyboard support
|
|
122
|
+
- [ ] Controller 2 keyboard support
|
|
123
|
+
- [ ] Configurable key bindings (config file)
|
|
124
|
+
|
|
125
|
+
### Gamepad Support
|
|
126
|
+
- [x] HID gamepad support via node-hid
|
|
127
|
+
- [x] Controller profiles (Xbox, PlayStation, Nintendo, 8BitDo)
|
|
128
|
+
- [x] Hotplug detection (3-second polling)
|
|
129
|
+
- [x] Controller 1 gamepad support
|
|
130
|
+
- [x] Controller 2 gamepad support
|
|
131
|
+
- [x] --list-gamepads and --debug-gamepad options
|
|
132
|
+
|
|
133
|
+
### Advanced Input
|
|
134
|
+
- [ ] Turbo A/B buttons
|
|
135
|
+
- [ ] Zapper light gun (mouse-based?)
|
|
136
|
+
- [ ] Save/load state hotkeys
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Milestone 5: Rendering & Display
|
|
141
|
+
|
|
142
|
+
### Kitty Graphics Renderer
|
|
143
|
+
- [x] Kitty graphics protocol support
|
|
144
|
+
- [x] Auto-scale to fit terminal
|
|
145
|
+
- [x] Aspect ratio correction (4:3)
|
|
146
|
+
- [x] Diff-based updates
|
|
147
|
+
- [x] Dynamic terminal resize detection
|
|
148
|
+
|
|
149
|
+
### Terminal Renderer (Half-blocks)
|
|
150
|
+
- [x] Unicode half-block characters (▀)
|
|
151
|
+
- [x] True color (24-bit) support
|
|
152
|
+
- [x] Aspect ratio correction (4:3)
|
|
153
|
+
- [x] Dynamic terminal resize detection
|
|
154
|
+
- [ ] ANSI 256-color fallback
|
|
155
|
+
- [ ] Diff-based rendering (only update changed characters)
|
|
156
|
+
|
|
157
|
+
### ASCII Renderer
|
|
158
|
+
- [x] ASCII grayscale mode
|
|
159
|
+
- [x] Optional color support
|
|
160
|
+
- [x] Configurable resolution
|
|
161
|
+
- [x] Dynamic terminal resize detection
|
|
162
|
+
|
|
163
|
+
### Display Features
|
|
164
|
+
- [ ] Configurable palette (different NES palettes)
|
|
165
|
+
- [ ] Grayscale mode via PPU mask
|
|
166
|
+
- [ ] Color emphasis bits
|
|
167
|
+
|
|
168
|
+
### CRT Effects (Kitty Renderer)
|
|
169
|
+
Post-processing effects for authentic retro display in Kitty graphics mode.
|
|
170
|
+
|
|
171
|
+
- [x] Gamma correction (`--gamma`) - Darken midtones for CRT-like contrast
|
|
172
|
+
- [x] Scanlines (`--scanlines`) - Horizontal line darkening to simulate CRT phosphor gaps
|
|
173
|
+
- [x] Color saturation (`--saturation`) - Boost color vibrancy like CRT displays
|
|
174
|
+
- [x] Brightness adjustment (`--brightness`) - Shift overall luminance levels
|
|
175
|
+
- [x] Contrast adjustment (`--contrast`) - Expand/compress tonal range around midpoint
|
|
176
|
+
- [x] Vignette (`--vignette`) - Darken screen edges to simulate CRT electron beam falloff
|
|
177
|
+
|
|
178
|
+
#### Future CRT Effects (More Complex)
|
|
179
|
+
- [x] Phosphor bloom/glow - Bright pixels bleed into neighbors
|
|
180
|
+
- [x] NTSC color artifacts - Horizontal color bleeding from composite video
|
|
181
|
+
- [ ] Color temperature/tint - Warmer CRT-like color balance
|
|
182
|
+
- [x] CRT curvature - Barrel distortion to simulate curved CRT screens
|
|
183
|
+
- [ ] Shadow mask / Aperture grille - Phosphor pattern overlay (dots or vertical lines)
|
|
184
|
+
- [ ] Sharpness control - Sharpen or blur the image
|
|
185
|
+
- [ ] Phosphor persistence / ghosting - Faint trails from slow phosphor decay
|
|
186
|
+
- [ ] Chromatic aberration - RGB color fringing at screen edges
|
|
187
|
+
- [ ] LCD grid effect - Visible pixel boundaries for handheld simulation (GB, GBA)
|
|
188
|
+
- [ ] Signal noise - Subtle static/grain for analog feel
|
|
189
|
+
- [ ] Interlace simulation - Flickering/combing for interlaced output
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Milestone 6: Performance & Optimization
|
|
194
|
+
|
|
195
|
+
### Quick Wins (Low Effort, High Impact)
|
|
196
|
+
- [x] Reuse Kitty RGB buffer (`ppu/kitty-renderer.ts:109`)
|
|
197
|
+
- Currently allocates 184KB every frame (11MB/sec GC pressure)
|
|
198
|
+
- Move `new Uint8Array()` to class property, reuse across frames
|
|
199
|
+
- [x] Audio buffer pool (`emulator.ts:370`)
|
|
200
|
+
- New buffer allocated per sample batch (~11×/frame)
|
|
201
|
+
- Use 2-3 pre-allocated buffers and rotate
|
|
202
|
+
- [x] Palette color escape sequence cache (`ppu/palette.ts` + `ppu/renderer.ts`)
|
|
203
|
+
- ANSI escape sequences generated per-pixel (61,440×/frame)
|
|
204
|
+
- Pre-compute lookup table of 64 formatted strings at init
|
|
205
|
+
|
|
206
|
+
### Medium Effort Optimizations
|
|
207
|
+
- [ ] Memory bus dispatch table (`memory/bus.ts:47-103`)
|
|
208
|
+
- Sequential if-else checks on every memory access (millions/frame)
|
|
209
|
+
- Use page lookup table: `handlers[address >> 8](address)` for O(1) dispatch
|
|
210
|
+
- [x] String concatenation in renderer (`ppu/renderer.ts:36-104`)
|
|
211
|
+
- Uses `+=` in hot loop (7,680 concatenations/frame)
|
|
212
|
+
- Switch to array + `.join('')`
|
|
213
|
+
- [x] Sprite bit reversal lookup table (`ppu/ppu.ts:365-452`)
|
|
214
|
+
- `reverseBits()` called up to 8× per scanline with 6 bit ops each
|
|
215
|
+
- Pre-compute 256-entry lookup table
|
|
216
|
+
- [x] Reusable DMA temp buffer (`memory/bus.ts:112`)
|
|
217
|
+
- 256-byte allocation per OAM DMA
|
|
218
|
+
- Use reusable class-level buffer
|
|
219
|
+
|
|
220
|
+
### Larger Refactors (Highest Impact)
|
|
221
|
+
- [x] PPU tile data caching (`ppu/ppu.ts:464-596`)
|
|
222
|
+
- `renderPixel()` does 4-6 PPU reads per pixel (61,440×/frame)
|
|
223
|
+
- Cache tile pattern data at start of each 8-pixel tile span
|
|
224
|
+
- Pre-compute attribute shifts per tile instead of per pixel
|
|
225
|
+
- [x] ASCII luminance lookup table (`ppu/renderer.ts:108-110`)
|
|
226
|
+
- Luminance calculated per pixel in ASCII mode
|
|
227
|
+
- Pre-compute for all 64 palette colors at init
|
|
228
|
+
|
|
229
|
+
### ROM Scanning & Playlist Optimizations
|
|
230
|
+
|
|
231
|
+
#### Completed
|
|
232
|
+
- [x] Consolidated file I/O - single file open for binary check + metadata extraction
|
|
233
|
+
- [x] Directory listing cache - avoid repeated stat() calls for save state/battery checks
|
|
234
|
+
- [x] Smart header read sizing - read only what's needed per ROM format (512B-66KB)
|
|
235
|
+
- [x] CRC32 caching - reuse CRCs from existing playlists on updates
|
|
236
|
+
- [x] Playlist index - O(1) runtime update lookups via `buildPlaylistIndex()`
|
|
237
|
+
- [x] Case-sensitive filesystem support - use `realpathSync()` for path normalization
|
|
238
|
+
|
|
239
|
+
#### Quick Wins (Low Effort)
|
|
240
|
+
- [x] Fix `checkForBatterySave()` redundant I/O (`src/frontend/playlist/reader.ts:241-256`)
|
|
241
|
+
- Removed redundant `existsSync()` call - `statSync()` throws on non-existent files
|
|
242
|
+
- [x] Export and share `DirectoryCache` (`src/frontend/directory-cache.ts`)
|
|
243
|
+
- Created shared `src/frontend/directory-cache.ts` module
|
|
244
|
+
- Exported from `src/frontend/index.ts` for easy access
|
|
245
|
+
- Rom-scanner now imports from shared module
|
|
246
|
+
- [x] Deduplicate ROM sorting logic (`src/frontend/rom-scanner/index.ts`)
|
|
247
|
+
- Exported `sortRoms()` function from rom-scanner
|
|
248
|
+
- Replaced 2 inline sorts in rom-scanner and 2 in playlist reader
|
|
249
|
+
|
|
250
|
+
#### Medium Effort
|
|
251
|
+
- [x] Cache `findMatchingCores()` result in RomInfo (`src/frontend/core-registry.ts:73-88`)
|
|
252
|
+
- Added `coreIds: string[]` to RomInfo interface
|
|
253
|
+
- ROM scanner populates coreIds during scanning
|
|
254
|
+
- Playlist generation uses cached coreIds via `getCoreFactory()` instead of calling `findMatchingCores()` again
|
|
255
|
+
- [x] Pre-compute extension→cores map in core registry
|
|
256
|
+
- Added `extensionToCoresCache` map built lazily on first access
|
|
257
|
+
- `findMatchingCores()` now does O(1) lookup instead of filtering
|
|
258
|
+
- Cache invalidated when new cores are registered
|
|
259
|
+
- [x] Use smart header sizing in `extractMetadata()` for `getRomTitle()`
|
|
260
|
+
- Was using fixed 64KB, now uses `getRequiredHeaderSize()` per format
|
|
261
|
+
- [x] Use directory cache in playlist reader (`src/frontend/playlist/reader.ts`)
|
|
262
|
+
- `checkForSaveState()` and `checkForBatterySave()` now use cached directory listings
|
|
263
|
+
- Added `dirCache` option to `ConversionOptions` for shared cache across calls
|
|
264
|
+
|
|
265
|
+
#### High Effort (Major Improvements)
|
|
266
|
+
- [ ] Streaming CRC32 calculation (`src/utils/crc32.ts:68-77`)
|
|
267
|
+
- Currently loads entire ROM into memory: `readFileSync(filePath)`
|
|
268
|
+
- Implement streaming in 64KB chunks to reduce memory pressure
|
|
269
|
+
- Prevents memory spikes for large ROM collections
|
|
270
|
+
- [ ] Optional CRC32 skip during initial scan
|
|
271
|
+
- Add `--skip-crc` flag to use "DETECT" like RetroArch
|
|
272
|
+
- CRC only computed on-demand or during explicit playlist refresh
|
|
273
|
+
- [x] Path normalization cache
|
|
274
|
+
- Added `normalizedPathCache` Map to cache `realpathSync()` results
|
|
275
|
+
- Avoids repeated syscalls for the same path
|
|
276
|
+
|
|
277
|
+
#### Pending Optimizations (Identified 2026-01)
|
|
278
|
+
|
|
279
|
+
**High Priority**
|
|
280
|
+
- [x] Fix redundant file open in `validateRomFile()`
|
|
281
|
+
- Already uses `extractMetadataFromBuffer()` with existing header buffer
|
|
282
|
+
- No extra file open per validated ROM
|
|
283
|
+
|
|
284
|
+
- [x] Convert sync directory walk to async in `scanDirectoryAsync()`
|
|
285
|
+
- Added `countFilesAsync()` for non-blocking file counting
|
|
286
|
+
- Replaced sync `collectFilePaths()` with async generator `collectFilePathsAsync()`
|
|
287
|
+
- Uses `fs/promises` async I/O and yields control every 50 entries
|
|
288
|
+
- Progress bar preserved by doing async count pass first
|
|
289
|
+
|
|
290
|
+
**Medium Priority**
|
|
291
|
+
- [x] Remove unused `scanDirectoryWithProgress()` function
|
|
292
|
+
- Was dead code superseded by `scanDirectoryAsync()`
|
|
293
|
+
- Had duplicate file counting (2x directory I/O)
|
|
294
|
+
- Removed entirely since not used anywhere
|
|
295
|
+
|
|
296
|
+
- [x] Move metadata lookup tables to module level
|
|
297
|
+
- Added `GB_CARTRIDGE_TYPES`, `GB_ROM_SIZES`, `GB_RAM_SIZES` to consts.ts
|
|
298
|
+
- Added `SNES_CHIP_TYPES` to consts.ts
|
|
299
|
+
- Lookup tables now shared across all extraction calls
|
|
300
|
+
|
|
301
|
+
**Low Priority**
|
|
302
|
+
- [x] Cache `getSupportedExtensions()` result
|
|
303
|
+
- Added `supportedExtensionsCache` in core-registry.ts
|
|
304
|
+
- Cache invalidated when cores are registered
|
|
305
|
+
- Eliminates repeated array building during scans
|
|
306
|
+
|
|
307
|
+
#### ROM Browser Optimizations (Identified 2026-01)
|
|
308
|
+
|
|
309
|
+
**High Priority**
|
|
310
|
+
- [x] Memoize empty space and scrollbar arrays (`src/ui/RomBrowser/index.tsx`)
|
|
311
|
+
- Added `emptySpaceElements` and `scrollbarElements` memoized with `useMemo`
|
|
312
|
+
- Eliminates array recreation on every render
|
|
313
|
+
|
|
314
|
+
- [x] Create memoized option value→label lookup (`src/ui/RomBrowser/index.tsx`)
|
|
315
|
+
- Added `optionLookups` Map providing O(1) value→{label, index} lookups
|
|
316
|
+
- Replaced O(n) find/findIndex calls in display and input handlers
|
|
317
|
+
|
|
318
|
+
- [x] Reduce cascading re-renders from `localConfig` (`src/ui/RomBrowser/index.tsx`)
|
|
319
|
+
- Added early return guards to skip state updates when value unchanged
|
|
320
|
+
- Prevents unnecessary re-renders when pressing at option boundaries
|
|
321
|
+
|
|
322
|
+
- [x] Extract duplicate settings filter logic (`src/ui/RomBrowser/index.tsx`)
|
|
323
|
+
- Created `filterSettingsCategories()` helper function
|
|
324
|
+
- Shared by both `useMemo` hook and `useState` initializer
|
|
325
|
+
|
|
326
|
+
- [x] Optimize save state lazy-loading (`src/frontend/rom-scanner/index.ts`)
|
|
327
|
+
- Check file existence with `existsSync` before reading
|
|
328
|
+
- Sort by mtime to find newest file first
|
|
329
|
+
- Only read the newest file instead of all files
|
|
330
|
+
|
|
331
|
+
**Medium Priority**
|
|
332
|
+
- [x] Add search input debouncing (`src/ui/RomBrowser/index.tsx`)
|
|
333
|
+
- Added 200ms debounce delay via `debouncedSearchQuery` state
|
|
334
|
+
- Filtering now uses debounced query while display uses immediate query
|
|
335
|
+
|
|
336
|
+
- [x] Optimize mouse event buffer scanning (`src/ui/RomBrowser/index.tsx`)
|
|
337
|
+
- Added 512-byte max buffer size guard
|
|
338
|
+
- Moved regex outside handler for reuse
|
|
339
|
+
- Simplified buffer clearing via lastMatchEnd tracking
|
|
340
|
+
|
|
341
|
+
- [x] Wrap MetadataPanel in React.memo (`src/ui/RomBrowser/index.tsx`)
|
|
342
|
+
- Prevents unnecessary re-renders when parent state changes
|
|
343
|
+
- Added MetadataPanelProps interface for cleaner typing
|
|
344
|
+
|
|
345
|
+
**Low Priority**
|
|
346
|
+
- [x] Optimize category index calculation (`src/ui/RomBrowser/index.tsx`)
|
|
347
|
+
- Replaced reduce with array spreading with simple for loop using push()
|
|
348
|
+
- Reduced time complexity from O(n²) to O(n)
|
|
349
|
+
|
|
350
|
+
### Libretro Core Optimizations (Identified 2026-01)
|
|
351
|
+
|
|
352
|
+
**Critical (Every Frame)**
|
|
353
|
+
- [x] Reuse audio Float32Array buffer (`src/cores/libretro/callbacks/index.ts:258-281`)
|
|
354
|
+
- Added `audioOutputBuffer` and `audioOutputCapacity` class properties
|
|
355
|
+
- `drainAudio()` now reuses buffer, only grows when capacity exceeded
|
|
356
|
+
- Returns `subarray()` view instead of new allocation each frame
|
|
357
|
+
|
|
358
|
+
- [x] Optimize framebuffer copy (`src/cores/libretro/callbacks/index.ts:122-154`)
|
|
359
|
+
- Already optimized: uses `koffi.view()` for zero-copy native memory access
|
|
360
|
+
- `.set()` is necessary since native memory only valid during callback
|
|
361
|
+
- No further optimization possible without FFI changes
|
|
362
|
+
|
|
363
|
+
- [x] Optimize pixel format conversion loops (`src/cores/libretro/pixel-format/index.ts:96-148`)
|
|
364
|
+
- Replaced manual byte reading `data[idx] | (data[idx + 1] << 8)` with `DataView.getUint16()`
|
|
365
|
+
- DataView provides optimized native 16-bit reads with endianness handling
|
|
366
|
+
- Removed unused `BYTE_SHIFT` constant
|
|
367
|
+
|
|
368
|
+
**Medium Priority**
|
|
369
|
+
- [x] Cache input state bitmask per port (`src/cores/libretro/callbacks/index.ts:214-236`)
|
|
370
|
+
- Replaced Map-based storage with sparse arrays for O(1) lookups
|
|
371
|
+
- Added `buttonBitmask[]` cache updated on `setButtonState()`
|
|
372
|
+
- Bitmask queries now O(1) instead of iterating all buttons
|
|
373
|
+
|
|
374
|
+
- [x] Optimize audio buffer growth strategy (`src/cores/libretro/callbacks/index.ts:197-212`)
|
|
375
|
+
- Changed from 2x to 1.5x growth factor (more memory-efficient)
|
|
376
|
+
- Added `AUDIO_BUFFER_GROWTH_FACTOR` constant
|
|
377
|
+
- Reduced initial buffer size from 8192 to 4096 samples
|
|
378
|
+
|
|
379
|
+
**Low Priority**
|
|
380
|
+
- [x] Cache pixel format (`src/cores/libretro/index.ts:45,260,320`)
|
|
381
|
+
- Added `cachedPixelFormat` property in LibretroCore
|
|
382
|
+
- Cache populated after ROM load, used in getFramebuffer()
|
|
383
|
+
- Eliminates method call overhead on hot path
|
|
384
|
+
|
|
385
|
+
- [x] Reuse DataView for message parsing (`src/cores/libretro/environment/index.ts:408-469`)
|
|
386
|
+
- Analyzed: message callbacks are infrequent (not per-frame)
|
|
387
|
+
- Optimization would provide minimal benefit
|
|
388
|
+
- Marked complete - no changes needed
|
|
389
|
+
|
|
390
|
+
- [x] Cache directory string buffers (`src/cores/libretro/environment/index.ts:355-366`)
|
|
391
|
+
- Replaced `allocatedStrings[]` array with `directoryBufferCache` Map
|
|
392
|
+
- Buffers now cached by path, reused across repeated queries
|
|
393
|
+
- Prevents unbounded memory growth
|
|
394
|
+
|
|
395
|
+
### General
|
|
396
|
+
- [ ] Profile hot paths with Node.js inspector
|
|
397
|
+
- [ ] Implement frame skipping option
|
|
398
|
+
- [x] Accurate frame timing (60 FPS)
|
|
399
|
+
- [x] Fix timer drift: use `lastFrameTime += targetFrameTime` instead of `lastFrameTime = now`
|
|
400
|
+
- [x] Add frame skipping: run multiple frames without rendering when behind schedule
|
|
401
|
+
- [ ] Handle Node.js event loop delays
|
|
402
|
+
- [x] Frame skipping when behind schedule (emulate without rendering to catch up)
|
|
403
|
+
- [ ] Audio-driven timing (let audio buffer drive frame pacing instead of timers)
|
|
404
|
+
- [ ] Adaptive frame timing (track actual elapsed time instead of assuming fixed intervals)
|
|
405
|
+
- [ ] Busy-wait for precision (spin-wait final few ms instead of setTimeout)
|
|
406
|
+
- [ ] Worker thread isolation (move emulation off main event loop)
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Code Simplification
|
|
411
|
+
|
|
412
|
+
Remove micro-optimizations that add complexity without meaningful performance benefits.
|
|
413
|
+
|
|
414
|
+
### High Priority (Most Complexity, Least Benefit)
|
|
415
|
+
|
|
416
|
+
- [x] Remove palette ANSI escape caches (`src/rendering/palette.ts:167-231`)
|
|
417
|
+
- 4 separate caches (trueColorCache, bgTrueColorCache, luminanceCache, emojiColorCache) pre-computed at module load
|
|
418
|
+
- ~4KB of module-level state for all 512 NES color combinations
|
|
419
|
+
- String generation for ANSI escapes is fast in modern JS - compute on-demand instead
|
|
420
|
+
|
|
421
|
+
- [x] Remove directory buffer caching (`src/cores/libretro/environment/index.ts:112-113, 352-366`)
|
|
422
|
+
- `directoryBufferCache` Map caches Buffer objects for system/save directory paths
|
|
423
|
+
- Only queried 1-3 times per core load, not per-frame
|
|
424
|
+
- Buffer creation for small strings is negligible - remove the Map overhead
|
|
425
|
+
|
|
426
|
+
- [x] Simplify normalized path cache (`src/frontend/playlist/utils.ts:14-39`)
|
|
427
|
+
- Module-level cache that persists and can become stale if files move
|
|
428
|
+
- Called during playlist generation which is already I/O-bound
|
|
429
|
+
- Consider session-scoped cache or remove entirely
|
|
430
|
+
|
|
431
|
+
### Medium Priority (Unnecessary but Less Harmful)
|
|
432
|
+
|
|
433
|
+
- [x] Remove useMemo for trivial UI elements (`src/ui/RomBrowser/index.tsx:1653-1686`)
|
|
434
|
+
- `emptySpaceElements` array memoization - simple array creation
|
|
435
|
+
- `scrollbarElements` array memoization - Ink re-renders anyway
|
|
436
|
+
- Scrollbar position/size calculation - trivial math that doesn't need memoization
|
|
437
|
+
|
|
438
|
+
- [x] Remove useMemo for settings option lookups (`src/ui/RomBrowser/index.tsx:494-506`)
|
|
439
|
+
- Pre-computed Maps for O(1) lookup on ~30 items
|
|
440
|
+
- `.find()` on 30 items is ~10 microseconds - not a bottleneck
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Milestone 7: Features & Polish
|
|
445
|
+
|
|
446
|
+
### Save States
|
|
447
|
+
- [ ] Design save state format
|
|
448
|
+
- [ ] Serialize CPU state
|
|
449
|
+
- [ ] Serialize PPU state (VRAM, OAM, registers)
|
|
450
|
+
- [ ] Serialize mapper state
|
|
451
|
+
- [ ] Save/load to file
|
|
452
|
+
- [ ] Quick save/load hotkeys
|
|
453
|
+
|
|
454
|
+
### Configuration
|
|
455
|
+
- [ ] Config file support (JSON)
|
|
456
|
+
- [ ] Key remapping
|
|
457
|
+
- [ ] Display settings persistence
|
|
458
|
+
- [ ] ROM-specific settings
|
|
459
|
+
|
|
460
|
+
### User Experience
|
|
461
|
+
- [ ] Better error messages for invalid ROMs
|
|
462
|
+
- [ ] ROM info display (mapper, PRG/CHR size)
|
|
463
|
+
- [ ] Debug mode (show registers, memory)
|
|
464
|
+
- [ ] Pause/resume functionality
|
|
465
|
+
- [ ] Frame advance (debug)
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## Milestone 8: Testing & Compatibility
|
|
470
|
+
|
|
471
|
+
### Test ROMs
|
|
472
|
+
- [ ] nestest.nes - CPU instruction test
|
|
473
|
+
- [ ] PPU tests (blargg's)
|
|
474
|
+
- [ ] Sprite tests
|
|
475
|
+
- [ ] Timing tests
|
|
476
|
+
|
|
477
|
+
### Game Compatibility
|
|
478
|
+
- [ ] Donkey Kong (Mapper 0)
|
|
479
|
+
- [ ] Super Mario Bros (Mapper 0)
|
|
480
|
+
- [ ] The Legend of Zelda (Mapper 1)
|
|
481
|
+
- [ ] Mega Man 2 (Mapper 1)
|
|
482
|
+
- [ ] Contra (Mapper 2)
|
|
483
|
+
- [ ] Super Mario Bros 3 (Mapper 4)
|
|
484
|
+
|
|
485
|
+
### Automated Testing
|
|
486
|
+
- [ ] Unit tests for CPU instructions
|
|
487
|
+
- [ ] Unit tests for PPU rendering
|
|
488
|
+
- [ ] Integration tests with test ROMs
|
|
489
|
+
- [ ] CI/CD pipeline
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Known Issues
|
|
494
|
+
|
|
495
|
+
- [x] ~~Sprites not rendering (not implemented)~~ - FIXED
|
|
496
|
+
- [ ] Some games may have graphical glitches (timing)
|
|
497
|
+
- [x] ~~No audio output~~ - FIXED (APU fully implemented)
|
|
498
|
+
- [x] ~~Key input uses timeout-based release~~ - FIXED (now uses Kitty keyboard protocol)
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## Milestone 9: RetroArch Compatibility
|
|
503
|
+
|
|
504
|
+
Improvements to make emoemu more compatible with RetroArch configurations, cores, and workflows.
|
|
505
|
+
|
|
506
|
+
### High Priority
|
|
507
|
+
|
|
508
|
+
#### Core Options Support
|
|
509
|
+
Libretro cores define configuration options via `SET_CORE_OPTIONS` but emoemu doesn't expose them to users. `GET_VARIABLE` always returns false (`src/cores/libretro/environment/index.ts:336-341`), so cores use defaults.
|
|
510
|
+
|
|
511
|
+
- [ ] Create core options module to parse `retro_core_option_v2_definition` structures
|
|
512
|
+
- [ ] Store option definitions from `SET_CORE_OPTIONS` / `SET_CORE_OPTIONS_V2`
|
|
513
|
+
- [ ] Implement `handleGetVariable()` to return configured values
|
|
514
|
+
- [ ] Add config file sections for per-core options: `[core_picodrive_options]`
|
|
515
|
+
- [ ] Add CLI flag: `--core-opt "Option Name"=value`
|
|
516
|
+
- [ ] Fall back to default/first option if not configured (RetroArch behavior)
|
|
517
|
+
|
|
518
|
+
#### Audio/Video Control
|
|
519
|
+
- [x] `GET_AUDIO_VIDEO_ENABLE` should respect `--no-audio` flag (`src/cores/libretro/environment/index.ts:227-232`)
|
|
520
|
+
- ~~Currently hardcoded to `0b11` (both enabled)~~
|
|
521
|
+
- Cores can now skip audio processing when disabled
|
|
522
|
+
|
|
523
|
+
### Medium Priority
|
|
524
|
+
|
|
525
|
+
#### Additional Config Keys
|
|
526
|
+
Add standard RetroArch config keys to `src/frontend/config.ts:48-130`:
|
|
527
|
+
- [ ] `video_aspect_ratio` - explicit aspect ratio control
|
|
528
|
+
- [ ] `video_force_aspect` - force a specific aspect ratio
|
|
529
|
+
- [ ] `input_driver` - specify input backend preference
|
|
530
|
+
- [ ] `savestate_thumbnail_enable` - thumbnail support in states
|
|
531
|
+
|
|
532
|
+
#### Read More from retroarch.cfg
|
|
533
|
+
When `--retroarch` is used, parse additional settings from `retroarch.cfg` (`src/cores/libretro/loader.ts:104-137`):
|
|
534
|
+
- [ ] `savefile_directory` - consistent battery save locations
|
|
535
|
+
- [ ] `savestate_directory` - consistent state save locations
|
|
536
|
+
- [ ] `system_directory` - BIOS files location
|
|
537
|
+
|
|
538
|
+
#### Environment Callback Improvements
|
|
539
|
+
- [ ] Increase `MAX_INPUT_USERS` from 2 to configurable (`src/cores/libretro/environment/consts.ts:13`)
|
|
540
|
+
- Some cores support 4+ players
|
|
541
|
+
- [ ] Update `MESSAGE_INTERFACE_VERSION` from 1 to 2 (`src/cores/libretro/environment/consts.ts:19`)
|
|
542
|
+
- Modern cores expect version 2+ for newer message formats
|
|
543
|
+
- [ ] Make `DEBUG_ENV` controllable via environment variable (e.g., `EMOEMU_DEBUG_ENV`)
|
|
544
|
+
|
|
545
|
+
### Low Priority
|
|
546
|
+
|
|
547
|
+
#### Save State Enhancements
|
|
548
|
+
- [ ] Save state thumbnail support (binary PNG instead of base64)
|
|
549
|
+
- [ ] Add additional metadata matching RetroArch: `systemId`, `configHash`
|
|
550
|
+
|
|
551
|
+
#### Per-Core Config Files
|
|
552
|
+
- [ ] Read per-core configurations from `~/.retroarch/config/[corename]/` when `--retroarch` is used
|
|
553
|
+
|
|
554
|
+
#### Memory Maps
|
|
555
|
+
- [ ] Handle additional memory regions beyond SRAM in `SET_MEMORY_MAPS` (`src/cores/libretro/environment/index.ts:409-410`)
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## Netplay Sync Improvements
|
|
560
|
+
|
|
561
|
+
When connecting as a client to a RetroArch host, emoemu's sync is playable but "jumpy/glitchy" compared to two official RetroArch apps. The following improvements would make sync smoother while staying compatible with RetroArch protocol.
|
|
562
|
+
|
|
563
|
+
### Critical Issues
|
|
564
|
+
|
|
565
|
+
#### 1. Stall threshold too aggressive
|
|
566
|
+
**File:** `src/netplay/consts.ts:213`
|
|
567
|
+
|
|
568
|
+
- emoemu: `MAX_FRAMES_BEHIND = 10`
|
|
569
|
+
- RetroArch: `NETPLAY_MAX_STALL_FRAMES = 60`
|
|
570
|
+
|
|
571
|
+
With only 10 frames tolerance, any network jitter causes constant micro-stalls. Increase to 60 frames (1 second at 60fps) to match RetroArch behavior.
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
// Change from:
|
|
575
|
+
export const MAX_FRAMES_BEHIND = 10;
|
|
576
|
+
// To:
|
|
577
|
+
export const MAX_FRAMES_BEHIND = 60;
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### 2. Missing catch-up mode
|
|
581
|
+
**Files:** `src/netplay/sync-manager.ts`, `src/Emulator/index.ts`
|
|
582
|
+
|
|
583
|
+
RetroArch has a `catch_up` boolean (netplay_private.h:671) that temporarily disables the frame limiter when the client is behind. This allows smooth fast-forward to catch up instead of stuttery pause/resume cycles.
|
|
584
|
+
|
|
585
|
+
emoemu only stalls when ahead, never accelerates when behind.
|
|
586
|
+
|
|
587
|
+
**Fix:**
|
|
588
|
+
- Add `shouldCatchUp: boolean` to preFrame() return value
|
|
589
|
+
- Detect when `unreadFrame - selfFrame > threshold` (e.g., 60 frames)
|
|
590
|
+
- Return `{ shouldStall: false, shouldCatchUp: true }`
|
|
591
|
+
- Emulator disables frame limiter when `shouldCatchUp` is true
|
|
592
|
+
- Allows client to fast-forward smoothly to catch up
|
|
593
|
+
|
|
594
|
+
#### 3. Single frame counter instead of two
|
|
595
|
+
**Files:** `src/netplay/sync-manager.ts:364-403`, `src/Emulator/index.ts:662-700`
|
|
596
|
+
|
|
597
|
+
RetroArch uses TWO independent frame counters:
|
|
598
|
+
- `self_frame_count` - where INPUT is being read from
|
|
599
|
+
- `run_frame_count` - where the core is actually executing
|
|
600
|
+
|
|
601
|
+
emoemu uses ONE (`_selfFrame`) for both purposes. This causes frame N to be executed with input from frame N+1, creating frame/input mismatch vs RetroArch.
|
|
602
|
+
|
|
603
|
+
**Fix:**
|
|
604
|
+
- Add `readFrame` pointer (input read position)
|
|
605
|
+
- Keep `selfFrame` as execution position
|
|
606
|
+
- Increment `readFrame` in preFrame() to read frame N's input
|
|
607
|
+
- Increment `selfFrame` AFTER core.runFrame() completes
|
|
608
|
+
- Base rollback decisions on `readFrame`, not `selfFrame`
|
|
609
|
+
|
|
610
|
+
### Medium Priority
|
|
611
|
+
|
|
612
|
+
#### 4. INPUT processing timing
|
|
613
|
+
**File:** `src/netplay/client.ts:658-671`
|
|
614
|
+
|
|
615
|
+
When remote INPUT arrives, rollback is queued but happens in the NEXT `postFrame()` - delayed by 1+ frame. With network latency, this adds perceivable input lag.
|
|
616
|
+
|
|
617
|
+
**Fix:** Use per-client `readFramePerClient` tracking to immediately detect when we have enough input to advance safely, rather than delayed rollback checking.
|
|
618
|
+
|
|
619
|
+
#### 5. NOINPUT doesn't update sync state
|
|
620
|
+
**File:** `src/netplay/client.ts:821-835`
|
|
621
|
+
|
|
622
|
+
`handleNoInput()` only updates `_serverFrame`, doesn't notify sync manager. Spectators may stall unnecessarily.
|
|
623
|
+
|
|
624
|
+
**Fix:** Add `syncManager.advanceFrameWithoutInput(frameNumber)` method for spectators.
|
|
625
|
+
|
|
626
|
+
### Implementation Priority
|
|
627
|
+
|
|
628
|
+
1. **Quick wins (high impact, low risk):**
|
|
629
|
+
- [x] Increase `MAX_FRAMES_BEHIND` to 60
|
|
630
|
+
- [x] Add catch-up mode flag to preFrame() return
|
|
631
|
+
|
|
632
|
+
2. **Medium effort:**
|
|
633
|
+
- [x] Implement dual frame counters for accurate frame/input sync
|
|
634
|
+
- [x] Fix NOINPUT not updating sync state (add advanceFrameWithoutInput)
|
|
635
|
+
|
|
636
|
+
**Note:** Moving rollback check before sending input was attempted but caused severe stuttering (feedback loop). The original order (send input → rollback) is correct.
|
|
637
|
+
|
|
638
|
+
### Test Cases
|
|
639
|
+
|
|
640
|
+
1. **Catch-up mode:** Run emoemu client with RetroArch server, introduce ~100ms latency, verify client speeds up smoothly without visible pausing
|
|
641
|
+
|
|
642
|
+
2. **Frame sync accuracy:** Run with `--netplay-connect --clear-logs`, check netplay.log for desync messages, should see 0 desyncs on identical cores
|
|
643
|
+
|
|
644
|
+
3. **Stall/resume smoothness:** Use variable latency (jitter 50-150ms), verify gameplay is smooth not jerky
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
## Future Ideas
|
|
649
|
+
|
|
650
|
+
- [ ] Web version (WebAssembly + canvas)
|
|
651
|
+
- [x] Netplay / online multiplayer
|
|
652
|
+
- [ ] TAS (Tool-Assisted Speedrun) recording
|
|
653
|
+
- [ ] ROM database integration
|
|
654
|
+
- [ ] Shader-like post-processing effects
|
|
655
|
+
- [ ] Game Genie code support
|