import simd
/// Byte-exact port of `src/engine/params.ts`. The packed layouts MUST match the
/// WGSL `Params` (304 B / 76 floats) and `PostParams` (80 B / 20 floats) structs
/// exactly — verified by `ParamsLayoutTests` and end-to-end by the keystone spike.
public struct UniformInput {
public var fieldWidth: UInt32
public var fieldHeight: UInt32
public var agentCount: UInt32
public var frame: UInt32
public var time: Float
public var decayRate: Float
public var fadeStrength: Float
public var trailSaturation: Float
public var diffusionFreq: UInt32
public var chaosInterval: UInt32
public var chaosStrength: Float
public var rhythmAmplitude: Float
public var exposure: Float
public var spawnPattern: SpawnPattern
public var headIntensity: Float
public var backgroundColor: RGB
public var species: (SpeciesParams, SpeciesParams, SpeciesParams)
public var matrix: InteractionMatrix
public struct Audio {
public var moveSpeedFactor: Float = 1
public var turnSpeedFactor: Float = 1
public var depositFactor: Float = 1
public var sensorDistanceFactor: Float = 1
public var rhythmAmplitudeFactor: Float = 1
public var chaosStrengthFactor: Float = 1
public init() {}
}
public var audio = Audio()
public static func zeroed(fieldWidth: UInt32, fieldHeight: UInt32, agentCount: UInt32) -> UniformInput {
let z = SpeciesParams(
color: .zero, populationShare: 0, sensorAngle: 0, sensorDistance: 1,
turnSpeed: 0, moveSpeed: 0, depositAmount: 0
)
return UniformInput(
fieldWidth: fieldWidth, fieldHeight: fieldHeight, agentCount: agentCount, frame: 0,
time: 0, decayRate: 0.95, fadeStrength: 0, trailSaturation: 100,
diffusionFreq: 1, chaosInterval: 0, chaosStrength: 0, rhythmAmplitude: 0, exposure: 1,
spawnPattern: .random, headIntensity: 0, backgroundColor: .zero,
species: (z, z, z), matrix: (.zero, .zero, .zero)
)
}
}
public func packParams(_ i: UniformInput) -> [UInt8] {
var f = [Float](repeating: 0, count: 76)
func su(_ s: Int, _ v: UInt32) { f[s] = Float(bitPattern: v) }
su(0, i.fieldWidth); su(1, i.fieldHeight); su(2, i.agentCount); su(3, i.frame)
f[4] = i.time; f[5] = i.decayRate; f[6] = i.fadeStrength; f[7] = i.trailSaturation
su(8, max(1, i.diffusionFreq)); su(11, i.chaosInterval)
f[12] = i.chaosStrength; f[13] = i.rhythmAmplitude; f[14] = i.exposure
su(15, UInt32(SpawnPattern.allCases.firstIndex(of: i.spawnPattern)!))
f[16] = i.audio.moveSpeedFactor
f[17] = i.audio.turnSpeedFactor
f[18] = i.audio.depositFactor
f[19] = i.audio.sensorDistanceFactor
f[20] = i.audio.rhythmAmplitudeFactor
f[21] = i.audio.chaosStrengthFactor
f[22] = i.headIntensity
f[24] = i.backgroundColor.x; f[25] = i.backgroundColor.y; f[26] = i.backgroundColor.z
let sp = [i.species.0, i.species.1, i.species.2]
for s in 0..<3 {
let o = 28 + s * 12
f[o + 0] = sp[s].color.x; f[o + 1] = sp[s].color.y; f[o + 2] = sp[s].color.z
f[o + 3] = sp[s].populationShare
f[o + 4] = sp[s].sensorAngle
f[o + 5] = sp[s].sensorDistance
f[o + 6] = sp[s].turnSpeed
f[o + 7] = sp[s].moveSpeed
f[o + 8] = sp[s].depositAmount
}
let rows = [i.matrix.0, i.matrix.1, i.matrix.2]
for r in 0..<3 {
let o = 64 + r * 4
f[o + 0] = rows[r].x; f[o + 1] = rows[r].y; f[o + 2] = rows[r].z
}
return f.withUnsafeBytes { Array($0) }
}
public struct PostUniformInput {
public var resolutionX: Float
public var resolutionY: Float
public var exposure: Float
public var time: Float
public var feedbackAmount: Float
public var bloomThreshold: Float
public var bloomIntensity: Float
public var vignette: Float
public var saturation: Float
public var contrast: Float
public var backgroundColor: RGB
public var blendMode: BlendMode
public var kaleidoscope: UInt32
public var exposureFactor: Float = 1
public var bloomIntensityFactor: Float = 1
public var saturationFactor: Float = 1
public var contrastFactor: Float = 1
public static func zeroed(resX: Float, resY: Float) -> PostUniformInput {
PostUniformInput(
resolutionX: resX, resolutionY: resY, exposure: 1, time: 0,
feedbackAmount: 0, bloomThreshold: 1, bloomIntensity: 1, vignette: 0,
saturation: 1, contrast: 1, backgroundColor: .zero, blendMode: .additive, kaleidoscope: 0
)
}
}
public func packPostParams(_ i: PostUniformInput) -> [UInt8] {
var f = [Float](repeating: 0, count: 20)
func su(_ s: Int, _ v: UInt32) { f[s] = Float(bitPattern: v) }
f[0] = i.resolutionX; f[1] = i.resolutionY; f[2] = i.exposure; f[3] = i.time
f[4] = i.feedbackAmount; f[5] = i.bloomThreshold; f[6] = i.bloomIntensity
f[7] = i.vignette; f[8] = i.saturation; f[9] = i.contrast
su(10, min(6, i.kaleidoscope))
f[12] = i.backgroundColor.x; f[13] = i.backgroundColor.y; f[14] = i.backgroundColor.z
su(15, UInt32(BlendMode.allCases.firstIndex(of: i.blendMode)!))
f[16] = i.exposureFactor; f[17] = i.bloomIntensityFactor
f[18] = i.saturationFactor; f[19] = i.contrastFactor
return f.withUnsafeBytes { Array($0) }
}