Files
AetherForge/server/web/src/help/fleetHeatMap.ts
AetherForge a32860b0d9
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
feat: alive UI wave, galaxy presence, spread and fleet enhancements
Dashboard ambient layer, comrade presence, Mission Deck and War Room, Emberwake supply chain, spread/docs publishing, fleet policy and modules API, CI docker mining, and refreshed USB pack.
2026-06-04 22:36:17 -07:00

130 lines
3.9 KiB
TypeScript

import type { Agent } from '../types';
import type { FleetGroup } from './fleetGroups';
import { FLEET_GROUP_COLORS, primaryGroupForAgent } from './fleetGroups';
export interface MapPoint {
id: string;
kind: 'agent' | 'comrade';
x: number;
y: number;
label: string;
color?: string;
online?: boolean;
}
export const COMRADE_DOT_COLOR = '#ffb020';
export const HASHRATE_SPIKE_RATIO = 1.25;
export const HASHRATE_SPIKE_MIN_DELTA = 50;
export function hashString(seed: string): number {
let h = 0;
for (let i = 0; i < seed.length; i++) {
h = (h * 31 + seed.charCodeAt(i)) >>> 0;
}
return h;
}
/** Stable pseudo-random position from a string seed (percent coords). */
export function hashPosition(seed: string, margin = 12): { x: number; y: number } {
const h = hashString(seed);
const range = 100 - margin * 2;
return {
x: margin + ((h % 1000) / 1000) * range,
y: margin + (((h >>> 10) % 1000) / 1000) * range,
};
}
export function groupClusterCenter(
groupIndex: number,
totalGroups: number,
margin = 14,
): { x: number; y: number } {
const angle = (groupIndex / Math.max(totalGroups, 1)) * Math.PI * 2 - Math.PI / 2;
const cx = 50 + Math.cos(angle) * 30;
const cy = 50 + Math.sin(angle) * 30;
return {
x: Math.max(margin, Math.min(100 - margin, cx)),
y: Math.max(margin, Math.min(100 - margin, cy)),
};
}
export function agentMapPosition(
agent: Agent,
group: FleetGroup | undefined,
groupIndex: number,
totalGroups: number,
agentIndexInCluster: number,
clusterSize: number,
): { x: number; y: number } {
const seed = agent.hostname || agent.name || agent.id;
if (group) {
const center = groupClusterCenter(groupIndex, totalGroups);
const jitter = hashPosition(`${group.id}:${agent.id}`, 0);
const spread = Math.min(9, 2.5 + clusterSize * 0.7);
const angle = (agentIndexInCluster / Math.max(clusterSize, 1)) * Math.PI * 2;
return {
x: center.x + Math.cos(angle) * spread + (jitter.x - 50) * 0.06,
y: center.y + Math.sin(angle) * spread + (jitter.y - 50) * 0.06,
};
}
return hashPosition(seed);
}
export function agentAccentColor(agentId: string, allIds: string[], groupColor?: string): string {
if (groupColor) return groupColor;
const idx = allIds.indexOf(agentId);
return FLEET_GROUP_COLORS[idx % FLEET_GROUP_COLORS.length] ?? FLEET_GROUP_COLORS[0];
}
export function hashrateSpiked(prev: number | undefined, current: number): boolean {
if (current <= 0) return false;
if (prev === undefined || prev <= 0) return current >= HASHRATE_SPIKE_MIN_DELTA;
const delta = current - prev;
return delta >= HASHRATE_SPIKE_MIN_DELTA && current >= prev * HASHRATE_SPIKE_RATIO;
}
export function layoutAgentPoints(agents: Agent[], groups: FleetGroup[]): MapPoint[] {
const groupsWithAgents = groups.filter((g) => agents.some((a) => g.agentIds.includes(a.id)));
return agents.map((agent) => {
const pg = primaryGroupForAgent(groups, agent.id);
const groupIndex = pg ? groupsWithAgents.findIndex((g) => g.id === pg.id) : -1;
const clusterAgents = pg ? agents.filter((a) => pg.agentIds.includes(a.id)) : [];
const agentIndexInCluster = pg ? clusterAgents.findIndex((a) => a.id === agent.id) : 0;
const pos = agentMapPosition(
agent,
pg,
groupIndex >= 0 ? groupIndex : 0,
groupsWithAgents.length || 1,
agentIndexInCluster,
clusterAgents.length,
);
return {
id: agent.id,
kind: 'agent' as const,
x: pos.x,
y: pos.y,
label: agent.name,
color: pg?.color,
online: agent.status === 'online',
};
});
}
export function layoutComradePoints(users: string[]): MapPoint[] {
return users.map((user) => {
const pos = hashPosition(`comrade:${user}`, 8);
return {
id: `comrade:${user}`,
kind: 'comrade' as const,
x: pos.x,
y: pos.y,
label: user,
color: COMRADE_DOT_COLOR,
online: true,
};
});
}