<!-- SOLID CITY · part 2453 -->
ell * 0.06);
    var stayU = 0.34 + (1 - b.needs.energy) * 0.35 - restlessness * 0.25 + b.rng() * 0.05;
    var best = null, bestU = stayU;
    var self = this;
    function consider(dest, u) { if (u > bestU) { bestU = u; best = dest; } }
    w.surfaces.forEach(function (s) {
      if (b.surface === s) return;
      if (b.memory.blocked[s.id] && b.time < b.memory.blocked[s.id]) return;
      var sc = w.scoreSurface(b, s); if (!sc) return;
      var dist = b.group.position.distanceTo(s.center);
      var u = 0.22 + sc.total * 0.34 + (1 - b.needs.energy) * 0.18
        + (s.id !== b.memory.lastSurfaceId ? 0.10 : 0)
        - dist * 0.016 + b.rng() * 0.07;
      if (b.journeyDest && b.journeyDest.kind === 'surface' && b.journeyDest.surface === s) u += 0.22; // finish the journey
      if (b.playerDist != null && s.center.distanceTo(b.lastPlayerPos || s.center) < sp.behavior.alertDistanceM) u -= 0.3;
      consider({ kind: 'surface', surface: s }, u);
    });
    w.groundSpots.forEach(function (g) {
      if (b.spotId === g.id) return;                          // already here — never "travel" to the same spot
      var dist = b.group.position.distanceTo(g.pos);
      var u = 0.30 + (g.kind === 'forage' ? b.needs.hunger * 0.55 : 0.12)
        + sp.behavior.curiosity * 0.14 - dist * 0.014 + b.rng() * 0.07;
      if (b.journeyDest && b.journeyDest.kind === 'ground' && b.journeyDest.spot === g) u += 0.30;
      consider({ kind: 'ground', spot: g }, u);
    });
    if (best) {
      b.dest = best; b.journeyDest = best;
      b.setState('TAKEOFF');
    }
  };
  BehaviorController.prototype.decideGround = function () {
    var b = this.bird, sp = b.species, w = b.world;
    if (b.state === 'FORAGE') return; // foraging runs its own course
    if (b.needs.hunger > 0.5 || b.rng() < 0.4) {
      var fp = w.meta.foragePatch || { x: 1.5, z: -3.5, r: 3 };
      var a = b.rng() * Math.PI * 2, r = b.rng() * fp.r * 0.7;
      b.walkTarget = V3(fp.x + Math.cos(a) * r, 0, fp.z + Math.sin(a) * r);
      b.pendingAction = 'forage';
    } else if (b.needs.energy < 0.3) {
      return; // stay and rest
    } else {
      var a2 = b.rng() * Math.PI * 2, r2 = 3 + b.rng() * 8;
      var tx = clamp(b.group.position.x + Math.cos(a2) * r2, -20, 20);
      var tz = clamp(b.group.position.z + Math.sin(a2) * r2, -20, 20);
      for (var i = 0; i < w.obstacles.length; i++) {
        if (w.distToObstacle({ x: tx, z: tz }, w.obstacles[i]) < 1.2) { tx += 3; tz += 2; }
      }
      b.walkTarget = V3(tx, 0, tz);
      b.pendingAction = 'rest';
    }
    var dh = Math.abs(angDiff(b.heading, Math.atan2(b.walkTarget.x - b.group.position.x, b.walkTarget.z - b.group.position.z)));
    b.setState(dh > 0.9 ? 'TURN' : 'WALK');
  };

  /* ================================================================
   * BirdEntity — state machines with phased transitions (Part II).
   * Every transition passes through its intermediate phases; the
   * history log is the evidence. Flight states are only registered
   * for species whose registry says canFly — a dodo's machine has
   * no aerial state to enter, by construction.
   * ================================================================ */
  function BirdEntity(speciesId, world, opts) {
    opts = opts || {};
    this.species = Registry.get(speciesId);
    this.world = world;
    this.rng = mulberry32(opts.seed || 1234);
    this.model = buildBirdModel(this.species);
    this.group = this.model.group;
    this.anim = new AnimationController(this);
    this.loco = new LocomotionController(this);
    this.behavior = new BehaviorController(this);
    this.vel = V3(); this.speed = 0; this.groundSpeed = 0;
    this.heading = opts.heading || 0; this.pitch = 0; this.bank = 0;
    this.flapMode = 'flap'; this.energyDemand = 0;
    this.needs = { energy: 0.85, hunger: 0.35 };
    this.memory = { lastSurfaceId: null, blocked: {} };
    this.pose = { foldTarget: 0, flapActive: false, flapRateMul: 1, flapAmp: 1, glide: false, legMode: 'stand', headYaw: 0, headPitch: 0, tailPitch: 0, tailBase: 0, bodyPitch: 0, bodyRoll: 0, gaitSpeed: 0, wingYaw: 0 };
    this.state = null; this.phase = null; this.phaseT = 0; this.stateT = 0; this.dwell = 0;
    this.time = 0; this.dest = null; this.journeyDest = null; this.intentSurfaceId = null;
    this.surface = null; this.spotId = null; this.viaPoints = [];
    this.encounterCooldown = {}; this.decisions = []; this.history = [];
    this.playerDist = Infinity; this.lastPlayerPos = null;
    this.scanYaw = 0; this.scanPitch = 0; this.scanT = 0;
    this.walkTarget = null; this.pendingAction = null; this.returnState = null;
    this.lodLevel = 2; this.detail = null;
    this.glideSeconds = 0; this.foldMidFrames = 0;   // validation instrumentation
    this.fsm = this.species.capabilities.canFly ? PARAKEET_FSM : DODO_FSM;
  }
  BirdEntity.prototype.clearanceRadius = function () {
    return this.species.capabilities.canFly ? this.species.size.wingspanM / 2