The number we threw away
Thomas Maarup · 23 September 2026
In short
On iOS, every
deviceorientationevent carries two non-standard fields:webkitCompassHeading, the direction the phone is facing, andwebkitCompassAccuracy, how far iOS thinks that direction can be off, in degrees. Our radar read the first and ignored the second. Measured on an iPhone 15 running iOS 26 on 1 August 2026, one reading in each cardinal direction, the accuracy was ±14 to ±16 degrees, on the run where the heading was visibly right. A value of-1means the reading is invalid, and we had been drawing those as well. The standardDeviceOrientationEventhas no accuracy field at all, so on every other platform the number simply does not exist.
The report
Raduno's radar puts you in the centre and your friends around you as dots, each at its direction and approximate distance. The disc turns with the phone's compass, so the top of the screen is always the way you are facing. If the compass is wrong, every dot on the disc is wrong by the same amount, in the same direction.
The day we added the four cardinal points to the disc, the report came back from a real phone: the letters feel about 30 degrees off.
The first thing to settle was whether the disc itself was right. Measured on the rendered geometry in the browser, the letters sat at exactly 0, 90, 180 and 270 degrees, and every dot sat at the bearing it had been given, except two that the overlap rule deliberately nudges apart by a degree and a half. The disc was internally consistent. If something was wrong, it was the angle the whole disc was turned by, which is to say the input.
What iOS actually hands you
The standard DeviceOrientationEvent gives you three angles: alpha, beta and gamma.
alpha is rotation around the axis pointing out of the screen, and the spec only promises
it is measured against north when the event is marked absolute. On an iPhone it isn't.
What iOS adds instead are two fields of its own, prefixed the old WebKit way:
interface WebKitDeviceOrientationEvent extends DeviceOrientationEvent {
webkitCompassHeading?: number; // degrees clockwise from north
webkitCompassAccuracy?: number; // ± degrees, or -1
}
webkitCompassHeading is the one everybody reaches for, because it is the one that
works: a ready-made compass bearing, clockwise from north, no conversion needed. We read
it, added the screen rotation for landscape, and turned the disc.
webkitCompassAccuracy sat on the same event the whole time. Apple's documentation is two
sentences long: a value of 10 means the heading is off by plus or minus 10 degrees, and
"-1 means that the compass is not calibrated and not giving usable readings"
(Apple Developer Documentation).
We had never logged it.
The measurement
To get numbers instead of impressions, the radar got a hidden diagnostics panel behind
?debug=heading, showing every raw field of the last event next to the angle the disc was
actually using. How that panel was built, and why its most important row turned out to be
an age rather than a value, is an article of its own. What matters here is what it showed.
Four screenshots, one facing each cardinal direction, on a run where the disc looked right all the way round. iPhone 15, iOS 26, 1 August 2026:
| Facing | webkitCompassHeading | webkitCompassAccuracy | Disc used | Off true bearing |
|---|---|---|---|---|
| North | 5.9° | ±16° | 5.9° | 5.9° |
| East | 92.3° | ±15° | 93.4° | 2.3° |
| South | 183.9° | ±14° | 183.9° | 3.9° |
| West | 271.3° | ±14° | 271.3° | 1.3° |
The last column is how far the reported heading sat from the cardinal direction the phone was pointed at. We knew where the four directions lay from where we were standing, and aimed the phone by hand. That is a known reference, not a surveyed one, and the column should not be read to the degree. The east row's 93.4° is not a disagreement either: the panel samples four times a second, and the disc had moved on between two samples.
The column that matters is the third one. On a run where the compass was, by every visible measure, working, iOS itself was saying it could be 14 to 16 degrees off. The heading was right within that band. Earlier the same day, the phone had seemed about 30 degrees off towards north and east, which is just outside it. We never reproduced the 30 degrees with the instrument running, so we cannot say what iOS reported at the time. We can say that there was no bug in the conversion. There was a number, and we were throwing it away.
Two things the same screenshots showed
alpha would have been useless. In the four screenshots, alpha read 357.7°, 0.4°,
0.3° and 0.8°: effectively the same value whichever way the phone was facing. And
event.absolute was undefined on every event, never true. Our fallback for platforms
without webkitCompassHeading reads alpha only when the event is marked absolute:
if (event.absolute && event.alpha != null) {
return (360 - event.alpha + screenAngle()) % 360;
}
On an iPhone that branch never fires. We had written the guard for Android's sake, and it turned out to be the only thing standing between the radar and a disc that points wherever it happened to start.
Accuracy is a per-reading number. It is not a property of the phone or of the session. It moves by a degree or two between events, and the events arrive around 60 times a second. Anything that shows it has to decide how often it is allowed to change.
What we changed
Three things, all small.
Readings iOS disowns are dropped. A negative accuracy is not "very imprecise", it is iOS saying the heading means nothing. The disc now skips those readings and holds the last good one:
if (typeof event.webkitCompassHeading === "number") {
if (typeof event.webkitCompassAccuracy === "number" && event.webkitCompassAccuracy < 0) {
return null; // skip: the disc keeps its last good heading
}
return (event.webkitCompassHeading + screenAngle()) % 360;
}
Tested afterwards with synthetic events in the browser, not on the phone: a reading of
350° with accuracy -1 produced no heading at all, and the disc stayed where it was, at
143.8°. A slightly stale direction is better than one the platform itself says is
meaningless. The same rule runs through the rest of the codebase: degrade, never guess.
The number is on the screen. Under the radar, on iPhone only, it now says how far the compass can be off right now, with one line of explanation and the one thing the user can do about it:
Compass ±13°
The radar's direction comes from the phone's compass, which can be off by that much right now.
Move the phone in a figure of eight to improve it.
When iOS reports -1, the line does not say ±-1°; it says the compass is not calibrated
and the direction is unreliable right now. The copy names the phone's compass as the cause,
not the app, because that is where the error is. The number updates every two seconds
rather than on every event. At 60 events a second the digit would never stand still long
enough to be read.
We considered drawing the uncertainty on the disc instead, as a cone of ±accuracy around "straight ahead". A sketch of it competed with the radar's sweep animation and lay on top of the dots it was meant to qualify. Two lines of text won.
Nothing is shown anywhere else. On Android and on desktop, the block is not rendered at all. There is no accuracy field to read there, and any number we displayed would be made up.
Why the standard has nothing to offer
The W3C DeviceOrientation spec did once have a way to say the compass was unreliable: a
compassneedscalibration event. It was removed in 2023, after W3C staff could find no
implementation other than Internet Explorer's
(w3c/deviceorientation#38). A 2014
issue asking whether the spec should absorb Apple's two fields was closed without them
being added (w3c/deviceorientation#6).
So the situation in the browser is lopsided. On iOS, the platform tells you with every reading how much to trust it. Everywhere else, you get a direction with no error bar, and no way to tell a good reading from a bad one. The honest thing a web app can do is show the number where it exists and not invent one where it doesn't.
What ±15 degrees means on the ground
Fifteen degrees sounds small. On the radar it is the difference between a friend being "straight ahead" and "a bit to the right", and at festival distances that is a stage.
It also does not come alone. The previous article worked through the other error: two phones' GPS positions, each off by around ten metres, turn into a bearing error of about 25 degrees when you are 30 metres apart. The compass error is independent of that and sits on top of it. The GPS error grows as you get closer; the compass error does not change with distance at all. At 500 metres the compass is most of what is wrong with the arrow. At 30 metres it is the smaller of two large problems.
That is the case for the near zone we have described and not yet built: below a certain distance, the right thing to show is not an arrow but a sentence.
What we don't know
- One phone, one day. Four readings on one iPhone 15 running iOS 26, on 1 August 2026. They show what iOS reports when things work, not how often they don't.
- Not tested on Android. There is nothing to test: the field does not exist there. Whether Android's compass is better or worse, the browser does not let us find out.
- We don't know how iOS computes the number. Apple documents what it means, not how it is estimated. We report what the phone said, and treat it as the phone's own opinion.
- The 30 degrees were never measured. They were observed by eye, before the panel existed. Everything in the table was measured with it.
- The "off true bearing" column is rough. The phone was aimed by hand at directions known from the surroundings, not at a surveyed reference. It is good enough to say the heading was within iOS's own band, and no better.