import SpriteKit import NetworkCityCore /// How a car of light looks for a given traffic class + magnitude. struct CarStyle { let color: SKColor let scale: CGFloat let elongation: CGFloat // 1 = round dot; >1 = streak along motion let speed: CGFloat // points/sec } /// Colour per traffic class — direction is read from motion, so hue is free to /// mean protocol. enum TrafficPalette { static func color(_ c: TrafficClass) -> SKColor { switch c { case .dns: return SKColor(red: 1.00, green: 0.82, blue: 0.25, alpha: 1) // gold case .https: return SKColor(red: 0.22, green: 0.88, blue: 1.00, alpha: 1) // cyan case .http: return SKColor(red: 1.00, green: 0.48, blue: 0.24, alpha: 1) // orange case .quic: return SKColor(red: 0.71, green: 0.48, blue: 1.00, alpha: 1) // violet case .other: return SKColor(red: 0.55, green: 0.60, blue: 0.72, alpha: 1) // slate } } } /// Translate a class + this-tick byte volume into a car's personality. Heavy /// flows become "freight": bigger, slower, stretched into a glowing comet. func carStyle(_ cls: TrafficClass, bytes: UInt64) -> CarStyle { let heavy = bytes > 196_608 // ~96 KB/s over a 2s tick var scale: CGFloat var elongation: CGFloat = 1 var speed: CGFloat = 230 switch cls { case .dns: scale = 0.055; speed = 360 // tiny, fast sparks case .https: scale = 0.085 case .http: scale = 0.085 case .quic: scale = 0.085; elongation = 2.4; speed = 285 // streaks case .other: scale = 0.065 } if heavy { scale *= 1.7 elongation = max(elongation, 3.0) // freight comet speed *= 0.7 // slow and heavy } return CarStyle(color: TrafficPalette.color(cls), scale: scale, elongation: elongation, speed: speed) } /// Neon-night palette. Bright, saturated colors on near-black so additive /// blending reads as glow. enum Palette { static let background = SKColor(red: 0.039, green: 0.055, blue: 0.102, alpha: 1) // #0a0e1a static let grid = SKColor(red: 0.13, green: 0.20, blue: 0.38, alpha: 1) static let hub = SKColor(red: 1.00, green: 0.85, blue: 0.62, alpha: 1) // warm downtown static let building = SKColor(red: 0.50, green: 0.82, blue: 0.78, alpha: 1) // teal static let road = SKColor(red: 0.17, green: 0.26, blue: 0.46, alpha: 1) static let download = SKColor(red: 0.22, green: 0.88, blue: 1.00, alpha: 1) // cyan, inbound static let upload = SKColor(red: 1.00, green: 0.62, blue: 0.30, alpha: 1) // amber, outbound } extension CGFloat { func clamped(_ lo: CGFloat, _ hi: CGFloat) -> CGFloat { Swift.min(Swift.max(self, lo), hi) } } enum Textures { /// A white radial-gradient disc (opaque center → transparent edge). Tint it /// per-node with `colorBlendFactor` + `.add` blend mode to get a glow. // Immutable after creation and only read; safe to share across actors. nonisolated(unsafe) static let softCircle: SKTexture = makeSoftCircle(diameter: 128) private static func makeSoftCircle(diameter: Int) -> SKTexture { let space = CGColorSpaceCreateDeviceRGB() let ctx = CGContext( data: nil, width: diameter, height: diameter, bitsPerComponent: 8, bytesPerRow: 0, space: space, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue )! let colors = [ SKColor.white.cgColor, SKColor.white.withAlphaComponent(0).cgColor, ] as CFArray let gradient = CGGradient(colorsSpace: space, colors: colors, locations: [0, 1])! let center = CGPoint(x: diameter / 2, y: diameter / 2) ctx.drawRadialGradient( gradient, startCenter: center, startRadius: 0, endCenter: center, endRadius: CGFloat(diameter) / 2, options: [] ) return SKTexture(cgImage: ctx.makeImage()!) } } /// Deterministic FNV-1a hash so a given host always lands in the same place, /// independent of Swift's per-process `Hasher` seed. func stableHash(_ s: String) -> UInt64 { var h: UInt64 = 1469598103934665603 for byte in s.utf8 { h = (h ^ UInt64(byte)) &* 1099511628211 } return h }