@tmustier/pi-nes 0.2.9 → 0.2.11
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/README.md +5 -3
- package/extensions/nes/config.ts +19 -7
- package/extensions/nes/index.ts +17 -29
- package/package.json +1 -1
- package/spec.md +4 -2
package/README.md
CHANGED
|
@@ -54,14 +54,15 @@ On first run, you'll be prompted to set your ROM directory and display quality.
|
|
|
54
54
|
|
|
55
55
|
## Configuration
|
|
56
56
|
|
|
57
|
-
Config is stored at `~/.pi/nes/config.json`. Use `/nes config` for
|
|
57
|
+
Config is stored at `~/.pi/nes/config.json`. Use `/nes config` for quick setup.
|
|
58
58
|
|
|
59
59
|
```json
|
|
60
60
|
{
|
|
61
61
|
"romDir": "/roms/nes",
|
|
62
62
|
"saveDir": "/roms/nes/saves",
|
|
63
63
|
"renderer": "image",
|
|
64
|
-
"
|
|
64
|
+
"imageQuality": "balanced",
|
|
65
|
+
"pixelScale": 1.0,
|
|
65
66
|
"keybindings": {
|
|
66
67
|
"up": ["up", "w"],
|
|
67
68
|
"down": ["down", "s"],
|
|
@@ -82,7 +83,8 @@ Config is stored at `~/.pi/nes/config.json`. Use `/nes config` for guided setup.
|
|
|
82
83
|
| `romDir` | `/roms/nes` | Where to look for ROM files |
|
|
83
84
|
| `saveDir` | `/roms/nes/saves` | Where to store battery saves (defaults to `<romDir>/saves`) |
|
|
84
85
|
| `renderer` | `"image"` | `"image"` (Kitty graphics) or `"text"` (ANSI) |
|
|
85
|
-
| `
|
|
86
|
+
| `imageQuality` | `"balanced"` | `"balanced"` (30 fps, max pixel scale 1.0) or `"high"` (60 fps, max pixel scale 1.5) |
|
|
87
|
+
| `pixelScale` | `1.0` | Display scale (0.5–1.0 balanced, 0.5–1.5 high) |
|
|
86
88
|
|
|
87
89
|
## Terminal Support
|
|
88
90
|
|
package/extensions/nes/config.ts
CHANGED
|
@@ -5,12 +5,14 @@ import { DEFAULT_INPUT_MAPPING, type InputMapping } from "./input-map.js";
|
|
|
5
5
|
import { normalizePath } from "./paths.js";
|
|
6
6
|
|
|
7
7
|
export type RendererMode = "image" | "text";
|
|
8
|
+
export type ImageQuality = "balanced" | "high";
|
|
8
9
|
|
|
9
10
|
export interface NesConfig {
|
|
10
11
|
romDir: string;
|
|
11
12
|
saveDir: string;
|
|
12
13
|
enableAudio: boolean;
|
|
13
14
|
renderer: RendererMode;
|
|
15
|
+
imageQuality: ImageQuality;
|
|
14
16
|
pixelScale: number;
|
|
15
17
|
keybindings: InputMapping;
|
|
16
18
|
}
|
|
@@ -26,7 +28,8 @@ export const DEFAULT_CONFIG: NesConfig = {
|
|
|
26
28
|
saveDir: getDefaultSaveDir(DEFAULT_ROM_DIR),
|
|
27
29
|
enableAudio: false,
|
|
28
30
|
renderer: "image",
|
|
29
|
-
|
|
31
|
+
imageQuality: "balanced",
|
|
32
|
+
pixelScale: 1.0,
|
|
30
33
|
keybindings: cloneMapping(DEFAULT_INPUT_MAPPING),
|
|
31
34
|
};
|
|
32
35
|
|
|
@@ -35,6 +38,7 @@ interface RawConfig {
|
|
|
35
38
|
saveDir?: unknown;
|
|
36
39
|
enableAudio?: unknown;
|
|
37
40
|
renderer?: unknown;
|
|
41
|
+
imageQuality?: unknown;
|
|
38
42
|
pixelScale?: unknown;
|
|
39
43
|
keybindings?: unknown;
|
|
40
44
|
}
|
|
@@ -54,12 +58,19 @@ export function normalizeConfig(raw: unknown): NesConfig {
|
|
|
54
58
|
typeof parsed.saveDir === "string" && parsed.saveDir.length > 0
|
|
55
59
|
? normalizePath(parsed.saveDir, saveDirFallback)
|
|
56
60
|
: saveDirFallback;
|
|
61
|
+
const imageQuality = normalizeImageQuality(parsed.imageQuality);
|
|
62
|
+
const maxPixelScale = imageQuality === "high" ? 1.5 : 1.0;
|
|
63
|
+
const pixelScale =
|
|
64
|
+
typeof parsed.pixelScale === "number" && !Number.isNaN(parsed.pixelScale)
|
|
65
|
+
? normalizePixelScale(parsed.pixelScale, maxPixelScale)
|
|
66
|
+
: maxPixelScale;
|
|
57
67
|
return {
|
|
58
68
|
romDir,
|
|
59
69
|
saveDir,
|
|
60
70
|
enableAudio: typeof parsed.enableAudio === "boolean" ? parsed.enableAudio : DEFAULT_CONFIG.enableAudio,
|
|
61
71
|
renderer: parsed.renderer === "text" ? "text" : DEFAULT_CONFIG.renderer,
|
|
62
|
-
|
|
72
|
+
imageQuality,
|
|
73
|
+
pixelScale,
|
|
63
74
|
keybindings: normalizeKeybindings(parsed.keybindings),
|
|
64
75
|
};
|
|
65
76
|
}
|
|
@@ -104,11 +115,12 @@ async function ensureDirectory(dirPath: string): Promise<void> {
|
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
117
|
|
|
107
|
-
function normalizePixelScale(raw:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
function normalizePixelScale(raw: number, maxPixelScale: number): number {
|
|
119
|
+
return Math.min(maxPixelScale, Math.max(0.5, raw));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeImageQuality(raw: unknown): ImageQuality {
|
|
123
|
+
return raw === "high" ? "high" : "balanced";
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
function normalizeKeybindings(raw: unknown): InputMapping {
|
package/extensions/nes/index.ts
CHANGED
|
@@ -18,7 +18,8 @@ import { NesSession } from "./nes-session.js";
|
|
|
18
18
|
import { listRoms } from "./roms.js";
|
|
19
19
|
import { loadSram } from "./saves.js";
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const IMAGE_RENDER_INTERVAL_BALANCED_MS = 1000 / 30;
|
|
22
|
+
const IMAGE_RENDER_INTERVAL_HIGH_MS = 1000 / 60;
|
|
22
23
|
const TEXT_RENDER_INTERVAL_MS = 1000 / 60;
|
|
23
24
|
|
|
24
25
|
let activeSession: NesSession | null = null;
|
|
@@ -143,8 +144,8 @@ async function configureWithWizard(
|
|
|
143
144
|
const romDirDisplay = displayPath(config.romDir);
|
|
144
145
|
const romDirDefaultLabel = config.romDir === DEFAULT_CONFIG.romDir ? "Use default" : "Use current";
|
|
145
146
|
const romDirOptions = [
|
|
146
|
-
`${romDirDefaultLabel} (${romDirDisplay})`,
|
|
147
|
-
"Enter a custom path",
|
|
147
|
+
`${romDirDefaultLabel} (${romDirDisplay}) — creates if missing`,
|
|
148
|
+
"Enter a custom path (must exist)",
|
|
148
149
|
];
|
|
149
150
|
const romDirChoice = await ctx.ui.select("ROM directory", romDirOptions);
|
|
150
151
|
if (!romDirChoice) {
|
|
@@ -153,7 +154,7 @@ async function configureWithWizard(
|
|
|
153
154
|
|
|
154
155
|
let romDir = config.romDir;
|
|
155
156
|
if (romDirChoice === romDirOptions[1]) {
|
|
156
|
-
const romDirInput = await ctx.ui.input("ROM directory", romDirDisplay);
|
|
157
|
+
const romDirInput = await ctx.ui.input("ROM directory (must exist)", romDirDisplay);
|
|
157
158
|
if (romDirInput === undefined) {
|
|
158
159
|
return false;
|
|
159
160
|
}
|
|
@@ -174,35 +175,17 @@ async function configureWithWizard(
|
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
|
|
177
|
-
const qualityChoice = await ctx.ui.select("Quality
|
|
178
|
-
"Balanced (
|
|
179
|
-
"
|
|
180
|
-
"High (1.5) - sharper",
|
|
181
|
-
"Custom...",
|
|
178
|
+
const qualityChoice = await ctx.ui.select("Quality", [
|
|
179
|
+
"Balanced (recommended) — 30 fps, max pixel scale 1.0",
|
|
180
|
+
"High — 60 fps, max pixel scale 1.5",
|
|
182
181
|
]);
|
|
183
182
|
if (!qualityChoice) {
|
|
184
183
|
return false;
|
|
185
184
|
}
|
|
186
185
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
} else if (qualityChoice.startsWith("Balanced")) {
|
|
191
|
-
pixelScale = 1.2;
|
|
192
|
-
} else if (qualityChoice.startsWith("High")) {
|
|
193
|
-
pixelScale = 1.5;
|
|
194
|
-
} else {
|
|
195
|
-
const pixelScaleInput = await ctx.ui.input("Pixel scale (0.5 - 4)", config.pixelScale.toString());
|
|
196
|
-
if (pixelScaleInput === undefined) {
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
const parsed = pixelScaleInput.trim() === "" ? config.pixelScale : Number(pixelScaleInput);
|
|
200
|
-
if (Number.isNaN(parsed)) {
|
|
201
|
-
ctx.ui.notify("Pixel scale must be a number.", "error");
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
pixelScale = parsed;
|
|
205
|
-
}
|
|
186
|
+
const isHighQuality = qualityChoice.startsWith("High");
|
|
187
|
+
const imageQuality = isHighQuality ? "high" : "balanced";
|
|
188
|
+
const pixelScale = isHighQuality ? 1.5 : 1.0;
|
|
206
189
|
|
|
207
190
|
const defaultSaveDir = getDefaultSaveDir(config.romDir);
|
|
208
191
|
const shouldSyncSaveDir = config.saveDir === defaultSaveDir;
|
|
@@ -211,6 +194,7 @@ async function configureWithWizard(
|
|
|
211
194
|
...config,
|
|
212
195
|
romDir,
|
|
213
196
|
saveDir,
|
|
197
|
+
imageQuality,
|
|
214
198
|
pixelScale,
|
|
215
199
|
});
|
|
216
200
|
await saveConfig(normalized);
|
|
@@ -318,7 +302,11 @@ async function attachSession(
|
|
|
318
302
|
}
|
|
319
303
|
: undefined;
|
|
320
304
|
|
|
321
|
-
const renderIntervalMs = config.renderer === "image"
|
|
305
|
+
const renderIntervalMs = config.renderer === "image"
|
|
306
|
+
? config.imageQuality === "high"
|
|
307
|
+
? IMAGE_RENDER_INTERVAL_HIGH_MS
|
|
308
|
+
: IMAGE_RENDER_INTERVAL_BALANCED_MS
|
|
309
|
+
: TEXT_RENDER_INTERVAL_MS;
|
|
322
310
|
session.setRenderIntervalMs(renderIntervalMs);
|
|
323
311
|
|
|
324
312
|
await ctx.ui.custom(
|
package/package.json
CHANGED
package/spec.md
CHANGED
|
@@ -70,7 +70,8 @@ pi-nes/
|
|
|
70
70
|
- `saveDir`
|
|
71
71
|
- `enableAudio`
|
|
72
72
|
- `renderer` ("image" or "text")
|
|
73
|
-
- `
|
|
73
|
+
- `imageQuality` ("balanced" or "high")
|
|
74
|
+
- `pixelScale` (float, e.g. 1.0)
|
|
74
75
|
- `keybindings` (button-to-keys map, e.g. `{ "a": ["z"] }`)
|
|
75
76
|
|
|
76
77
|
Note: audio output is currently disabled; setting `enableAudio` will show a warning.
|
|
@@ -88,5 +89,6 @@ Note: audio output is currently disabled; setting `enableAudio` will show a warn
|
|
|
88
89
|
- Audio: disabled (no safe dependency selected).
|
|
89
90
|
- Default ROM dir: `/roms/nes` (configurable).
|
|
90
91
|
- Default core: `native`.
|
|
91
|
-
- Default
|
|
92
|
+
- Default image quality: `balanced`.
|
|
93
|
+
- Default pixel scale: `1.0`.
|
|
92
94
|
- Default save dir: `/roms/nes/saves` (configurable).
|