The moment a line is due
Prefetching is a hope. The moment of truth is VoicePlayer.start, when a line is due and the player asks the cache for it. ChunkCache.line answers with every chunk of the line or with nothing: it walks the chunks in order, and the first one that is not on disk ends the walk with nil. Use is counted only for a line that will actually play.
A full answer goes to playPippa. It decodes each take, trims the silence each one carries at its start and end, and schedules them back to back on one AVAudioPlayerNode, so the joins are sample-accurate and five takes do not stretch one line by a second. A nil sends the whole line to AVSpeechSynthesizer with the ko-KR voice, and the missing chunks are reported so the prefetcher can fetch them for next time. Even a full set can fall through: if one take cannot be read, or two takes disagree on audio format, playPippa returns false and the line goes to the system voice rather than half of it playing.
Why not fill the gap with the system voice?
The tempting optimisation is obvious. Play the four chunks you have in Pippa's voice, and let the system voice say the one road name you do not have. Invariant 6 forbids it in one sentence: a line that is missing any chunk at speak time is spoken whole in the system voice, and two voices never mix inside one line.
The reason is how listening works at 100 km/h. A line is one utterance from one speaker. When the timbre changes halfway, the ear hears a second speaker cutting in, and the attention that should go to "left, in 300 metres" goes to who just spoke. A plain voice all the way through is easier to follow than a lovely voice with a hole in it. Being one voice is part of the message.
The rule also keeps the failure readable. Every line writes one drive-log event: spoke when it was Pippa's voice, voice_fallback when it was not, with the text. Fallbacks that cluster on road names are a prefetch that did not finish; fallbacks on fixed phrases mean the closed vocabulary never arrived. Either way the record says which, and the missing chunks are already on their way.
Priorities and the audio session
Each line carries one of three priorities. An urgent line ("잠시 후" just before a turn, "속도 줄여" at a camera) interrupts anything less urgent. A guidance line waits its turn. An info line ("back on route") is dropped when something is already waiting. The session is .playback in the .voicePrompt mode with .duckOthers and .interruptSpokenAudioAndMixWithOthers: Dad's music lowers under a line and comes back after it, while spoken audio such as a podcast pauses instead of talking over her.
The player asserts that session before every line, because a phone call or Pippa's talk may have taken it since the last one; a flag that only remembered "activated once" left the engine refusing to start after a call. A call that interrupts a line loses that line, and a media-server reset rebuilds the engine and its node. Dad picks the mode in Settings: Pippa's voice, the system voice, or silent. A simulated drive is always silent, so a test route never speaks and never spends a synthesis.
The same rule one level up: whole trips
Abroad, Google's Navigation SDK guides its own routes and Pathfinder follows its turn feed. By default Pippa still speaks each turn in her own words, built from the maneuver's shape and distance, without the road names Google's terms keep to its own voice. A setting, off by default, hands the turns to Google's voice instead, road names and all, and then Pippa says nothing over it. If Google's turn feed never arrives, no line of hers can be made, so Google's own voice takes the trip and the screen and the drive log say why.
Google's voice plays outside Pathfinder's player, so the app tells it the three things that silence the player: Dad's mute, a talk with Pippa holding the microphone, and the last stretch to a large building's door, where the provider's turns belong to the wrong level. The rule scales from a line to a trip: a trip is never guided by two voices, and never by none without saying so.