johnosborne/splay-network-tracking-detection
0
1import math2import random3from collections import defaultdict4 5# --- Configuration (Defaults) ---6AREA_SIZE = 1000.07N_CLUSTERS = 58T_MAX = 309ALPHA = 0.5; BETA = 0.3; KAPPA = 0.1; ZETA = 0.110 11def dist(a, b): return math.hypot(a[0]-b[0], a[1]-b[1])12 13class Node:14 def __init__(self, idx, pos, cluster):15 self.idx = idx; self.pos = pos; self.cluster = int(cluster)16 self.batt = 100.0; self.dead = False; self.S = 0.0; self.fair = 0.017 self.is_head = False; self.head_since = 018 self.dead_since = None19 20 def get_color(self):21 if self.dead: return '#ff0000' # Red22 if self.batt > 50: return '#00ff00' # Green23 if self.batt > 20: return '#ffff00' # Yellow24 return '#ff9900' # Orange25 26 def consume(self, amount, sim_time):27 if self.dead: return28 self.batt -= amount29 if self.batt <= 0: 30 self.batt = 0; self.dead = True; self.is_head = False31 self.dead_since = sim_time32 33def calculate_utility(node, gateway):34 term_S = min(node.S, 1.0); term_E = node.batt / 100.035 term_fair = min(node.fair, 1.0)36 d_gate = dist(node.pos, gateway)37 term_lq = 1.0 - (d_gate / (AREA_SIZE * 1.414))38 return ALPHA*term_S + BETA*term_E + KAPPA*term_fair + ZETA*term_lq39 40class Simulation:41 def __init__(self, n_nodes=50):42 self.n_nodes = n_nodes43 self.rng = random.Random() # New random instance44 self.nodes = []45 self.clusters = defaultdict(list)46 self.current_heads = {}47 self.sim_time = 048 self.gateway = (AREA_SIZE/2, AREA_SIZE/2)49 self.reset(n_nodes)50 51 def reset(self, n_nodes):52 self.n_nodes = int(n_nodes)53 self.sim_time = 054 55 # Setup Network56 centers = [(self.rng.uniform(100, AREA_SIZE-100), self.rng.uniform(100, AREA_SIZE-100)) for _ in range(N_CLUSTERS)]57 self.nodes = []58 for i in range(self.n_nodes):59 c_idx = self.rng.randint(0, N_CLUSTERS-1)60 cx, cy = centers[c_idx]61 nx = cx + self.rng.gauss(0, 80)62 ny = cy + self.rng.gauss(0, 80)63 pos = (max(0, min(AREA_SIZE, nx)), max(0, min(AREA_SIZE, ny)))64 self.nodes.append(Node(i, pos, c_idx))65 66 self.clusters = defaultdict(list)67 for n in self.nodes: self.clusters[n.cluster].append(n)68 self.current_heads = {c: None for c in self.clusters}69 70 def step(self):71 # Run logic 1x per frame to slow down backend progression too, or keep it 3x? 72 # User said "simulation is going really fast", often better to slow down updates.73 # Let's reduce internal ticks to 1 per step call as well.74 for _ in range(1):75 self._run_simulation_step()76 77 return self.get_state()78 79 def _run_simulation_step(self):80 self.sim_time += 181 ev_pos = (self.rng.uniform(0, AREA_SIZE), self.rng.uniform(0, AREA_SIZE))82 83 for n in self.nodes:84 if n.dead: continue85 n.consume(0.2, self.sim_time)86 n.S *= 0.987 if not n.is_head: n.fair += 0.0288 if dist(n.pos, ev_pos) < 150: n.S += 0.8; n.consume(0.5, self.sim_time)89 90 for c_id, members in self.clusters.items():91 head = self.current_heads.get(c_id)92 if head is None or head.dead or (head and (self.sim_time - head.head_since) > T_MAX):93 candidates = [n for n in members if not n.dead]94 if candidates:95 winner = max(candidates, key=lambda n: calculate_utility(n, self.gateway))96 if head and head != winner: head.is_head = False97 self.current_heads[c_id] = winner; winner.is_head = True98 winner.head_since = self.sim_time; winner.fair = 0.0; winner.consume(1.5, self.sim_time)99 100 def get_state(self):101 # Return serializable state102 nodes_data = []103 links = []104 dead_nodes_stats = []105 106 for n in self.nodes:107 color = n.get_color()108 109 # Logic for links: if not head, link to head110 if not n.is_head and not n.dead:111 head = self.current_heads.get(n.cluster)112 if head and not head.dead:113 links.append({114 'start': n.pos,115 'end': head.pos116 })117 118 if n.dead:119 downtime = self.sim_time - n.dead_since if n.dead_since is not None else 0120 dead_nodes_stats.append({121 'id': n.idx,122 'dead_since': n.dead_since,123 'downtime': downtime124 })125 126 nodes_data.append({127 'id': n.idx,128 'x': n.pos[0],129 'y': n.pos[1],130 'color': color,131 'is_head': n.is_head,132 'dead': n.dead,133 'batt': n.batt,134 'cluster': n.cluster135 })136 137 return {138 'sim_time': self.sim_time,139 'gateway': self.gateway,140 'nodes': nodes_data,141 'links': links,142 'dead_stats': dead_nodes_stats143 }144 