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,401 @@
1
+ import type { VideoDriver } from '../frontend/config';
2
+
3
+ // Rendering constants for the emulator display system
4
+ // This file contains magic numbers extracted from the rendering modules
5
+ // to satisfy ESLint no-magic-numbers rule and improve code readability
6
+
7
+ // Luminance coefficients (ITU-R BT.601)
8
+ export { LUMINANCE_R, LUMINANCE_G, LUMINANCE_B } from '../utils/color';
9
+
10
+ /** Luminance threshold for binary color decisions (e.g., black/white emoji) */
11
+ export const LUMINANCE_THRESHOLD = 0.5;
12
+
13
+ // =============================================================================
14
+ // Terminal Rendering Constants (renderer.ts)
15
+ // =============================================================================
16
+
17
+ /** Default terminal width for modern terminals (wider default) */
18
+ export const DEFAULT_TERMINAL_WIDTH_WIDE = 120;
19
+
20
+ /** Default terminal height for modern terminals (taller default) */
21
+ export const DEFAULT_TERMINAL_HEIGHT_TALL = 40;
22
+
23
+ /** Character aspect ratio for 4:3 display (8/3 ≈ 2.67, accounts for terminal chars being ~2x taller than wide) */
24
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
25
+ export const CHAR_ASPECT_RATIO_4_3 = 8 / 3;
26
+
27
+ /** Default source framebuffer width */
28
+ export const DEFAULT_SOURCE_WIDTH = 256;
29
+
30
+ /** Default source framebuffer height */
31
+ export const DEFAULT_SOURCE_HEIGHT = 240;
32
+
33
+ /** Default renderer display width in characters */
34
+ export const DEFAULT_DISPLAY_WIDTH = 128;
35
+
36
+ /** Default renderer display height in characters */
37
+ export const DEFAULT_DISPLAY_HEIGHT = 60;
38
+
39
+ /** Rows reserved for status line */
40
+ export const STATUS_LINE_ROWS = 2;
41
+
42
+ /** Gap threshold for diff rendering (unchanged chars between runs) */
43
+ export const DIFF_GAP_THRESHOLD = 5;
44
+
45
+ /** Emoji display width (each emoji takes 2 terminal columns) */
46
+ export const EMOJI_COLUMN_WIDTH = 2;
47
+
48
+ // ANSI 256-color cube constants
49
+ /** Base index for 6x6x6 color cube in ANSI 256 palette */
50
+ export const ANSI_256_COLOR_CUBE_BASE = 16;
51
+
52
+ /** Number of levels per channel in 6x6x6 color cube (0-5) */
53
+ export const ANSI_256_COLOR_LEVELS = 6;
54
+
55
+ /** Divisor for mapping 8-bit color to 6-level cube (255/5 = 51) */
56
+ export const ANSI_256_COLOR_DIVISOR = 51;
57
+
58
+ /** Multiplier for red channel in 6x6x6 cube index calculation */
59
+ export const ANSI_256_RED_MULTIPLIER = 36;
60
+
61
+ /** Multiplier for green channel in 6x6x6 cube index calculation */
62
+ export const ANSI_256_GREEN_MULTIPLIER = 6;
63
+
64
+ /** Maximum value for 8-bit color channel */
65
+ export const COLOR_CHANNEL_MAX = 255;
66
+
67
+ /** Middle gray value (128) for contrast adjustment */
68
+ export const CONTRAST_MIDPOINT = 128;
69
+
70
+ /** Number of entries in gamma lookup table (256 for 8-bit) */
71
+ export const GAMMA_LUT_SIZE = 256;
72
+
73
+ // Post-processing effect default values
74
+ /** Default gamma value (no correction) */
75
+ export const DEFAULT_GAMMA = 1.0;
76
+
77
+ /** Default scanlines intensity (disabled) */
78
+ export const DEFAULT_SCANLINES = 0;
79
+
80
+ /** Default saturation multiplier (normal) */
81
+ export const DEFAULT_SATURATION = 1.0;
82
+
83
+ /** Default brightness multiplier (normal) */
84
+ export const DEFAULT_BRIGHTNESS = 1.0;
85
+
86
+ /** Default contrast multiplier (normal) */
87
+ export const DEFAULT_CONTRAST = 1.0;
88
+
89
+ /** Default vignette intensity (disabled) */
90
+ export const DEFAULT_VIGNETTE = 0;
91
+
92
+ /** Default bloom intensity (disabled) */
93
+ export const DEFAULT_BLOOM = 0;
94
+
95
+ /** Default bloom brightness threshold */
96
+ export const DEFAULT_BLOOM_THRESHOLD = 0.6;
97
+
98
+ /** Default NTSC artifact intensity (disabled) */
99
+ export const DEFAULT_NTSC = 0;
100
+
101
+ /** Default CRT curvature (disabled) */
102
+ export const DEFAULT_CURVATURE = 0;
103
+
104
+ /** Default chromatic aberration intensity (disabled) */
105
+ export const DEFAULT_CHROMATIC_ABERRATION = 0;
106
+
107
+ // =============================================================================
108
+ // Kitty Graphics Protocol Constants (kitty-renderer.ts)
109
+ // =============================================================================
110
+
111
+ /** Default native width in pixels */
112
+ export const DEFAULT_NATIVE_WIDTH = 256;
113
+
114
+ /** Default native height in pixels */
115
+ export const DEFAULT_NATIVE_HEIGHT = 240;
116
+
117
+ /** Typical terminal cell width in pixels */
118
+ export const CELL_WIDTH_PX = 9;
119
+
120
+ /** Typical terminal cell height in pixels (roughly 2x width for most fonts) */
121
+ export const CELL_HEIGHT_PX = 18;
122
+
123
+ /** Number of initial frames to force full rendering (no diff optimization) */
124
+ export const INITIAL_FULL_RENDER_FRAMES = 5;
125
+
126
+ /** Minimum display columns for Kitty renderer */
127
+ export const MIN_DISPLAY_COLS = 32;
128
+
129
+ /** Minimum display rows for Kitty renderer */
130
+ export const MIN_DISPLAY_ROWS = 15;
131
+
132
+ /** Chunk size for Kitty graphics protocol base64 transmission (256KB) */
133
+ export const KITTY_CHUNK_SIZE = 262144;
134
+
135
+ /** PNG bit depth (8 bits per channel) */
136
+ export const PNG_BIT_DEPTH = 8;
137
+
138
+ /** PNG color type for indexed palette */
139
+ export const PNG_COLOR_TYPE_INDEXED = 3;
140
+
141
+ /** PNG color type for RGB */
142
+ export const PNG_COLOR_TYPE_RGB = 2;
143
+
144
+ /** Default PNG compression level (1-9 scale, higher = smaller files but more CPU) */
145
+ export const DEFAULT_PNG_COMPRESSION = 7;
146
+
147
+ /** Minimum PNG compression level */
148
+ export const PNG_COMPRESSION_MIN = 1;
149
+
150
+ /** Maximum PNG compression level */
151
+ export const PNG_COMPRESSION_MAX = 9;
152
+
153
+ /** Default internal render scale factor */
154
+ export const DEFAULT_RENDER_SCALE = 2;
155
+
156
+ /** Minimum render scale (0.25x = quarter resolution) */
157
+ export const MIN_RENDER_SCALE = 0.25;
158
+
159
+ /** Maximum render scale (4x) */
160
+ export const MAX_RENDER_SCALE = 4;
161
+
162
+ /**
163
+ * System-specific default render scales.
164
+ * Keys are system names (e.g., "Nintendo - Nintendo 64") matching RetroArch database names.
165
+ * The default for any system not listed here is DEFAULT_RENDER_SCALE (2x).
166
+ */
167
+ export const SYSTEM_DEFAULT_SCALES: Record<string, number> = {
168
+ // N64: Use 0.5x scale since native resolution (320x240) is larger than 8-bit consoles
169
+ // and renders slowly with software rendering
170
+ 'Nintendo - Nintendo 64': 0.5,
171
+ };
172
+
173
+ /**
174
+ * Get the default render scale for a system based on its name.
175
+ * Uses exact matching against known system names from RetroArch database format.
176
+ *
177
+ * @param systemName The system name (e.g., "Nintendo - Nintendo 64")
178
+ * @returns The default scale for this system
179
+ */
180
+ export const getDefaultScaleForSystem = (systemName: string): number => {
181
+ return SYSTEM_DEFAULT_SCALES[systemName] ?? DEFAULT_RENDER_SCALE;
182
+ };
183
+
184
+ /** Default render mode for most systems */
185
+ export const DEFAULT_RENDER_MODE: VideoDriver = 'kitty';
186
+
187
+ /**
188
+ * System-specific default render modes.
189
+ * Keys are system names (e.g., "Nintendo - Nintendo 64") matching RetroArch database names.
190
+ * The default for any system not listed here is DEFAULT_RENDER_MODE ('kitty').
191
+ */
192
+ export const SYSTEM_DEFAULT_RENDER_MODES: Record<string, VideoDriver> = {
193
+ // N64: Use terminal mode since Kitty graphics are slow with software rendering
194
+ 'Nintendo - Nintendo 64': 'terminal',
195
+ };
196
+
197
+ /**
198
+ * Get the default render mode for a system based on its name.
199
+ * Uses exact matching against known system names from RetroArch database format.
200
+ *
201
+ * @param systemName The system name (e.g., "Nintendo - Nintendo 64")
202
+ * @returns The default render mode for this system
203
+ */
204
+ export const getDefaultRenderModeForSystem = (systemName: string): VideoDriver => {
205
+ return SYSTEM_DEFAULT_RENDER_MODES[systemName] ?? DEFAULT_RENDER_MODE;
206
+ };
207
+
208
+ /** Memory logging interval in frames (60 frames ≈ 1 second at 60fps) */
209
+ export const MEMORY_LOG_INTERVAL = 60;
210
+
211
+ /** Size of 256-color palette buffer (256 colors * 3 bytes RGB) */
212
+ export const PALETTE_BUFFER_SIZE = 768;
213
+
214
+ // =============================================================================
215
+ // RGB15/RGB16 Color Format Constants
216
+ // =============================================================================
217
+
218
+ /** Bitmask for 5-bit channel in RGB15/16 format */
219
+ export const RGB15_CHANNEL_MASK = 0x1f;
220
+
221
+ /** Full 16-bit mask for RGB15 format validation */
222
+ export const RGB15_FULL_MASK = 0x001f;
223
+
224
+ /** Bit shift for green channel in RGB15 format */
225
+ export const RGB15_GREEN_SHIFT = 5;
226
+
227
+ /** Bit shift for blue channel in RGB15 format */
228
+ export const RGB15_BLUE_SHIFT = 10;
229
+
230
+ /** Left shift for converting 5-bit to 8-bit (multiply by 8) */
231
+ export const RGB5_TO_RGB8_SHIFT = 3;
232
+
233
+ // =============================================================================
234
+ // RGB24 Color Format Constants
235
+ // =============================================================================
236
+
237
+ /** Bytes per pixel in RGB24 format */
238
+ export const RGB24_BYTES_PER_PIXEL = 3;
239
+
240
+ // =============================================================================
241
+ // Color Packing Constants
242
+ // =============================================================================
243
+
244
+ /** Bit shift for red channel when packing RGB into 24-bit number */
245
+ export const PACK_RED_SHIFT = 16;
246
+
247
+ /** Bit shift for green channel when packing RGB into 8-bit number */
248
+ export const PACK_GREEN_SHIFT = 8;
249
+
250
+ // =============================================================================
251
+ // PNG Encoding Constants
252
+ // =============================================================================
253
+
254
+ /** IHDR total length (13 bytes) */
255
+ export const PNG_IHDR_LENGTH = 13;
256
+
257
+ /** Offset for height field in IHDR (after 4-byte width) */
258
+ export const PNG_IHDR_HEIGHT_OFFSET = 4;
259
+
260
+ // =============================================================================
261
+ // Memory Size Constants
262
+ // =============================================================================
263
+
264
+ /** Bytes in a kilobyte */
265
+ export const BYTES_PER_KB = 1024;
266
+
267
+ // =============================================================================
268
+ // Post-Processing Effects Constants (effects.ts)
269
+ // =============================================================================
270
+
271
+ /** Fixed-point scale factor for vignette/scanline calculations (256 = 2^8) */
272
+ export const FIXED_POINT_SCALE = 256;
273
+
274
+ /** Bit shift for fixed-point division (>> 8 = divide by 256) */
275
+ export const FIXED_POINT_SHIFT = 8;
276
+
277
+ /** Fast integer luminance red coefficient (77 ≈ 0.299 * 256) */
278
+ export const FAST_LUMINANCE_R = 77;
279
+
280
+ /** Fast integer luminance green coefficient (150 ≈ 0.587 * 256) */
281
+ export const FAST_LUMINANCE_G = 150;
282
+
283
+ /** Fast integer luminance blue coefficient (29 ≈ 0.114 * 256) */
284
+ export const FAST_LUMINANCE_B = 29;
285
+
286
+ /** Curvature distortion factor multiplier */
287
+ export const CURVATURE_FACTOR = 0.25;
288
+
289
+ /** Default bloom blur radius in pixels */
290
+ export const BLOOM_BLUR_RADIUS = 2;
291
+
292
+ /** NTSC chroma blur radius in half-pixels */
293
+ export const NTSC_BLUR_RADIUS = 5;
294
+
295
+ // =============================================================================
296
+ // ANSI Parser Constants
297
+ // =============================================================================
298
+
299
+ /** Number of standard (non-bright) colors in ANSI 16 palette */
300
+ export const ANSI_STANDARD_COLOR_COUNT = 8;
301
+
302
+ /** Tab stop width in columns */
303
+ export const ANSI_TAB_WIDTH = 8;
304
+
305
+ /** ANSI erase display mode: clear entire screen */
306
+ export const ANSI_ERASE_ENTIRE_SCREEN = 2;
307
+
308
+ /** ANSI erase display mode: clear to end and beyond */
309
+ export const ANSI_ERASE_TO_END_AND_BEYOND = 3;
310
+
311
+ /** Extended color parse offset increment */
312
+ export const ANSI_EXTENDED_COLOR_OFFSET_256 = 2;
313
+
314
+ /** Extended RGB color check: need at least 3 more params (R, G, B) */
315
+ export const ANSI_EXTENDED_RGB_MIN_PARAMS = 3;
316
+
317
+ /** Extended RGB color parse offset increment */
318
+ export const ANSI_EXTENDED_COLOR_OFFSET_RGB = 4;
319
+
320
+ /** Extended RGB R offset from mode */
321
+ export const ANSI_RGB_R_OFFSET = 1;
322
+
323
+ /** Extended RGB G offset from mode */
324
+ export const ANSI_RGB_G_OFFSET = 2;
325
+
326
+ /** Extended RGB B offset from mode */
327
+ export const ANSI_RGB_B_OFFSET = 3;
328
+
329
+ // =============================================================================
330
+ // ANSI 256 Color Cube Constants
331
+ // =============================================================================
332
+
333
+ /** Red channel multiplier in 6x6x6 cube */
334
+ export const ANSI_CUBE_RED_MULTIPLIER = 36;
335
+
336
+ /** Cube step size for non-zero values */
337
+ export const ANSI_CUBE_STEP = 40;
338
+
339
+ /** Cube base for non-zero values */
340
+ export const ANSI_CUBE_BASE = 55;
341
+
342
+ // =============================================================================
343
+ // Input Bridge Constants
344
+ // =============================================================================
345
+
346
+ /** ASCII code for lowercase 'a' */
347
+ export const ASCII_A_LOWER = 97;
348
+
349
+ /** ASCII code for lowercase 'z' */
350
+ export const ASCII_Z_LOWER = 122;
351
+
352
+ /** Ctrl key offset (Ctrl+A = 1) */
353
+ export const CTRL_KEY_OFFSET = 96;
354
+
355
+ /** ASCII code for '[' */
356
+ export const ASCII_BRACKET_OPEN = 91;
357
+
358
+ /** ASCII code for '\' */
359
+ export const ASCII_BACKSLASH = 92;
360
+
361
+ /** ASCII code for ']' */
362
+ export const ASCII_BRACKET_CLOSE = 93;
363
+
364
+ /** ASCII code for '^' */
365
+ export const ASCII_CARET = 94;
366
+
367
+ /** ASCII code for '_' */
368
+ export const ASCII_UNDERSCORE = 95;
369
+
370
+ /** Function key offset to index (F1 = 0) */
371
+ export const FUNCTION_KEY_OFFSET_3 = 3;
372
+ export const FUNCTION_KEY_OFFSET_4 = 4;
373
+ export const FUNCTION_KEY_OFFSET_5 = 5;
374
+ export const FUNCTION_KEY_OFFSET_6 = 6;
375
+ export const FUNCTION_KEY_OFFSET_7 = 7;
376
+ export const FUNCTION_KEY_OFFSET_8 = 8;
377
+ export const FUNCTION_KEY_OFFSET_9 = 9;
378
+ export const FUNCTION_KEY_OFFSET_10 = 10;
379
+
380
+ // =============================================================================
381
+ // Text Renderer Constants
382
+ // =============================================================================
383
+
384
+ /** Maximum glyph cache size before LRU eviction */
385
+ export const MAX_GLYPH_CACHE_SIZE = 1000;
386
+
387
+ /** Fraction of cache to evict when full (1/4) */
388
+ export const GLYPH_CACHE_EVICT_DIVISOR = 4;
389
+
390
+ // =============================================================================
391
+ // Native Rendering Constants (NativeRenderer)
392
+ // =============================================================================
393
+
394
+ /** Default integer scale for native window mode */
395
+ export const DEFAULT_NATIVE_SCALE = 3;
396
+
397
+ /** Minimum native window scale */
398
+ export const MIN_NATIVE_SCALE = 1;
399
+
400
+ /** Maximum native window scale */
401
+ export const MAX_NATIVE_SCALE = 8;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Rendering Module Exports
3
+ *
4
+ * Re-exports all rendering components.
5
+ */
6
+
7
+ export { TerminalRenderer } from './TerminalRenderer';
8
+ export type { RendererOptions } from './TerminalRenderer';
9
+
10
+ export { KittyRenderer } from './KittyRenderer';
11
+ export type { KittyRendererOptions } from './KittyRenderer';
12
+
13
+ export { NativeRenderer } from './NativeRenderer';
14
+ export type { NativeRendererOptions } from './NativeRenderer';
15
+
16
+ // Shared utilities
17
+ export * from './shared/ansi';
18
+ export * from './shared/consts';
19
+ export * from './shared/fitToTerminal';
20
+
21
+ // Post-processing effects
22
+ export { PostProcessingPipeline } from './postProcessing';
23
+ export type { EffectOptions } from './postProcessing';
24
+
25
+ // Re-export constants
26
+ export * from './consts';
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Native Window Manager
3
+ *
4
+ * Singleton that owns the single ink-native window/streams, created once via
5
+ * createStreams() and reused for both Ink UI rendering and game rendering.
6
+ * ink-native owns the window; this manager holds the streams and hands out
7
+ * stdin/stdout (UI) and the framebuffer + pause/resume (game).
8
+ */
9
+ import { createStreams, type Streams, type Window, type UiRenderer, type Framebuffer } from 'ink-native';
10
+ import { DEFAULT_UI_WIDTH, DEFAULT_UI_HEIGHT } from '..';
11
+ import { logger } from '@/utils/logger';
12
+ import { NATIVE_WM_LOG_CATEGORY } from '..';
13
+
14
+ export type NativeWindowManagerErrorCode = 'NOT_INITIALIZED';
15
+
16
+ export class NativeWindowManagerError extends Error {
17
+ readonly code: NativeWindowManagerErrorCode;
18
+ constructor(code: NativeWindowManagerErrorCode) {
19
+ super(code);
20
+ this.name = 'NativeWindowManagerError';
21
+ this.code = code;
22
+ }
23
+ }
24
+
25
+ export const isNativeWindowManagerError = (error: unknown): error is NativeWindowManagerError => {
26
+ return error instanceof NativeWindowManagerError;
27
+ };
28
+
29
+ /** Window mode */
30
+ export type WindowMode = 'ui' | 'game';
31
+
32
+ /** Window configuration */
33
+ export interface WindowConfig {
34
+ title?: string;
35
+ width?: number;
36
+ height?: number;
37
+ /** HiDPI scale factor override (null = auto-detect) */
38
+ scaleFactor?: number | null;
39
+ /** Target frame rate (default: ink-native's 60) */
40
+ frameRate?: number;
41
+ }
42
+
43
+ export class NativeWindowManager {
44
+ private static instance: NativeWindowManager | null = null;
45
+
46
+ private streams: Streams | null = null;
47
+ private closed = false;
48
+
49
+ private constructor() {
50
+ // Private constructor for singleton
51
+ }
52
+
53
+ static getInstance(): NativeWindowManager {
54
+ if (!NativeWindowManager.instance) {
55
+ NativeWindowManager.instance = new NativeWindowManager();
56
+ }
57
+ return NativeWindowManager.instance;
58
+ }
59
+
60
+ /** Create the window/streams once. Idempotent. */
61
+ init(config: WindowConfig = {}): void {
62
+ if (this.streams) {
63
+ return;
64
+ }
65
+ const width = config.width ?? DEFAULT_UI_WIDTH;
66
+ const height = config.height ?? DEFAULT_UI_HEIGHT;
67
+ this.streams = createStreams({
68
+ title: config.title ?? 'emoemu',
69
+ width,
70
+ height,
71
+ scaleFactor: config.scaleFactor ?? null,
72
+ frameRate: config.frameRate,
73
+ });
74
+ this.streams.window.on('close', () => {
75
+ this.closed = true;
76
+ });
77
+ logger.info(`Native window manager initialized (${width}x${height})`, NATIVE_WM_LOG_CATEGORY);
78
+ }
79
+
80
+ isInitialized(): boolean {
81
+ return this.streams !== null;
82
+ }
83
+
84
+ private requireStreams(): Streams {
85
+ if (!this.streams) {
86
+ throw new NativeWindowManagerError('NOT_INITIALIZED');
87
+ }
88
+ return this.streams;
89
+ }
90
+
91
+ getStdin(): Streams['stdin'] {
92
+ return this.requireStreams().stdin;
93
+ }
94
+
95
+ getStdout(): Streams['stdout'] {
96
+ return this.requireStreams().stdout;
97
+ }
98
+
99
+ getWindow(): Window {
100
+ return this.requireStreams().window;
101
+ }
102
+
103
+ getRenderer(): UiRenderer {
104
+ return this.requireStreams().renderer;
105
+ }
106
+
107
+ getFramebuffer(): Framebuffer {
108
+ return this.requireStreams().renderer.getFramebuffer();
109
+ }
110
+
111
+ getDimensions(): { columns: number; rows: number } {
112
+ return this.requireStreams().window.getDimensions();
113
+ }
114
+
115
+ isClosed(): boolean {
116
+ if (this.closed) {
117
+ return true;
118
+ }
119
+ return this.streams?.window.isClosed() ?? false;
120
+ }
121
+
122
+ /**
123
+ * Switch between UI (Ink) and game (direct framebuffer) rendering.
124
+ * game → pause Ink; ui → reset+clear the renderer and resume Ink.
125
+ */
126
+ setMode(mode: WindowMode): void {
127
+ const streams = this.requireStreams();
128
+ if (mode === 'game') {
129
+ streams.window.pause();
130
+ } else {
131
+ streams.renderer.reset();
132
+ streams.renderer.clear();
133
+ streams.window.resume();
134
+ }
135
+ }
136
+
137
+ /** Clear the screen to the background color. */
138
+ clearScreen(): void {
139
+ this.streams?.renderer.clear();
140
+ }
141
+
142
+ /** Tear down the window. Call only at final app exit. */
143
+ destroy(): void {
144
+ if (this.streams) {
145
+ // window.close() already stops the event loop, closes the input stream,
146
+ // and destroys the renderer internally — do not also call
147
+ // renderer.destroy() here, as that would double-close the native window.
148
+ this.streams.window.close();
149
+ this.streams = null;
150
+ }
151
+ NativeWindowManager.instance = null;
152
+ logger.info('Native window manager destroyed', NATIVE_WM_LOG_CATEGORY);
153
+ }
154
+ }
155
+
156
+ export const getWindowManager = (): NativeWindowManager => {
157
+ return NativeWindowManager.getInstance();
158
+ };
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ // Mock ink-native so tests never open a real window.
4
+ // Fixtures must be created inside vi.hoisted() so they exist before the
5
+ // hoisted vi.mock() factory runs (vi.mock calls are hoisted above regular
6
+ // top-level const declarations, and importing NativeWindowManager below
7
+ // pulls in 'ink-native' before the rest of this file's body executes).
8
+ const { fakeWindow, fakeRenderer, createStreamsMock } = vi.hoisted(() => {
9
+ const fakeWindow = {
10
+ on: vi.fn(),
11
+ pause: vi.fn(),
12
+ resume: vi.fn(),
13
+ clear: vi.fn(),
14
+ close: vi.fn(),
15
+ isClosed: vi.fn(() => false),
16
+ getDimensions: vi.fn(() => ({ columns: 80, rows: 24 })),
17
+ };
18
+ const fakeRenderer = {
19
+ reset: vi.fn(),
20
+ clear: vi.fn(),
21
+ destroy: vi.fn(),
22
+ present: vi.fn(),
23
+ getFramebuffer: vi.fn(() => ({ pixels: new Uint32Array(4), width: 2, height: 2 })),
24
+ };
25
+ const createStreamsMock = vi.fn(() => ({
26
+ stdin: {}, stdout: {}, window: fakeWindow, renderer: fakeRenderer,
27
+ }));
28
+ return { fakeWindow, fakeRenderer, createStreamsMock };
29
+ });
30
+ vi.mock('ink-native', () => ({
31
+ createStreams: createStreamsMock,
32
+ isFensterAvailable: () => true,
33
+ }));
34
+
35
+ import { NativeWindowManager } from '.';
36
+
37
+ describe('NativeWindowManager', () => {
38
+ beforeEach(() => {
39
+ vi.clearAllMocks();
40
+ // Reset singleton between tests
41
+ (NativeWindowManager as unknown as { instance: unknown }).instance = null;
42
+ });
43
+
44
+ it('creates the window exactly once across repeated init calls', () => {
45
+ const wm = NativeWindowManager.getInstance();
46
+ wm.init({ title: 'emoemu', width: 640, height: 480 });
47
+ wm.init({ title: 'emoemu', width: 640, height: 480 });
48
+ expect(createStreamsMock).toHaveBeenCalledTimes(1);
49
+ expect(wm.isInitialized()).toBe(true);
50
+ });
51
+
52
+ it('pauses Ink in game mode and resumes + resets in ui mode', () => {
53
+ const wm = NativeWindowManager.getInstance();
54
+ wm.init({});
55
+ wm.setMode('game');
56
+ expect(fakeWindow.pause).toHaveBeenCalledTimes(1);
57
+ wm.setMode('ui');
58
+ expect(fakeRenderer.reset).toHaveBeenCalledTimes(1);
59
+ expect(fakeRenderer.clear).toHaveBeenCalledTimes(1);
60
+ expect(fakeWindow.resume).toHaveBeenCalledTimes(1);
61
+ });
62
+
63
+ it('reports closed after the window close event fires', () => {
64
+ const wm = NativeWindowManager.getInstance();
65
+ wm.init({});
66
+ const closeHandler = fakeWindow.on.mock.calls.find(([evt]) => evt === 'close')?.[1] as () => void;
67
+ expect(closeHandler).toBeDefined();
68
+ expect(wm.isClosed()).toBe(false);
69
+ closeHandler();
70
+ expect(wm.isClosed()).toBe(true);
71
+ });
72
+
73
+ it('tears down solely through window.close() on destroy (no separate renderer.destroy())', () => {
74
+ const wm = NativeWindowManager.getInstance();
75
+ wm.init({});
76
+ wm.destroy();
77
+ expect(fakeWindow.close).toHaveBeenCalledTimes(1);
78
+ expect(fakeRenderer.destroy).not.toHaveBeenCalled();
79
+ expect(wm.isInitialized()).toBe(false);
80
+ });
81
+ });
@@ -0,0 +1,6 @@
1
+ /** Default UI window width in pixels */
2
+ export const DEFAULT_UI_WIDTH = 800;
3
+ /** Default UI window height in pixels */
4
+ export const DEFAULT_UI_HEIGHT = 600;
5
+ /** Log category for the native window manager */
6
+ export const NATIVE_WM_LOG_CATEGORY = 'Native-WM';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Native UI Module
3
+ *
4
+ * Provides the shared window manager for native window mode and re-exports
5
+ * ink-native components for UI rendering.
6
+ */
7
+
8
+ // Window manager for the shared native window (UI + game)
9
+ export {
10
+ NativeWindowManager,
11
+ getWindowManager,
12
+ type WindowMode,
13
+ type WindowConfig,
14
+ } from './NativeWindowManager';
15
+
16
+ // Re-export ink-native for native UI rendering
17
+ export { createStreams, isFensterAvailable } from 'ink-native';
18
+
19
+ // Re-export constants
20
+ export * from './consts';