import Foundation
/// How Randomize samples a single scalar param. Bands come from the observed
/// value ranges of the 25 built-in presets, kept inside the "alive" region.
enum RandomStrategy {
case uniform(ClosedRange<Double>)
/// Emit 0 with probability `zeroProb`, else uniform in `band` (off-by-default accents).
case mostlyZero(zeroProb: Double, ClosedRange<Double>)
/// Pick band A with probability `probA`, else band B, then uniform inside it.
case bimodal(probA: Double, ClosedRange<Double>, ClosedRange<Double>)
func sample<G: RandomNumberGenerator>(using g: inout G) -> Double {
switch self {
case let .uniform(r):
return Double.random(in: r, using: &g)
case let .mostlyZero(p, r):
return Double.random(in: 0...1, using: &g) < p ? 0 : Double.random(in: r, using: &g)
case let .bimodal(p, a, b):
let r = Double.random(in: 0...1, using: &g) < p ? a : b
return Double.random(in: r, using: &g)
}
}
/// Representative band used for Mutate magnitude + clamping (keeps mutate inside the lively zone).
var span: ClosedRange<Double> {
switch self {
case let .uniform(r): return r
case let .mostlyZero(_, r): return 0...r.upperBound
case let .bimodal(_, a, b): return min(a.lowerBound, b.lowerBound)...max(a.upperBound, b.upperBound)
}
}
}
enum RandomizeProfiles {
/// Safe sampling strategy for every scalar ParamID. Species index and matrix
/// position are collapsed (all species share a profile; matrix diagonal vs
/// off-diagonal differ).
static func strategy(for id: ParamID) -> RandomStrategy {
switch id {
case .agentCount: return .bimodal(probA: 0.8, 6_000...14_000, 100_000...180_000)
case .exposure: return .uniform(1.5...3.6)
case .simSpeed: return .uniform(1...1) // time knob, not a look knob → keep at 1
case .decayRate: return .uniform(0.94...0.99)
case .diffusionFreq: return .uniform(1...4)
case .fadeStrength: return .uniform(0.03...0.12)
case .trailSaturation: return .uniform(110...255)
case .chaosInterval: return .mostlyZero(zeroProb: 0.7, 20...120)
case .chaosStrength: return .mostlyZero(zeroProb: 0.7, 0.1...0.6)
case .rhythmAmplitude: return .uniform(0.05...0.2)
case .headIntensity: return .mostlyZero(zeroProb: 0.6, 0.3...1.2)
case .kaleidoscope: return .mostlyZero(zeroProb: 0.8, 4...6)
case .feedbackAmount: return .uniform(0...0.6)
case .bloomThreshold: return .uniform(0.15...1.3)
case .bloomIntensity: return .uniform(0.3...2.4)
case .vignette: return .uniform(0...0.6)
case .saturation: return .uniform(0.6...1.5)
case .contrast: return .uniform(0.85...1.4)
case .populationShare(_): return .uniform(0.1...1)
case .sensorAngle(_): return .uniform(0.3...0.95)
case .sensorDistance(_): return .uniform(12...40)
case .turnSpeed(_): return .uniform(0.2...0.55)
case .moveSpeed(_): return .uniform(0.5...1.9)
case .depositAmount(_): return .uniform(4...13)
case let .matrix(r, c): return .uniform(r == c ? 0.5...1.5 : -0.6...0.6)
// non-scalars are not sampled here (handled explicitly in randomize())
case .spawnPattern, .blendMode, .backgroundColor, .speciesColor:
return .uniform(0...0)
}
}
}