Zum Inhalt

src/engine/types.ts

export type RGB = readonly [number, number, number];

export interface PresetParams {
  agentCount: number;
  spawnPattern: SpawnPattern;
  species: readonly [SpeciesParams, SpeciesParams, SpeciesParams];
  interactionMatrix: InteractionMatrix;
  // Pheromone medium (global) — M1 classical model
  decayRate: number; // 0.85..0.999 trail persistence per diffuse pass
  diffusionFreq: number; // 1..10 — diffuse runs every Nth frame
  fadeStrength: number; // 0..0.4 — extra decay above the fade threshold
  trailSaturation: number; // 8..300 — trail value cap
  // Motion perturbation (global)
  chaosInterval: number; // 0..500 — frames between chaos kicks (0 = off)
  chaosStrength: number; // 0..1 — chaos kick magnitude
  rhythmAmplitude: number; // 0..0.3 — per-agent sine-wander amplitude
  // Pacing — how many sim steps to run per rendered frame (1..8). Multiplies
  // physics speed without changing rendering / audio cadence.
  simSpeed: number;
  // Render
  backgroundColor: RGB;
  blendMode: BlendMode; // how agents compose over bg
  exposure: number; // tone-map exposure
  // Long-exposure agent-head highlight: how bright the current-frame
  // agent positions appear on top of the persistent pheromone trail.
  // 0 = off (trail only). 1 ≈ matches trail brightness. 2..3 = comet head.
  headIntensity: number;
  // Kaleidoscope mirror axes (0-6). 0 = off; otherwise the composite pass
  // folds the image into N-fold rotational symmetry about the centre.
  kaleidoscope: number;
  // Post-processing effects
  feedbackAmount: number; // 0..0.97 — echo/trail persistence
  bloomThreshold: number; // 0..2 — HDR brightness where bloom starts
  bloomIntensity: number; // 0..3 — glow strength
  vignette: number; // 0..1 — edge darkening
  saturation: number; // 0..2 — colour saturation (1 = neutral)
  contrast: number; // 0..2 — contrast (1 = neutral)
  audioReactive: AudioReactive;
}

export const AUDIO_SOURCES = ['bass', 'mid', 'high'] as const;
export type AudioBand = (typeof AUDIO_SOURCES)[number];

// Modulation targets that the engine knows how to multiply by an audio factor.
// Split into agent-side (applied per agent in agentStep/deposit shaders) and
// post-side (applied in the composite shader). The shader uniforms have
// dedicated factor slots for each — adding a target here requires wiring
// the matching uniform in engine/params.ts and the matching shader code.
export const MOD_TARGETS_AGENT = [
  'moveSpeed',
  'turnSpeed',
  'depositAmount',
  'sensorDistance',
  'rhythmAmplitude',
  'chaosStrength',
] as const;
export const MOD_TARGETS_POST = ['exposure', 'bloomIntensity', 'saturation', 'contrast'] as const;
export const MOD_TARGETS = [...MOD_TARGETS_AGENT, ...MOD_TARGETS_POST] as const;
export type ModTarget = (typeof MOD_TARGETS)[number];

export interface AudioRoute {
  source: AudioBand;
  target: ModTarget;
  depth: number; // -2..+2 — multiplier on the source band's 0..1 level
}

export interface AudioReactive {
  enabled: boolean;
  /** Manual multiplier applied to every band before routing. 0.1..10. */
  gain: number;
  /** If true, AudioCapture continuously normalises levels to a target range. */
  autoGain: boolean;
  /** Free routing from audio bands to modulation targets. */
  routes: readonly AudioRoute[];
}

export interface Preset {
  id: string;
  name: string;
  author?: string;
  builtIn: boolean;
  tags: readonly string[];
  tagSignal: SignalName;
  params: PresetParams;
  /** Optional URL to a pre-baked thumbnail. Built-ins ship with one in
   *  /preset-thumbs/<id>.png; custom presets rely on the runtime
   *  thumbnailStore (snapshot at frame ~240, cached in localStorage). */
  thumbnail?: string;
}

export const SIGNAL_NAMES = [
  'pearl',
  'crimson',
  'phosphor',
  'ember',
  'spectre',
  'circuit',
  'ghost',
  'biolink',
  'neural-bleed',
  'rust',
  'toxic',
  'voidwitch',
] as const;
export type SignalName = (typeof SIGNAL_NAMES)[number];

export const SPAWN_PATTERNS = ['random', 'clusters', 'center', 'ring'] as const;
export type SpawnPattern = (typeof SPAWN_PATTERNS)[number];

// How agents are composited over the background:
//   additive — agents emit light on top of bg. Works with dark backgrounds.
//              Bright bg saturates everything to white (ACES tonemap clips).
//   paint    — agents are coloured ink, bg is paper. Tonemap agents alone,
//              then alpha-blend over bg in display space. Works at any bg.
export const BLEND_MODES = ['additive', 'paint'] as const;
export type BlendMode = (typeof BLEND_MODES)[number];

export interface SpeciesParams {
  color: RGB;
  populationShare: number; // relative weight ≥ 0
  sensorAngle: number; // radians
  sensorDistance: number; // sim-grid pixels
  turnSpeed: number; // radians per step
  moveSpeed: number; // sim-grid pixels per frame
  depositAmount: number; // trail units deposited per frame
}

// matrix[sensingSpecies][channel] — Wertebereich -2..+2
export type MatrixRow = readonly [number, number, number];
export type InteractionMatrix = readonly [MatrixRow, MatrixRow, MatrixRow];

export type PerfTier = 'desktop' | 'mid' | 'mobile' | 'minimum';

export interface PerfBudget {
  agentCount: number;
  fieldScale: number; // 1.0 = full canvas, 0.5 = half resolution
}

export interface AudioFrame {
  bass: number; // 0..1 RMS
  mid: number;
  high: number;
}

export interface EngineHandle {
  setPreset(preset: PresetParams): void;
  respawn(): void;
  setAudio(frame: AudioFrame | null): void;
  resize(width: number, height: number): void;
  tick(): void;
  /** Returns a downscaled JPEG data URL of the current canvas, or '' on failure. */
  snapshot(longEdge?: number): string;
  dispose(): void;
  readonly canvas: HTMLCanvasElement;
  readonly tier: PerfTier;
}