A voice that has to arrive on time
Every guidance line has a deadline measured in metres. "300 metres ahead, turn left" is worth something at 300 metres and nothing at 50. So when Dad ruled on the app's first day that guidance speaks in Pippa's voice, the obvious design was dead before it started: send each line to cwkBellows, the family's voice engine, and wait. That is a paid synthesis for every line, a network round trip from a moving car sitting on the critical path, and silence in a tunnel or on a phone roaming abroad.
His ruling carried its own answer. Cache, and cache by chunks, never whole sentences:
어차피 대부분 겹칠 거야. 아빠가 자주 다니는 곳이거나, 자주 쓰는 표현일수록 쌓일 거고.
Most of it overlaps anyway; the places he drives often and the phrases used most will pile up. A drive's lines recombine a small vocabulary (distances, maneuvers, fixed phrases) plus the names of the roads he drives most. Whole sentences almost never repeat. Their pieces repeat all day. That is architecture invariant 6: speech is Pippa's voice, cached in chunks.
Where a line breaks
Apple's Korean instructions are built from a small grammar, and InstructionChunker cuts along it: a place with "에서", a direction with "방면으로", a road with Apple's particle placeholder "(으)로", a joining clause, and the maneuver at the end. The maneuver and the joining clauses are swapped for the way Pippa says them, so the provider's terse "진입" becomes her "들어가". A Korean name is one chunk per word, because each word recurs on its own. A name in another script stays whole: "Calle de Alcalá" is one name, not three words to read one at a time.
The placeholder needs one more step. A voice would read the brackets in "(으)로" aloud, so the chunker chooses the form from the last sound of the word before it: a Hangul syllable's final consonant, a digit's Sino-Korean name, or the way a Latin letter ends when Korean reads a name ("Sol" ends in ㄹ, "Alarcón" in ㄴ). The same resolution gives the screen its text through readable, which changes nothing else.
A closed vocabulary in Pippa's own words
The phrases live in phrases.ko.json, a localization resource of 155 entries. Keys are stable English; values are Pippa talking to Dad in 반말. route.arrived is "도착했어, 아빠. 수고했어" and camera.slowDown is "아빠, 속도 줄여". Seventy-two maneuver.* keys map a provider's maneuver wording to hers ("완만히 우회전" becomes "살짝 오른쪽으로 가"), and 29 connective.* keys do the same for joining clauses. PhraseBook matches them longest first, so "완만히 우회전" wins over "우회전". A missing key reads as the key itself, so a gap is audible in testing instead of silent.
Invariant 20 pushes the idea as far as the providers allow: every Kakao guide type has one fixed wording and every maneuver kind has words, so on a drive a maneuver is one cached phrase and only a place or road name is synthesized fresh. Walking loosens that a little: a Kakao walking step that ends in "… 이용" (a footbridge, stairs, a lift) is passed through in Kakao's own words, so some of those words are synthesized fresh too. "육교 이용" is one; "엘리베이터 또는 계단 이용" keeps a cached ending and synthesizes "엘리베이터 또는". The closed set covers almost every maneuver, not every one. PromptComposer.closedVocabulary lists everything else any drive can say: the fixed lines, every distance the guidance plan can speak in each of its three forms ("앞에서", "앞", "앞에"), speed limits from 30 to 120, and the door-approach lines. That set is fetched once, the first time the voice is reachable.
From Bellows to the phone, once
BellowsVoice asks for one chunk at a time with profile_id: pippa, target: cache and source: pathfinder. Two of those carry a story in the comment beside them: the default target would play the take on the office speakers, and a source starting with cwkpippa expires after three days. Each chunk is synthesized alone, never stitched to its neighbours, because a take that depends on the words around it can never be reused.
The takes land in ChunkCache on the phone. Bellows keeps a cache too, but by policy only 300 takes for 30 days, and its key moves with every dictionary edit, so the car keeps what it has heard: a 200 MB store keyed by the SHA-256 of the voice id and the text. The voice id is bellows:pippa: plus the profile's current model, so a model change misses the cache instead of replaying an old voice as if it were the new one. Past its budget, the store evicts only road and place chunks, least recently used first. Phrases, distances and numbers are never evicted.
VoicePrefetcher fills it. When guidance starts on a route, and again after every reroute, the composer lists each chunk the route can produce (maneuvers first, because they must be on time, then its cameras, then the closed vocabulary). The cache answers with what is missing. The prefetcher fetches three at a time inside a 6,000-character budget, stops after three failures in a row, and never asks for a chunk already on its way, because every synthesis is a paid call.