From 574df1471c0a6deff808fb9fae0e80326662c1e2 Mon Sep 17 00:00:00 2001 From: Mikkeli Date: Fri, 4 Sep 2026 15:23:08 +0900 Subject: [PATCH] Rewrite reference docs as current state; record the style rule Reference, guidelines and plan documents describe what is and what will be. Retirement stories, resolved-risk narration and against-the-alternative argument moved out or were cut; reports keep that material. What survives the cut is the mechanism that can bite again (driving side is a country-level property) and the condition that would change a decision (a permanently installed, car-powered panel), both stated generally. Also removes a duplicated Japan section from a bad edit. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013kj58Jugkic783b8ZrtTVZ --- docs/ARCHITECTURE.md | 65 ++++++++++++++++++++++---------------------- docs/ORIN-RUNTIME.md | 25 ++++++++--------- docs/REPOS.md | 44 ++++++++++++++++++++++-------- docs/ROADMAP.md | 3 -- 4 files changed, 75 insertions(+), 62 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b0601b9..1d19105 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -91,8 +91,8 @@ rather than duplicated per client. ### Swappable providers -The same shape has now been chosen independently three times, so it is the -project's idiom rather than three coincidences: +A component with an expensive or not-yet-existing dependency puts it behind an +interface, with a trivial implementation alongside the real one: | Component | Interface | Real | Trivial | |---|---|---|---| @@ -194,27 +194,22 @@ owns the boundary. For transports without IP — BT SPP, plain USB serial — Because every display is a bus subscriber, adding, removing or swapping one touches no acquisition code. -### Video out was dropped +### Directly-driven screens -An earlier revision had a second class: a screen driven directly by the Orin over -SPI/DP/HDMI, rendered by a local process. It is gone, and with it `app-hud`. +A screen driven over DP or HDMI is viable when it is **permanently installed and +powered by the car**. A dash-integrated panel qualifies: plug in DP and it is a +screen, and it adds a renderer without touching the contract, since the bus does +not care what draws it. -The reasoning, in order: +It is not viable for a removable display unit, which would need a second cable: -- **No connector-powered display exists.** DisplayPort's `DP_PWR` supplies 3.3 V - at 500 mA — and VESA-compliant cables deliberately leave it unconnected at the - sink. HDMI's +5 V pin gives 55 mA. A panel needs 5–15 W. So a directly-driven - screen always needs its own power feed, which is the cable the removable display - unit was meant to avoid. -- **DSI is not available.** The Orin Nano module carries DSI on SODIMM pins, but - the developer kit's carrier board does not break them out; DisplayPort is its - only display output. DSI would require a custom carrier board. -- **It bought nothing the browser-shaped path does not.** A data-only client on - one USB cable is simpler, and the display unit stays swappable. +| Supply | Available | A panel needs | +|---|---|---| +| DisplayPort `DP_PWR` | 3.3 V / 500 mA, and VESA cables leave it unconnected at the sink | 5–15 W | +| HDMI +5 V | 55 mA | | -Video out is what generated the enclosure, power and cabling complexity this -project started out trying to solve. Removing it removed the problem rather than -solving it. +DSI is unavailable on this carrier board — the module carries it on SODIMM pins, +but the developer kit does not break them out, so DSI needs a custom carrier. ## Navigation @@ -252,8 +247,7 @@ Guidance is state, so it belongs on the bus: `yukart/nav/guidance` carries the next manoeuvre, retained, and a display connecting mid-drive renders immediately. Route *requests* are request/response and fit MQTT badly. Displays call the -Valhalla HTTP API through `telemetry-gw` instead of inventing a request/reply -topic pair. Recorded as decision 9. +Valhalla HTTP API through `svc-gw`. Recorded as decision 9. ### Map data is not source @@ -287,18 +281,23 @@ checksum at both ends. | **CJK labels** | MapLibre cannot ship Japanese glyph sets as SDF ranges — far too large. Set `localIdeographFontFamily` so the browser renders CJK from on-device fonts. Protomaps tiles also carry `name:en`, so romanised labels are an option. | | **Addressing** | Japanese addresses are block-based, not street-based. Valhalla does not geocode at all, so destination search is a separate problem — do not assume a western geocoder transfers. | -### arm64 — resolved +### Verified on the Jetson -No longer a risk. Verified on `mikkeli-orin-nano-2` (L4T R36.4.7, JetPack 6) on -2026-09-04: `ghcr.io/valhalla/valhalla` publishes a `linux/arm64` manifest, the -image runs natively, and the full pipeline works end to end — config, timezones, -admins, tiles, and a served route. +`ghcr.io/valhalla/valhalla` publishes `linux/arm64` and runs natively on +L4T R36.4.7. Measured: -Measured: Shikoku, 49 MB PBF → 149 MB of routing tiles; Takamatsu → Matsuyama -routed in 157 km / 112 min with Japanese street names intact in the narrative. +| | | +|---|---| +| Shikoku PBF → routing tiles | 49 MB → 149 MB | +| Whole-Japan PBF | 2.4 GB | +| Admin DB, whole Japan | 17 MB | +| Timezone DB | 116 MB | +| Takamatsu → Matsuyama | 157 km, 112 min | -The server logs a benign `tiles.tar No such file or directory` warning and falls -back to the tile directory, which is the form decision 10 selected anyway. +Japanese street names survive intact in the route narrative. + +The server logs `tiles.tar No such file or directory` at startup and falls back to +the tile directory, which is the form decision 10 selects. Not an error. ## Decisions @@ -318,12 +317,12 @@ rewriting the document. | 9 | Nav guidance published on the bus; route requests over HTTP via `telemetry-gw` | **Proposed** | 2026-09-03 | | 10 | Map artifacts delivered by `rsync` over LAN only; Valhalla tiles kept as a directory, not a tar | Accepted | 2026-09-03 | | 11 | Boot ordering is capture-first: acquisition reaches ready before serving or voice | Accepted | 2026-09-03 | -| 12 | The **tablet** owns the car's aux input; TTS crosses as chunked utterances, streaming-capable | Accepted | 2026-09-04 | +| 12 | The tablet owns the car's aux input; TTS crosses as chunked utterances, streaming-capable | Accepted | 2026-09-04 | | 13 | Reasoning stays in the cloud — a local LLM does not fit beside the voice stack | Accepted | 2026-09-03 | | 14 | Recordings are bounded with rotation | Accepted | 2026-09-04 | | 15 | Boundary is two simplex channels — lossy stream down, acknowledged request/response up | Accepted | 2026-09-03 | | 16 | Cloud links are outbound-initiated only; nothing calls in | Accepted | 2026-09-03 | | 17 | `svc-gw` combines local hub and app gateway; no client may block the hub | Accepted | 2026-09-03 | | 18 | Stubs publish the real topic and schema, never a fixture-specific one | Accepted | 2026-09-03 | -| 19 | Services mock themselves behind their own input interface; no separate stub implementations exist | Accepted | 2026-09-04 | -| 20 | Video out (Class A) dropped; every display is data-only. `app-hud` cancelled | Accepted | 2026-09-04 | +| 19 | A service mocks itself behind its own input interface; there is one implementation of each contract | Accepted | 2026-09-04 | +| 20 | Every display is data-only. Directly-driven screens are for permanently installed, car-powered panels only | Accepted | 2026-09-04 | diff --git a/docs/ORIN-RUNTIME.md b/docs/ORIN-RUNTIME.md index faeb5b3..5af8007 100644 --- a/docs/ORIN-RUNTIME.md +++ b/docs/ORIN-RUNTIME.md @@ -44,9 +44,9 @@ have taken their share. Four rules follow. -**The cloud LLM is load-bearing.** [OVERVIEW.md](../OVERVIEW.md) puts reasoning in -the cloud. That decision is what makes a local voice stack fit at all — moving the -LLM on-device collapses this budget entirely. It is not a free choice to revisit. +**Reasoning runs in the cloud.** The local voice stack fits only because the LLM +is not resident. An on-device LLM and the voice stack do not coexist in this +budget. **Size Whisper by vocabulary, not by benchmark.** In-car commands are a narrow, predictable set. `large` buys accuracy this application cannot spend, at a cost @@ -156,10 +156,10 @@ Same protocol, same receiver. The receiver starts playback on the first chunk, appends as more arrive, and finishes on the terminator — which is what Android's `AudioTrack` streaming mode does natively. -**What must not be built:** an utterance as a single response body with a -"download the file, then play the file" player. That works today and forecloses -streaming permanently. Whether the TTS we land on streams is still open, so the -transport must not assume either way. +⚠ **Never ship an utterance as a single response body with a "download, then +play" player.** It works, and it forecloses streaming permanently — the receiver +has no way to begin before the last byte. The chunk boundary is what keeps both +TTS modes available. ### The transport is testable with no real TTS @@ -171,10 +171,8 @@ completely. Voice quality is irrelevant to every one of those. ### Ducking is the tablet's job Android's `AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK` ducks other applications' audio — -Spotify included — for the duration of a request, then restores it. This is the -mechanism every navigation app already uses, and it is less work than the PipeWire -policy it replaces. Cross-application ducking is a solved problem on the tablet -and would have been a bespoke one on the Orin. +Spotify included — for the duration of a request, then restores it. Cross- +application ducking is native on the tablet. ### The microphone stays on the Orin @@ -188,6 +186,5 @@ With no tablet connected the Orin can still hear — the mic is local — but ca speak. The voice stack expires or drops pending utterances rather than queueing them unboundedly or blocking on a sink that may never arrive. -The consequence to accept: **unplug the tablet and the car has no voice.** That is -tolerable because unplugging it also removes the music, so the tablet was already -load-bearing for audio. Speech does not make it more so. +**The tablet is required for any audio at all** — it is both the media source and +the speech sink. diff --git a/docs/REPOS.md b/docs/REPOS.md index 885b075..7bf9508 100644 --- a/docs/REPOS.md +++ b/docs/REPOS.md @@ -115,10 +115,8 @@ it records the investigation and the dead ends, not just the final commands. ### Scaffolding conventions -No template repository. Template instantiation is fork-at-create, so it could not -reach repos that already exist, and the scaffolding is a handful of markdown files -— lighter than the machinery to manage it. A template earns its place where there -is a real toolchain to carry, which is the ESP32 case above. +Component repos are scaffolded by hand. A template repo is for carrying a real +toolchain, which is the ESP32 case above. Every component repo carries: @@ -131,6 +129,33 @@ docs/impl/reports/ docs/BOM.md only where the repo owns hardware ``` +### Writing style + +Documents describe **what is and what will be**. They are not changelogs — git +holds the sequence. + +| Instead of | Write | +|---|---| +| "X was dropped because Y" | what the design **is** | +| "this was a risk, now resolved" | what runs, and the measurements | +| "the earlier decision assumed Z" | the current constraint | +| "X was rejected in favour of Y" | Y, stated as the rule | + +⚠ **A sentence justifying a current state by a current reason becomes false when +the reason changes, and nothing announces it** — it stays, confident and +well-written, and reads as fact. + +Two things survive the cut, because they stay true when their subject is gone: + +- **A mechanism that can bite again.** Not "we picked the wrong extract once", but + "driving side is a country-level property, so a clipped extract routes mirrored". +- **A condition that would change the decision**, stated generally — "a + permanently installed, car-powered panel" rather than a story about a panel that + was considered. + +Reports under `impl/reports/` are the exception and the place for all of it: the +investigation, the dead ends, what was measured and found wrong. + ### Front matter Every markdown document carries front matter, with **no exceptions for the hub or @@ -145,14 +170,9 @@ for any particular repo**. The `type` discriminates: `status` is one of `active`, `superseded` or `done`. -The extra fields exist for one reason. This document already observes that **plans -go stale and reports do not** — but nothing made staleness visible, so a plan -written months ago looked identical to one written this morning. `status` and -`updated` fix that; `date` on a report records the moment it describes, which is -the whole value of a report. - -A `type` with one possible value would be decoration. This one has four and they -are worth grepping for. +`status` and `updated` make a plan's staleness visible — plans go stale and +reports do not, and without them a plan from months ago reads identically to one +written this morning. `date` on a report records the moment it describes. ## Naming diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 0bd8e15..3d11ee4 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -59,9 +59,6 @@ Order chosen by feedback speed, not ambition. quickest to iterate on, and is the only display in current scope - [ ] `app-esp32` — RLCD/e-ink glanceable subset. Deferred, no hardware in scope -Video out was dropped along with `app-hud`; see the display contract in -[ARCHITECTURE.md](ARCHITECTURE.md#display-contract). - ## Phase 5 — Camera and inference · *separate task* Deliberately deferred. It is the reason the compute is a Jetson rather than a