Files
nvcurve/frontend/src/utils/curveHelpers.ts
T
ARIA 930e56bd07 Clean up LSP diagnostics across backend and frontend
Backend (nvcurve/):
- hal/fans.py, hal/limits.py, hal/gpu.py: replace conditional pynvml
  imports with the established 'pynvml: Any = _pynvml_import' pattern
  (fixes ~50 'possibly unbound' errors); type the result dicts; guard
  query_interface() results; explicit uuid/pci-bus parsing (int, hex
  convention documented); modernize Optional[T] -> T | None
- cli.py: fix 'curve_state' possibly-unbound and snap_path None handling
  in cmd_setup; wrap unchecked int()/open()/makedirs() calls in
  try/except with clean CLI errors; add module logger for silent
  except-pass blocks; raise ... from exc; fix unused loop vars and
  set-comprehension
- hal/snapshot.py: filepath: str | None; wrap all file ops; sorted
  imports; remove unused CT_POINTS import
- daemon.py: extract 0o666 to _SOCKET_MODE constant (intentional for
  /run sockets) with nosemgrep
- server.py: nosemgrep for Python 3.7-compat false positive (project
  requires >= 3.12); log previously-swallowed exception
- profiles/native.py, profiles/apply.py: wrap file ops and int(k)
  profile-key parsing; sorted imports; modernize typing

Frontend (frontend/src):
- Add .js extensions to all relative imports (standard TS-ESM; Vite
  resolves .js -> .ts)
- React.FormEvent (deprecated in React 19 types) -> React.SubmitEvent
- catch (e: any) -> catch (e: unknown) + instanceof Error narrowing
- React-hooks: move ref writes from render into effects; convert
  viewport reset to render-phase state adjustment; split
  selectPoint(index, multi) into selectPoint + togglePoint (no flag
  argument); remove non-null assertion
- Static inline styles -> Tailwind classes (dynamic positioning/cursor
  styles kept)
- Remove non-standard 'container' option from scrollIntoView (browsers
  ignore unknown options) which had orphaned a @ts-expect-error
- Object.fromEntries for Map -> Record conversion

Tooling:
- .gitignore: ignore .codegraph/ local tool data

Verified: tsc --noEmit, vite production build, python imports, and
full LSP scan (0 errors/warnings in both projects).
2026-09-08 23:57:30 +02:00

78 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { VFPoint } from '../types.js';
/**
* Approximate reference frequency (MHz) for a point: effective − delta.
*
* `p.freq_mhz` is the current effective frequency as reported by GetVFPCurve
* (already including any applied boost delta). Subtracting the delta gives an
* approximation of the hardware base.
*
* Note: NVIDIA enforces monotonicity across the V/F curve — a large delta on
* a lower-voltage point pushes up neighbouring points' effective frequencies
* even when those points have zero delta. So for monotonicity-affected points,
* `effective - delta` doesn't recover the true unmodified base. It is still
* useful as a faint reference line ("where this point would sit with no boost").
*/
export function refBaseMhz(p: VFPoint): number {
return p.freq_mhz - p.delta_mhz;
}
/** Find which VF point the GPU is currently near based on voltage reading */
export function findCurrentPoint(
points: VFPoint[],
voltage_mv: number | null,
): VFPoint | null {
if (voltage_mv == null || points.length === 0) return null;
return points.reduce((best, p) =>
Math.abs(p.volt_mv - voltage_mv) < Math.abs(best.volt_mv - voltage_mv) ? p : best,
);
}
/** Voltage domain extent, with padding */
export function voltExtent(points: VFPoint[], padMv = 20): [number, number] {
if (points.length === 0) return [600, 1100];
const min = Math.min(...points.map((p) => p.volt_mv));
const max = Math.max(...points.map((p) => p.volt_mv));
return [min - padMv, max + padMv];
}
/** Frequency domain extent for the effective (boosted) curve, with padding */
export function freqExtent(points: VFPoint[], padMhz = 50): [number, number] {
if (points.length === 0) return [1000, 3000];
const allFreqs = points.flatMap((p) => [p.freq_mhz, refBaseMhz(p)]);
const min = Math.min(...allFreqs);
const max = Math.max(...points.map((p) => p.freq_mhz));
return [min - padMhz, max + padMhz];
}
/**
* Detect points whose effective frequency is being held up by NVIDIA's
* monotonicity enforcement rather than their own offset.
*
* Walk points in voltage order, tracking the "ceiling" — the highest
* effective frequency seen so far and the offset that produced it. A point is
* clamped when:
* 1. Its effective freq is at or below the ceiling (hasn't moved past it)
* 2. Its own offset is lower than the offset that set the ceiling
*
* This catches cases like: point 103 has +950 MHz → effective 3907 MHz,
* point 104 has +315 MHz → effective also 3907 MHz (clamped).
*/
export function detectClampedPoints(points: VFPoint[]): Set<number> {
const clamped = new Set<number>();
let ceiling = -Infinity;
let ceilingOffset = -Infinity;
for (const p of points) {
if (p.freq_mhz <= ceiling && p.delta_khz < ceilingOffset) {
clamped.add(p.index);
}
if (p.freq_mhz > ceiling) {
ceiling = p.freq_mhz;
ceilingOffset = p.delta_khz;
}
}
return clamped;
}