diff --git a/public/index.html b/public/index.html
index cb6dea5b..586a6c9a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -83,7 +83,7 @@
-
+
diff --git a/public/map.js b/public/map.js
index a037eb2d..e56ea535 100644
--- a/public/map.js
+++ b/public/map.js
@@ -62,9 +62,12 @@
function makeRepeaterLabelIcon(node) {
var s = ROLE_STYLE['repeater'] || ROLE_STYLE.companion;
- var label = node.hash_size ? node.hash_size + 'B' : '?';
- var bgColor = node.hash_size ? s.color : '#999';
- var html = '
' + label + '
';
+ var hs = node.hash_size || 1;
+ // Show the short mesh hash ID (first N bytes of pubkey, uppercased)
+ var shortHash = node.public_key ? node.public_key.slice(0, hs * 2).toUpperCase() : '??';
+ var bgColor = node.hash_size ? s.color : '#888';
+ var html = '' +
+ shortHash + '
';
return L.divIcon({
html: html,
className: 'meshcore-marker meshcore-label-marker',
@@ -374,31 +377,39 @@
var _renderingMarkers = false;
var _lastDeconflictZoom = null;
- function deconflictLabels(labelMarkers, map) {
+ function deconflictLabels(labelMarkers, mapRef) {
const placed = [];
- const LABEL_W = 32;
- const LABEL_H = 22;
- const PAD = 4;
+ const LABEL_W = 38;
+ const LABEL_H = 24;
+ const PAD = 6;
- const overlaps = (b) => placed.some(p =>
- b.x < p.x + p.w + PAD && b.x + b.w + PAD > p.x &&
- b.y < p.y + p.h + PAD && b.y + b.h + PAD > p.y
- );
+ const overlaps = function(b) {
+ return placed.some(function(p) {
+ return b.x < p.x + p.w + PAD && b.x + b.w + PAD > p.x &&
+ b.y < p.y + p.h + PAD && b.y + b.h + PAD > p.y;
+ });
+ };
- for (const m of labelMarkers) {
- const pt = map.latLngToLayerPoint(m.latLng);
- let bestPt = pt;
- let box = { x: pt.x - LABEL_W / 2, y: pt.y - LABEL_H / 2, w: LABEL_W, h: LABEL_H };
+ // Generate spiral offsets — 5 rings, 8 directions each, up to 120px out
+ var offsets = [];
+ for (var ring = 1; ring <= 6; ring++) {
+ var dist = ring * 28;
+ for (var angle = 0; angle < 360; angle += 45) {
+ var rad = angle * Math.PI / 180;
+ offsets.push([Math.round(Math.cos(rad) * dist), Math.round(Math.sin(rad) * dist)]);
+ }
+ }
+
+ for (var i = 0; i < labelMarkers.length; i++) {
+ var m = labelMarkers[i];
+ var pt = mapRef.latLngToLayerPoint(m.latLng);
+ var bestPt = pt;
+ var box = { x: pt.x - LABEL_W / 2, y: pt.y - LABEL_H / 2, w: LABEL_W, h: LABEL_H };
if (overlaps(box)) {
- const offsets = [
- [0, -25], [0, 25], [-30, 0], [30, 0],
- [-25, -20], [25, -20], [-25, 20], [25, 20],
- [0, -45], [0, 45], [-50, 0], [50, 0]
- ];
- for (const [dx, dy] of offsets) {
- const tryPt = L.point(pt.x + dx, pt.y + dy);
- const tryBox = { x: tryPt.x - LABEL_W / 2, y: tryPt.y - LABEL_H / 2, w: LABEL_W, h: LABEL_H };
+ for (var j = 0; j < offsets.length; j++) {
+ var tryPt = L.point(pt.x + offsets[j][0], pt.y + offsets[j][1]);
+ var tryBox = { x: tryPt.x - LABEL_W / 2, y: tryPt.y - LABEL_H / 2, w: LABEL_W, h: LABEL_H };
if (!overlaps(tryBox)) {
bestPt = tryPt;
box = tryBox;
@@ -408,7 +419,7 @@
}
placed.push(box);
- m.adjustedLatLng = map.layerPointToLatLng(bestPt);
+ m.adjustedLatLng = mapRef.layerPointToLatLng(bestPt);
m.offset = Math.sqrt(Math.pow(bestPt.x - pt.x, 2) + Math.pow(bestPt.y - pt.y, 2));
}
}
diff --git a/server.js b/server.js
index f04c5a94..b0599311 100644
--- a/server.js
+++ b/server.js
@@ -973,20 +973,45 @@ app.get('/api/nodes', (req, res) => {
counts[r + 's'] = db.db.prepare(`SELECT COUNT(*) as count FROM nodes WHERE role = ?`).get(r).count;
}
- // Compute hash_size for each node from latest ADVERT packets
+ // Compute hash_size for each node from ADVERT path byte or path hop lengths
const hashSizeMap = new Map();
+ // Pass 1: from ADVERT packets (most authoritative — path byte bits 7-6)
for (const p of pktStore.packets) {
- if (p.payload_type === 4 && p.decoded_json) {
+ if (p.payload_type === 4 && p.raw_hex) {
try {
- const d = JSON.parse(p.decoded_json);
+ const d = JSON.parse(p.decoded_json || '{}');
const pk = d.pubKey || d.public_key;
- if (pk && p.raw_hex && !hashSizeMap.has(pk)) {
+ if (pk && !hashSizeMap.has(pk)) {
const pathByte = parseInt(p.raw_hex.slice(2, 4), 16);
hashSizeMap.set(pk, ((pathByte >> 6) & 0x3) + 1);
}
} catch {}
}
}
+ // Pass 2: for nodes without ADVERTs, derive from path hop lengths in any packet
+ // Path hops are hex strings; their length / 2 = hash_size in bytes
+ for (const p of pktStore.packets) {
+ if (p.path_json) {
+ try {
+ const hops = JSON.parse(p.path_json);
+ if (hops.length > 0) {
+ const hopLen = hops[0].length / 2; // hex chars / 2 = bytes
+ if (hopLen >= 1 && hopLen <= 4) {
+ // The path byte tells us hash_size for ALL nodes in this packet's mesh
+ const pathByte = p.raw_hex ? parseInt(p.raw_hex.slice(2, 4), 16) : -1;
+ const hs = pathByte >= 0 ? ((pathByte >> 6) & 0x3) + 1 : hopLen;
+ // We can't map hops to pubkeys directly (hops are truncated hashes)
+ // but we know the packet's source node uses this hash_size
+ if (p.decoded_json) {
+ const d = JSON.parse(p.decoded_json);
+ const pk = d.pubKey || d.public_key;
+ if (pk && !hashSizeMap.has(pk)) hashSizeMap.set(pk, hs);
+ }
+ }
+ }
+ } catch {}
+ }
+ }
for (const node of nodes) {
node.hash_size = hashSizeMap.get(node.public_key) || null;
}