Zum Inhalt

src/engine/seeding.ts

// src/engine/seeding.ts
// Pure agent-buffer seeding for the 4 spawn patterns.
// Layout per agent: 24 bytes / 6 float slots =
//   pos.x:f32, pos.y:f32, angle:f32, species:u32, rhythmPhase:f32, pad.
import type { SpawnPattern } from './types';

type Shares = readonly [number, number, number];

/** Bytes per agent in the GPU buffer (WGSL struct stride, 8-byte aligned). */
export const AGENT_BYTE_STRIDE = 24;

/** Builds a flat array that maps agent index -> species, weighted by shares. */
function speciesPlan(count: number, shares: Shares): Uint8Array {
  const total = shares[0] + shares[1] + shares[2];
  const plan = new Uint8Array(count);
  if (total <= 0) return plan; // all-zero → every agent is species 0
  let written = 0;
  for (let s = 0; s < 3; s++) {
    const n = s === 2 ? count - written : Math.round(((shares[s] ?? 0) / total) * count);
    for (let i = 0; i < n && written < count; i++) plan[written++] = s;
  }
  return plan;
}

export function seedAgents(
  pattern: SpawnPattern,
  agentCount: number,
  shares: Shares,
  fieldWidth: number,
  fieldHeight: number,
): ArrayBuffer {
  const buf = new ArrayBuffer(agentCount * AGENT_BYTE_STRIDE);
  const f = new Float32Array(buf);
  const u = new Uint32Array(buf);
  const plan = speciesPlan(agentCount, shares);

  const cx = fieldWidth / 2;
  const cy = fieldHeight / 2;
  const minDim = Math.min(fieldWidth, fieldHeight);

  // clusters: one centre per species, arranged on an equilateral triangle
  const clusterR = minDim * 0.3;
  const clusterSpread = minDim * 0.12;
  const clusterCentres = [0, 1, 2].map((s) => {
    const a = -Math.PI / 2 + (s * 2 * Math.PI) / 3;
    return { x: cx + Math.cos(a) * clusterR, y: cy + Math.sin(a) * clusterR };
  });

  const ringR = minDim * 0.35;
  const centerR = minDim * 0.1;
  const stride = AGENT_BYTE_STRIDE / 4; // 6 float slots

  for (let i = 0; i < agentCount; i++) {
    const o = i * stride;
    const species = plan[i] ?? 0;
    let x: number;
    let y: number;
    let angle = Math.random() * Math.PI * 2;

    switch (pattern) {
      case 'clusters': {
        const c = clusterCentres[species] ?? { x: cx, y: cy };
        x = c.x + (Math.random() - 0.5) * 2 * clusterSpread;
        y = c.y + (Math.random() - 0.5) * 2 * clusterSpread;
        break;
      }
      case 'center': {
        const a = Math.random() * Math.PI * 2;
        const r = Math.random() * centerR;
        x = cx + Math.cos(a) * r;
        y = cy + Math.sin(a) * r;
        break;
      }
      case 'ring': {
        const a = Math.random() * Math.PI * 2;
        x = cx + Math.cos(a) * ringR;
        y = cy + Math.sin(a) * ringR;
        angle = a; // heading outward
        break;
      }
      default: {
        // 'random'
        x = Math.random() * fieldWidth;
        y = Math.random() * fieldHeight;
        break;
      }
    }

    f[o + 0] = Math.max(0, Math.min(fieldWidth, x));
    f[o + 1] = Math.max(0, Math.min(fieldHeight, y));
    f[o + 2] = angle;
    u[o + 3] = species;
    f[o + 4] = Math.random() * Math.PI * 2; // rhythmPhase
    f[o + 5] = 0; // pad
  }

  return buf;
}