A walking step that describes the road, not the turn
In Korea a walk can be routed by Kakao, and Kakao's route reaches the phone through cwkPippa's maps door, which keeps the key on the server and hands the phone a route in Pathfinder's own shape. Pathfinder's engine then guides it, so the route has to say what every guided route says: at this point on the line, this maneuver. Invariant 3 is the contract. A step's instruction is the maneuver at the end of the step, and each provider translates its own convention into NavStep.maneuverAlong before guidance ever sees it.
Kakao's walking steps do not speak that way. They describe the stretch you are about to walk. "122m 이동(세종대로20길)" means walk 122 m along that road. "루쏘소까지 횡단보도 이용" means use the crossing as far as a landmark. A side, left or right, appears in the words only at a fork: "퀸프라자 앞에서 오른쪽길로 39m 이동". So where a step begins is where something happens, and very often the words do not say what.
The clearest case was measured on a real Kakao answer: a step whose words said only "…까지 31m 이동", walk 31 m to a junction, while the line itself turned by -88° at that step's start. Left, nearly square. After a crossing, the turn onto the far pavement lives in the geometry alone. A voice that read only Kakao's words would say nothing at that corner, and a person who missed it would walk on.
Words first, then the line
kakao_walk_words in cwkPippa's pathfinder_maps.py rebuilds the maneuver at the start of each step. It parses Kakao's words with one grammar: an optional where-clause ("세종대로사거리에서 "), an optional destination ("…까지"), an optional fork, and what the stretch is ("31m 이동", "횡단보도 이용", "역사 내 이동", stairs or a footbridge "… 이용"). If the words name a fork, that is the side. If they do not, the side comes from the line: turn_at measures how far the route turns at the point where the step begins.
turn_at looks 10 m back and 10 m ahead from that point and compares the two bearings, so a jog in the digitizing a few metres long is not read as a turn. Its answer is signed: left is negative, right is positive, wrapped into -180…180. Then the thresholds decide. A turn between 45° and 150° gets a side. Under 45° the path is bending, not turning. Over 150° the line is doubling back on itself, and a reversal is not a side to turn to.
| Kakao's step | Turn at its start | What Pippa's route says |
|---|---|---|
| a crossing | -91° | 왼쪽 횡단보도 건너기 (turnLeft) |
| a crossing, with a where-clause | 88° | …에서 오른쪽 횡단보도 건너기 (turnRight) |
| a crossing | 3° | …에서 횡단보도 건너기 (straight) |
| a stretch on a named road | 80° | {road}(으)로 우회전 |
| a stretch on a named road | 0° | nothing: the walk just goes on |
Those rows are the server's own test table. Stairs, a footbridge, an underpass or a lift keep Kakao's words exactly, because nothing better can be said about them. A station exit becomes "3번 출구로 나가서 …". Inside a station and at its exit the line is only a sketch, so there the side comes from Kakao's words or not at all. And nothing is said at the route's first point: the phone speaks a maneuver only when there is a way to it.
The crossing that turned left by itself
The first version of turn_at looked its 10 m ahead wherever that led. A second read of the code found the flaw. A crossing shorter than 10 m has its own corner at each end. Reading 10 m ahead from the near kerb went past the far kerb and picked up the next corner's turn, and a crossing straight ahead came out as "왼쪽 횡단보도", a left crossing. The fix gives turn_at a floor and a ceiling: it never looks past the neighbouring steps' starts. Each step's turn is measured inside its own step.
From the server's words to Pippa's voice
Most of the words the server writes come from a closed set. On the phone, phrases.ko.json maps each to how Pippa says it: "왼쪽 횡단보도 건너기" becomes "왼쪽 횡단보도 건너", in her 반말, as one cached chunk, and "계단 이용" becomes "계단으로 가". The set has one open edge, and it is the pass-through above: a wording the server keeps verbatim reaches the phone whether or not the phrase book knows it. "육교 이용" has no entry, so the voice synthesizes "육교" and "이용" fresh, word by word. "엘리베이터 또는 계단 이용" gets its cached "계단으로 가" ending but synthesizes "엘리베이터 또는" fresh. So a road or landmark name is synthesized fresh, and so is any Kakao wording for stairs, a footbridge, an underpass or a lift that the phrase book has not learned yet. The server also attaches a kind to every maneuver (turnLeft, turnRight, straight, other), so the arrow on the banner and the tap on the wrist agree with the words.
The architecture records the limit honestly: turns are read only at step boundaries, so a bend inside one step is not announced. Kakao's step boundaries usually fall at junctions and crossings, where a walker needs them most, but a long step through a park that curves is walked in silence.