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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kj58Jugkic783b8ZrtTVZ
This commit is contained in:
2026-09-04 15:23:08 +09:00
parent 102cf38082
commit 574df1471c
4 changed files with 75 additions and 62 deletions
+32 -33
View File
@@ -91,8 +91,8 @@ rather than duplicated per client.
### Swappable providers ### Swappable providers
The same shape has now been chosen independently three times, so it is the A component with an expensive or not-yet-existing dependency puts it behind an
project's idiom rather than three coincidences: interface, with a trivial implementation alongside the real one:
| Component | Interface | Real | Trivial | | 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 Because every display is a bus subscriber, adding, removing or swapping one
touches no acquisition code. 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 A screen driven over DP or HDMI is viable when it is **permanently installed and
SPI/DP/HDMI, rendered by a local process. It is gone, and with it `app-hud`. 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 | Supply | Available | A panel needs |
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 | DisplayPort `DP_PWR` | 3.3 V / 500 mA, and VESA cables leave it unconnected at the sink | 5–15 W |
screen always needs its own power feed, which is the cable the removable display | HDMI +5 V | 55 mA | |
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.
Video out is what generated the enclosure, power and cabling complexity this DSI is unavailable on this carrier board — the module carries it on SODIMM pins,
project started out trying to solve. Removing it removed the problem rather than but the developer kit does not break them out, so DSI needs a custom carrier.
solving it.
## Navigation ## 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. next manoeuvre, retained, and a display connecting mid-drive renders immediately.
Route *requests* are request/response and fit MQTT badly. Displays call the Route *requests* are request/response and fit MQTT badly. Displays call the
Valhalla HTTP API through `telemetry-gw` instead of inventing a request/reply Valhalla HTTP API through `svc-gw`. Recorded as decision 9.
topic pair. Recorded as decision 9.
### Map data is not source ### 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. | | **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. | | **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 `ghcr.io/valhalla/valhalla` publishes `linux/arm64` and runs natively on
2026-09-04: `ghcr.io/valhalla/valhalla` publishes a `linux/arm64` manifest, the L4T R36.4.7. Measured:
image runs natively, and the full pipeline works end to end — config, timezones,
admins, tiles, and a served route.
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 Japanese street names survive intact in the route narrative.
back to the tile directory, which is the form decision 10 selected anyway.
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 ## 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 19 | A service mocks itself behind its own input interface; there is one implementation of each contract | Accepted | 2026-09-04 |
| 20 | Video out (Class A) dropped; every display is data-only. `app-hud` cancelled | 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 |
+11 -14
View File
@@ -44,9 +44,9 @@ have taken their share.
Four rules follow. Four rules follow.
**The cloud LLM is load-bearing.** [OVERVIEW.md](../OVERVIEW.md) puts reasoning in **Reasoning runs in the cloud.** The local voice stack fits only because the LLM
the cloud. That decision is what makes a local voice stack fit at all — moving the is not resident. An on-device LLM and the voice stack do not coexist in this
LLM on-device collapses this budget entirely. It is not a free choice to revisit. budget.
**Size Whisper by vocabulary, not by benchmark.** In-car commands are a narrow, **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 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 appends as more arrive, and finishes on the terminator — which is what Android's
`AudioTrack` streaming mode does natively. `AudioTrack` streaming mode does natively.
**What must not be built:** an utterance as a single response body with a ⚠ **Never ship an utterance as a single response body with a "download, then
"download the file, then play the file" player. That works today and forecloses play" player.** It works, and it forecloses streaming permanently — the receiver
streaming permanently. Whether the TTS we land on streams is still open, so the has no way to begin before the last byte. The chunk boundary is what keeps both
transport must not assume either way. TTS modes available.
### The transport is testable with no real TTS ### 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 ### Ducking is the tablet's job
Android's `AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK` ducks other applications' audio — 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 Spotify included — for the duration of a request, then restores it. Cross-
mechanism every navigation app already uses, and it is less work than the PipeWire application ducking is native on the tablet.
policy it replaces. Cross-application ducking is a solved problem on the tablet
and would have been a bespoke one on the Orin.
### The microphone stays on the Orin ### 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 speak. The voice stack expires or drops pending utterances rather than queueing
them unboundedly or blocking on a sink that may never arrive. 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 **The tablet is required for any audio at all** — it is both the media source and
tolerable because unplugging it also removes the music, so the tablet was already the speech sink.
load-bearing for audio. Speech does not make it more so.
+32 -12
View File
@@ -115,10 +115,8 @@ it records the investigation and the dead ends, not just the final commands.
### Scaffolding conventions ### Scaffolding conventions
No template repository. Template instantiation is fork-at-create, so it could not Component repos are scaffolded by hand. A template repo is for carrying a real
reach repos that already exist, and the scaffolding is a handful of markdown files toolchain, which is the ESP32 case above.
— 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.
Every component repo carries: Every component repo carries:
@@ -131,6 +129,33 @@ docs/impl/reports/
docs/BOM.md only where the repo owns hardware 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 ### Front matter
Every markdown document carries front matter, with **no exceptions for the hub or 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`. `status` is one of `active`, `superseded` or `done`.
The extra fields exist for one reason. This document already observes that **plans `status` and `updated` make a plan's staleness visible — plans go stale and
go stale and reports do not** — but nothing made staleness visible, so a plan reports do not, and without them a plan from months ago reads identically to one
written months ago looked identical to one written this morning. `status` and written this morning. `date` on a report records the moment it describes.
`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.
## Naming ## Naming
-3
View File
@@ -59,9 +59,6 @@ Order chosen by feedback speed, not ambition.
quickest to iterate on, and is the only display in current scope quickest to iterate on, and is the only display in current scope
- [ ] `app-esp32` — RLCD/e-ink glanceable subset. Deferred, no hardware in 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* ## Phase 5 — Camera and inference · *separate task*
Deliberately deferred. It is the reason the compute is a Jetson rather than a Deliberately deferred. It is the reason the compute is a Jetson rather than a