<!-- SOLID CITY · part 2452 -->
0.25;
    b.groundSpeed = lerp(b.groundSpeed || 0, spd, clamp(dt * 5, 0, 1));
    // obstacle push-out / slide for ground movers
    var nx = pos.x + Math.sin(b.heading) * b.groundSpeed * dt;
    var nz = pos.z + Math.cos(b.heading) * b.groundSpeed * dt;
    for (var i = 0; i < b.world.obstacles.length; i++) {
      var obs = b.world.obstacles[i];
      var d = b.world.distToObstacle({ x: nx, z: nz }, obs);
      var margin = 0.35 + sp.size.lengthM * 0.3;
      if (d < margin) { nx = pos.x; nz = pos.z; b.groundSpeed *= 0.4; }
    }
    // habitat bounds
    var bx = nx - b.world.bounds.cx, bz = nz - b.world.bounds.cz;
    var bl = Math.sqrt(bx * bx + bz * bz);
    if (bl > b.world.bounds.radius - 1) { nx = pos.x; nz = pos.z; }
    pos.x = nx; pos.z = nz; pos.y = b.world.groundHeightAt(nx, nz);
    return dist;
  };

  /* ================================================================
   * BehaviorController — utility decisions over real destinations,
   * needs (energy, hunger), compact memory, disturbance response.
   * Randomness only varies choices WITHIN biological constraints.
   * ================================================================ */
  function BehaviorController(bird) {
    this.bird = bird;
    this.decideT = 1.0 + bird.rng() * 2.0;
  }
  BehaviorController.prototype.isRestState = function () {
    var s = this.bird.state;
    return s === 'PERCHED' || s === 'SURFACE_REST' || s === 'GROUND_IDLE' || s === 'GROUND_FORAGE' || s === 'REST' || s === 'FORAGE';
  };
  BehaviorController.prototype.update = function (dt, ctx) {
    var b = this.bird, sp = b.species;
    // needs drift
    if (this.isRestState()) b.needs.energy = clamp(b.needs.energy + 0.030 * dt, 0, 1);
    b.needs.hunger = clamp(b.needs.hunger + 0.006 * dt, 0, 1);
    // disturbance (player) — species-specific distances, never uniform
    if (ctx && ctx.playerPos) {
      var d = b.group.position.distanceTo(ctx.playerPos);
      b.playerDist = d;
      if (b.species.capabilities.canFly) this.flyDisturbance(d, ctx);
      else this.groundDisturbance(d, ctx);
    }
    // decision clock in rest states
    if (this.isRestState()) {
      this.decideT -= dt;
      if (this.decideT <= 0) {
        this.decideT = sp.behavior.decisionIntervalS * (0.7 + b.rng() * 0.6);
        this.decide();
      }
    }
  };
  BehaviorController.prototype.flyDisturbance = function (d, ctx) {
    var b = this.bird, sp = b.species;
    var grounded = (b.state === 'GROUND_IDLE' || b.state === 'GROUND_FORAGE');
    var fleeD = sp.behavior.fleeDistanceM * (grounded ? 1.3 : 1.0);
    if (!this.isRestState() && b.state !== 'ALERT') return;
    if (d < fleeD) {
      // escape: safest reachable destination, away from the player
      var best = null, bestScore = -Infinity;
      var consider = function (dest, base) {
        var p = dest.kind === 'surface' ? dest.surface.center : dest.spot.pos;
        var pd = p.distanceTo(ctx.playerPos);
        var sc = base + pd * 0.35 - b.group.position.distanceTo(p) * 0.02;
        if (sc > bestScore) { bestScore = sc; best = dest; }
      };
      for (var i = 0; i < b.world.surfaces.length; i++) {
        var s = b.world.surfaces[i];
        if (b.world.scoreSurface(b, s)) consider({ kind: 'surface', surface: s }, 0.3);
      }
      for (i = 0; i < b.world.groundSpots.length; i++) consider({ kind: 'ground', spot: b.world.groundSpots[i] }, 0);
      if (best && !(best.kind === 'surface' && b.surface === best.surface)) {
        b.dest = best; b.journeyDest = best;
        b.setState('TAKEOFF');
      }
    } else if (d < sp.behavior.alertDistanceM && this.isRestState() && b.state !== 'ALERT') {
      b.returnState = b.state;
      b.setState('ALERT');
    }
  };
  BehaviorController.prototype.groundDisturbance = function (d, ctx) {
    var b = this.bird, sp = b.species;
    if (d < sp.behavior.fleeDistanceM) {
      if (b.state !== 'HURRY') {
        var away = b.group.position.clone().sub(ctx.playerPos); away.y = 0;
        if (away.lengthSq() < 0.01) away.set(1, 0, 0);
        away.normalize().multiplyScalar(4.2).add(b.group.position);
        away.x = clamp(away.x, -b.world.bounds.radius + 2, b.world.bounds.radius - 2);
        away.z = clamp(away.z, -b.world.bounds.radius + 2, b.world.bounds.radius - 2);
        b.walkTarget = away; b.setState('HURRY');
      }
    } else if (d < sp.behavior.alertDistanceM) {
      if (b.state === 'REST' || b.state === 'WALK' || b.state === 'FORAGE' || b.state === 'TURN') {
        b.returnState = (b.state === 'WALK' || b.state === 'TURN') ? 'REST' : b.state;
        b.setState('ALERT');
      }
    }
  };
  BehaviorController.prototype.decide = function () {
    var b = this.bird;
    if (b.species.capabilities.canFly) this.decideFlight();
    else this.decideGround();
  };
  BehaviorController.prototype.decideFlight = function () {
    var b = this.bird, sp = b.species, w = b.world;
    var restlessness = sp.behavior.restlessness * (0.5 + b.dw