Field diagnosis · shared-truth review · build de2f0c7 · capture 4 folded in

The variable echoes are gone — a fixed cross-room stretch is what's left

You re-aimed the anchors and cleared the sight lines, and the capture proves it worked: the jittery echo on A3's long links collapsed. But those two cross-room links still read a steady ~0.6 m too long — a fixed stretch, not a jitter — and that is what still breaks the map. Here's the updated shared story, and the fix that doesn't depend on the room getting any cleaner.

Capture 4 result — the re-aim test you ran
Re-aiming the anchors eliminated the variable echo: A3's long-link jitter collapsed (tails 0.70→0.11 and 0.54→0.15 m). What remains is a steady over-range on the same two cross-room links (A1–A3 +0.57, A2–A3 +0.75 m) that no calibration can remove — so the fix is to stop each phone guessing its own map and have them all agree on one shared, self-solved map, made robust so it doesn't hinge on those two ranges.

Two things changed our shared picture: (1) your orientation fix worked — orientation is now sufficient, don't chase more angling; (2) the leftover error is fixed, not jittery, and it lives on those specific long links — A3 ranges fine to the phone (+0.10) and to A4 (−0.15 m), so it's the path, not A3's radio.

And on glass (you had the Pixel in first-person the whole time): the phone now localizes itself and the anchors move smoothly — the client-side fixes work. But the view still points the wrong way and slowly slides the anchors in and out of frame, from two separate causes: (a) the map is warped by that fixed stretch, so bearings are off; and (b) the camera has no compass lock — the AoA heading was disabled because it read ±40° garbage, so it drifts on raw gyro. Two faults, two fixes — and neither is "more smoothing."

First, the mechanism

A radio ranges by timing a pulse — and an echo takes the long way

Each anchor measures distance to another by timing how long a radio pulse takes to travel between them. If the straight path is clear, the timing is the true distance. But if something blocks or weakens the straight path, the radio locks onto the next thing it hears — a reflection off a wall, the floor, or a metal object. That bounce travelled farther, so the anchor reports a distance that is too long, and it jitters as the reflection shifts. Engineers call this "non-line-of-sight." I'll just call it an echo.

Anchor Anchor true distance — the straight path blocked / weak echo — a longer bounced path → reads too far  ))))
Why an echo always reads long. The reflected pulse literally travels a greater distance than the straight line, so the measured range is inflated — never shortened. That single fact is the fingerprint we look for in the data.
The room, to scale

Which measurements to trust — and the two that are broken

Your four anchors and the six links between them, drawn to scale from the locations you gave me (A4 dropped to the floor at about 20 cm). Green links measure true. The two red links — both belonging to A3 — read about 0.6 m too long. One link (amber) reads oddly short.

)))) )))) 2.44 m · true 2.29 · +0.15 3.88 · true 3.85 · ok 2.72 · true 2.72 · ok 2.85 · true 3.15 −0.30 short ? 4.29 true 3.70 +0.59 ✕ 5.02 · true 4.35 · +0.67 ✕ A1 root · 1.23 m high A2 1.38 m high A3 1.45 m high ← the culprit A4 0.20 m — on the floor 1 metre
The pattern points straight at A3 — and the teal arrows are the fix. A3's short link (to the nearby floor anchor A4) is perfect; its two long cross-room links to A1 and A2 are the broken ones, so the trouble travels with A3's far side, not the room. Set every device the same deterministic way: upright, screen to the room, top edge (the antenna) aimed at the mesh centre — the arrows — so each anchor has a clear shot at all the others.
The measurements · all five devices agree

The six links, side by side

"Measured" is the steady value each pair reported over the whole recording; "true" is the distance from your layout with A4 on the floor. "Spread" is the shape of the readings — a clean link sits on one value; an echo has a long tail of even-longer readings.

LinkMeasuredTrue (A4=0.20)DifferenceSpreadReading
A1 – A22.40 m2.29 m+0.11 tight · small offset
A1 – A34.27 m3.70 m+0.57 ↔ tight · steady +0.57
A1 – A43.79 m3.85 m−0.06 clean ✓
A2 – A35.10 m4.35 m+0.75 ↔ tight · steady +0.75
A2 – A42.82 m3.15 m−0.33 ? short · noisy tail (A4)
A3 – A42.57 m2.72 m−0.15 clean ✓

