<!-- SOLID CITY · part 2445 -->
roup; },
  baseY: function () { return M.baseY; }
};
})();


===== FILE: public/city/lost-wings/bird-engine.js =====
/* OPERATION LOST WINGS — Bird Engine
 * Part VII modules in one shared file (logical modules, no server processes):
 *   SpatialController   — world representation + AVOID / LAND / RETREAT evaluation
 *   ModelFactory        — articulated bird models from species proportions
 *   AnimationController — joint posing from controller intent (fold, flap, gait, look)
 *   LocomotionController— flight model + ground gait model
 *   BehaviorController  — utility decisions, needs, memory, disturbance response
 *   BirdEntity          — state machines with phased, blended transitions
 *   ObservationController — field descriptions keyed to live state
 * UMD: browser global (LostWings.Engine) + Node require for headless tests.
 * Requires: THREE (r128 global or global.THREE in Node), LostWings.Registry.
 */
(function (root, factory) {
  var THREEref = (typeof THREE !== 'undefined') ? THREE
    : (typeof global !== 'undefined' && global.THREE) ? global.THREE : null;
  var Registry = (typeof module !== 'undefined' && module.exports)
    ? require('./species-registry.js')
    : root.LostWings.Registry;
  var api = factory(THREEref, Registry);
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
  root.LostWings = root.LostWings || {};
  root.LostWings.Engine = api;
})(typeof self !== 'undefined' ? self : this, function (THREE, Registry) {
  'use strict';

  /* ---------------- utils ---------------- */
  function clamp(v, a, b) { return v < a ? a : (v > b ? b : v); }
  function lerp(a, b, t) { return a + (b - a) * t; }
  function ease(t) { return t * t * (3 - 2 * t); }
  function angLerp(a, b, t) {
    var d = (b - a) % (Math.PI * 2);
    if (d > Math.PI) d -= Math.PI * 2;
    if (d < -Math.PI) d += Math.PI * 2;
    return a + d * t;
  }
  function angDiff(a, b) {
    var d = (b - a) % (Math.PI * 2);
    if (d > Math.PI) d -= Math.PI * 2;
    if (d < -Math.PI) d += Math.PI * 2;
    return d;
  }
  function mulberry32(seed) {
    var a = seed >>> 0;
    return function () {
      a |= 0; a = (a + 0x6D2B79F5) | 0;
      var t = Math.imul(a ^ (a >>> 15), 1 | a);
      t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
      return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
    };
  }
  function V3(x, y, z) { return new THREE.Vector3(x || 0, y || 0, z || 0); }

  /* ================================================================
   * SpatialController — the bird's model of its world.
   * Obstacles are detected on the projected flight path BEFORE contact.
   * Every encounter is resolved by evaluateResponse(): AVOID / LAND / RETREAT,
   * scored explicitly. Landing surfaces are judged on size, angle,
   * clearance, stability and species suitability. Glass never perches.
   * ================================================================ */
  function SpatialWorld(def) {
    this.bounds = def.bounds;                    // {cx, cz, radius}
    this.obstacles = def.obstacles || [];        // cylinders + boxes
    this.surfaces = def.surfaces || [];          // landable candidates (incl. unsuitable ones, judged by scoring)
    this.groundSpots = def.groundSpots || [];    // {id, kind:'meadow'|'forage', pos}
    this.meta = def.meta || {};                  // mesh hints for the scene builder
    var i;
    for (i = 0; i < this.surfaces.length; i++) this.surfaces[i].occupiedBy = null;
  }

  SpatialWorld.prototype.surfaceById = function (id) {
    for (var i = 0; i < this.surfaces.length; i++) if (this.surfaces[i].id === id) return this.surfaces[i];
    return null;
  };
  SpatialWorld.prototype.spotById = function (id) {
    for (var i = 0; i < this.groundSpots.length; i++) if (this.groundSpots[i].id === id) return this.groundSpots[i];
    return null;
  };
  SpatialWorld.prototype.groundHeightAt = function () { return 0; };

  // Horizontal distance from point to obstacle footprint (0 if inside footprint).
  SpatialWorld.prototype.distToObstacle = function (p, obs) {
    if (obs.kind === 'cylinder') {
      var dx = p.x - obs.x, dz = p.z - obs.z;
      return Math.max(0, Math.sqrt(dx * dx + dz * dz) - obs.r);
    }
    // box
    var bx = Math.max(obs.minX - p.x, 0, p.x - obs.maxX);
    var bz = Math.max(obs.minZ - p.z, 0, p.z - obs.maxZ);
    return Math.sqrt(bx * bx + bz * bz);
  };
  SpatialWorld.prototype.insideBounds = function (p, margin) {
    var dx = p.x - this.bounds.cx, dz = p.z - this.bounds.cz;
    return Math.sqrt(dx * dx + dz * dz) <= this.bounds.radius - (margin || 0);
  };

  /* Project the bird forward along its velocity; return the first obstacle
   * whose body the path would meet (accounting for wingspan clearance and
   * the obstacle's top height), or null. Detection happens at lookahead
   * range — long before physical contact. */
  SpatialWorld.prototype.projectEncounter = function (bird, lookAheadS) {
    if (!bird.vel || bird.vel.le