diff --git a/docs/HASH-PREFIX-DISAMBIGUATION.md b/docs/HASH-PREFIX-DISAMBIGUATION.md
index e126d44c..00caa783 100644
--- a/docs/HASH-PREFIX-DISAMBIGUATION.md
+++ b/docs/HASH-PREFIX-DISAMBIGUATION.md
@@ -87,6 +87,61 @@ The primary workhorse. Used by most API endpoints. Algorithm:
5. **Sanity check**: Mark hops as `unreliable` if they're >MAX_HOP_DIST (default 1.8° ≈ 200 km) from both neighbors. Clear their lat/lon.
+#### Visual: Forward + Backward Pass
+
+```mermaid
+graph LR
+ subgraph "Raw path (after candidate lookup)"
+ A["?A
ambiguous
3 candidates"]
+ B["?B
ambiguous
2 candidates"]
+ C["C ✅
known
San Jose"]
+ D["?D
ambiguous
4 candidates"]
+ E["E ✅
known
Oakland"]
+ end
+
+ A --> B --> C --> D --> E
+```
+
+```mermaid
+graph LR
+ subgraph "Forward pass →"
+ A1["?A
⏭ skip
(no anchor)"]
+ B1["?B
⏭ skip
(no anchor)"]
+ C1["C ✅
anchor set"]
+ D1["?D → D ✅
pick nearest to C"]
+ E1["E ✅
anchor set"]
+ end
+
+ A1 -->|"→"| B1 -->|"→"| C1 -->|"→"| D1 -->|"→"| E1
+
+ style C1 fill:#166534,color:#fff
+ style D1 fill:#166534,color:#fff
+ style E1 fill:#166534,color:#fff
+ style A1 fill:#991b1b,color:#fff
+ style B1 fill:#991b1b,color:#fff
+```
+
+```mermaid
+graph RL
+ subgraph "Backward pass ←"
+ E2["E ✅
anchor set"]
+ D2["D ✅
already resolved"]
+ C2["C ✅
anchor set"]
+ B2["?B → B ✅
pick nearest to C"]
+ A2["?A → A ✅
pick nearest to B"]
+ end
+
+ E2 -->|"←"| D2 -->|"←"| C2 -->|"←"| B2 -->|"←"| A2
+
+ style A2 fill:#166534,color:#fff
+ style B2 fill:#166534,color:#fff
+ style C2 fill:#166534,color:#fff
+ style D2 fill:#166534,color:#fff
+ style E2 fill:#166534,color:#fff
+```
+
+**Why two passes?** The forward pass can't resolve hops at the *start* of the path because there's no preceding anchor. The backward pass catches these by anchoring from the right. Together, every ambiguous hop is guaranteed at least one resolved neighbor to measure distance against.
+
**Callsites** (all in `server.js`):
| Line | Context |
|------|---------|