Capture 4 flipped one of these tells. Last capture the two red links were inflated and carried a long tail — a jittery echo. After your re-aim the tails are gone (they read tight now) but the inflation stayed: a fixed +0.57 / +0.75 m stretch. It still isn't a per-device miscalibration — that would shift all of A3's links, yet A3–A4 is clean and the phone ranges A3 fine (+0.10 m) — so the stretch is tied to those two long cross-room paths, not to A3's radio. The one remaining tail is now on A2–A4, the loose floor anchor.

Why two bad links wreck everything

The anchors can't agree on the room's shape

To pin four anchors into one rigid 3-D shape, the mesh needs all six distances to be right — there's no slack. A3 is held in place by three "ropes" (its three links). Two of them are too long, so they pull A3 to the wrong spot. Because every anchor's position is measured relative to the others, one wrong corner twists the whole shape — and each phone, solving from its own slightly different readings, lands on a different wrong shape. That's the "every diagnostics screen shows a different mesh" you saw.

TRUE SHAPE A1 A2 A3 A4 WHAT THE MESH SOLVES A1 A2 A4 A3 pulled here
One wrong corner twists the whole room. A3's two long links drag it off its true spot (grey ring → red). The rigid shape rotates and flattens to absorb the error, the vertical collapses so nothing looks "raised," and it never settles — so it never locks, and no two phones agree.
What specifically — updated after capture 4

What's left on A3's two long links — now that the jitter is gone

The data still says A3's far side is the problem (short link clean, both long links off by a similar amount) — but capture 4 changed which cause is in play. The variable echo cleared, so a switching-reflection story no longer fits; a fixed stretch does. One candidate is ruled out and two remain, and a single A/B test tells them apart.

Ruled outA

Something blocks A3's view

Was my first guess — an obstruction (TV/shelf/metal/person) on the cross-room lines. You confirmed the sight line is clear, and capture 4 agrees: a blocked path would have kept a jittery tail, but the tails collapsed while the offset stayed. A clear line that still reads long isn't a blockage. Dropped.

LeadingB

The direct pulse is simply weak over the long span

Across 3.7–4.3 m the straight signal is faint (but present), and the radio's first-arrival detector consistently latches a hair late → a fixed over-range. Fits the evidence: short/near links are clean, only the long ones stretch, and it's the same in both directions. This is orientation-independent — pinning bypasses it; removing it at source needs first-path-aware ranging inside the phone's radio, which we don't control.

Still possibleC

A3's antenna adds a fixed delay in that direction

UWB antennas delay the pulse by a small amount that varies with the angle you present them at (group delay). If A1/A2↔A3's mutual bearing sits in a slow part of A3's pattern, every reading on those links gains a constant offset. This is orientation-dependent — so the A/B rotation test (turn A3 90°) separates it from B: bias moves ⇒ C, bias holds ⇒ B.

Separate, smaller puzzleA2 – A4

One link reads short, which an echo can't explain

A2–A4 comes in ~30 cm shorter than it should — in fact slightly shorter than the straight-line floor distance, which isn't physically possible if both anchors are exactly where we think. Echoes only make links longer, so this one is different: most likely A2 or the floor-anchor A4 isn't quite at its surveyed spot (A4 is loose on the floor at 20 cm and easy to nudge), or a strong floor-bounce is confusing that one low link. Minor next to the A3 echoes, but worth a glance while you're there.

Fix #1 · point the radios at the mesh

These exact phones have a one-sided antenna — aim it

Your four anchors are Samsung Galaxy S21+ phones; the moving observer is a Pixel 8 Pro. On all of them the ultra-wideband antenna sits near the top edge and is directional — strong out one face, weak out the others. Aim a weak side at an anchor and its straight signal loses to a reflection: that is the echo on A3's far links. Since you can set orientation exactly, use one deterministic rule for all four: upright, screen toward the room, top edge (the antenna) aimed at the mesh centre — the teal arrows on the map above. That gives every anchor a clear shot at every other, and it's repeatable test to test.

DO — stand it upright top edge A1 A2 straight signal reaches them ✓ DON'T — lie it flat floor into the floor → echo ✗ A1/A2
Upright, top edge toward the mesh — the deterministic rule. A phone lying flat fires its pattern into the floor and ceiling and hears the bounce instead of the straight path. A4 isn't flat — you have it propped at an angle in a cup — but it's the only anchor not on a proper stand, which is the likeliest reason its A2–A4 link is the one that's still noisy.
A3 · re-aim it

