RE paperStrong evidence

Reverse engineering paper #10 — roundabout icons are angle-indexed, not ordinal

The ROUNDABOUT_EXIT_1..18 icon ids are not exit numbers. The unit picks them by exit angle, in eight 45-degree buckets.

BYD LabVerified 5 min read

90° 180° 270° 0° 61 17 62 18 63 64 19 16 entry eight 45° buckets, indexed by exit angle id 16 is named ROUNDABOUT_EXIT_1 and it draws a left exit send it for “take the first exit” and the arrow points the wrong way

Our own app. Sealion 7 Pilot is developed by BYD Lab. It is held to the same standards as every other app on this site — read with that in mind.

Tested on

Vehicle
BYD Sealion 7 · Morocco
Head-unit version
51.1.4.2606220.1
Android
11
Verified

Every frame of the cluster’s native turn card carries a maneuver icon id (paper #9 is the endpoint those frames travel on). Eighteen of the ids are named ROUNDABOUT_EXIT_1 through ROUNDABOUT_EXIT_18, which reads like an instruction: send the exit number. It is the wrong reading, and it draws the wrong arrow.

Question#

Are the cluster’s ROUNDABOUT_EXIT_1..18 maneuver icon ids indexed by exit number?

Environment#

Decompiled read-only with jadx 1.5.5 from APKs pulled off the unit. The mapping lives in GuidanceLineUtils.convertManeuverIconId in the Android Auto proxy (com.ts.androidauto.app), and is only readable with jadx -f: the structured decompile of that method drops the return statements, so at default settings the function appears to return nothing. Icon ids were cross-checked against BYDAutoInstrumentDevice.TBT_ICON001_TURN_LEFT, then exercised on the road by feeding the gauge from our own app.

Observation#

The icon table the gauge is keyed on is Utils.BydManeuver — 1 TURN_LEFT, 2 TURN_RIGHT, 8 STRAIGHT_AHEAD, 12 DESTINATION, and so on. Its roundabout entries are three non-contiguous blocks, 16–23, 44–47 and 59–64, which together are ROUNDABOUT_EXIT_1 through ROUNDABOUT_EXIT_18 in name order.

Eighteen ids, named for eighteen exits. We indexed them that way. On a real drive the cluster drew a left arrow for a first exit that was on the right.

Evidence#

Exit angle Draws Counter-clockwise ring (right-hand traffic — this car) Clockwise ring (left-hand traffic)
1–67 sharp right 61 44
68–111 right 17 20
112–157 half right 62 45
158–202 straight on 18 22
203–247 half left 63 47
248–292 left 16 21
293–337 sharp left 64 46
338–360 back around 19 23
0 / unknown straight on 18 22

Interpretation#

The names are the trap. Id 16 is the first entry of the first block, so by name it is ROUNDABOUT_EXIT_1 — and in the bucket table it is the icon for 248–292 degrees, a left exit. An app that sends 16 for “take the first exit” gets a left arrow on a right-hand roundabout, which is exactly what the drive produced. The bytecode and the road agree on that one point, and that agreement is what lifts the reading above a guess.

The proxy is a different consumer of the same icon vocabulary, and the only readable statement on this unit of what each id draws; we are inferring that the cluster’s own turn card reads them the same way. One point of that inference has been checked on the road, and seventeen have not.

Result#

Strong: the ROUNDABOUT_EXIT_1..18 ids are selected by exit angle in eight 45-degree buckets, and their names do not describe what they draw.

Anyone feeding this gauge from a routing API therefore needs an angle, and Google’s directions carry none — only an ordinal, inside an English sentence, and “2nd exit” is a right turn at one roundabout and straight on at the next. Our approach is to derive the angle from the route polyline: entry heading over the last 30 m of approach, the unwrapped net heading change accumulated through the ring, settled on the first near-straight segment of at least 25 m that follows at least 15 m of ring travel (RoundaboutExitAngle.kt counts a per-segment change under 20 degrees as near-straight). It rides through the app as NavManeuver.exitAngleDeg → GuidanceState.exitAngleDeg → the mirror JSON’s angle. Where the geometry cannot say, the ordinal is the fallback — 1 right, 2 straight, 3 left, 4 or more around — and no exit number at all means straight on.

Limitations#

The mapping is read from bytecode, and one of its eighteen ids has been checked against what the cluster actually drew. At the time of this record the corrected mapping had not been driven: it is unit-tested against synthetic rings (RoundaboutExitAngleTest) and the suite is green, but the car had not seen it. That is why this paper is strong and not confirmed.

Only the counter-clockwise table has been exercised on a real road. The clockwise one is transcribed from the same decompile and untested — there is no left-hand-traffic car here — and we have not established what makes the proxy choose between them, beyond the traffic-side labelling recorded with the tables.

The polyline derivation is a reconstruction of a number the provider never sends, and is only as good as the geometry it is given: a roundabout with too few vertices, or a step running far past the ring, falls back to the ordinal — the same misleading assumption at one remove.

One car, one market, one head-unit build, one proxy build. Nothing here says these ids behave the same way on the domestic HAL path, on another market’s firmware, or on a different cluster.

Reproduction#

On a car with adb reachable (our guide covers getting there):

Shell
adb shell pm path com.ts.androidauto.app
adb pull /path/from/the/line/above ts-androidauto.apk
jadx -f -d out ts-androidauto.apk
grep -rn "convertManeuverIconId" out/

-f is the point. Without it the method decompiles into a body whose branches assign an icon id and then return nothing; with it you get the raw control flow, and the eight ranges with their two id tables read off directly.

To see the failure rather than the code, feed the turn card with nextTbtIcon set from the exit ordinal and drive one roundabout whose first exit is on the right. The arrow points left.