|
<title>Aether mesh map diagnosis — the fixed cross-room stretch on A3</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<style>
|
|
:root{
|
|
--bg:#e9eef4; --panel:#ffffff; --panel2:#f3f7fb; --line:#d3dde8;
|
|
--ink:#15212f; --muted:#54677e; --faint:#7f90a6;
|
|
--accent:#0e9f92; --accent-ink:#0b7d73;
|
|
--good:#1f9e57; --good-bg:#e6f5ec;
|
|
--bad:#dd382c; --bad-bg:#fceae8;
|
|
--warn:#bd7d1f; --warn-bg:#faf0da;
|
|
--true:#1391a6;
|
|
--shadow:0 1px 2px rgba(20,40,70,.06),0 8px 24px rgba(20,40,70,.06);
|
|
--radius:14px;
|
|
--font-sans:"Segoe UI",system-ui,-apple-system,Roboto,Helvetica,Arial,sans-serif;
|
|
--font-mono:ui-monospace,"SF Mono","Cascadia Code","JetBrains Mono","Roboto Mono",Menlo,Consolas,monospace;
|
|
}
|
|
@media (prefers-color-scheme:dark){
|
|
:root{
|
|
--bg:#080e17; --panel:#0f1928; --panel2:#152238; --line:#243549;
|
|
--ink:#e9eff7; --muted:#95a7c0; --faint:#6d7f98;
|
|
--accent:#33d6c6; --accent-ink:#7ff0e4;
|
|
--good:#40cf79; --good-bg:rgba(64,207,121,.13);
|
|
--bad:#ff5d50; --bad-bg:rgba(255,93,80,.14);
|
|
--warn:#f4ab41; --warn-bg:rgba(244,171,65,.14);
|
|
--true:#5fd6e8;
|
|
--shadow:0 1px 2px rgba(0,0,0,.4),0 10px 30px rgba(0,0,0,.35);
|
|
}
|
|
}
|
|
:root[data-theme="light"]{
|
|
--bg:#e9eef4; --panel:#ffffff; --panel2:#f3f7fb; --line:#d3dde8;
|
|
--ink:#15212f; --muted:#54677e; --faint:#7f90a6;
|
|
--accent:#0e9f92; --accent-ink:#0b7d73;
|
|
--good:#1f9e57; --good-bg:#e6f5ec; --bad:#dd382c; --bad-bg:#fceae8;
|
|
--warn:#bd7d1f; --warn-bg:#faf0da; --true:#1391a6;
|
|
--shadow:0 1px 2px rgba(20,40,70,.06),0 8px 24px rgba(20,40,70,.06);
|
|
}
|
|
:root[data-theme="dark"]{
|
|
--bg:#080e17; --panel:#0f1928; --panel2:#152238; --line:#243549;
|
|
--ink:#e9eff7; --muted:#95a7c0; --faint:#6d7f98;
|
|
--accent:#33d6c6; --accent-ink:#7ff0e4;
|
|
--good:#40cf79; --good-bg:rgba(64,207,121,.13); --bad:#ff5d50; --bad-bg:rgba(255,93,80,.14);
|
|
--warn:#f4ab41; --warn-bg:rgba(244,171,65,.14); --true:#5fd6e8;
|
|
--shadow:0 1px 2px rgba(0,0,0,.4),0 10px 30px rgba(0,0,0,.35);
|
|
}
|
|
*{box-sizing:border-box}
|
|
html{-webkit-text-size-adjust:100%}
|
|
body{margin:0; background:var(--bg); color:var(--ink);
|
|
font-family:var(--font-sans); line-height:1.65; font-size:17px; letter-spacing:.005em}
|
|
.wrap{max-width:1000px; margin:0 auto; padding:0 22px}
|
|
.measure{max-width:66ch}
|
|
.eyebrow{font-family:var(--font-mono); font-size:.72rem; text-transform:uppercase;
|
|
letter-spacing:.16em; color:var(--accent-ink); font-weight:600}
|
|
h1{font-size:clamp(1.9rem,4.6vw,2.9rem); line-height:1.08; font-weight:800;
|
|
letter-spacing:-.02em; text-wrap:balance; margin:.35em 0 .3em}
|
|
h2{font-size:clamp(1.3rem,2.6vw,1.7rem); font-weight:750; letter-spacing:-.015em;
|
|
text-wrap:balance; margin:0 0 .1em}
|
|
h3{font-size:1.02rem; font-weight:700; margin:0 0 .3em; letter-spacing:-.01em}
|
|
p{margin:.55em 0}
|
|
a{color:var(--accent-ink)}
|
|
.lead{font-size:1.12rem}
|
|
.mono{font-family:var(--font-mono)}
|
|
.num{font-family:var(--font-mono); font-variant-numeric:tabular-nums}
|
|
section{padding:44px 0; border-top:1px solid var(--line)}
|
|
section:first-of-type{border-top:0}
|
|
.stack{display:flex; flex-direction:column; gap:14px}
|
|
header{padding:54px 0 40px; position:relative; overflow:hidden}
|
|
.scope{position:absolute; inset:0; z-index:0; opacity:.45; pointer-events:none; width:100%; height:100%}
|
|
header .wrap{position:relative; z-index:1}
|
|
.kicker-row{display:flex; align-items:center; gap:12px; flex-wrap:wrap}
|
|
.dot{width:9px;height:9px;border-radius:50%;background:var(--bad);box-shadow:0 0 0 4px var(--bad-bg)}
|
|
.verdict{background:var(--panel); border:1px solid var(--line); border-left:5px solid var(--bad);
|
|
border-radius:var(--radius); padding:20px 22px; box-shadow:var(--shadow); margin-top:8px}
|
|
.verdict .big{font-size:1.22rem; font-weight:700; letter-spacing:-.01em}
|
|
figure{margin:0}
|
|
.fig{background:var(--panel); border:1px solid var(--line); border-radius:var(--radius);
|
|
box-shadow:var(--shadow); padding:18px; overflow:hidden}
|
|
.fig svg{display:block; width:100%; height:auto}
|
|
figcaption{color:var(--muted); font-size:.9rem; margin-top:12px; padding-top:12px; border-top:1px dashed var(--line)}
|
|
figcaption b{color:var(--ink)}
|
|
.legend{display:flex; gap:8px 18px; flex-wrap:wrap; font-family:var(--font-mono);
|
|
font-size:.76rem; letter-spacing:.02em; margin-top:4px}
|
|
.legend span{display:inline-flex; align-items:center; gap:7px; color:var(--muted)}
|
|
.swatch{width:22px; height:0; border-top-width:3px; border-top-style:solid; border-radius:2px}
|
|
.tablewrap{overflow-x:auto; border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); background:var(--panel)}
|
|
table{border-collapse:collapse; width:100%; min-width:660px; font-size:.95rem}
|
|
th,td{padding:12px 14px; text-align:left; border-bottom:1px solid var(--line); white-space:nowrap}
|
|
thead th{font-family:var(--font-mono); font-size:.68rem; text-transform:uppercase;
|
|
letter-spacing:.11em; color:var(--muted); font-weight:600; background:var(--panel2)}
|
|
tbody tr:last-child td{border-bottom:0}
|
|
td.n{font-family:var(--font-mono); font-variant-numeric:tabular-nums}
|
|
.pill{display:inline-flex; align-items:center; gap:6px; font-family:var(--font-mono);
|
|
font-size:.72rem; font-weight:600; padding:3px 9px; border-radius:999px; letter-spacing:.02em}
|
|
.pill.good{background:var(--good-bg); color:var(--good)}
|
|
.pill.bad{background:var(--bad-bg); color:var(--bad)}
|
|
.pill.warn{background:var(--warn-bg); color:var(--warn)}
|
|
.rowmark{width:4px; padding:0}
|
|
tr.good .rowmark{background:var(--good)} tr.bad .rowmark{background:var(--bad)} tr.warn .rowmark{background:var(--warn)}
|
|
.delta-bad{color:var(--bad); font-weight:700} .delta-warn{color:var(--warn); font-weight:700} .delta-ok{color:var(--muted)}
|
|
.grid{display:grid; gap:16px}
|
|
@media(min-width:640px){ .grid.cols3{grid-template-columns:repeat(3,1fr)} }
|
|
.card{background:var(--panel); border:1px solid var(--line); border-radius:var(--radius); padding:18px; box-shadow:var(--shadow)}
|
|
.card .tag{font-family:var(--font-mono); font-size:.68rem; letter-spacing:.12em; text-transform:uppercase; color:var(--accent-ink); font-weight:600}
|
|
.rank{font-family:var(--font-mono); font-size:.75rem; color:var(--faint)}
|
|
ul.check{list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:12px}
|
|
ul.check li{display:flex; gap:12px; align-items:flex-start; background:var(--panel);
|
|
border:1px solid var(--line); border-radius:12px; padding:14px 16px; box-shadow:var(--shadow)}
|
|
ul.check .box{flex:0 0 auto; width:20px; height:20px; border-radius:6px; border:2px solid var(--accent); margin-top:2px}
|
|
ul.check .q{font-weight:700; letter-spacing:-.01em}
|
|
ul.check small{color:var(--muted)}
|
|
.note{color:var(--muted); font-size:.95rem}
|
|
.kbd{font-family:var(--font-mono); font-size:.85em; background:var(--panel2); border:1px solid var(--line); border-radius:6px; padding:1px 6px}
|
|
footer{padding:34px 0 60px; color:var(--faint); font-size:.85rem}
|
|
hr.soft{border:0; border-top:1px dashed var(--line); margin:26px 0}
|
|
/* SVG color utilities (var() is not honored in SVG presentation attributes) */
|
|
svg text{font-family:var(--font-mono); fill:var(--ink)}
|
|
.st-accent{stroke:var(--accent)} .fl-accent{fill:var(--accent)}
|
|
.st-good{stroke:var(--good)} .fl-good{fill:var(--good)}
|
|
.st-bad{stroke:var(--bad)} .fl-bad{fill:var(--bad)}
|
|
.st-warn{stroke:var(--warn)} .fl-warn{fill:var(--warn)}
|
|
.st-true{stroke:var(--true)} .fl-true{fill:var(--true)}
|
|
.st-muted{stroke:var(--muted)} .fl-muted{fill:var(--muted)}
|
|
.st-line{stroke:var(--line)} .st-faint{stroke:var(--faint)} .fl-faint{fill:var(--faint)}
|
|
.fl-panel{fill:var(--panel)} .fl-ink{fill:var(--ink)} .fl-badbg{fill:var(--bad-bg)}
|
|
@media (prefers-reduced-motion:reduce){*{animation:none!important; transition:none!important}}
|
|
</style>
|
|
|
|
<header>
|
|
<svg class="scope" viewBox="0 0 1000 300" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
|
|
<path class="st-accent" fill="none" stroke-width="1.5" d="M0 210 H360 l14 -20 l16 44 l12 -128 l16 150 l14 -46 H520"/>
|
|
<path class="st-bad" fill="none" stroke-width="1.5" opacity="0.8" d="M520 210 l18 -8 l22 34 l30 -150 l30 150 l22 -34 l18 8 H1000"/>
|
|
</svg>
|
|
<div class="wrap">
|
|
<div class="kicker-row">
|
|
<span class="dot"></span>
|
|
<span class="eyebrow">Field diagnosis · shared-truth review · build de2f0c7 · capture 4 folded in</span>
|
|
</div>
|
|
<h1>The variable echoes are gone — a <em>fixed</em> cross-room stretch is what's left</h1>
|
|
<p class="lead measure">You re-aimed the anchors and cleared the sight lines, and the capture proves it
|
|
worked: the jittery echo on A3's long links <strong>collapsed</strong>. But those two cross-room links
|
|
still read a steady <span class="num">~0.6 m</span> 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.</p>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="wrap">
|
|
|
|
<section id="verdict" style="border-top:0; padding-top:8px">
|
|
<div class="verdict stack">
|
|
<div><span class="eyebrow" style="color:var(--good)">Capture 4 result — the re-aim test you ran</span></div>
|
|
<div class="big">Re-aiming the anchors <strong>eliminated the variable echo</strong>: A3's long-link jitter
|
|
collapsed (tails <span class="num">0.70→0.11</span> and <span class="num">0.54→0.15 m</span>). What
|
|
remains is a <em>steady</em> over-range on the same two cross-room links
|
|
(<span class="num">A1–A3 +0.57</span>, <span class="num">A2–A3 +0.75 m</span>) that
|
|
<strong>no calibration can remove</strong> — so the fix is to stop each phone guessing its own map and
|
|
have them all <strong>agree on one shared, self-solved map</strong>, made robust so it doesn't hinge on
|
|
those two ranges.</div>
|
|
<p class="note" style="margin:0">Two things changed our shared picture: (1) your orientation fix
|
|
<strong>worked</strong> — orientation is now <em>sufficient</em>, don't chase more angling; (2) the
|
|
leftover error is <em>fixed, not jittery</em>, and it lives on those specific long links — A3 ranges fine
|
|
to the phone (<span class="num">+0.10</span>) and to A4 (<span class="num">−0.15 m</span>), so it's
|
|
the <em>path</em>, not A3's radio.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="c4" style="border-top:0; padding-top:0">
|
|
<div class="card measure" style="border-left:5px solid var(--good)">
|
|
<p style="margin:.1em 0"><strong>And on glass (you had the Pixel in first-person the whole time):</strong>
|
|
the phone now <em>localizes itself</em> and the anchors move <strong>smoothly</strong> — the client-side
|
|
fixes work. But the view still <em>points the wrong way and slowly slides the anchors in and out of
|
|
frame</em>, from two separate causes: <strong>(a)</strong> the map is warped by that fixed stretch, so
|
|
bearings are off; and <strong>(b)</strong> 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."</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="echo">
|
|
<div class="stack" style="gap:22px">
|
|
<div class="measure">
|
|
<span class="eyebrow">First, the mechanism</span>
|
|
<h2>A radio ranges by timing a pulse — and an echo takes the long way</h2>
|
|
<p>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 <strong>next thing it hears</strong> — a
|
|
reflection off a wall, the floor, or a metal object. That bounce travelled <strong>farther</strong>,
|
|
so the anchor reports a distance that is <strong>too long</strong>, and it jitters as the reflection
|
|
shifts. Engineers call this "non-line-of-sight." I'll just call it an <strong>echo</strong>.</p>
|
|
</div>
|
|
<figure class="fig">
|
|
<svg viewBox="0 0 720 236" role="img" aria-label="A straight radio path is blocked, so the receiver hears a longer bounced echo and reports a distance that is too long.">
|
|
<defs>
|
|
<marker id="ah" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="fl-true" d="M0 0 L10 5 L0 10 z"/></marker>
|
|
<marker id="ahb" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="fl-bad" d="M0 0 L10 5 L0 10 z"/></marker>
|
|
</defs>
|
|
<circle class="fl-accent" cx="70" cy="150" r="9"/>
|
|
<text x="70" y="182" text-anchor="middle" font-size="13">Anchor</text>
|
|
<circle class="fl-accent" cx="650" cy="150" r="9"/>
|
|
<text x="650" y="182" text-anchor="middle" font-size="13">Anchor</text>
|
|
<line class="st-true" x1="82" y1="150" x2="638" y2="150" stroke-width="2.5" stroke-dasharray="7 6" marker-end="url(#ah)"/>
|
|
<text class="fl-true" x="360" y="142" text-anchor="middle" font-size="12.5">true distance — the straight path</text>
|
|
<rect class="fl-badbg st-bad" x="330" y="120" width="60" height="60" rx="6" stroke-width="2"/>
|
|
<text class="fl-bad" x="360" y="157" text-anchor="middle" font-size="22">✕</text>
|
|
<text class="fl-bad" x="360" y="205" text-anchor="middle" font-size="12">blocked / weak</text>
|
|
<path class="st-bad" d="M82 145 Q 360 20 638 145" fill="none" stroke-width="3" marker-end="url(#ahb)"/>
|
|
<text class="fl-bad" x="360" y="46" text-anchor="middle" font-size="12.5">echo — a longer bounced path → reads too far ))))</text>
|
|
</svg>
|
|
<figcaption><b>Why an echo always reads long.</b> 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.</figcaption>
|
|
</figure>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="map">
|
|
<div class="stack" style="gap:20px">
|
|
<div class="measure">
|
|
<span class="eyebrow">The room, to scale</span>
|
|
<h2>Which measurements to trust — and the two that are broken</h2>
|
|
<p>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 <strong>red</strong>
|
|
links — both belonging to <strong>A3</strong> — read about 0.6 m too long. One link
|
|
(<strong>amber</strong>) reads oddly short.</p>
|
|
</div>
|
|
<figure class="fig">
|
|
<svg viewBox="0 0 720 560" role="img" aria-label="Top-down room map. Anchor A3's two long cross-room links to A1 and A2 read about 0.6 metres too long; its short link to A4 and the other links are clean; the A2 to A4 link reads short.">
|
|
<defs>
|
|
<pattern id="floor" width="26" height="26" patternUnits="userSpaceOnUse">
|
|
<path class="st-line" d="M0 26 L26 0 M-6 6 L6 -6 M20 32 L32 20" stroke-width="1"/>
|
|
</pattern>
|
|
<marker id="aim" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7.5" markerHeight="7.5" orient="auto"><path class="fl-accent" d="M0 0 L10 5 L0 10 z"/></marker>
|
|
</defs>
|
|
<rect x="250" y="130" width="210" height="340" fill="url(#floor)" class="st-line" stroke-width="1.5" rx="6" opacity="0.9"/>
|
|
|
|
<line class="st-good" x1="250" y1="470" x2="460" y2="470" stroke-width="4"/>
|
|
<line class="st-good" x1="250" y1="470" x2="460" y2="201" stroke-width="4"/>
|
|
<line class="st-good" x1="250" y1="130" x2="460" y2="201" stroke-width="4"/>
|
|
<line class="st-warn" x1="460" y1="470" x2="460" y2="201" stroke-width="4"/>
|
|
<line class="st-bad" x1="250" y1="470" x2="250" y2="130" stroke-width="5"/>
|
|
<line class="st-bad" x1="460" y1="470" x2="250" y2="130" stroke-width="5"/>
|
|
<text class="fl-bad" x="238" y="300" text-anchor="end" font-size="12">))))</text>
|
|
<text class="fl-bad" x="372" y="292" text-anchor="middle" font-size="12" transform="rotate(-59 372 292)">))))</text>
|
|
|
|
<g font-size="12.5" font-variant-numeric="tabular-nums">
|
|
<text class="fl-good" x="355" y="492" text-anchor="middle">2.44 m · true 2.29 · +0.15</text>
|
|
<text class="fl-good" x="372" y="352" text-anchor="middle" transform="rotate(-52 372 352)">3.88 · true 3.85 · ok</text>
|
|
<text class="fl-good" x="352" y="150" text-anchor="middle" transform="rotate(19 352 150)">2.72 · true 2.72 · ok</text>
|
|
<text class="fl-warn" x="470" y="335">2.85 · true 3.15</text>
|
|
<text class="fl-warn" x="470" y="352">−0.30 short ?</text>
|
|
<text class="fl-bad" x="242" y="300" text-anchor="end" font-weight="bold">4.29</text>
|
|
<text class="fl-bad" x="242" y="316" text-anchor="end">true 3.70</text>
|
|
<text class="fl-bad" x="242" y="332" text-anchor="end" font-weight="bold">+0.59 ✕</text>
|
|
<text class="fl-bad" x="388" y="278" text-anchor="middle" font-weight="bold" transform="rotate(-59 388 278)">5.02 · true 4.35 · +0.67 ✕</text>
|
|
</g>
|
|
|
|
<g class="st-accent" stroke-width="3" fill="none">
|
|
<line x1="259" y1="457" x2="283" y2="422" marker-end="url(#aim)"/>
|
|
<line x1="451" y1="457" x2="427" y2="422" marker-end="url(#aim)"/>
|
|
<line x1="258" y1="144" x2="278" y2="181" marker-end="url(#aim)"/>
|
|
<line x1="449" y1="213" x2="421" y2="244" marker-end="url(#aim)"/>
|
|
</g>
|
|
<circle class="fl-accent fl-panel" cx="250" cy="470" r="13" style="fill:var(--accent); stroke:var(--panel)" stroke-width="3"/>
|
|
<text class="fl-ink" x="234" y="500" text-anchor="end" font-size="14" font-weight="bold">A1</text>
|
|
<text class="fl-muted" x="234" y="517" text-anchor="end" font-size="11.5">root · 1.23 m high</text>
|
|
<circle cx="460" cy="470" r="12" style="fill:var(--accent); stroke:var(--panel)" stroke-width="3"/>
|
|
<text class="fl-ink" x="478" y="500" font-size="14" font-weight="bold">A2</text>
|
|
<text class="fl-muted" x="478" y="517" font-size="11.5">1.38 m high</text>
|
|
<circle cx="250" cy="130" r="16" style="fill:var(--bad); stroke:var(--panel)" stroke-width="3"/>
|
|
<circle class="st-bad" cx="250" cy="130" r="23" fill="none" stroke-width="1.5" opacity="0.5"/>
|
|
<text class="fl-ink" x="234" y="112" text-anchor="end" font-size="14" font-weight="bold">A3</text>
|
|
<text class="fl-muted" x="234" y="128" text-anchor="end" font-size="11.5">1.45 m high</text>
|
|
<text class="fl-bad" x="234" y="144" text-anchor="end" font-size="11">← the culprit</text>
|
|
<circle cx="460" cy="201" r="12" style="fill:var(--accent); stroke:var(--panel)" stroke-width="3"/>
|
|
<text class="fl-ink" x="478" y="196" font-size="14" font-weight="bold">A4</text>
|
|
<text class="fl-warn" x="478" y="213" font-size="11.5">0.20 m — on the floor</text>
|
|
|
|
<line class="st-muted" x1="250" y1="536" x2="342" y2="536" stroke-width="2"/>
|
|
<line class="st-muted" x1="250" y1="532" x2="250" y2="540" stroke-width="2"/>
|
|
<line class="st-muted" x1="342" y1="532" x2="342" y2="540" stroke-width="2"/>
|
|
<text class="fl-muted" x="296" y="552" text-anchor="middle" font-size="11">1 metre</text>
|
|
</svg>
|
|
<div class="legend" aria-hidden="true">
|
|
<span><span class="swatch" style="border-top-color:var(--good)"></span> measures true</span>
|
|
<span><span class="swatch" style="border-top-color:var(--bad)"></span> broken — reads ~0.6 m long</span>
|
|
<span><span class="swatch" style="border-top-color:var(--warn)"></span> suspicious — reads short</span>
|
|
<span><span class="swatch" style="border-top-color:var(--accent)"></span> anchor · ))) echo</span>
|
|
<span class="mono" style="color:var(--accent-ink)">▸ desired orientation — top edge to centre</span>
|
|
</div>
|
|
<figcaption><b>The pattern points straight at A3 — and the teal arrows are the fix.</b> A3's <b>short</b>
|
|
link (to the nearby floor anchor A4) is perfect; its two <b>long</b> 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: <b>upright, screen to the room, top edge (the antenna) aimed at the mesh centre</b>
|
|
— the arrows — so each anchor has a clear shot at all the others.</figcaption>
|
|
</figure>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="evidence">
|
|
<div class="stack" style="gap:18px">
|
|
<div class="measure">
|
|
<span class="eyebrow">The measurements · all five devices agree</span>
|
|
<h2>The six links, side by side</h2>
|
|
<p>"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 <strong>long tail</strong> of even-longer readings.</p>
|
|
</div>
|
|
<div class="tablewrap">
|
|
<table>
|
|
<thead>
|
|
<tr><th class="rowmark" aria-hidden="true"></th><th>Link</th><th>Measured</th><th>True (A4=0.20)</th><th>Difference</th><th>Spread</th><th>Reading</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="good"><td class="rowmark"></td><td class="mono">A1 – A2</td><td class="n">2.40 m</td><td class="n">2.29 m</td><td class="n delta-ok">+0.11</td>
|
|
<td><svg width="66" height="22" viewBox="0 0 66 22" aria-hidden="true"><path class="st-good" d="M2 20 L28 20 L33 4 L38 20 L64 20" fill="none" stroke-width="2"/></svg></td>
|
|
<td><span class="pill good">tight · small offset</span></td></tr>
|
|
<tr class="bad"><td class="rowmark"></td><td class="mono">A1 – A3</td><td class="n">4.27 m</td><td class="n">3.70 m</td><td class="n delta-bad">+0.57</td>
|
|
<td><svg width="66" height="22" viewBox="0 0 66 22" aria-hidden="true"><path class="st-bad" d="M2 20 L28 20 L33 4 L38 20 L64 20" fill="none" stroke-width="2"/></svg></td>
|
|
<td><span class="pill bad">↔ tight · steady +0.57</span></td></tr>
|
|
<tr class="good"><td class="rowmark"></td><td class="mono">A1 – A4</td><td class="n">3.79 m</td><td class="n">3.85 m</td><td class="n delta-ok">−0.06</td>
|
|
<td><svg width="66" height="22" viewBox="0 0 66 22" aria-hidden="true"><path class="st-good" d="M2 20 L28 20 L33 4 L38 20 L64 20" fill="none" stroke-width="2"/></svg></td>
|
|
<td><span class="pill good">clean ✓</span></td></tr>
|
|
<tr class="bad"><td class="rowmark"></td><td class="mono">A2 – A3</td><td class="n">5.10 m</td><td class="n">4.35 m</td><td class="n delta-bad">+0.75</td>
|
|
<td><svg width="66" height="22" viewBox="0 0 66 22" aria-hidden="true"><path class="st-bad" d="M2 20 L28 20 L33 4 L38 20 L64 20" fill="none" stroke-width="2"/></svg></td>
|
|
<td><span class="pill bad">↔ tight · steady +0.75</span></td></tr>
|
|
<tr class="warn"><td class="rowmark"></td><td class="mono">A2 – A4</td><td class="n">2.82 m</td><td class="n">3.15 m</td><td class="n delta-warn">−0.33</td>
|
|
<td><svg width="66" height="22" viewBox="0 0 66 22" aria-hidden="true"><path class="st-warn" d="M2 20 L20 20 L25 5 L30 14 C40 15 52 18 64 19" fill="none" stroke-width="2"/></svg></td>
|
|
<td><span class="pill warn">? short · noisy tail (A4)</span></td></tr>
|
|
<tr class="good"><td class="rowmark"></td><td class="mono">A3 – A4</td><td class="n">2.57 m</td><td class="n">2.72 m</td><td class="n delta-ok">−0.15</td>
|
|
<td><svg width="66" height="22" viewBox="0 0 66 22" aria-hidden="true"><path class="st-good" d="M2 20 L28 20 L33 4 L38 20 L64 20" fill="none" stroke-width="2"/></svg></td>
|
|
<td><span class="pill good">clean ✓</span></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p class="note measure"><strong>Capture 4 flipped one of these tells.</strong> Last capture the two
|
|
red links were inflated <em>and</em> carried a long tail — a jittery echo. After your re-aim the
|
|
<em>tails are gone</em> (they read tight now) but the <em>inflation stayed</em>: a <strong>fixed</strong>
|
|
+0.57 / +0.75 m stretch. It still isn't a per-device miscalibration — that would shift <em>all</em>
|
|
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 <em>paths</em>, not to A3's radio. The one remaining tail is now on
|
|
<strong>A2–A4</strong>, the loose floor anchor.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="whybreaks">
|
|
<div class="stack" style="gap:20px">
|
|
<div class="measure">
|
|
<span class="eyebrow">Why two bad links wreck everything</span>
|
|
<h2>The anchors can't agree on the room's shape</h2>
|
|
<p>To pin four anchors into one rigid 3-D shape, the mesh needs <strong>all six</strong> distances to
|
|
be right — there's no slack. A3 is held in place by three "ropes" (its three links). Two of them are
|
|
<strong>too long</strong>, so they pull A3 to the wrong spot. Because every anchor's position is
|
|
measured relative to the others, one wrong corner <strong>twists the whole shape</strong> — and each
|
|
phone, solving from its own slightly different readings, lands on a <strong>different</strong> wrong
|
|
shape. That's the "every diagnostics screen shows a different mesh" you saw.</p>
|
|
</div>
|
|
<figure class="fig">
|
|
<svg viewBox="0 0 720 250" role="img" aria-label="Anchor A3 is pulled to a wrong position by its two overly-long links, twisting the whole solved shape away from the true rectangle.">
|
|
<defs><marker id="ah2" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fl-muted" d="M0 0 L10 5 L0 10 z"/></marker></defs>
|
|
<text class="fl-muted" x="150" y="26" text-anchor="middle" font-size="12" letter-spacing=".05em">TRUE SHAPE</text>
|
|
<polygon class="st-good" points="70,200 230,200 230,70 70,70" fill="none" stroke-width="2.5"/>
|
|
<circle class="fl-accent" cx="70" cy="200" r="6"/><text class="fl-muted" x="58" y="220" text-anchor="end" font-size="12">A1</text>
|
|
<circle class="fl-accent" cx="230" cy="200" r="6"/><text class="fl-muted" x="238" y="220" font-size="12">A2</text>
|
|
<circle class="fl-good" cx="70" cy="70" r="8"/><text class="fl-muted" x="58" y="60" text-anchor="end" font-size="12">A3</text>
|
|
<circle class="fl-accent" cx="230" cy="70" r="6"/><text class="fl-muted" x="238" y="66" font-size="12">A4</text>
|
|
|
|
<line class="st-muted" x1="300" y1="135" x2="382" y2="135" stroke-width="2" marker-end="url(#ah2)"/>
|
|
|
|
<text class="fl-muted" x="558" y="26" text-anchor="middle" font-size="12" letter-spacing=".05em">WHAT THE MESH SOLVES</text>
|
|
<polygon class="st-faint" points="470,200 630,200 630,70 470,70" fill="none" stroke-width="1.5" stroke-dasharray="4 5" opacity="0.8"/>
|
|
<polygon class="st-bad" points="470,200 630,200 640,120 435,32" fill="none" stroke-width="2.5"/>
|
|
<line class="st-bad" x1="470" y1="200" x2="435" y2="32" stroke-width="2" opacity="0.55"/>
|
|
<line class="st-bad" x1="630" y1="200" x2="435" y2="32" stroke-width="2" opacity="0.55"/>
|
|
<circle class="fl-accent" cx="470" cy="200" r="6"/><text class="fl-muted" x="458" y="220" text-anchor="end" font-size="12">A1</text>
|
|
<circle class="fl-accent" cx="630" cy="200" r="6"/><text class="fl-muted" x="638" y="220" font-size="12">A2</text>
|
|
<circle class="fl-accent" cx="640" cy="120" r="6"/><text class="fl-muted" x="650" y="120" font-size="12">A4</text>
|
|
<circle class="st-faint" cx="470" cy="70" r="5" fill="none" stroke-width="1.5"/>
|
|
<line class="st-bad" x1="470" y1="70" x2="435" y2="32" stroke-width="1.5" stroke-dasharray="3 3"/>
|
|
<circle class="fl-bad" cx="435" cy="32" r="8"/>
|
|
<text class="fl-bad" x="415" y="26" text-anchor="end" font-size="12">A3 pulled here</text>
|
|
</svg>
|
|
<figcaption><b>One wrong corner twists the whole room.</b> 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.</figcaption>
|
|
</figure>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="hypothesis">
|
|
<div class="stack" style="gap:18px">
|
|
<div class="measure">
|
|
<span class="eyebrow">What specifically — updated after capture 4</span>
|
|
<h2>What's left on A3's two long links — now that the jitter is gone</h2>
|
|
<p>The data still says <em>A3's far side is the problem</em> (short link clean, both long links off by a
|
|
similar amount) — but capture 4 changed <em>which</em> cause is in play. The variable echo cleared,
|
|
so a switching-reflection story no longer fits; a <strong>fixed</strong> stretch does. One candidate is
|
|
ruled out and two remain, and a single A/B test tells them apart.</p>
|
|
</div>
|
|
<div class="grid cols3">
|
|
<div class="card stack" style="gap:8px; opacity:.72">
|
|
<div style="display:flex; justify-content:space-between; align-items:baseline"><span class="tag" style="color:var(--bad)">Ruled out</span><span class="rank">A</span></div>
|
|
<h3><s>Something blocks A3's view</s></h3>
|
|
<p class="note">Was my first guess — an obstruction (TV/shelf/metal/person) on the cross-room lines.
|
|
<strong>You confirmed the sight line is clear, and capture 4 agrees:</strong> a blocked path would
|
|
have kept a jittery tail, but the tails <em>collapsed</em> while the offset stayed. A clear line that
|
|
still reads long isn't a blockage. <strong>Dropped.</strong></p>
|
|
</div>
|
|
<div class="card stack" style="gap:8px">
|
|
<div style="display:flex; justify-content:space-between; align-items:baseline"><span class="tag" style="color:var(--good)">Leading</span><span class="rank">B</span></div>
|
|
<h3>The direct pulse is simply weak over the long span</h3>
|
|
<p class="note">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 <em>fixed</em> 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 <strong>orientation-independent</strong> — pinning bypasses it; removing it at
|
|
source needs first-path-aware ranging inside the phone's radio, which we don't control.</p>
|
|
</div>
|
|
<div class="card stack" style="gap:8px">
|
|
<div style="display:flex; justify-content:space-between; align-items:baseline"><span class="tag" style="color:var(--warn)">Still possible</span><span class="rank">C</span></div>
|
|
<h3>A3's antenna adds a fixed delay in that direction</h3>
|
|
<p class="note">UWB antennas delay the pulse by a small amount that varies with the <em>angle</em> 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 <strong>orientation-dependent</strong> — so
|
|
the A/B rotation test (turn A3 90°) separates it from B: bias moves ⇒ C, bias holds ⇒ B.</p>
|
|
</div>
|
|
</div>
|
|
<div class="card" style="border-left:5px solid var(--warn)">
|
|
<div style="display:flex; justify-content:space-between; align-items:baseline; margin-bottom:2px"><span class="tag" style="color:var(--warn)">Separate, smaller puzzle</span><span class="rank">A2 – A4</span></div>
|
|
<h3>One link reads <em>short</em>, which an echo can't explain</h3>
|
|
<p class="note measure">A2–A4 comes in ~30 cm <strong>shorter</strong> 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 <strong>A2 or the floor-anchor A4 isn't quite at its surveyed spot</strong> (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.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="placement">
|
|
<div class="stack" style="gap:20px">
|
|
<div class="measure">
|
|
<span class="eyebrow">Fix #1 · point the radios at the mesh</span>
|
|
<h2>These exact phones have a one-sided antenna — aim it</h2>
|
|
<p>Your four anchors are <strong>Samsung Galaxy S21+</strong> phones; the moving observer is a
|
|
<strong>Pixel 8 Pro</strong>. On all of them the ultra-wideband antenna sits near the
|
|
<strong>top edge</strong> and is <strong>directional</strong> — 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 <strong>one deterministic rule for all
|
|
four</strong>: <strong>upright, screen toward the room, top edge (the antenna) aimed at the mesh
|
|
centre</strong> — the teal arrows on the map above. That gives every anchor a clear shot at every
|
|
other, and it's repeatable test to test.</p>
|
|
</div>
|
|
<figure class="fig">
|
|
<svg viewBox="0 0 720 240" role="img" aria-label="Stand each anchor phone upright with its top edge toward the other anchors. A phone lying flat on the floor sends its signal into the floor and hears echoes.">
|
|
<line class="st-line" x1="352" y1="18" x2="352" y2="226" stroke-width="1" stroke-dasharray="4 5"/>
|
|
<text class="fl-good" x="40" y="32" font-size="13" font-weight="bold">DO — stand it upright</text>
|
|
<rect x="76" y="64" width="56" height="118" rx="9" style="fill:var(--panel2);stroke:var(--good)" stroke-width="3"/>
|
|
<rect class="fl-good" x="88" y="70" width="32" height="6" rx="3"/>
|
|
<text class="fl-muted" x="104" y="200" text-anchor="middle" font-size="11">top edge</text>
|
|
<path class="st-good" d="M132 92 L252 74 M132 104 L252 130" fill="none" stroke-width="2"/>
|
|
<circle class="fl-accent" cx="268" cy="74" r="6"/><text class="fl-muted" x="280" y="78" font-size="12">A1</text>
|
|
<circle class="fl-accent" cx="268" cy="130" r="6"/><text class="fl-muted" x="280" y="134" font-size="12">A2</text>
|
|
<text class="fl-good" x="178" y="150" text-anchor="middle" font-size="12">straight signal reaches them ✓</text>
|
|
<text class="fl-bad" x="400" y="32" font-size="13" font-weight="bold">DON'T — lie it flat</text>
|
|
<line class="st-muted" x1="396" y1="188" x2="700" y2="188" stroke-width="2"/>
|
|
<text class="fl-muted" x="400" y="204" font-size="11">floor</text>
|
|
<rect x="432" y="170" width="96" height="18" rx="6" style="fill:var(--panel2);stroke:var(--bad)" stroke-width="3"/>
|
|
<path class="st-bad" d="M480 188 L524 222 L582 150" fill="none" stroke-width="2" stroke-dasharray="6 4"/>
|
|
<text class="fl-bad" x="612" y="126" text-anchor="middle" font-size="12">into the floor → echo ✗</text>
|
|
<circle class="fl-accent" cx="662" cy="82" r="6"/><text class="fl-muted" x="648" y="86" text-anchor="end" font-size="12">A1/A2</text>
|
|
</svg>
|
|
<figcaption><b>Upright, top edge toward the mesh — the deterministic rule.</b> 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 <em>not</em> on a proper stand,
|
|
which is the likeliest reason its A2–A4 link is the one that's still noisy.</figcaption>
|
|
</figure>
|
|
<div class="grid cols3">
|
|
<div class="card stack" style="gap:6px">
|
|
<span class="tag" style="color:var(--bad)">A3 · re-aim it</span>
|
|
<p class="note">Turn A3 so its <strong>top edge points at the mesh centre</strong> (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.</p>
|
|
</div>
|
|
<div class="card stack" style="gap:6px">
|
|
<span class="tag" style="color:var(--warn)">A4 · put it on a stand</span>
|
|
<p class="note">A4 is the only anchor <strong>not on a tripod</strong> — 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.)</p>
|
|
</div>
|
|
<div class="card stack" style="gap:6px">
|
|
<span class="tag" style="color:var(--good)">All four · basics</span>
|
|
<p class="note">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.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="engine">
|
|
<div class="stack" style="gap:20px">
|
|
<div class="measure">
|
|
<span class="eyebrow">Fix #2 · the software should have flagged this</span>
|
|
<h2>Should the engine have caught the echo? Today it doesn't</h2>
|
|
<p>The engine <em>does</em> have echo defenses — but they're built to catch a sudden <strong>blip</strong>,
|
|
not a <strong>steady</strong> 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.</p>
|
|
</div>
|
|
<figure class="fig">
|
|
<svg viewBox="0 0 720 210" role="img" aria-label="The measurement passes through several software guards, but none catch a steady echo on a four-anchor mesh. The fix is to use the radio's own echo signal and flag lopsided links.">
|
|
<defs>
|
|
<marker id="ahm" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fl-muted" d="M0 0 L10 5 L0 10 z"/></marker>
|
|
<marker id="ahg" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fl-good" d="M0 0 L10 5 L0 10 z"/></marker>
|
|
</defs>
|
|
<g font-size="12.5" text-anchor="middle">
|
|
<rect x="10" y="34" width="150" height="54" rx="10" style="fill:var(--panel2);stroke:var(--line)" stroke-width="1.5"/>
|
|
<text class="fl-ink" x="85" y="58">anchor ↔ anchor</text><text class="fl-ink" x="85" y="75">range</text>
|
|
<rect x="196" y="34" width="150" height="54" rx="10" style="fill:var(--bad-bg);stroke:var(--bad)" stroke-width="1.5"/>
|
|
<text class="fl-ink" x="271" y="58">smooth +</text><text class="fl-ink" x="271" y="75">blip gate</text>
|
|
<text class="fl-bad" x="333" y="49" font-size="14">✗</text>
|
|
<rect x="382" y="34" width="150" height="54" rx="10" style="fill:var(--bad-bg);stroke:var(--bad)" stroke-width="1.5"/>
|
|
<text class="fl-ink" x="457" y="58">average into</text><text class="fl-ink" x="457" y="75">a shape</text>
|
|
<text class="fl-bad" x="519" y="49" font-size="14">✗</text>
|
|
<rect x="568" y="34" width="142" height="54" rx="10" style="fill:var(--bad-bg);stroke:var(--bad)" stroke-width="2.5"/>
|
|
<text class="fl-bad" x="639" y="58" font-weight="bold">wrong room</text><text class="fl-bad" x="639" y="75" font-weight="bold">shape</text>
|
|
</g>
|
|
<line class="st-muted" x1="162" y1="61" x2="194" y2="61" stroke-width="2" marker-end="url(#ahm)"/>
|
|
<line class="st-muted" x1="348" y1="61" x2="380" y2="61" stroke-width="2" marker-end="url(#ahm)"/>
|
|
<line class="st-muted" x1="534" y1="61" x2="566" y2="61" stroke-width="2" marker-end="url(#ahm)"/>
|
|
<text class="fl-muted" x="271" y="104" text-anchor="middle" font-size="11">steady echo slips through</text>
|
|
<text class="fl-muted" x="457" y="104" text-anchor="middle" font-size="11">(this check is skipped here)</text>
|
|
<rect x="150" y="150" width="360" height="46" rx="10" style="fill:var(--good-bg);stroke:var(--good)" stroke-width="1.5"/>
|
|
<text class="fl-good" x="330" y="171" text-anchor="middle" font-size="12.5" font-weight="bold">ADD — use the radio's own echo signal,</text>
|
|
<text class="fl-good" x="330" y="188" text-anchor="middle" font-size="12.5">flag lopsided links, and share one robust solve</text>
|
|
<line class="st-good" x1="271" y1="150" x2="271" y2="90" stroke-width="2" marker-end="url(#ahg)"/>
|
|
</svg>
|
|
<figcaption><b>Every current guard misses a steady echo — and the radio's own echo signal is thrown
|
|
away.</b> 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.</figcaption>
|
|
</figure>
|
|
<div class="grid cols2" style="grid-template-columns:1fr 1fr">
|
|
<div class="card stack" style="gap:8px">
|
|
<span class="tag">What exists — and why it misses this</span>
|
|
<ul class="note" style="margin:.1em 0 0; padding-left:1.1em; line-height:1.75">
|
|
<li><b>Blip gate</b> (3-sigma): the smoother settles onto the biased value, so every steady-long reading then looks normal.</li>
|
|
<li><b>Signal-quality gate:</b> present, but <b>fed a constant</b> — the real echo indicator (signal strength / first-path) is discarded in the phone layer before it arrives.</li>
|
|
<li><b>Triangle check:</b> the biased distances still form a valid (distorted) triangle, so nothing trips — and it doesn't run on the anchor-to-anchor path anyway.</li>
|
|
<li><b>Link-health report:</b> calls the bad links "strong," because the shape is fit <em>to</em> those biased numbers, so they look self-consistent.</li>
|
|
<li><b>Four anchors = no spare:</b> with exactly four, there's no extra measurement to cross-check, so one bad link can't be caught by maths alone.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="card stack" style="gap:8px">
|
|
<span class="tag" style="color:var(--good)">The update I recommend</span>
|
|
<ul class="note" style="margin:.1em 0 0; padding-left:1.1em; line-height:1.75">
|
|
<li><b>Capture the radio's echo signal</b> (signal strength + first-path) instead of a constant, and let a poor value distrust that reading — this revives the dead guard.</li>
|
|
<li><b>Guard the anchor-to-anchor path:</b> flag a link whose readings are <b>lopsided</b> (a long high tail) as echo-suspect and trust it less when solving the shape.</li>
|
|
<li><b>With only four anchors there's no spare</b> to cross-check by maths (a fifth isn't available), so that radio echo signal is the <em>essential</em> guard, not a nice-to-have — and it makes the deterministic orientation above matter even more.</li>
|
|
<li><b>Tell the operator:</b> say "A3's links look like echoes," not "strong."</li>
|
|
<li class="note" style="opacity:.85"><b>The real fix:</b> 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.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<p class="note measure">Bottom line: the engine can't <em>remove</em> a real echo — clean signal has to come
|
|
from the room — but it should <strong>detect it, distrust it, and say so</strong>, 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.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="confirm">
|
|
<div class="stack" style="gap:18px">
|
|
<div class="measure">
|
|
<span class="eyebrow">You already answered — here's what we now agree on</span>
|
|
<h2>What you confirmed, and what it ruled out</h2>
|
|
<p>You corrected the room for me last round. Two of your answers <em>changed</em> the diagnosis — flagged
|
|
below — and that is exactly what this loop is for.</p>
|
|
</div>
|
|
<ul class="check">
|
|
<li><span class="box" style="background:var(--good);border-color:var(--good)"></span><div><div class="q">Layout confirmed (A4 height corrected to 0.20 m)</div>
|
|
<small>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.</small></div></li>
|
|
<li><span class="box" style="background:var(--bad);border-color:var(--bad)"></span><div><div class="q">Nothing blocks the sight line A3↔A1/A2 — this ruled out my first guess</div>
|
|
<small>My original "most likely" cause was an obstruction on those two lines; <strong>you refuted it.</strong>
|
|
That's decisive: a <em>clear</em> 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 <strong>pinning</strong>.</small></div></li>
|
|
<li><span class="box" style="background:var(--good);border-color:var(--good)"></span><div><div class="q">Orientation is deterministic — and sufficient</div>
|
|
<small>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.</small></div></li>
|
|
<li><span class="box" style="background:var(--warn);border-color:var(--warn)"></span><div><div class="q">A4 is the loose one — and it shows</div>
|
|
<small>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.</small></div></li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="viewchain">
|
|
<div class="stack" style="gap:18px">
|
|
<div class="measure">
|
|
<span class="eyebrow">Your aim-and-view drawings → the fix sequence</span>
|
|
<h2>The picture is built in five steps — and they must be fixed in order</h2>
|
|
<p>You drew the goal: point the phone at A1, and A1 shows up in the right place and <em>stays</em> there
|
|
as you turn and orbit. The image on the screen is built as a <strong>chain</strong> — 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 <em>before the next one means anything</em>. Two
|
|
links are broken, and that order is exactly why the fixes are sequenced the way they are.</p>
|
|
</div>
|
|
<figure class="fig">
|
|
<svg viewBox="0 0 740 250" role="img" aria-label="The on-screen picture is a five-step chain: where the phone is (done), where the anchors are (fix now, every phone agrees on one self-solved map), direction to each (automatic), which way you look (fix next, heading lock), where it lands on screen (done plus an aim-sign check). All five correct gives: point at A1 and A1 stays locked in the view.">
|
|
<defs>
|
|
<marker id="ahv" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fl-muted" d="M0 0 L10 5 L0 10 z"/></marker>
|
|
<marker id="ahvg" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path class="fl-good" d="M0 0 L10 5 L0 10 z"/></marker>
|
|
</defs>
|
|
<g font-size="11.5" text-anchor="middle">
|
|
<!-- 1 done -->
|
|
<rect x="8" y="30" width="128" height="66" rx="10" style="fill:var(--good-bg);stroke:var(--good)" stroke-width="1.5"/>
|
|
<text class="fl-ink" x="72" y="52">where the</text><text class="fl-ink" x="72" y="67">phone is</text>
|
|
<text class="fl-good" x="72" y="87" font-size="10.5" font-weight="bold">✓ done · self-seed</text>
|
|
<!-- 2 fix now -->
|
|
<rect x="156" y="30" width="128" height="66" rx="10" style="fill:var(--bad-bg);stroke:var(--bad)" stroke-width="2.5"/>
|
|
<text class="fl-ink" x="220" y="52">where the</text><text class="fl-ink" x="220" y="67">anchors are</text>
|
|
<text class="fl-bad" x="220" y="87" font-size="10.5" font-weight="bold">① FIX NOW · one shared solve</text>
|
|
<!-- 3 auto -->
|
|
<rect x="304" y="30" width="128" height="66" rx="10" style="fill:var(--panel2);stroke:var(--line)" stroke-width="1.5"/>
|
|
<text class="fl-ink" x="368" y="52">direction to</text><text class="fl-ink" x="368" y="67">each anchor</text>
|
|
<text class="fl-muted" x="368" y="87" font-size="10.5">auto · once 1+2 right</text>
|
|
<!-- 4 fix next -->
|
|
<rect x="452" y="30" width="128" height="66" rx="10" style="fill:var(--warn-bg);stroke:var(--warn)" stroke-width="2.5"/>
|
|
<text class="fl-ink" x="516" y="52">which way</text><text class="fl-ink" x="516" y="67">you're looking</text>
|
|
<text class="fl-warn" x="516" y="87" font-size="10.5" font-weight="bold">② THEN · heading lock</text>
|
|
<!-- 5 done+check -->
|
|
<rect x="600" y="30" width="128" height="66" rx="10" style="fill:var(--good-bg);stroke:var(--good)" stroke-width="1.5"/>
|
|
<text class="fl-ink" x="664" y="52">where it lands</text><text class="fl-ink" x="664" y="67">on the glass</text>
|
|
<text class="fl-good" x="664" y="87" font-size="10.5" font-weight="bold">✓ done · +aim-sign check</text>
|
|
</g>
|
|
<!-- chain arrows -->
|
|
<g>
|
|
<line class="st-muted" x1="138" y1="63" x2="154" y2="63" stroke-width="2" marker-end="url(#ahv)"/>
|
|
<line class="st-muted" x1="286" y1="63" x2="302" y2="63" stroke-width="2" marker-end="url(#ahv)"/>
|
|
<line class="st-muted" x1="434" y1="63" x2="450" y2="63" stroke-width="2" marker-end="url(#ahv)"/>
|
|
<line class="st-muted" x1="582" y1="63" x2="598" y2="63" stroke-width="2" marker-end="url(#ahv)"/>
|
|
</g>
|
|
<!-- result -->
|
|
<line class="st-good" x1="368" y1="96" x2="368" y2="150" stroke-width="2.5" marker-end="url(#ahvg)"/>
|
|
<rect x="150" y="152" width="440" height="72" rx="12" style="fill:var(--good-bg);stroke:var(--good)" stroke-width="2"/>
|
|
<text class="fl-good" x="370" y="180" text-anchor="middle" font-size="13.5" font-weight="bold">All five correct →</text>
|
|
<text class="fl-ink" x="370" y="201" text-anchor="middle" font-size="12.5">point the phone at A1, and A1 appears at its true spot</text>
|
|
<text class="fl-ink" x="370" y="217" text-anchor="middle" font-size="12.5">and <tspan font-weight="bold">stays locked</tspan> as you turn in place and orbit — exactly your drawings.</text>
|
|
</svg>
|
|
<figcaption><b>Why the order is forced.</b> 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
|
|
<em>right places</em>. 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. <b>And step ① is mesh-wide:</b> right now the root and each
|
|
anchor each draw a <em>different</em> shape, because every phone solves the room from its own noisy
|
|
measurements and ignores the others. The mesh <em>already</em> computes one shared answer on the lead
|
|
phone and broadcasts it — the fix is to make every phone <em>use that one</em> 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). <b>No hand-entered locations</b> — 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.</figcaption>
|
|
</figure>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="next">
|
|
<div class="stack" style="gap:14px">
|
|
<div class="measure">
|
|
<span class="eyebrow">The re-test is in — here's what each side does next</span>
|
|
<h2>Fix the map in software; the room is already good enough</h2>
|
|
<p>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.</p>
|
|
</div>
|
|
<ul class="check">
|
|
<li><span class="box" style="background:var(--good);border-color:var(--good)"></span><div><div class="q">On my side — make every phone agree on one self-solved map (the real fix)</div>
|
|
<small>The mesh already works out one shared map on the lead phone and sends it to the others — but each
|
|
phone currently <strong>ignores it and draws its own guess</strong>, which is why you get four
|
|
different shapes. I make every phone <strong>use the one shared map</strong>, and make that shared
|
|
solve tougher so it doesn't hinge on the two bad links: it leans on the phones' <em>direction</em>
|
|
readings between anchors and on the <em>moving</em> phone's own (clean) distance readings as it walks
|
|
the room. <strong>No hand-entered locations</strong> — it stays self-solving for any sensible layout,
|
|
and flags a layout that genuinely can't be solved rather than locking a wrong one.</small></div></li>
|
|
<li><span class="box" style="background:var(--good);border-color:var(--good)"></span><div><div class="q">On my side — give the camera a compass again</div>
|
|
<small>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.</small></div></li>
|
|
<li><span class="box" style="background:var(--good);border-color:var(--good)"></span><div><div class="q">On my side — let the engine see the weak links</div>
|
|
<small>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.</small></div></li>
|
|
<li><span class="box"></span><div><div class="q">On your side — <em>only if</em> we want to kill the stretch at its source</div>
|
|
<small>One optional A/B: rotate <strong>A3</strong> 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.</small></div></li>
|
|
</ul>
|
|
<p class="measure" style="margin-top:6px">What the re-test actually showed, against the precise, falsifiable
|
|
prediction I made last round:</p>
|
|
<div class="card measure" style="border-left:5px solid var(--warn)">
|
|
<p style="margin:.2em 0"><strong>Predicted:</strong> A3's two links drop from
|
|
<span class="num">+0.59</span> / <span class="num">+0.67 m</span> to near zero <em>and</em> their long
|
|
tails disappear. <strong>Actual:</strong> the long <em>tails</em> disappeared
|
|
(<span class="num">0.70→0.11</span>, <span class="num">0.54→0.15 m</span>) — the echo half was real and
|
|
your re-aim killed it — but the <em>offsets stayed</em> (<span class="num">+0.57</span> /
|
|
<span class="num">+0.75 m</span>). So the map still won't lock.</p>
|
|
<p class="note" style="margin:.4em 0 0">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 <em>fixed</em> path-length or antenna effect — which is why the answer is now <strong>pin to the
|
|
survey</strong>, not chase the room further. The optional A/B above is the only thing that tells us which
|
|
of the two it physically is.</p>
|
|
</div>
|
|
<p class="note measure">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 <em>un</em>-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.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<footer>
|
|
<hr class="soft">
|
|
<p>Updated from capture <span class="kbd">mofetrace · 1785969…</span> (capture 4, the re-aim re-test) —
|
|
five devices, one build (<span class="kbd">de2f0c7</span>), all agreeing on the numbers above; supersedes
|
|
the first capture <span class="kbd">1785924…</span>. Distances are the steady (median) values over the
|
|
recording; before→after tails compare A3's long links across the two captures; "true" uses your layout with
|
|
A4 at 0.20 m. This is our converged picture — still offered to be corrected.</p>
|
|
</footer>
|
|
</div>
|