Files
app-flutter/docs/impl/plans/INITIAL.md
T
mikkeli 77952cb6c1 Adopt designed front matter: typed, with staleness on plans
Replaces `type: default` — one value across every document, read by nothing,
so it discriminated nothing. Types are now reference, guidelines, plan and
report, applied to every markdown file with no exceptions for the hub.

Plans carry status and updated, reports carry date. REPOS.md already noted
that plans go stale and reports do not, but nothing made that visible: a plan
from months ago looked identical to one written today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kj58Jugkic783b8ZrtTVZ
2026-09-04 14:14:17 +09:00

72 lines
3.0 KiB
Markdown

---
type: plan
status: active
updated: 2026-09-04
---
# Initial plan
## Order
1. MQTT client, subscribing to the retained vehicle topics. Text readout only —
prove the contract before drawing anything.
2. Gauges.
3. Moving map: MapLibre against the PMTiles basemap served by the Orin, with the
position from `yukart/gnss/fix`. No routing involved.
4. Guidance from `yukart/nav/guidance`, once [svc-nav](gitea:yukart/svc-nav) publishes it.
A moving map with position, heading and trace is most of the value in a car and
needs no routing engine at all. Routing is the expensive last fraction.
## Open
- **Transport.** Over USB the tablet has an IP link via RNDIS, so it can reach the
broker directly. Whether it should, or should go through `svc-gw`, is decided in
that repo — see its initial plan.
- **CJK labels.** MapLibre cannot ship Japanese glyph sets as SDF ranges. Local
font rendering must be configured, or labels will be slow or absent.
- **Sunlight.** A typical tablet is unreadable through a windscreen in daylight.
This is a hardware problem, but the UI should not make it worse.
## The boundary, from this side
Two channels with different guarantees, not one socket:
| | Down (state) | Up (commands) |
|---|---|---|
| Transport | push stream | request/response |
| Loss | tolerated; next sample supersedes | **not** tolerated; retry until acknowledged |
| On reconnect | expect current state immediately | resend anything pending |
This client always initiates and owns reconnection — the Orin never dials out.
Assume the link drops constantly: cable knocked, screen off, WiFi lost.
## Session marking is worth building early
A button that stamps an event (`indicator on`, `cold start`, `hard braking`) into
the recording, on the same clock as the CAN frames, turns the Phase 1 manual event
log from a notebook-and-wristwatch exercise into structured data. It is what makes
a raw capture decodable later, and it is a Phase 1 capability that happens to live
in this repo.
## Mock data must look like mock data
`svc-gw` can synthesise vehicle state without any real readings, which is how this
app is developed with no Jetson present. The stream carries a flag when it does.
Rendering synthetic readings indistinguishable from real ones, on a screen in a
moving car, is a safety problem. The indication belongs on the display surface
itself, not in a settings page or a startup log.
## This app is the audio sink
The tablet owns the car's aux input and is the music player, so the Orin's speech
has to arrive here to be heard.
- TTS arrives as **discrete utterances** over the reliable channel, not as a
real-time stream. Receive the whole thing, then play it — no jitter buffer.
- Duck other audio with `AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK` for the duration of
the utterance, then release. That is what makes Spotify drop under the voice
without either side coordinating.
- The microphone stays on the Orin. This app never captures audio; ASR input never
crosses the boundary.