diff --git a/frontend/src/components/PacketVisualizer3D.tsx b/frontend/src/components/PacketVisualizer3D.tsx index 520d1c8..b472218 100644 --- a/frontend/src/components/PacketVisualizer3D.tsx +++ b/frontend/src/components/PacketVisualizer3D.tsx @@ -29,6 +29,9 @@ export function PacketVisualizer3D({ const [showAmbiguousPaths, setShowAmbiguousPaths] = useState(savedSettings.showAmbiguousPaths); const [showAmbiguousNodes, setShowAmbiguousNodes] = useState(savedSettings.showAmbiguousNodes); const [useAdvertPathHints, setUseAdvertPathHints] = useState(savedSettings.useAdvertPathHints); + const [collapseLikelyKnownSiblingRepeaters, setCollapseLikelyKnownSiblingRepeaters] = useState( + savedSettings.collapseLikelyKnownSiblingRepeaters + ); const [splitAmbiguousByTraffic, setSplitAmbiguousByTraffic] = useState( savedSettings.splitAmbiguousByTraffic ); @@ -52,6 +55,7 @@ export function PacketVisualizer3D({ showAmbiguousPaths, showAmbiguousNodes, useAdvertPathHints, + collapseLikelyKnownSiblingRepeaters, splitAmbiguousByTraffic, chargeStrength, observationWindowSec, @@ -66,6 +70,7 @@ export function PacketVisualizer3D({ showAmbiguousPaths, showAmbiguousNodes, useAdvertPathHints, + collapseLikelyKnownSiblingRepeaters, splitAmbiguousByTraffic, chargeStrength, observationWindowSec, @@ -108,6 +113,7 @@ export function PacketVisualizer3D({ showAmbiguousPaths, showAmbiguousNodes, useAdvertPathHints, + collapseLikelyKnownSiblingRepeaters, splitAmbiguousByTraffic, chargeStrength, letEmDrift, @@ -143,6 +149,8 @@ export function PacketVisualizer3D({ setShowAmbiguousNodes={setShowAmbiguousNodes} useAdvertPathHints={useAdvertPathHints} setUseAdvertPathHints={setUseAdvertPathHints} + collapseLikelyKnownSiblingRepeaters={collapseLikelyKnownSiblingRepeaters} + setCollapseLikelyKnownSiblingRepeaters={setCollapseLikelyKnownSiblingRepeaters} splitAmbiguousByTraffic={splitAmbiguousByTraffic} setSplitAmbiguousByTraffic={setSplitAmbiguousByTraffic} observationWindowSec={observationWindowSec} diff --git a/frontend/src/components/visualizer/AGENTS_packet_visualizer.md b/frontend/src/components/visualizer/AGENTS_packet_visualizer.md index 31dbf93..7d9b82d 100644 --- a/frontend/src/components/visualizer/AGENTS_packet_visualizer.md +++ b/frontend/src/components/visualizer/AGENTS_packet_visualizer.md @@ -94,8 +94,9 @@ When a new packet arrives from the WebSocket: ```typescript packets.forEach((packet) => { - if (processedRef.current.has(packet.id)) return; // Skip duplicates - processedRef.current.add(packet.id); + const observationKey = getRawPacketObservationKey(packet); + if (processedRef.current.has(observationKey)) return; // Skip duplicates + processedRef.current.add(observationKey); const parsed = parsePacket(packet.data); const key = generatePacketKey(parsed, packet); @@ -215,6 +216,8 @@ When a winner is found, the ambiguous node gets a `probableIdentity` label (the **Interaction with traffic splitting:** Advert-path hints run first. If a probable identity is found, the display name is set. Traffic splitting can still produce separate node IDs (`?XX:>YY`), but won't overwrite the advert-path display name. +**Sibling collapse projection:** When an ambiguous repeater has a high-confidence likely identity and that likely repeater also appears as a definitely-known sibling connecting to the same next hop, the projection layer can collapse the ambiguous node into the known repeater. This is projection-only: canonical observations and canonical neighbor truth remain unchanged. + **Toggle:** "Use repeater advert-path identity hints" checkbox (enabled by default, disabled when ambiguous repeaters are hidden). ### Traffic Pattern Splitting (Experimental) @@ -331,21 +334,22 @@ function buildPath(parsed, packet, myPrefix): string[] { ## Configuration Options -| Option | Default | Description | -| -------------------------- | ------- | --------------------------------------------------------- | -| Ambiguous repeaters | On | Show nodes when only partial prefix known | -| Ambiguous sender/recipient | Off | Show placeholder nodes for unknown senders | -| Advert-path identity hints | On | Use stored advert paths to label ambiguous repeaters | -| Split by traffic pattern | Off | Split ambiguous repeaters by next-hop routing (see above) | -| Observation window | 15 sec | Wait time for duplicate packets before animating (1-60s) | -| Let 'em drift | On | Continuous layout optimization | -| Repulsion | 200 | Force strength (50-2500) | -| Packet speed | 2x | Particle animation speed multiplier (1x-5x) | -| Shuffle layout | - | Button to randomize node positions and reheat sim | -| Oooh Big Stretch! | - | Button to temporarily increase repulsion then relax | -| Clear & Reset | - | Button to clear all nodes, links, and packets | -| Hide UI | Off | Hide legends and most controls for cleaner view | -| Full screen | Off | Hide the packet feed panel (desktop only) | +| Option | Default | Description | +| -------------------------- | ------- | ----------------------------------------------------------- | +| Ambiguous repeaters | On | Show nodes when only partial prefix known | +| Ambiguous sender/recipient | Off | Show placeholder nodes for unknown senders | +| Advert-path identity hints | On | Use stored advert paths to label ambiguous repeaters | +| Collapse sibling repeaters | On | Merge likely ambiguous repeater with known sibling repeater | +| Split by traffic pattern | Off | Split ambiguous repeaters by next-hop routing (see above) | +| Observation window | 15 sec | Wait time for duplicate packets before animating (1-60s) | +| Let 'em drift | On | Continuous layout optimization | +| Repulsion | 200 | Force strength (50-2500) | +| Packet speed | 2x | Particle animation speed multiplier (1x-5x) | +| Shuffle layout | - | Button to randomize node positions and reheat sim | +| Oooh Big Stretch! | - | Button to temporarily increase repulsion then relax | +| Clear & Reset | - | Button to clear all nodes, links, and packets | +| Hide UI | Off | Hide legends and most controls for cleaner view | +| Full screen | Off | Hide the packet feed panel (desktop only) | ## File Structure diff --git a/frontend/src/components/visualizer/VisualizerControls.tsx b/frontend/src/components/visualizer/VisualizerControls.tsx index 4dd4be8..a27ca85 100644 --- a/frontend/src/components/visualizer/VisualizerControls.tsx +++ b/frontend/src/components/visualizer/VisualizerControls.tsx @@ -13,6 +13,8 @@ interface VisualizerControlsProps { setShowAmbiguousNodes: (value: boolean) => void; useAdvertPathHints: boolean; setUseAdvertPathHints: (value: boolean) => void; + collapseLikelyKnownSiblingRepeaters: boolean; + setCollapseLikelyKnownSiblingRepeaters: (value: boolean) => void; splitAmbiguousByTraffic: boolean; setSplitAmbiguousByTraffic: (value: boolean) => void; observationWindowSec: number; @@ -46,6 +48,8 @@ export function VisualizerControls({ setShowAmbiguousNodes, useAdvertPathHints, setUseAdvertPathHints, + collapseLikelyKnownSiblingRepeaters, + setCollapseLikelyKnownSiblingRepeaters, splitAmbiguousByTraffic, setSplitAmbiguousByTraffic, observationWindowSec, @@ -149,55 +153,77 @@ export function VisualizerControls({ Show ambiguous sender/recipient - - -
- - - setObservationWindowSec( - Math.max(1, Math.min(60, parseInt(e.target.value, 10) || 1)) - ) - } - className="w-12 px-1 py-0.5 bg-background border border-border rounded text-xs text-center" - /> - sec -
+
+ + Advanced + +
+ + + +
+ + + setObservationWindowSec( + Math.max(1, Math.min(60, parseInt(e.target.value, 10) || 1)) + ) + } + className="w-12 px-1 py-0.5 bg-background border border-border rounded text-xs text-center" + /> + sec +
+
+