Files
MultiRoombaRover/perf/spectate-issues/001-telemetry-fanout-hud-maps.md
2026-06-12 01:01:53 -04:00

6.6 KiB

Issue 001: Telemetry Fan-Out And Always-On HUD Maps

Summary

/spectate renders multiple rover cards at once, and every rover card includes a live HUD with an always-on top-down SVG map. During the 30s throttled audit, the page received 1,354 sensorFrame events and produced more than 5,300 SVG path attribute mutations.

This is the strongest measured /spectate CPU problem.

Severity

High.

This is high-frequency, always-on work. It scales with the number of visible rovers and hits exactly the devices we care about: mobile browsers and weaker computers.

Upstream Likelihood

Medium.

The / telemetry issue will likely help if it introduces a central throttled visual telemetry path. However, /spectate has a unique multiplier: it renders several rover HUDs and several TopDownMap instances at the same time. Even after upstream telemetry throttling, /spectate probably still needs a policy for how many maps update and how often.

Related upstream issue:

perf/issues/004-sensor-telemetry-render-frequency.md

Affected Files

  • webui/src/components/SpectateVideo/index.jsx
  • webui/src/components/HudOverlays/HudOverlay/index.jsx
  • webui/src/components/HudOverlays/HudOverlay/HudMapOverlay.jsx
  • webui/src/components/TopDownMap/TopDownMapContent.jsx
  • webui/src/components/TopDownMap/visuals.jsx
  • webui/src/context/TelemetryContext.jsx
  • webui/src/components/RoverMediaPlayer/index.jsx
  • webui/src/components/HudOverlays/OvercurrentOverlay/index.jsx
  • webui/src/components/HudOverlays/LowBatteryOverlay/index.jsx
  • webui/src/components/HudOverlays/VerticalBatteryOverlay/index.jsx

Evidence

Artifact:

perf/results/2026-06-12T04-55-16-888Z-root-runtime/root-runtime-report.json

30s sample, mobile viewport, 6x CPU throttle:

sensorFrame events:        1,354
sensorFrame bytes:         2,531,090
path d mutations:          2,688
path fill mutations:       2,682
Long tasks:                   81
Worst long task:             375ms
Average frame gap:         113.9ms
p95 frame gap:             233.3ms

The path mutation pattern is the important clue. Normal text/status updates do not mutate thousands of SVG path d and fill attributes. TopDownMap does.

Current Code Path

SpectateVideo mounts a full media and overlay stack for each rover:

webui/src/components/SpectateVideo/index.jsx:17
RoverMediaPlayer

webui/src/components/SpectateVideo/index.jsx:26
HudOverlay variant="spectator"

webui/src/components/SpectateVideo/index.jsx:33
OvercurrentOverlay

webui/src/components/SpectateVideo/index.jsx:35
VerticalBatteryOverlay

HudOverlay subscribes to telemetry:

webui/src/components/HudOverlays/HudOverlay/index.jsx:27
const frame = useTelemetryFrame(effectiveRoverId);

For spectator mode, it forces the top-down map on:

webui/src/components/HudOverlays/HudOverlay/index.jsx:50
variant === 'spectator' ? true : ...

Then HudMapOverlay renders:

webui/src/components/HudOverlays/HudOverlay/HudMapOverlay.jsx:35
<TopDownMap sensors={sensors} size={240} overlay />

There is also duplicated telemetry subscription pressure. RoverMediaPlayer subscribes to telemetry for audio ducking and media state, while HudOverlay, battery overlays, and warning overlays can subscribe for the same rover.

Why This Matters

The raw socket message rate is not automatically a problem. The expensive part is waking multiple React subscribers, recomputing visual sensor geometry, and mutating SVG attributes for every visual update.

On /, there is generally one primary driver view. On /spectate, the same sensor stream can be multiplied across several rover cards.

Fix Strategies

Option A: Add A Throttled Visual Telemetry Channel

This is the most reusable upstream-friendly fix.

Keep TelemetryContext storing the latest frame immediately, but notify visual consumers at a lower cadence:

useTelemetryFrameRaw(roverId)     high-frequency, only where truly needed
useTelemetryFrameVisual(roverId)  throttled display data

Use visual telemetry for:

  • TopDownMap
  • HudOverlay
  • SpectatorTelemetryOverlay
  • battery overlays
  • warning overlays where 100-200ms delay is acceptable

Suggested rates:

desktop visual telemetry: 10-15Hz
mobile visual telemetry:   5-10Hz

Option B: Pass One Telemetry Frame Through SpectateVideo

Right now, each overlay can subscribe independently. Instead, let SpectateVideo subscribe once per rover and pass sensors down:

const frame = useTelemetryFrameVisual(roverId);
const sensors = frame?.sensors ?? null;

<RoverMediaPlayer roverId={roverId} sensors={sensors} />
<HudOverlay roverId={roverId} sensors={sensors} />
<OvercurrentOverlay roverId={roverId} sensors={sensors} />
<LowBatteryOverlay roverId={roverId} sensors={sensors} />
<VerticalBatteryOverlay roverId={roverId} sensors={sensors} />

This may require small prop additions to the warning/battery overlays.

Option C: Make Spectator HUD Maps Adaptive

Spectator maps are currently always on. For low-end/mobile:

  • hide maps by default on portrait mobile
  • update maps at a lower cadence than text warnings
  • only animate the focused/first rover's map
  • show static/minimal maps for secondary rovers
  • make map visibility a spectator setting

Option D: Memoize TopDownMap Geometry

This is useful after throttling. Avoid recalculating path data when the sensor fields that drive a particular path did not change.

Do this after the / telemetry issue if that work touches TelemetryContext.

Best first implementation:

  1. Add useTelemetryFrameVisual(roverId) with a 100-150ms visual notification cadence.
  2. Switch HudOverlay and TopDownMap consumers to visual telemetry.
  3. In SpectateVideo, subscribe once and pass sensors down where practical.
  4. Add a spectator/mobile map policy so only the most important maps update live.

Validation

Run:

CPU_THROTTLE=6 VIEWPORT=390x844 MOBILE=1 SAMPLE_MS=30000 \
  node perf/live-root-runtime-audit.mjs https://rover.otter.land/spectate perf/results

Expected improvements:

  • path d and path fill mutations should drop sharply.
  • ScriptDuration should drop.
  • Long task count should drop.
  • Frame p95/p99 should improve.
  • sensorFrame socket count may stay the same if only rendering is throttled.

Risks

  • Do not throttle control/safety logic that needs immediate data.
  • Keep battery/overcurrent warnings responsive enough to be trusted.
  • If map updates become too slow, operators may perceive the HUD as stale. A 5-10Hz map is usually enough for spectators, but verify by watching the live page.