Turn A3 so its top edge points at the mesh centre (which faces it back across the room at A1/A2), stand it upright, and leave a few centimetres between it and the wall or any metal behind it. Its clean link to A4 proves the radio works — it's just pointed the wrong way.

A4 · put it on a stand

A4 is the only anchor not on a tripod — propped at an angle in a cup at ~20 cm. Stand it upright on a proper mount, top edge toward the room. This should clear the noisy, slightly-short A2–A4 reading. (With pinning it won't block the map either way.)

All four · basics

Upright; top edge toward the middle of the mesh; out of any case with metal or magnets; not held in a hand (a body absorbs the signal); clear line of sight to every other anchor.

Fix #2 · the software should have flagged this

Should the engine have caught the echo? Today it doesn't

The engine does have echo defenses — but they're built to catch a sudden blip, not a steady echo, and the check that could catch a steady one doesn't run on the anchor-to-anchor measurements that build the map. So a link that is quietly 0.6 m too long on every single reading sails straight through.

anchor ↔ anchorrange smooth +blip gate average intoa shape wrong roomshape steady echo slips through (this check is skipped here) ADD — use the radio's own echo signal, flag lopsided links, and share one robust solve
Every current guard misses a steady echo — and the radio's own echo signal is thrown away. These phones report a signal-strength / first-path indicator that flags a reflection, but the app passes a constant "perfect" value, so the guard meant to use it never fires.
What exists — and why it misses this
  • Blip gate (3-sigma): the smoother settles onto the biased value, so every steady-long reading then looks normal.
  • Signal-quality gate: present, but fed a constant — the real echo indicator (signal strength / first-path) is discarded in the phone layer before it arrives.
  • Triangle check: the biased distances still form a valid (distorted) triangle, so nothing trips — and it doesn't run on the anchor-to-anchor path anyway.
  • Link-health report: calls the bad links "strong," because the shape is fit to those biased numbers, so they look self-consistent.
  • Four anchors = no spare: with exactly four, there's no extra measurement to cross-check, so one bad link can't be caught by maths alone.
The update I recommend
  • Capture the radio's echo signal (signal strength + first-path) instead of a constant, and let a poor value distrust that reading — this revives the dead guard.
  • Guard the anchor-to-anchor path: flag a link whose readings are lopsided (a long high tail) as echo-suspect and trust it less when solving the shape.
  • With only four anchors there's no spare to cross-check by maths (a fifth isn't available), so that radio echo signal is the essential guard, not a nice-to-have — and it makes the deterministic orientation above matter even more.
  • Tell the operator: say "A3's links look like echoes," not "strong."
  • The real fix: share ONE robust self-solved map (every phone uses the lead phone's solve, made tough with direction readings + the moving phone's clean ranges) — no hand-entered locations. I use your surveyed layout only offline, to check the answer.

Bottom line: the engine can't remove a real echo — clean signal has to come from the room — but it should detect it, distrust it, and say so, and it should be able to stand the map up on the survey when the raw measurements can't be trusted. Worth doing; I can deliver the detect-and-flag parts as tested patches.

You already answered — here's what we now agree on

What you confirmed, and what it ruled out

You corrected the room for me last round. Two of your answers changed the diagnosis — flagged below — and that is exactly what this loop is for.

  • Layout confirmed (A4 height corrected to 0.20 m)
    A1 (root)+A2 on stands against the wall behind them, upright, screens to the room; A3 across the room ~1 m off the far wall; A4 loose in a cup on the floor, top ~20 cm out, angled toward centre. The map above is drawn to these.
  • Nothing blocks the sight line A3↔A1/A2 — this ruled out my first guess
    My original "most likely" cause was an obstruction on those two lines; you refuted it. That's decisive: a clear line of sight that still reads long cannot be a blockage echo — it's a fixed path-length or antenna effect. That single correction is why the fix moved to pinning.
  • Orientation is deterministic — and sufficient
    A1/A2 screens face +y, A3 faces −y, all upright; A4 the only one off a tripod. Capture 4 proves this orientation is enough: the jitter cleared. No more angling needed.
  • A4 is the loose one — and it shows
    The A2↔A4 link is now the one noisy link (fat tail, reads a bit short) — consistent with A4 low and angled in a cup. Standing it on a tripod would clean that link; pinning makes it non-blocking for the map either way.
