Symptom
Activating the `autospin` plugin (`:Toggle autospin` in the palette) saturates a CPU core at 100 %. Visually it works — the camera rotates smoothly — but the cost is way out of proportion to what's actually happening on screen.
Repro
- Launch `ttymap`.
- `:` → "Toggle autospin".
- Observe `top`: ttymap process pinned at ~100 % of one core.
Same shape of cost will hit any plugin that drives continuous viewport changes: a cinematic tour scripted via `ttymap.director`, a follow-marker camera in a future tracker plugin, a "scrub through time" UI, etc. `autospin` is just the most pathological case (no human-cadence dampening).
Cost model (hypothesis)
`autospin` calls `ttymap.map:jump(lon, lat)` from `on_tick` at the engine's tick rate (~30 Hz). Each jump enqueues `UserCommand::Map(Jump)` → dispatch → render thread receives a fresh `Draw { viewport, overlays }` → full re-render:
- `Viewport` → visible-tiles set
- per tile, R-tree query for in-view features
- draw fills / lines, then sorted symbols → Braille canvas
- composed into `MapFrame` → ratatui paint
That's ~30 full re-renders per second for what's effectively a 0.3° pan between consecutive frames. Most of the visible content is identical frame-to-frame, but the engine doesn't notice — it redoes the whole pipeline.
Possible optimization avenues (not committed to any of these — profile first)
- Viewport-delta caching: when the centre shift between two consecutive Draws is small enough that most pixels overlap, reuse the previous `MapFrame` shifted by the delta and only redraw the newly-visible margin.
- Draw debounce on the render thread: if a newer `Draw` arrives while one is in flight, drop the older one. We'd render at whatever frequency the render thread can sustain, never the input frequency. (Probably already-true for tile fetch, but worth confirming for the full pipeline.)
- Identify the actual hot spot: profile a release-mode `autospin` run with `cargo flamegraph` / `perf` and look at the top frames. Could be Braille canvas, earcut polygon fill, R-tree, channel/IPC overhead, or ratatui paint — different causes have different fixes.
Why this is worth fixing
ttymap's positioning is "scriptable globe" — plugins that drive the camera continuously (animation libraries, cinematic tours, follow-camera trackers) are core to the value prop. The current cost model makes "any plugin that touches the viewport per-frame" a CPU footgun. Fixing this once at the engine layer unlocks the whole class of plugins.
Workaround for now
The `autospin` plugin keeps the per-tick design — the user feedback was that throttling makes the spin choppy enough that the CPU savings aren't worth it. So this issue stands as the real fix.
Symptom
Activating the `autospin` plugin (`:Toggle autospin` in the palette) saturates a CPU core at 100 %. Visually it works — the camera rotates smoothly — but the cost is way out of proportion to what's actually happening on screen.
Repro
Same shape of cost will hit any plugin that drives continuous viewport changes: a cinematic tour scripted via `ttymap.director`, a follow-marker camera in a future tracker plugin, a "scrub through time" UI, etc. `autospin` is just the most pathological case (no human-cadence dampening).
Cost model (hypothesis)
`autospin` calls `ttymap.map:jump(lon, lat)` from `on_tick` at the engine's tick rate (~30 Hz). Each jump enqueues `UserCommand::Map(Jump)` → dispatch → render thread receives a fresh `Draw { viewport, overlays }` → full re-render:
That's ~30 full re-renders per second for what's effectively a 0.3° pan between consecutive frames. Most of the visible content is identical frame-to-frame, but the engine doesn't notice — it redoes the whole pipeline.
Possible optimization avenues (not committed to any of these — profile first)
Why this is worth fixing
ttymap's positioning is "scriptable globe" — plugins that drive the camera continuously (animation libraries, cinematic tours, follow-camera trackers) are core to the value prop. The current cost model makes "any plugin that touches the viewport per-frame" a CPU footgun. Fixing this once at the engine layer unlocks the whole class of plugins.
Workaround for now
The `autospin` plugin keeps the per-tick design — the user feedback was that throttling makes the spin choppy enough that the CPU savings aren't worth it. So this issue stands as the real fix.