Skip to content

Correcting anchors while nobody is looking

S edited this page Aug 20, 2026 · 3 revisions

Correcting anchors while nobody is looking

An estimator that improves has to express improvements somewhere. In AR, the place it usually expresses them is the one thing the user is staring at.

createGpsAnchor exists to route that improvement somewhere less rude.

The problem in one sentence

Every refinement of the GPS↔AR alignment moves world-anchored content slightly — and a small movement is far more noticeable when it happens to an object in the centre of the screen than when it happens to one behind the user's shoulder.

What the anchor does

It can bootstrap from a median rather than a mean. Where the anchor's starting coordinate comes from live GPS, it is the median of a handful of samples rather than their average — multipath produces outliers that are large and one-sided, and a mean chases them while a median mostly ignores them.

The default path does not need that protection: the anchor derives its coordinate from the object's own world pose, which has no multipath in it. The median matters when you supply a sampler that reads GPS directly, which is precisely the case where raw fixes are what you have.

It keeps the object positioned from its GPS target. The object's job is to sit at its coordinate, converted through the current alignment, inside the AR world group. It is not given a fixed local position that then has to be patched — the position is derived, so a better alignment produces a better position for free.

It defers small corrections until the object is off-screen. This is the part with taste in it. If the alignment shifts a little while the user is looking straight at the marker, applying it now yields a visible twitch on the object under maximum scrutiny. Holding the correction until the object leaves the frustum, then applying it, means the user never sees a correction at all — they look away, they look back, it is right.

It does NOT make an exception for large corrections — and that is the part worth understanding. An earlier version did: a big enough alignment delta overrode the off-screen gate and snapped the object under the user's gaze. That bypass was removed in June 2026, and the reasoning is a good illustration of how the layers fit together.

Smoothing does not live in the anchor. It lives once, in the interpolated alignment matrix on the AR world group — so when the alignment changes a lot, the entire view eases into it together, camera and all anchored content at once. The large jump is therefore already being handled, smoothly, at a layer above. A per-anchor on-screen snap did not rescue the object from staleness; it manufactured a visible hard jump that the group lerp would otherwise have absorbed.

So the policy is simpler than the one you would design from first principles:

  • Below a distance-scaled threshold (about 2 m, growing with how far the object is from the camera) → no correction at all. Nothing to hide.
  • Above it, object off-screen → committed instantly, invisibly.
  • Above it, object on-screen → suppressed, whatever the size.

The single exception is the one-time initial placement of an anchor created with bootstrapping skipped: it must escape the AR origin even while the user is watching, or it would sit "inside" them. That exemption is consumed by the first commit and never re-armed.

If you would rather be right than smooth, there is a mode for it — snap-every-tick — and choosing it is a deliberate act rather than a threshold you have to guess.

Why "off-screen" and not "faded"

Alternatives exist. You could cross-fade the object between positions, or move it slowly enough to sit under the motion-detection threshold.

Both fight human vision, which is extremely good at detecting motion and change, especially in peripheral vision and especially for things that should be part of the world. Not rendering a correction at all is better than rendering it subtly.

The distinction to hold onto: this is an argument against per-object easing, not against smoothing in general. The framework does interpolate — once, on the alignment matrix for the whole AR world — so everything shifts together and the relationship between objects never changes. What that buys is a scene that eases as a rigid whole rather than a collection of things each drifting on its own schedule, which is the version the eye reads as unstable.

Choosing this or not choosing it

Not every object should be a GPS anchor. The framework distinguishes deliberately:

  • Session-local objects live in the AR scene and stay at the same 3D position for the session, untouched by alignment updates. Perfect for content the user creates live — a 3D trail of the path they walked, a temporary marker, something they positioned by hand. These are supposed to be where the user put them, not where GPS thinks they should be.
  • GPS anchors are for content that must survive a reload, be shared with another device, or be replayed later. The coordinate is the source of truth and the local position is derived from it.

The mistake is to make everything an anchor. A hand-placed decoration that gets nudged by an alignment correction has been corrected away from where the user deliberately put it — technically more accurate, experientially wrong.

Two lessons

The interesting design question in interactive systems is rarely "how do we compute the correct value". It is "when is the user allowed to see us change our mind".

The second took this project a revision to learn: before adding an escape hatch for the big cases, check whether a layer above you is already handling them. The large-jump bypass was a reasonable-sounding rule that turned out to be actively harmful once smoothing moved to the shared alignment matrix — it re-introduced, per object, exactly the discontinuity the shared lerp existed to remove.

Clone this wiki locally