From c10f4b43cb87cb416e189b65c056c41dbdc15ee2 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sat, 9 Aug 2025 21:33:13 +0200 Subject: [PATCH] refactor(RegionMap.tsx): rename meanInboundAngle to meanNeighborAngle and update logic to compute mean angle to neighbors --- frontend/src/components/RegionMap.tsx | 89 ++++++++++++--------------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/frontend/src/components/RegionMap.tsx b/frontend/src/components/RegionMap.tsx index e4b9b71..18ce64d 100644 --- a/frontend/src/components/RegionMap.tsx +++ b/frontend/src/components/RegionMap.tsx @@ -96,7 +96,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho type OffIndicator = { from: string; toRegion: string; count: number; color: string; angle: number; sampleTo?: string }; const [offRegionIndicators, setOffRegionIndicators] = useState([]); - const [meanInboundAngle, setMeanInboundAngle] = useState>({}); + const [meanNeighborAngle, setMeanNeighborAngle] = useState>({}); const [charLocs, setCharLocs] = useState>([]); useEffect(() => { @@ -133,7 +133,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho setPositions(positions); const connections = computeNodeConnections(systems); setConnections(connections); - // Compute per-system mean outbound BEARING (0=north, clockwise positive) to in-region neighbors + // Compute per-system mean outbound angle in screen coords (atan2(dy,dx)) to in-region neighbors const angleMap: Record = {}; systems.forEach((sys, name) => { const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean); @@ -142,17 +142,17 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho const neighbor = systems.get(n); if (!neighbor) continue; const dx = neighbor.x - sys.x; - const dy = neighbor.y - sys.y; // screen coords (y down) - const bearing = Math.atan2(dx, -dy); // bearing relative to north - sumSin += Math.sin(bearing); - sumCos += Math.cos(bearing); + const dy = neighbor.y - sys.y; // y-down screen + const a = Math.atan2(dy, dx); + sumSin += Math.sin(a); + sumCos += Math.cos(a); count++; } if (count > 0) { - angleMap[name] = Math.atan2(sumSin, sumCos); // average bearing + angleMap[name] = Math.atan2(sumSin, sumCos); // average angle } }); - setMeanInboundAngle(angleMap); + setMeanNeighborAngle(angleMap); }, [systems]); // Poll character locations every 7s and store those in this region @@ -208,58 +208,49 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho } await ensureUniversePositions(); - // Build indicators: group by from+toRegion - const grouped: Map = new Map(); + // Build indicators: group by from+toRegion; angle from local geometry only (meanNeighborAngle + PI) + type Agg = { from: string; toRegion: string; count: number; sumRemoteSec: number; sampleTo?: string }; + const grouped: Map = new Map(); for (const [fromName, sys] of systems.entries()) { const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean); for (const n of neighbors) { if (systems.has(n)) continue; const toRegion = nameToRegion[n]; if (!toRegion || toRegion === regionName) continue; - - // compute color const remote = regionSystemsCache.get(toRegion)?.get(n); - const avgSec = ((sys.security || 0) + (remote?.security || 0)) / 2; - const color = getSecurityColor(avgSec); - - // compute angle via universe region centroids - let angle: number | undefined = undefined; - const inbound = meanInboundAngle[fromName]; - if (inbound !== undefined) { - angle = inbound; // mean outbound bearing already computed - } else { - const curPos = universeRegionPosCache.get(regionName); - const toPos = universeRegionPosCache.get(toRegion); - if (curPos && toPos) { - const dxr = toPos.x - curPos.x; - const dyr = toPos.y - curPos.y; - angle = Math.atan2(dxr, -dyr); // bearing to remote region - } - } - if (angle === undefined) { - // final fallback deterministic angle - let h = 0; const key = `${fromName}->${toRegion}`; - for (let i = 0; i < key.length; i++) h = (h * 31 + key.charCodeAt(i)) >>> 0; - angle = (h % 360) * (Math.PI / 180); - } - // Flip 180° so indicator points away from existing connections - angle = angle + Math.PI; - const gkey = `${fromName}__${toRegion}`; - const prev = grouped.get(gkey); - if (prev) { - prev.count += 1; - if (!prev.sampleTo) prev.sampleTo = n; - } else { - grouped.set(gkey, { from: fromName, toRegion, count: 1, color, angle, sampleTo: n }); - } + const agg = grouped.get(gkey) || { from: fromName, toRegion, count: 0, sumRemoteSec: 0, sampleTo: n }; + agg.count += 1; + if (remote) agg.sumRemoteSec += (remote.security || 0); + grouped.set(gkey, agg); } } + const out: OffIndicator[] = []; + for (const [, agg] of grouped) { + if (agg.count === 0) continue; + // Angle: point away from existing connections = meanNeighborAngle + PI + let angle = meanNeighborAngle[agg.from]; + if (angle === undefined) { + // fallback: away from region centroid + // compute centroid of current region nodes + let cx = 0, cy = 0, c = 0; + systems.forEach(s => { cx += s.x; cy += s.y; c++; }); + if (c > 0) { cx /= c; cy /= c; } + const sys = systems.get(agg.from)!; + angle = Math.atan2(sys.y - cy, sys.x - cx); + } + angle = angle + Math.PI; - setOffRegionIndicators(Array.from(grouped.values())); + // Color from avg of local system sec and avg remote sec; local from this system + const localSec = (systems.get(agg.from)?.security || 0); + const remoteAvg = agg.count > 0 ? (agg.sumRemoteSec / agg.count) : 0; + const color = getSecurityColor((localSec + remoteAvg) / 2); + out.push({ from: agg.from, toRegion: agg.toRegion, count: agg.count, color, angle, sampleTo: agg.sampleTo }); + } + setOffRegionIndicators(out); }; computeOffRegion(); - }, [systems, regionName]); + }, [systems, regionName, meanNeighborAngle]); useEffect(() => { if (isWormholeRegion) { @@ -686,8 +677,8 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho if (!pos) return null; const len = 26; const r0 = 10; // start just outside node - const dx = Math.sin(ind.angle); - const dy = -Math.cos(ind.angle); + const dx = Math.cos(ind.angle); + const dy = Math.sin(ind.angle); const x1 = pos.x + dx * r0; const y1 = pos.y + dy * r0; const x2 = x1 + dx * len;