diff --git a/LilAgents/LilAgentsController.swift b/LilAgents/LilAgentsController.swift index ec6fce9..e355e83 100644 --- a/LilAgents/LilAgentsController.swift +++ b/LilAgents/LilAgentsController.swift @@ -41,8 +41,9 @@ class LilAgentsController { char1.flipXOffset = 0 char2.flipXOffset = -9 - char1.positionProgress = 0.3 - char2.positionProgress = 0.7 + // Start near the ends of the track so the first walk tends to spread them along the dock. + char1.positionProgress = 0.1 + char2.positionProgress = 0.9 char1.pauseEndTime = CACurrentMediaTime() + Double.random(in: 0.5...2.0) char2.pauseEndTime = CACurrentMediaTime() + Double.random(in: 8.0...14.0) @@ -129,6 +130,12 @@ class LilAgentsController { // Small fudge factor for dock edge padding dockWidth *= 1.15 + + // Ensure a minimum width so characters aren't bunched together when + // dock defaults under-report the icon count (common in sandboxed apps). + let minDockWidth = screenWidth * 0.5 + dockWidth = max(dockWidth, minDockWidth) + let dockX = (screenWidth - dockWidth) / 2.0 return (dockX, dockWidth) } diff --git a/LilAgents/WalkerCharacter.swift b/LilAgents/WalkerCharacter.swift index fa30824..ff88573 100644 --- a/LilAgents/WalkerCharacter.swift +++ b/LilAgents/WalkerCharacter.swift @@ -848,6 +848,43 @@ class WalkerCharacter { // MARK: - Walking + /// Other dock characters whose positions we use to avoid landing stacked (no teleporting — only the planned walk endpoint moves). + private func peerCharactersForSeparation() -> [WalkerCharacter] { + guard let all = controller?.characters else { return [] } + return all.filter { other in + other !== self && other.window.isVisible && other.isManuallyVisible && !other.isIdleForPopover + } + } + + private var minWalkSeparationPixels: CGFloat { + max(displayWidth * 0.35, 72) + } + + /// Nudges `end` along the same direction as `start → end` so the stop stays at least `minWalkSeparationPixels` from each peer’s current progress. + private func applyPeerWalkEndSeparation(start: CGFloat, end: CGFloat) -> CGFloat { + let peers = peerCharactersForSeparation() + guard !peers.isEmpty, currentTravelDistance > 1 else { return end } + let minProg = minWalkSeparationPixels / currentTravelDistance + var e = end + for _ in 0..<6 { + var changed = false + for peer in peers { + let p = peer.positionProgress + guard abs(e - p) < minProg else { continue } + if e >= start { + let n = max(e, p + minProg) + if n != e { e = n; changed = true } + } else { + let n = min(e, p - minProg) + if n != e { e = n; changed = true } + } + } + e = min(max(e, 0), 1) + if !changed { break } + } + return e + } + func startWalk() { isPaused = false isWalking = true @@ -860,6 +897,17 @@ class WalkerCharacter { goingRight = true } else { goingRight = Bool.random() + let peers = peerCharactersForSeparation() + if currentTravelDistance > 1, + let nearest = peers.min(by: { + abs($0.positionProgress - positionProgress) < abs($1.positionProgress - positionProgress) + }) { + let sep = abs(nearest.positionProgress - positionProgress) * currentTravelDistance + if sep < minWalkSeparationPixels { + // Peer is to the left on the dock → go right (increase progress), and vice versa. + goingRight = nearest.positionProgress < positionProgress + } + } } walkStartPos = positionProgress @@ -867,29 +915,31 @@ class WalkerCharacter { let referenceWidth: CGFloat = 500.0 let walkPixels = CGFloat.random(in: walkAmountRange) * referenceWidth let walkAmount = currentTravelDistance > 0 ? walkPixels / currentTravelDistance : 0.3 + let tentativeEnd: CGFloat if goingRight { - walkEndPos = min(walkStartPos + walkAmount, 1.0) + tentativeEnd = min(walkStartPos + walkAmount, 1.0) } else { - walkEndPos = max(walkStartPos - walkAmount, 0.0) + tentativeEnd = max(walkStartPos - walkAmount, 0.0) } + walkEndPos = applyPeerWalkEndSeparation(start: walkStartPos, end: tentativeEnd) + + // If separation pinned us to essentially no move, try the other direction with the same stride length. + let minStrideProg = 8 / max(currentTravelDistance, 1) + if abs(walkEndPos - walkStartPos) < minStrideProg { + goingRight.toggle() + let altEnd: CGFloat + if goingRight { + altEnd = min(walkStartPos + walkAmount, 1.0) + } else { + altEnd = max(walkStartPos - walkAmount, 0.0) + } + walkEndPos = applyPeerWalkEndSeparation(start: walkStartPos, end: altEnd) + } + // Store pixel positions so walk speed stays consistent if screen changes mid-walk walkStartPixel = walkStartPos * currentTravelDistance walkEndPixel = walkEndPos * currentTravelDistance - let minSeparation: CGFloat = 0.12 - if let siblings = controller?.characters { - for sibling in siblings where sibling !== self { - let sibPos = sibling.positionProgress - if abs(walkEndPos - sibPos) < minSeparation { - if goingRight { - walkEndPos = max(walkStartPos, sibPos - minSeparation) - } else { - walkEndPos = min(walkStartPos, sibPos + minSeparation) - } - } - } - } - updateFlip() queuePlayer.seek(to: .zero) queuePlayer.play()