Files
timban c08fc277a9 Initial commit: NetworkCity — network traffic as a neon city
A native macOS network monitor that renders live traffic as a top-down
neon city: your Mac is downtown, remote orgs are glowing districts, and
traffic is cars of light driving the roads.

Architecture:
- NetworkCityCore: swappable data layer behind a ConnectionSource protocol
  (nettop-backed today), plus pure/tested logic — traffic diffing, org
  classification (reverse-DNS + CIDR), traffic classes, hub-and-spoke
  expansion policy, label anti-overlap, and latency→distance layout.
- NetworkCityApp: SwiftUI + SpriteKit city — auto-expanding districts,
  protocol-coloured cars, click-to-inspect panel, and latency-as-distance
  (districts glide to their measured RTT).
- nettop-probe: CLI proof of the data layer.

44 tests passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 08:43:32 -07:00

61 lines
2.3 KiB
Swift

import Foundation
/// Decides which districts auto-expand into hub-and-spoke each tick.
///
/// Rules: a district expands only if it has at least two endpoints (a single-IP
/// org has nothing to fan out) and ranks among the busiest `topK` above
/// `floorBytes`. Once chosen it stays expanded for `ttlTicks` ticks, so a burst
/// leaves a smooth tail instead of flickering collapsed the moment it idles.
///
/// Pure and deterministic given its input no SpriteKit, fully unit-testable.
public final class ExpansionPolicy {
public struct District: Sendable {
public let key: String
public let bytes: UInt64
public let endpointCount: Int
public init(key: String, bytes: UInt64, endpointCount: Int) {
self.key = key
self.bytes = bytes
self.endpointCount = endpointCount
}
}
private let topK: Int
private let ttlTicks: Int
private let floorBytes: UInt64
private var ttl: [String: Int] = [:]
public init(topK: Int = 4, ttlTicks: Int = 4, floorBytes: UInt64 = 8 * 1024) {
self.topK = topK
self.ttlTicks = ttlTicks
self.floorBytes = floorBytes
}
/// Advance one tick over the full set of currently-known districts (include
/// idle ones so their tail can decay). Returns the set of keys to render
/// expanded.
public func update(_ districts: [District]) -> Set<String> {
// Decay every existing timer.
for key in ttl.keys { ttl[key] = max(0, (ttl[key] ?? 0) - 1) }
// Refresh the busiest multi-endpoint districts.
var winners = 0
for d in districts.sorted(by: { $0.bytes > $1.bytes }) {
guard winners < topK else { break }
if d.bytes >= floorBytes && d.endpointCount >= 2 {
ttl[d.key] = ttlTicks
winners += 1
}
}
// Expanded = live timer AND still has something to fan out.
let endpointCounts = Dictionary(districts.map { ($0.key, $0.endpointCount) },
uniquingKeysWith: { a, _ in a })
var expanded = Set<String>()
for (key, remaining) in ttl where remaining > 0 {
if (endpointCounts[key] ?? 0) >= 2 { expanded.insert(key) }
}
return expanded
}
}