# Simulator Dev Mode — design

> 2026-06-25 · session 0017 · planning-and-executing
> Anchor: leaf task (simulator dev-tooling — R2b, no upstream design needed)

## Problem

While previewing the experience, the operator can't choose *which* clip in an
altitude's rotating pool is shown — the server picks a random pool member on each
landing (content-pipeline §11.1). For by-eye review they want to step through every
clip in a pool on demand, and to see the engine/clip data behind what's on screen.
The RenderPlan JSON readout is also always visible, cluttering what should read as
an audience-facing preview.

## Goal

A single **Dev Mode** toggle at the bottom of the control panel. Off by default
(persisted in `localStorage`). When on, a panel appears directly below it with all
dev controls and data; when off, the normal view is just the experience + the three
control fieldsets.

## Scope

Frontend only — `simulator/static/{index.html,app.js,style.css}`. No API changes:
every datum Dev Mode shows is already available client-side (`/api/clips`,
`/api/ring`, the `/api/alteration` response, and the in-memory preload cache). The
separate `/author.html` remains the place to *edit* labels; Dev Mode is
inspect-and-select only.

## Dev panel contents (top → bottom)

1. **Pool picker** — a `<select>` of the current altitude's pool members
   (`clip_id · title`), its value reflecting the clip currently playing. Choosing a
   member sets `activeClipId` to it and reloads the base media, overriding the random
   landing pick. A `🎲 Re-roll random` button re-runs the server-canonical random
   pick for the current scale. The dropdown is rebuilt on every scale landing, so it
   always lists the current pool; single-clip scales (cosmos/orbit) show one entry.
2. **Active clip** — `id`, `title`, `source`, `license`, `base_file`.
3. **Annotations & affect** — each annotation key with salience (or legacy
   `min_level`), time window (`appear`–`disappear`), and tier count; each affect key
   with `min_level` and tier count. Read-only.
4. **RenderPlan** — the relocated `#readout` (the `/api/alteration` JSON).
5. **Cache & playback** — preload status (`N/total cached`), whether the active
   clip is served **from memory** (a preloaded blob) vs the network, and live video
   resolution / duration / loop position.

## Behavior

- **Pool override is per-landing.** Changing altitude re-rolls randomly as today;
  the operator then picks again from the new scale's pool. The pick is not persisted
  across landings (YAGNI — "select any video while on an altitude").
- **Refresh points.** Pool picker + clip metadata + annotations rebuild whenever the
  scale lands (hooked into `renderScaleReadout()`) and after a manual pick/re-roll.
  The RenderPlan readout updates on each `update()` (knob change) as today. The
  cache/playback stats refresh on a light interval (~500 ms) while Dev Mode is on,
  and the cache count also ticks up as the preloader fills.
- **`pickRandomMember()`** is factored out of `landScale()` so the initial landing,
  ring advances, and the Re-roll button all share the one server-canonical pick.

## Testing

No JS unit harness in the repo and no API change, so verification is: `node --check`
on `app.js`, the Python suite stays green (unaffected), and by-eye review by the
operator (no headless browser yet — §9 E2E policy noted, machinery pending).
