Add idle district lifecycle: decay then reap
The map only ever gained density: hubs were sized by lifetime-cumulative bytes (so they never shrank) and were never removed. Now each hub has a decaying activity level — quiet districts visibly shrink toward a dormant dot — and after a configurable idle timeout (~40s, NC_REAP_TICKS) the hub is demolished: faded out, removed with its road, and its per-org state pruned in the controller. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,10 @@ final class CityScene: SKScene {
|
||||
/// Called with a district key when a hub is clicked, or nil when empty space
|
||||
/// is clicked (deselect).
|
||||
var onSelect: ((String?) -> Void)?
|
||||
/// Called when a district is demolished, so the controller can prune its state.
|
||||
var onReap: ((String) -> Void)?
|
||||
/// Demolish a district after this many idle ticks (~2s each). Env-tunable for testing.
|
||||
private let reapAfterTicks = Int(ProcessInfo.processInfo.environment["NC_REAP_TICKS"] ?? "") ?? 20
|
||||
private let selectionRing = SKShapeNode(circleOfRadius: 24)
|
||||
private var dragDistance: CGFloat = 0
|
||||
|
||||
@@ -160,9 +164,17 @@ final class CityScene: SKScene {
|
||||
/// Render a tick of per-org traffic: ensure each district exists, decide
|
||||
/// which ones are busy enough to fan out, then dispatch cars accordingly.
|
||||
func apply(_ districts: [DistrictTraffic]) {
|
||||
// 0. Age every hub: decay its glow and count idle ticks. Active hubs
|
||||
// reset below; dead ones get reaped at the end of the tick.
|
||||
for (_, hub) in buildings {
|
||||
hub.decay()
|
||||
hub.idleTicks += 1
|
||||
}
|
||||
|
||||
// 1. Ensure hubs exist and learn their endpoints.
|
||||
for t in districts {
|
||||
let hub = building(forKey: t.key, title: t.title, subtitle: t.subtitle)
|
||||
hub.idleTicks = 0
|
||||
hub.note(activeHosts: t.endpoints.map(\.host))
|
||||
hub.receive(bytes: Double(t.inBytes + t.outBytes))
|
||||
if let rtt = t.rttMs { repositionToLatency(key: t.key, rttMs: rtt) }
|
||||
@@ -218,6 +230,26 @@ final class CityScene: SKScene {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Demolish districts that have been idle too long.
|
||||
for key in buildings.keys where (buildings[key]?.idleTicks ?? 0) > reapAfterTicks {
|
||||
reap(key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fade out and remove a dead district, clearing all of its scene state.
|
||||
private func reap(_ key: String) {
|
||||
buildings[key]?.run(.sequence([.fadeOut(withDuration: 0.7), .removeFromParent()]))
|
||||
roads[key]?.run(.sequence([.fadeOut(withDuration: 0.7), .removeFromParent()]))
|
||||
buildings[key] = nil
|
||||
roads[key] = nil
|
||||
latencyRadius[key] = nil
|
||||
targetPos[key] = nil
|
||||
if ringKey == key { highlight(key: nil); onSelect?(nil) }
|
||||
onReap?(key)
|
||||
if ProcessInfo.processInfo.environment["NC_DEBUG"] != nil {
|
||||
FileHandle.standardError.write(Data("[reap] \(key)\n".utf8))
|
||||
}
|
||||
}
|
||||
|
||||
private func carCount(_ bytes: UInt64) -> Int {
|
||||
|
||||
Reference in New Issue
Block a user