<!-- SOLID CITY · part 2458 -->
huffleTarget = null; b.forageLeft = 8 + b.rng() * 8; },
      update: function (b, dt) {
        restPoseCommon(b, 'stand');
        b.forageLeft -= dt; b.peckT -= dt;
        if (b.shuffleTarget) {
          var rem = b.loco.moveGround(dt, b.shuffleTarget, b.species.ground.walkSpeed * 0.5);
          b.pose.legMode = 'gait'; b.pose.gaitSpeed = b.groundSpeed;
          if (rem < 0.06) b.shuffleTarget = null;
        } else {
          var pk = Math.max(0, Math.sin(b.time * 1.5));
          b.pose.headPitch = 0.30 + 0.62 * pk;      // the great bill dips to the litter
          b.pose.headYaw = Math.sin(b.time * 0.5) * 0.35;
          if (b.peckT <= 0) {
            b.peckT = 2 + b.rng() * 2.5;
            var a = b.rng() * Math.PI * 2, r = 0.4 + b.rng() * 0.9;
            b.shuffleTarget = V3(b.group.position.x + Math.cos(a) * r, 0, b.group.position.z + Math.sin(a) * r);
          }
        }
        b.needs.hunger = clamp(b.needs.hunger - 0.02 * dt, 0, 1);
        if (b.forageLeft <= 0 || b.needs.hunger < 0.12) b.setState('REST');
      }
    },
    ALERT: {
      next: ['REST', 'FORAGE', 'HURRY'],
      enter: function (b) { b.alertT = 0; },
      update: function (b, dt, ctx) {
        restPoseCommon(b, 'stand');
        b.alertT += dt;
        if (ctx && ctx.playerPos) {
          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.1, 1.1);
          b.pose.headPitch = -0.12;                  // head UP — watching
        }
        var calm = !ctx || !ctx.playerPos || b.group.position.distanceTo(ctx.playerPos) > b.species.behavior.alertDistanceM * 1.2;
        if (b.alertT > 2.2 && calm) { var back = b.returnState || 'REST'; b.returnState = null; b.setState(back); }
      }
    },
    HURRY: {
      next: ['ALERT', 'REST'],
      update: function (b, dt, ctx) {
        restPoseCommon(b, 'gait');
        if (!b.walkTarget) { b.setState('ALERT'); return; }
        var rem = b.loco.moveGround(dt, b.walkTarget, b.species.ground.hurrySpeed);
        b.pose.gaitSpeed = b.groundSpeed;
        b.pose.bodyPitch = 0.06;
        var d = (ctx && ctx.playerPos) ? b.group.position.distanceTo(ctx.playerPos) : 99;
        if (rem < 0.3 || d > b.species.behavior.alertDistanceM + 1.2) { b.walkTarget = null; b.returnState = 'REST'; b.setState('ALERT'); }
      }
    }
  };

  /* ================================================================
   * ObservationController — descriptions keyed to LIVE state.
   * describe().state always equals the bird's current state; the text
   * variant is stable within a state visit, so the panel never shows
   * a landing text for an airborne bird.
   * ================================================================ */
  var Observation = {
    describe: function (bird) {
      var key = bird.state;
      if (key === 'FLIGHT' && bird.flapMode === 'glide' && bird.species.descriptions.FLIGHT_GLIDE) key = 'FLIGHT_GLIDE';
      var arr = bird.species.descriptions[key] || bird.species.descriptions[bird.state] || [''];
      var visit = 0;
      for (var i = bird.history.length - 1; i >= 0; i--) { if (bird.history[i].kind === 'state') { visit = i; break; } }
      var idx = visit % arr.length;
      return { state: bird.state, key: key, text: arr[idx] };
    },
    evidenceLine: function (bird) {
      var ev = bird.species.evidence, parts = [];
      var order = ['coloration', 'size', 'flight', 'gait', 'behavior'];
      for (var i = 0; i < order.length; i++) {
        var k = order[i]; if (!ev[k]) continue;
        var label = ev[k].display || ev[k]['class'];
        parts.push(k.charAt(0).toUpperCase() + k.slice(1) + ': ' + label);
      }
      return parts.join(' · ');
    }
  };

  /* ================================================================
   * Hi-detail builder + LOD (Photography extension).
   * LOD 2 telephoto: eyelids (blink), claws, covert rows, breast
   * scallops, bill micro-geometry. LOD 1 mid: primary-feather fans,
   * tail fan, shoulder patch / neck ruff. LOD 0: base silhouette.
   * Detail choice is driven by EFFECTIVE distance = range / zoom,
   * so a distant bird under 20x still earns its full anatomy.
   * ================================================================ */
  var FEATHER_GEO = function () { return geo('feather', function () { return new THREE.ConeGeometry(0.5, 1, 6); }); };
  function plume(parent, color, x, y, z, chord, len, thick, rx, ry, rz) {
    var m = mesh(FEATHER_GEO(), mat(color), x, y, z);
    m.scale.set(chord, len, thick);
    m.rotation.set(rx || 0, ry || 0, rz || 0);
    parent.add(m);
    return m;
  }
  function buildDetailHi(bird) {
    var sp = bird.species, P = sp.proportions, C = sp.colors, J = bird.model.joints;
    var hi = [], mid = [];
    function reg(m, level) { (level >= 2 ? hi : mid).push(m); return m; }
    var side, i;
    // eyelids — lid group origin at the TOP of the eyeba