@tmustier/pi-nes 0.2.11 → 0.2.13

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 CHANGED
@@ -83,8 +83,8 @@ Config is stored at `~/.pi/nes/config.json`. Use `/nes config` for quick setup.
83
83
  | `romDir` | `/roms/nes` | Where to look for ROM files |
84
84
  | `saveDir` | `/roms/nes/saves` | Where to store battery saves (defaults to `<romDir>/saves`) |
85
85
  | `renderer` | `"image"` | `"image"` (Kitty graphics) or `"text"` (ANSI) |
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
+ | `imageQuality` | `"balanced"` | `"balanced"` (30 fps) or `"high"` (60 fps) |
87
+ | `pixelScale` | `1.0` | Display scale (0.5–4.0) |
88
88
 
89
89
  ## Terminal Support
90
90
 
@@ -59,11 +59,7 @@ export function normalizeConfig(raw: unknown): NesConfig {
59
59
  ? normalizePath(parsed.saveDir, saveDirFallback)
60
60
  : saveDirFallback;
61
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;
62
+ const pixelScale = normalizePixelScale(parsed.pixelScale);
67
63
  return {
68
64
  romDir,
69
65
  saveDir,
@@ -115,8 +111,11 @@ async function ensureDirectory(dirPath: string): Promise<void> {
115
111
  }
116
112
  }
117
113
 
118
- function normalizePixelScale(raw: number, maxPixelScale: number): number {
119
- return Math.min(maxPixelScale, Math.max(0.5, raw));
114
+ function normalizePixelScale(raw: unknown): number {
115
+ if (typeof raw !== "number" || Number.isNaN(raw)) {
116
+ return DEFAULT_CONFIG.pixelScale;
117
+ }
118
+ return Math.min(4, Math.max(0.5, raw));
120
119
  }
121
120
 
122
121
  function normalizeImageQuality(raw: unknown): ImageQuality {
@@ -176,8 +176,8 @@ async function configureWithWizard(
176
176
  }
177
177
 
178
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",
179
+ "Balanced (recommended) — 30 fps",
180
+ "High — 60 fps",
181
181
  ]);
182
182
  if (!qualityChoice) {
183
183
  return false;
@@ -185,7 +185,7 @@ async function configureWithWizard(
185
185
 
186
186
  const isHighQuality = qualityChoice.startsWith("High");
187
187
  const imageQuality = isHighQuality ? "high" : "balanced";
188
- const pixelScale = isHighQuality ? 1.5 : 1.0;
188
+ const pixelScale = config.pixelScale;
189
189
 
190
190
  const defaultSaveDir = getDefaultSaveDir(config.romDir);
191
191
  const shouldSyncSaveDir = config.saveDir === defaultSaveDir;
@@ -289,18 +289,16 @@ async function attachSession(
289
289
  ): Promise<boolean> {
290
290
  let shouldStop = false;
291
291
  try {
292
- const useOverlay = config.renderer !== "image";
293
- const overlayOptions = useOverlay
294
- ? {
295
- overlay: true,
296
- overlayOptions: {
297
- width: "85%",
298
- maxHeight: "90%",
299
- anchor: "center",
300
- margin: { top: 1 },
301
- },
302
- }
303
- : undefined;
292
+ const isImageRenderer = config.renderer === "image";
293
+ const overlayOptions = {
294
+ overlay: true,
295
+ overlayOptions: {
296
+ width: isImageRenderer ? "90%" : "85%",
297
+ maxHeight: "90%",
298
+ anchor: "center",
299
+ margin: { top: 1 },
300
+ },
301
+ };
304
302
 
305
303
  const renderIntervalMs = config.renderer === "image"
306
304
  ? config.imageQuality === "high"
@@ -323,6 +321,7 @@ async function attachSession(
323
321
  config.keybindings,
324
322
  config.renderer,
325
323
  config.pixelScale,
324
+ isImageRenderer,
326
325
  debug,
327
326
  () => session.getStats(),
328
327
  "native",
@@ -84,6 +84,7 @@ export class NesOverlayComponent implements Component {
84
84
  private readonly imageRenderer = new NesImageRenderer();
85
85
  private readonly rendererMode: RendererMode;
86
86
  private readonly pixelScale: number;
87
+ private readonly windowed: boolean;
87
88
  private readonly debug: boolean;
88
89
  private readonly statsProvider?: () => NesSessionStats;
89
90
  private readonly debugLabel?: string;
@@ -97,6 +98,7 @@ export class NesOverlayComponent implements Component {
97
98
  inputMapping: InputMapping = DEFAULT_INPUT_MAPPING,
98
99
  rendererMode: RendererMode = "image",
99
100
  pixelScale = 1,
101
+ windowed = false,
100
102
  debug = false,
101
103
  statsProvider?: () => NesSessionStats,
102
104
  debugLabel?: string,
@@ -104,6 +106,7 @@ export class NesOverlayComponent implements Component {
104
106
  this.inputMapping = inputMapping;
105
107
  this.rendererMode = rendererMode;
106
108
  this.pixelScale = pixelScale;
109
+ this.windowed = windowed;
107
110
  this.debug = debug;
108
111
  this.statsProvider = statsProvider;
109
112
  this.debugLabel = debugLabel;
@@ -154,6 +157,7 @@ export class NesOverlayComponent implements Component {
154
157
  width,
155
158
  footerRows,
156
159
  this.pixelScale,
160
+ !this.windowed,
157
161
  );
158
162
  if (debugLine) {
159
163
  lines.push(truncateToWidth(debugLine, width));
@@ -68,17 +68,18 @@ export class NesImageRenderer {
68
68
  widthCells: number,
69
69
  footerRows = 1,
70
70
  pixelScale = 1,
71
+ padToHeight = true,
71
72
  ): string[] {
72
73
  const caps = getCapabilities();
73
74
  if (caps.images === "kitty") {
74
- const shared = this.renderKittySharedMemory(frameBuffer, tui, widthCells, footerRows, pixelScale);
75
+ const shared = this.renderKittySharedMemory(frameBuffer, tui, widthCells, footerRows, pixelScale, padToHeight);
75
76
  if (shared) {
76
77
  return shared;
77
78
  }
78
- return this.renderKittyRaw(frameBuffer, tui, widthCells, footerRows, pixelScale);
79
+ return this.renderKittyRaw(frameBuffer, tui, widthCells, footerRows, pixelScale, padToHeight);
79
80
  }
80
81
 
81
- return this.renderPng(frameBuffer, tui, widthCells, footerRows, pixelScale);
82
+ return this.renderPng(frameBuffer, tui, widthCells, footerRows, pixelScale, padToHeight);
82
83
  }
83
84
 
84
85
  dispose(tui: TUI): void {
@@ -121,6 +122,7 @@ export class NesImageRenderer {
121
122
  widthCells: number,
122
123
  footerRows: number,
123
124
  pixelScale: number,
125
+ padToHeight: boolean,
124
126
  ): string[] | null {
125
127
  const module = this.getSharedMemoryModule();
126
128
  if (!module) {
@@ -155,7 +157,8 @@ export class NesImageRenderer {
155
157
  const marker = `\x1b_pi:nes:${this.rawVersion}\x07`;
156
158
  lines.push(`${moveUp}${sequence}${marker}`);
157
159
 
158
- return centerLines(applyHorizontalPadding(lines, padLeft), availableRows);
160
+ const padded = applyHorizontalPadding(lines, padLeft);
161
+ return padToHeight ? centerLines(padded, availableRows) : padded;
159
162
  }
160
163
 
161
164
  private renderKittyRaw(
@@ -164,6 +167,7 @@ export class NesImageRenderer {
164
167
  widthCells: number,
165
168
  footerRows: number,
166
169
  pixelScale: number,
170
+ padToHeight: boolean,
167
171
  ): string[] {
168
172
  const layout = computeKittyLayout(tui, widthCells, footerRows, pixelScale);
169
173
  const { availableRows, columns, rows, padLeft } = layout;
@@ -199,7 +203,8 @@ export class NesImageRenderer {
199
203
  const marker = `\x1b_pi:nes:${this.rawVersion}\x07`;
200
204
  lines.push(`${moveUp}${cached.sequence}${marker}`);
201
205
 
202
- return centerLines(applyHorizontalPadding(lines, padLeft), availableRows);
206
+ const padded = applyHorizontalPadding(lines, padLeft);
207
+ return padToHeight ? centerLines(padded, availableRows) : padded;
203
208
  }
204
209
 
205
210
  private renderPng(
@@ -208,6 +213,7 @@ export class NesImageRenderer {
208
213
  widthCells: number,
209
214
  footerRows: number,
210
215
  pixelScale: number,
216
+ padToHeight: boolean,
211
217
  ): string[] {
212
218
  const layout = computePngLayout(tui, widthCells, footerRows, pixelScale);
213
219
  const { availableRows, maxWidthCells, padLeft, targetHeight, targetWidth } = layout;
@@ -244,7 +250,8 @@ export class NesImageRenderer {
244
250
  { widthPx: this.cachedImage.width, heightPx: this.cachedImage.height },
245
251
  );
246
252
 
247
- return centerLines(applyHorizontalPadding(image.render(widthCells), padLeft), availableRows);
253
+ const padded = applyHorizontalPadding(image.render(widthCells), padLeft);
254
+ return padToHeight ? centerLines(padded, availableRows) : padded;
248
255
  }
249
256
 
250
257
  private fillRawBuffer(frameBuffer: FrameBuffer): void {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmustier/pi-nes",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "NES emulator extension for pi",
5
5
  "keywords": [
6
6
  "pi-package",
package/spec.md CHANGED
@@ -70,7 +70,7 @@ pi-nes/
70
70
  - `saveDir`
71
71
  - `enableAudio`
72
72
  - `renderer` ("image" or "text")
73
- - `imageQuality` ("balanced" or "high")
73
+ - `imageQuality` ("balanced" or "high", controls render fps)
74
74
  - `pixelScale` (float, e.g. 1.0)
75
75
  - `keybindings` (button-to-keys map, e.g. `{ "a": ["z"] }`)
76
76
 
@@ -89,6 +89,6 @@ Note: audio output is currently disabled; setting `enableAudio` will show a warn
89
89
  - Audio: disabled (no safe dependency selected).
90
90
  - Default ROM dir: `/roms/nes` (configurable).
91
91
  - Default core: `native`.
92
- - Default image quality: `balanced`.
92
+ - Default image quality: `balanced` (30 fps).
93
93
  - Default pixel scale: `1.0`.
94
94
  - Default save dir: `/roms/nes/saves` (configurable).