# Perf

Import these from `@grafloria/renderer`.

## Functions

### `formatSnapshot`

Format a snapshot for a human.

Deliberately calls out the ratios that reveal a bug rather than just printing
numbers: "culled 0/10000" reads as fine until you notice it means nothing was
culled. The HUD's job is to make that impossible to miss.

```ts
function formatSnapshot(s: PerfSnapshot): string[]
```

## Classes

### `PerfHud`

A DOM overlay, framework-free.

Absolutely positioned, `pointer-events: none` — a debug HUD that eats clicks is a
bug generator of its own. It is opt-in and never mounted unless asked for.

```ts
class PerfHud
```

**Methods**

- `constructor(private readonly host: HTMLElement)`
- `show(): void`
- `update(snapshot: PerfSnapshot): void`
- `hide(): void`

### `QualityGovernor`

```ts
class QualityGovernor
```

**Methods**

- `constructor(options: GovernorOptions = {})`
- `record(frameMs: number): QualityBias` — Feed the governor one frame time (ms). Returns the bias to render the NEXT frame at.
- `getBias(): QualityBias` — The bias to apply right now.
- `getState(): GovernorState`
- `effectiveTier(zoomTier: string, tiers: readonly string[]): string` — Apply the bias to a zoom-derived tier.

`tiers` must be ordered richest → poorest, which is the order the LOD config
declares them in. The governor can only ever make the picture SIMPLER than the
zoom asked for — never richer. A governor that could upgrade detail beyond what
the zoom wants would draw labels on 4px nodes to fill spare budget, which is
not a feature.
- `reset(): void` — Forget everything — e.g. after a diagram swap, where past frames say nothing.

## Constants

### `EMPTY_SNAPSHOT`

```ts
const EMPTY_SNAPSHOT: PerfSnapshot
```

## Interfaces

### `GovernorOptions`

```ts
interface GovernorOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `budgetMs?` | `number` |  | The frame budget. 16.7ms = 60fps. |
| `downFactor?` | `number` |  | Step DOWN when the median frame exceeds budget × this. Default 1.0 — the budget IS the line; there is no point having a budget you are content to miss. |
| `upFactor?` | `number` |  | Step UP only when the median frame is below budget × this. Default 0.55 — the DEAD BAND. A tier that renders at 0.9× budget is doing its job; restoring detail would put us straight back over it, which is the oscillation this exists to prevent. |
| `window?` | `number` |  | Frames in the rolling window. |
| `recoveryWindows?` | `number` |  | Consecutive fast windows required before restoring a tier. Recovery is patient. |
| `maxBias?` | `QualityBias` |  | Worst tier the governor may impose. |
| `panicFactor?` | `number` |  | ESCALATION. A frame this many times over budget is not a slow frame, it is a structurally wrong one — and waiting a full window to notice means 12 frames of a visibly locked-up canvas. Default 4× (≈67ms: a third of a second of these and the user is already reaching for the tab close button). |
| `panicWindow?` | `number` |  | How many frames the escalation path looks at. THREE, NOT ONE — and that is the whole subtlety. Reacting to a single catastrophic frame would make one GC pause indistinguishable from a scene the machine genuinely cannot draw. A median over three still rejects a lone spike (two of the three must be bad for the median to be bad) while reacting 4× sooner than the main window. |

### `GovernorState`

```ts
interface GovernorState
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `bias` | `QualityBias` |  |  |
| `medianMs` | `number` |  | Median frame time over the window — what the decision is actually made on. |
| `samples` | `number` |  | Frames recorded so far in the current window. |
| `recoveryStreak` | `number` |  | Consecutive fast windows accumulated toward a step back up. |
| `lastDecision` | `'steady' \| 'stepped-down' \| 'stepped-up' \| 'escalated'` |  | Why the governor last changed its mind — surfaced in the HUD, because an invisible governor is indistinguishable from a bug. |

### `PerfSnapshot`

```ts
interface PerfSnapshot
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `fps` | `number` |  | Rolling FPS. |
| `frameMs` | `number` |  | Last frame, ms. |
| `nodes` | `number` |  | Entities in the model. |
| `links` | `number` |  |  |
| `visibleNodes` | `number` |  | Entities that survived viewport culling — i.e. what we actually paid for. |
| `visibleLinks` | `number` |  |  |
| `mountedViews` | `number` |  | Entities whose views exist in the DOM right now. |
| `dirtyNodes` | `number` |  | Entities re-rendered this frame. |
| `dirtyLinks` | `number` |  |  |
| `routedLinks` | `number` |  | Links whose route was recomputed this frame. Should be ~0 on an idle frame. |
| `tier` | `string` |  | The LOD tier actually rendered, and the governor's reasoning. |
| `governor?` | `GovernorState` |  |  |

## Types

### `QualityBias`

How many tiers below the zoom-derived one we are currently rendering.

```ts
type QualityBias = 0 | 1 | 2;
```

**Members**

- `toString(radix?: number): string` — Returns a string representation of an object.
- `toFixed(fractionDigits?: number): string` — Returns a string representing a number in fixed-point notation.
- `toExponential(fractionDigits?: number): string` — Returns a string containing a number represented in exponential notation.
- `toPrecision(precision?: number): string` — Returns a string containing a number represented either in exponential or fixed-point notation with a specified number of digits.
- `valueOf(): number` — Returns the primitive value of the specified object.
- `toLocaleString(locales?: string | string[], options?: Intl.NumberFormatOptions): string` — Converts a number to a string by using the current or specified locale.
