<!-- SOLID CITY · part 2457 -->
if (b.stepTarget) {
          var d = b.group.position.distanceTo(b.stepTarget);
          if (d < 0.03) b.stepTarget = null;
          else {
            b.group.position.addScaledVector(b.stepTarget.clone().sub(b.group.position).normalize(), Math.min(d, 0.16 * dt));
            b.group.position.y = b.surface.center.y;
            b.pose.legMode = 'gait'; b.pose.gaitSpeed = 0.16;
          }
        }
      }
    },
    GROUND_IDLE: {
      next: ['TAKEOFF', 'GROUND_FORAGE', 'ALERT'],
      update: function (b, dt) {
        restPoseCommon(b, 'stand'); b.pose.tailBase = 0.22; b.scanUpdate(dt);
        if (b.behavior.decideT < 0.1 && b.needs.hunger > 0.45 && b.spotId) { /* forage handled by decide */ }
        if (b.pendingAction === 'forage') { b.pendingAction = null; b.setState('GROUND_FORAGE'); }
      }
    },
    GROUND_FORAGE: {
      next: ['GROUND_IDLE', 'TAKEOFF', 'ALERT'],
      enter: function (b) { b.peckT = 0; b.hopTarget = null; b.forageLeft = 6 + b.rng() * 6; },
      update: function (b, dt) {
        restPoseCommon(b, 'stand'); b.pose.tailBase = 0.22;
        b.forageLeft -= dt;
        b.peckT -= dt;
        if (b.hopTarget) {
          var rem = b.loco.moveGround(dt, b.hopTarget, b.species.ground.walkSpeed);
          b.pose.legMode = 'gait'; b.pose.gaitSpeed = b.groundSpeed;
          if (rem < 0.05) b.hopTarget = null;
        } else if (b.peckT <= 0) {
          b.peckT = 1.4 + b.rng() * 1.6;
          if (b.rng() < 0.55) {
            var a = b.rng() * Math.PI * 2, r = 0.3 + b.rng() * 0.7;
            b.hopTarget = V3(b.group.position.x + Math.cos(a) * r, 0, b.group.position.z + Math.sin(a) * r);
          }
        }
        // pecking envelope between hops
        var pk = Math.max(0, Math.sin(b.time * 2.4));
        b.pose.headPitch = b.hopTarget ? 0.1 : 0.55 * pk;
        b.pose.headYaw = Math.sin(b.time * 0.8) * 0.3;
        b.needs.hunger = clamp(b.needs.hunger - 0.012 * dt, 0, 1);
        if (b.forageLeft <= 0) { b.pendingAction = null; b.setState('GROUND_IDLE'); }
      }
    },
    ALERT: {
      next: ['PERCHED', 'SURFACE_REST', 'GROUND_IDLE', 'GROUND_FORAGE', 'TAKEOFF'],
      enter: function (b) { b.alertT = 0; },
      update: function (b, dt, ctx) {
        restPoseCommon(b, b.surface ? (b.surface.kind === 'branch' ? 'grip' : 'stand') : 'stand');
        b.pose.tailBase = 0.3;
        b.alertT += dt;
        if (ctx && ctx.playerPos) {  // face the disturbance
          var want = Math.atan2(ctx.playerPos.x - b.group.position.x, ctx.playerPos.z - b.group.position.z);
          b.pose.headYaw = clamp(angDiff(b.heading, want), -1.3, 1.3);
          b.pose.headPitch = -0.08;
        }
        var calm = !ctx || !ctx.playerPos || b.group.position.distanceTo(ctx.playerPos) > b.species.behavior.alertDistanceM * 1.15;
        if (b.alertT > 1.2 && calm) {
          var back = b.returnState || 'PERCHED';
          b.returnState = null;
          b.setState(back);
        }
      }
    },
    TAKEOFF: { next: ['FLIGHT'], enter: takeoffEnter, update: takeoffUpdate },
    FLIGHT: { next: ['LAND_BRANCH', 'LAND_SURFACE', 'LAND_GROUND'], enter: flightEnter, update: flightUpdate },
    LAND_BRANCH: makeLanding('branch'),
    LAND_SURFACE: makeLanding('surface'),
    LAND_GROUND: makeLanding('ground')
  };
  // forage entry wiring: ground decide may route IDLE->FORAGE via pendingAction
  PARAKEET_FSM.GROUND_IDLE.next.push('GROUND_FORAGE');

  var DODO_FSM = {
    REST: {
      next: ['WALK', 'TURN', 'FORAGE', 'ALERT', 'HURRY'],
      update: function (b, dt) { restPoseCommon(b, 'stand'); b.pose.tailBase = 0.05; b.scanUpdate(dt); }
    },
    TURN: {
      next: ['WALK', 'REST', 'ALERT', 'HURRY'],
      update: function (b, dt) {
        restPoseCommon(b, 'stand');
        if (!b.walkTarget) { b.setState('REST'); return; }
        var want = Math.atan2(b.walkTarget.x - b.group.position.x, b.walkTarget.z - b.group.position.z);
        var dh = angDiff(b.heading, want);
        // small careful steps while turning (gait at creep speed)
        b.pose.legMode = 'gait'; b.pose.gaitSpeed = 0.10;
        b.heading += clamp(dh, -b.species.ground.turnRateRad * 0.8 * dt, b.species.ground.turnRateRad * 0.8 * dt);
        if (Math.abs(dh) < 0.07) b.setState('WALK');
      }
    },
    WALK: {
      next: ['REST', 'FORAGE', 'ALERT', 'HURRY', 'TURN'],
      update: function (b, dt) {
        restPoseCommon(b, 'gait');
        if (!b.walkTarget) { b.setState('REST'); return; }
        var rem = b.loco.moveGround(dt, b.walkTarget, b.species.ground.walkSpeed);
        b.pose.gaitSpeed = b.groundSpeed;
        b.pose.bodyPitch = 0.035;
        b.scanUpdate(dt);
        if (rem < 0.3) {
          b.walkTarget = null;
          if (b.pendingAction === 'forage') { b.pendingAction = null; b.setState('FORAGE'); }
          else b.setState('REST');
        }
      }
    },
    FORAGE: {
      next: ['REST', 'WALK', 'ALERT', 'HURRY'],
      enter: function (b) { b.peckT = 0; b.s