Fix label overlap when districts glide to their latency distance

The latency reposition moved hubs to their RTT radius without checking
label collisions, so a gliding district could land on top of a neighbour.
Make both placement paths overlap-aware via the existing LayoutSolver:
keep distance = latency (the meaningful axis) but rotate the arbitrary
angle to clear other labels. Track each hub's target position so
same-tick moves avoid where others are heading, not just where they are.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 10:46:53 -07:00
parent c08fc277a9
commit 19f8cf08d7
+17 -3
View File
@@ -38,6 +38,7 @@ final class CityScene: SKScene {
private var buildings: [String: BuildingNode] = [:]
private var roads: [String: SKShapeNode] = [:]
private var latencyRadius: [String: CGFloat] = [:]
private var targetPos: [String: CGPoint] = [:] // where each hub is heading
private var ringKey: String?
private let roadLayer = SKNode()
private let carLayer = SKNode()
@@ -234,6 +235,7 @@ final class CityScene: SKScene {
let node = BuildingNode(title: title, subtitle: subtitle)
node.position = placement(for: key, footprint: node.labelFootprint)
targetPos[key] = node.position
node.alpha = 0
node.run(.fadeIn(withDuration: 0.6))
buildingLayer.addChild(node)
@@ -266,8 +268,19 @@ final class CityScene: SKScene {
if let current = latencyRadius[key], abs(current - target) < 18 { return }
latencyRadius[key] = target
// Keep distance = latency, but rotate/nudge the (arbitrary) angle to
// clear other labels otherwise gliding hubs land on top of each other.
let angle = atan2(building.position.y, building.position.x)
let dest = CGPoint(x: cos(angle) * Double(target), y: sin(angle) * Double(target))
let others = buildings.compactMap { (k, b) -> CGRect? in
guard k != key else { return nil }
let p = targetPos[k] ?? b.position
return b.labelFootprint.offsetBy(dx: p.x, dy: p.y)
}
let dest = LayoutSolver.placeNonOverlapping(
baseAngle: angle, baseRadius: Double(target),
localRect: building.labelFootprint, existing: others, pad: 18
)
targetPos[key] = dest
building.run(.move(to: dest, duration: 0.8))
if let road = roads[key] {
@@ -287,8 +300,9 @@ final class CityScene: SKScene {
let ring = Double((h >> 16) % 1000) / 1000
let baseRadius = 165 + Double(ring) * Double(worldRadius - 165)
let existing = buildings.values.map {
$0.labelFootprint.offsetBy(dx: $0.position.x, dy: $0.position.y)
let existing = buildings.map { (k, b) in
let p = targetPos[k] ?? b.position
return b.labelFootprint.offsetBy(dx: p.x, dy: p.y)
}
return LayoutSolver.placeNonOverlapping(
baseAngle: baseAngle, baseRadius: baseRadius,