Zum Inhalt

apple/PhysarumApp/Sources/Model/ParamRegistry.swift

import PhysarumEngine
import simd

enum EditorSection: String, CaseIterable {
    case global, species, matrix, pheromone, dynamics, effects
}

/// Stable identity for every editable parameter (scalars + non-scalars), so the
/// editor, locks, undo and morph (later plans) can address each one.
enum ParamID: Hashable {
    // global scalars
    case agentCount, exposure, simSpeed
    // global non-scalars (own controls, not in the scalar registry)
    case spawnPattern, blendMode, backgroundColor
    // per-species (index 0..2)
    case speciesColor(Int), populationShare(Int), sensorAngle(Int), sensorDistance(Int)
    case turnSpeed(Int), moveSpeed(Int), depositAmount(Int)
    // matrix cell (row, col)
    case matrix(Int, Int)
    // pheromone
    case decayRate, diffusionFreq, fadeStrength, trailSaturation
    // dynamics
    case chaosInterval, chaosStrength, rhythmAmplitude
    // effects
    case headIntensity, kaleidoscope, feedbackAmount, bloomThreshold, bloomIntensity
    case vignette, saturation, contrast
}

/// A scalar, slider-editable parameter. Non-scalar params (enums/colors) are not
/// in the registry — they get bespoke controls but share ParamID identity.
struct ParamDescriptor: Identifiable {
    let id: ParamID
    let label: String
    let section: EditorSection
    let range: ClosedRange<Double>
    let isInt: Bool
    let isRespawn: Bool
    let get: (PresetParams) -> Double
    let set: (inout PresetParams, Double) -> Void
    let tooltip: String
    var idKey: String { "\(id)" }
}

enum ParamRegistry {
    /// All scalar editable params with web-schema ranges (src/presets/schema.ts).
    static let all: [ParamDescriptor] = {
        var d: [ParamDescriptor] = []
        func add(_ id: ParamID, _ label: String, _ s: EditorSection, _ r: ClosedRange<Double>,
                 int: Bool = false, respawn: Bool = false,
                 _ get: @escaping (PresetParams) -> Double,
                 _ set: @escaping (inout PresetParams, Double) -> Void) {
            d.append(ParamDescriptor(id: id, label: label, section: s, range: r,
                                     isInt: int, isRespawn: respawn, get: get, set: set,
                                     tooltip: ParamHelp.text(for: label)))
        }
        // Global
        add(.agentCount, "Agents", .global, 100...500_000, int: true, respawn: true,
            { Double($0.agentCount) }, { $0.agentCount = Int($1.rounded()) })
        add(.exposure, "Exposure", .global, 0.1...10, { Double($0.exposure) }, { $0.exposure = Float($1) })
        add(.simSpeed, "Sim speed", .global, 1...8, int: true,
            { Double($0.simSpeed) }, { $0.simSpeed = Int($1.rounded()) })
        // Per-species (i = 0..2)
        for i in 0..<3 {
            add(.populationShare(i), "Population", .species, 0...3, respawn: true,
                { Double($0.species[i].populationShare) }, { $0.species[i].populationShare = Float($1) })
            add(.sensorAngle(i), "Sensor angle", .species, 0...Double.pi,
                { Double($0.species[i].sensorAngle) }, { $0.species[i].sensorAngle = Float($1) })
            add(.sensorDistance(i), "Sensor distance", .species, 1...80,
                { Double($0.species[i].sensorDistance) }, { $0.species[i].sensorDistance = Float($1) })
            add(.turnSpeed(i), "Turn speed", .species, 0...2,
                { Double($0.species[i].turnSpeed) }, { $0.species[i].turnSpeed = Float($1) })
            add(.moveSpeed(i), "Move speed", .species, 0...10,
                { Double($0.species[i].moveSpeed) }, { $0.species[i].moveSpeed = Float($1) })
            add(.depositAmount(i), "Deposit", .species, 0...100,
                { Double($0.species[i].depositAmount) }, { $0.species[i].depositAmount = Float($1) })
        }
        // Matrix (r,c)
        for r in 0..<3 {
            for c in 0..<3 {
                add(.matrix(r, c), "M[\(r),\(c)]", .matrix, -2...2,
                    { Double($0.interactionMatrix[r][c]) }, { $0.interactionMatrix[r][c] = Float($1) })
            }
        }
        // Pheromone
        add(.decayRate, "Decay", .pheromone, 0.5...1, { Double($0.decayRate) }, { $0.decayRate = Float($1) })
        add(.diffusionFreq, "Diffusion", .pheromone, 1...20, int: true,
            { Double($0.diffusionFreq) }, { $0.diffusionFreq = Int($1.rounded()) })
        add(.fadeStrength, "Fade", .pheromone, 0...1, { Double($0.fadeStrength) }, { $0.fadeStrength = Float($1) })
        add(.trailSaturation, "Trail cap", .pheromone, 1...500,
            { Double($0.trailSaturation) }, { $0.trailSaturation = Float($1) })
        // Dynamics
        add(.chaosInterval, "Chaos interval", .dynamics, 0...2000, int: true,
            { Double($0.chaosInterval) }, { $0.chaosInterval = Int($1.rounded()) })
        add(.chaosStrength, "Chaos strength", .dynamics, 0...2,
            { Double($0.chaosStrength) }, { $0.chaosStrength = Float($1) })
        add(.rhythmAmplitude, "Rhythm", .dynamics, 0...0.5,
            { Double($0.rhythmAmplitude) }, { $0.rhythmAmplitude = Float($1) })
        // Effects
        add(.headIntensity, "Head glow", .effects, 0...3, { Double($0.headIntensity) }, { $0.headIntensity = Float($1) })
        add(.kaleidoscope, "Kaleidoscope", .effects, 0...6, int: true,
            { Double($0.kaleidoscope) }, { $0.kaleidoscope = Int($1.rounded()) })
        add(.feedbackAmount, "Feedback", .effects, 0...0.97, { Double($0.feedbackAmount) }, { $0.feedbackAmount = Float($1) })
        add(.bloomThreshold, "Bloom threshold", .effects, 0...2, { Double($0.bloomThreshold) }, { $0.bloomThreshold = Float($1) })
        add(.bloomIntensity, "Bloom", .effects, 0...3, { Double($0.bloomIntensity) }, { $0.bloomIntensity = Float($1) })
        add(.vignette, "Vignette", .effects, 0...1, { Double($0.vignette) }, { $0.vignette = Float($1) })
        add(.saturation, "Saturation", .effects, 0...2, { Double($0.saturation) }, { $0.saturation = Float($1) })
        add(.contrast, "Contrast", .effects, 0...2, { Double($0.contrast) }, { $0.contrast = Float($1) })
        return d
    }()

    static func descriptors(in section: EditorSection) -> [ParamDescriptor] {
        all.filter { $0.section == section }
    }

    static let respawnIDs: Set<String> = Set(all.filter { $0.isRespawn }.map { "\($0.id)" })
}