// The 244-float generalized plane-wave rule (docs/generalized_engine_spec.md). // // Flat layout (std430 GenCenter order), the same one the shader's uRule[61] expects: // per center i (0..9), 24 floats at offset i*24: // [ 0..15] freq[0..3] — four 4D frequency vectors, one per output channel // [16..19] amp — per-channel amplitude // [20..23] phase — per-channel phase // then [240..243] dc — per-channel DC term // As vec4[61]: center i -> indices i*6..i*6+5 = { freq0,freq1,freq2,freq3, amp, phase }; // uRule[60] = dc. export const RULE_FLOATS = 244; /** A direct (uncompiled) genome: exactly 244 floats. */ export type Rule = Float32Array; export function toRule(values: ArrayLike): Rule { if (values.length !== RULE_FLOATS) { throw new Error(`Rule must be ${RULE_FLOATS} floats, got ${values.length}.`); } return new Float32Array(values); } // The legacy desktop GPU rule: 10 centers × [freq(4) | amp(4)] = 80 floats, the coupled // `fourier_noise` basis. Every Fluoddity physics-config preset stores its rule in this form. const GPU_RULE_FLOATS = 80; /** * Fold a legacy 80-float desktop GPU rule into the 244-float generalized plane-wave rule, * losslessly. This is the exact fold the desktop engine itself bakes into * `generate_random_centers` (shaders/fourier4_4.glsl) and that docs/generalized_engine_spec.md * specifies: per center i with omega = freq, amp4 = amplitude, and the truncated shader * literals, psi = 2·0.6283·i + 3.14159·amp4.w, then per output channel k: * freq[k] = H[k]·omega, amp[k] = amp4[k], phase[k] = Q[k]·psi + TAU[k] * with H=(1,1,2,2), Q=(1,0.7,1.3,0.5), TAU=(−π/2,0,−π/2,0); dc = 0. Channels 2,3 carry the * octave band 2·omega. The shader's truncated literals are used deliberately so the result is * bit-faithful to what the original engine computed. */ export function foldGpuRule(values: ArrayLike): Rule { if (values.length !== GPU_RULE_FLOATS) { throw new Error(`GPU rule must be ${GPU_RULE_FLOATS} floats, got ${values.length}.`); } const H = [1, 1, 2, 2]; const Q = [1, 0.7, 1.3, 0.5]; const TAU = [-1.5707963, 0, -1.5707963, 0]; const out = new Float32Array(RULE_FLOATS); for (let i = 0; i < 10; i++) { const src = i * 8; const o0 = src; // omega = freq[0..3] const a0 = src + 4; // amp4 = amplitude[0..3] const psi = 2.0 * 0.6283 * i + 3.14159 * values[a0 + 3]; const base = i * 24; for (let k = 0; k < 4; k++) { const fo = base + k * 4; // freq[k] (a 4-vector) out[fo] = H[k] * values[o0]; out[fo + 1] = H[k] * values[o0 + 1]; out[fo + 2] = H[k] * values[o0 + 2]; out[fo + 3] = H[k] * values[o0 + 3]; out[base + 16 + k] = values[a0 + k]; // amp[k] out[base + 20 + k] = Q[k] * psi + TAU[k]; // phase[k] } } // dc (240..243) stays 0 — the pure-sinusoid template has no DC term. return out; }