 
 class LabelLayoutEngine {
 	let maxLabels: Int
-	var labelSizeCache: [Int : (w: Float, h: Float)] = [:]
+	var labelSizeCache: [Int : (w: Float, h: Float)] = [:]	// $ Limit size of this
 	
 	init(maxLabels: Int) {
 		self.maxLabels = maxLabels
+		// Collision detection structure for screen-space layout
 	}
 	
-	func layoutLabels(visibleMarkers: [LabelMarker],
+	// $ Re-implement zoom culling as a 3D box sweeping through point cloud
+	
+	func layoutLabels(labeledMarkers: [LabelMarker],
+										unlabeledMarkers: [LabelMarker],
 										projection project: (Vertex) -> CGPoint) -> [Int : LabelPlacement] {
-		let prioritizedMarkers = visibleMarkers.sorted(by: <)	// $ Prioritize by age too, so we don't get flickering. Older labels should not get nixed by newer
+		let newCandidates = unlabeledMarkers.sorted(by: <)
+		let prioritizedMarkers = labeledMarkers + newCandidates
 		let workingSet = prioritizedMarkers.prefix(maxLabels * 2)	// Take enough markers to give each label two candidates
 		
-		// Collision detection structure for screen-space layout
 		let screen = UIScreen.main.bounds
 		var labelQuadTree = QuadTree<LabelPlacement>(minX: Float(screen.minX),
 																								 minY: Float(screen.minY),

																								 maxY: Float(screen.maxY),
																								 maxDepth: 6)
		
		var layout: [Int : LabelPlacement] = [:]
		let margin: Float = 3.0 // $ Stylesheet
		for marker in workingSet {
			var anchor: LayoutAnchor? = (marker.kind == .Region ? .Center : .NE)	// Choose starting layout anchor
			let origin = project(marker.worldPos)

	static func < (lhs: LabelMarker, rhs: LabelMarker) -> Bool {
		let lhsScore = lhs.rank - (lhs.kind == .Region ? 1 : 0)	// Value regions one step higher
		let rhsScore = rhs.rank - (rhs.kind == .Region ? 1 : 0)
		
		let lVal = (lhsScore, lhs.ownerHash)
		let rVal = (rhsScore, rhs.ownerHash)
		
		return lVal < rVal
	}
}

