The canonical child is a thin translator
AppleMapsProvider is the first and canonical child of RouteProvider, and Dad picked it for plain reasons: no key, no cost, and his own driving found Apple's routing in Korea about as good as the domestic maps. It lives in its own package target, PathfinderAppleMaps, which is the only place in the package that imports MapKit and Core Location. Its whole job is translation. MapKit types come in; Place and NavRoute go out. The child pays the translation cost, so nothing downstream ever sees an MKRoute.
Routes: one request, the drive's own options
A route request is an MKDirections.Request built from the trip's RouteOptions:
transportTypecomes fromTravelMode:.walkingfor a walk,.automobileotherwise.requestsAlternateRoutesfollowsoptions.alternatives. The preview asks for alternatives; a reroute asks for one.- Toll and highway preferences are set only when driving. A footpath has no toll to avoid.
departureDateis now, so traffic counts.
A thrown error becomes RouteProviderError.unavailable, an empty answer becomes .noRoute, and every route that comes back is stamped with the mode it was asked in. That stamp matters more than it looks: lesson five shows a walk that once came back from a reroute as a driving route. travelTime builds the same request and calls calculateETA(), which is all the mid-drive ETA refresh needs.
One parameter goes unused. The root's routes takes a heading "so a reroute does not begin with a U-turn the car cannot make", but an MKDirections.Request has nowhere to put one. The architecture lists it as a known limit instead of faking it. A child is allowed to be honest about what its SDK cannot do.
Asking Apple for Korean
MapKit writes route names, map labels and step instructions in the app process's language, not the bundle's. Measured on an English simulator, Apple answered "Jayu-ro" and "Turn left onto Jeongbalsan-ro". With Korean asked for, the same request gave "자유로" and "백마로(으)로 우회전". Pippa's voice and the chunker that splits lines into cached pieces are Korean, so English step words would be read a word at a time in the wrong language.
The first fix was PathfinderApp.preferKorean, which put ko-KR first in the app's AppleLanguages default. The code has moved on since: commit a1729c1 replaced it with AppLanguage.apply, called from the app's init, plus a map-language setting. Korean comes first by default. With the local option, the country's own language goes first and Korean second, so the map speaks the country's language while Pathfinder's own words still resolve to Korean (the bundle localizes Korean only). Either way it takes effect from the next launch, because iOS reads AppleLanguages when the process starts. The architecture doc and the repo's house file still say preferKorean; the code says AppLanguage, and the code is what runs.
One more Korean wrinkle: Apple writes "(으)로" when it will not pick the particle for a road name. The screen resolves it the same way the voice does, through InstructionChunker.readable.
When MapKit said "not found" in Hangul
On Dad's phone, on-device MKLocalSearch answered every Hangul query with MKErrorDomain 4 (GEOError -8): 백석역, 온봄의원, 한국외국어대학교, all with a correct centre, while English queries worked. The simulator did the same. Apple's Maps Server API, asked from cwkPippa, found every one. So the app grew a door: GET /api/location/search?q=&lat=&lon= on cwkPippa, which runs the Server API search for the phone (commit 371baa9).
The door is a first try, never a dependency. It is used only when the selected stack is Apple, Dad's PIN is on the phone, and nothing is being simulated. An empty or failed answer falls straight through to on-device MapKit. Its places come back tagged apple, because they are Apple's data either way. It is not magic, either: it still misses abbreviations ("한국외대" found nothing, and neither did "hufs" nearby).
Inside the child, MapKit's own "no results" is not treated as a failure. MKError.placemarkNotFound, which is MKErrorDomain 4, is caught and returned as an empty list. "Nothing matched" is an answer, and it must not look like an outage that would hide the next fallback.
A hint, then a fence
Todo #300 found the other half of bad search: results that were real places, just absurdly far away. A search sends a 30 km region around Dad as a hint, which keeps a far name far (부산역 and 에버랜드 should still answer). But a generic word got read as a far place: "hospital" by Ilsan answered one 9,614 km away, "병원" a village 154 km off, "편의점" by Kyoto Station a shop in Laos.
So when nothing in the hint's answer lies within NearbySearch.reach (100 km), the child asks the same region again with regionPriority = .required (iOS 18 and later): a fence. The two answers merge in NearbySearch.merge. A far place in Dad's own country keeps the top (해운대 really is Haeundae), the nearby places follow nearest first, and a far place in another country goes last. Dad's country is the one most of the nearby places share. Without a fence, a last guard drops anything beyond 300 km whenever something closer exists ("hufs" around Ilsan once answered Hays, Texas). cwkPippa's door runs the same rule in Python, apple_maps.search_near, with the same 100 km reach.
AppLanguage.apply runs in the app's init. And the Apple child is built with nothing (AppleMapsProvider()), so it holds neither the link to cwkPippa nor Dad's PIN; that is why AppModel asks selected.id == "apple" and tries the door before the child. It is the one search branch on a provider's name, and it stands in the code in plain sight. A leak you can point at is cheaper than one you pretend away.