Your aim-and-view drawings → the fix sequence

The picture is built in five steps — and they must be fixed in order

You drew the goal: point the phone at A1, and A1 shows up in the right place and stays there as you turn and orbit. The image on the screen is built as a chain — where the phone is, then where the anchors are, then the direction to each, then which way you're looking, then where that lands on the glass. Each link has to be right before the next one means anything. Two links are broken, and that order is exactly why the fixes are sequenced the way they are.

where thephone is ✓ done · self-seed where theanchors are ① FIX NOW · one shared solve direction toeach anchor auto · once 1+2 right which wayyou're looking ② THEN · heading lock where it landson the glass ✓ done · +aim-sign check All five correct → point the phone at A1, and A1 appears at its true spot and stays locked as you turn in place and orbit — exactly your drawings.
Why the order is forced. Step ② (which way you're looking) is worked out from the phone's own radio bearings to the anchors — so it can only be right once step ① has the anchors in the right places. That's the whole reason the heading came out as garbage before and had to be switched off: it was being computed against a warped map. Fix the map first, and the heading has a correct thing to lock onto. The aim-sign check on step ⑤ is the last guard from your drawings — screen toward A1 must show A1, never the far wall. And step ① is mesh-wide: right now the root and each anchor each draw a different shape, because every phone solves the room from its own noisy measurements and ignores the others. The mesh already computes one shared answer on the lead phone and broadcasts it — the fix is to make every phone use that one instead of guessing its own, and to make that shared solve tougher (leaning on the phones' direction sensors and the moving phone's own clean distance readings). No hand-entered locations — it stays self-solving, works for any sensible layout, and flags one that genuinely can't be solved. That shared agreement is what "the map is locked" really means, and it must hold before steps ②–⑤ can.
The re-test is in — here's what each side does next

Fix the map in software; the room is already good enough

Capture 4 was the clean re-test, and it half-confirmed the theory: the variable echo is gone, a fixed stretch remains. That splits the way forward cleanly — I fix the map and the heading in code; you only touch the room again if we decide to chase the stretch to its physical source.

  • On my side — make every phone agree on one self-solved map (the real fix)
    The mesh already works out one shared map on the lead phone and sends it to the others — but each phone currently ignores it and draws its own guess, which is why you get four different shapes. I make every phone use the one shared map, and make that shared solve tougher so it doesn't hinge on the two bad links: it leans on the phones' direction readings between anchors and on the moving phone's own (clean) distance readings as it walks the room. No hand-entered locations — it stays self-solving for any sensible layout, and flags a layout that genuinely can't be solved rather than locking a wrong one.
  • On my side — give the camera a compass again
    Once the map is correct, the phone's own bearings to the anchors become trustworthy, so the heading lock I had to disable can be re-checked and re-enabled — that's what stops the anchors sliding out of frame. If the bearings still won't settle, I fall back to the phone's magnetometer.
  • On my side — let the engine see the weak links
    Right now the engine is blind to signal strength (every reading claims "perfect"), so it can't down-weight the long links on its own. I wire the real first-path strength through so it can.
  • On your side — only if we want to kill the stretch at its source
    One optional A/B: rotate A3 a known angle (say 90°), change nothing else, record once. If the +0.6 m moves, it's the antenna's aim and we can tune it out; if it holds, it's the room's geometry and pinning is the only client-side answer. Skip it if pinning's accuracy is enough.

What the re-test actually showed, against the precise, falsifiable prediction I made last round:

Predicted: A3's two links drop from +0.59 / +0.67 m to near zero and their long tails disappear. Actual: the long tails disappeared (0.70→0.11, 0.54→0.15 m) — the echo half was real and your re-aim killed it — but the offsets stayed (+0.57 / +0.75 m). So the map still won't lock.

That's the useful half of "if they don't drop, I'm partly wrong": a purely jittery echo would have cleared completely. A steady offset that survives a clean line of sight is a fixed path-length or antenna effect — which is why the answer is now pin to the survey, not chase the room further. The optional A/B above is the only thing that tells us which of the two it physically is.

One honest limit: I can prove from your data that these ranges are corrupted and that no calibration can fix them — but I can't un-corrupt them from here. Clean measurements have to come from the room. That's the only reason this needs a device, and it's a confirmation step, not a diagnosis gap.