Zum Inhalt

src/presets/builtIn.ts

// src/presets/builtIn.ts
import type { InteractionMatrix, Preset, PresetParams, RGB, SpeciesParams } from '~/engine/types';
import { CURATED_PRESETS } from './curated';

const hex = (h: string): RGB => {
  const n = Number.parseInt(h.replace('#', ''), 16);
  return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
};

/** Builds a SpeciesParams with M1-tuned defaults; override per preset. */
export function sp(color: string, share: number, over: Partial<SpeciesParams> = {}): SpeciesParams {
  return {
    color: hex(color),
    populationShare: share,
    sensorAngle: 0.5,
    sensorDistance: 24,
    turnSpeed: 0.32,
    moveSpeed: 1.0,
    depositAmount: 9,
    ...over,
  };
}

/** Symmetric interaction matrix: `self` on the diagonal, `cross` elsewhere. */
export function symMatrix(self: number, cross: number): InteractionMatrix {
  return [
    [self, cross, cross],
    [cross, self, cross],
    [cross, cross, self],
  ];
}

type PresetOverrides = Partial<Omit<PresetParams, 'species' | 'interactionMatrix'>> & {
  species: readonly [SpeciesParams, SpeciesParams, SpeciesParams];
  // Legacy fields kept for source-readability: the matrix + the two strengths
  // are folded into a single matrix at construction time. See the fold logic
  // below — the runtime PresetParams no longer carries the strengths.
  interactionMatrix?: InteractionMatrix;
  attractionStrength?: number;
  repulsionStrength?: number;
};

/** Builds PresetParams with M1-classical defaults; override per preset.
 *  Pre-1.2 attractionStrength/repulsionStrength are folded into the matrix:
 *    matrix[r][c] *= (r === c ? attractionStrength : repulsionStrength)
 *  This matches the old shader behaviour exactly (own × attrK + cross × repulK)
 *  while consolidating to a single per-species-pair scalar. */
function mk(o: PresetOverrides): PresetParams {
  const rawMatrix = o.interactionMatrix ?? symMatrix(1.0, -0.3);
  const attrK = o.attractionStrength ?? 1.0;
  const repulK = o.repulsionStrength ?? -0.2;
  const matrix = rawMatrix.map((row, r) =>
    row.map((v, c) => v * (r === c ? attrK : repulK)),
  ) as unknown as InteractionMatrix;

  const {
    interactionMatrix: _im,
    attractionStrength: _a,
    repulsionStrength: _r,
    species,
    ...overrides
  } = o;

  return {
    agentCount: 9000,
    spawnPattern: 'random',
    decayRate: 0.97,
    diffusionFreq: 2,
    fadeStrength: 0.12,
    trailSaturation: 255,
    chaosInterval: 0,
    chaosStrength: 0,
    rhythmAmplitude: 0.05,
    simSpeed: 1,
    blendMode: 'additive',
    backgroundColor: hex('#05030a'),
    exposure: 3.0,
    headIntensity: 0,
    kaleidoscope: 0,
    feedbackAmount: 0.2,
    bloomThreshold: 0.25,
    bloomIntensity: 0.9,
    vignette: 0.3,
    saturation: 1.1,
    contrast: 1.05,
    audioReactive: { enabled: false, gain: 1, autoGain: false, routes: [] },
    ...overrides,
    species,
    interactionMatrix: matrix,
  };
}

/** Attaches `thumbnail: /preset-thumbs/<id>.png` to every built-in so the
 *  baked images surface in the library without each literal having to
 *  repeat itself. Re-run `npm run bake:thumbnails` after edits to refresh
 *  the PNGs that ship in public/. */
function withThumb<T extends Preset>(p: T): T {
  return { ...p, thumbnail: `preset-thumbs/${p.id}.png` };
}

// The library is exactly the 7 curated factory presets (curated.ts). The hand-built
// classics were retired 2026-06-21 (Johannes' call) — git history holds them.
export const BUILT_IN_PRESETS: readonly Preset[] = CURATED_PRESETS.map(withThumb);

// biome-ignore lint/style/noNonNullAssertion: literal index 0 is safe on a non-empty const array
export const DEFAULT_PRESET = BUILT_IN_PRESETS[0]!; // Aurora

export const SIGNAL_CSS_VAR: Record<string, string> = {
  pearl: 'var(--signal-pearl)',
  crimson: 'var(--signal-crimson)',
  phosphor: 'var(--signal-phosphor)',
  ember: 'var(--signal-ember)',
  spectre: 'var(--signal-spectre)',
  circuit: 'var(--signal-circuit)',
  ghost: 'var(--signal-ghost)',
  biolink: 'var(--signal-biolink)',
  'neural-bleed': 'var(--signal-neural-bleed)',
  rust: 'var(--signal-rust)',
  toxic: 'var(--signal-toxic)',
  voidwitch: 'var(--signal-voidwitch)',
};