<!-- SOLID CITY · part 1827 -->

  function updateFlight(dt){
    var d=distObj(ships[0],ships[1]);flight.lastDistance=d;
    if(flight.mode==='formation'){
      metrics.formationTime+=dt;
      if(d>470){flight.mode='separation';flight.wasSeparated=true;metrics.separations++;if(ships[1].coveringTarget!==null)ships[1].coverBroke=true;enemies.concat(meteors).forEach(function(o){if(o.rescuePilot>=0)o.rescueBroke=true;o.lastHitBy=-1;o.lastHitAt=0});flightStateEl.textContent='SEPARATION';flightStateEl.className='flight-state separation';notify(0,'DIRECTION HOME',850);notify(1,'DIRECTION HOME',850)}
    }else if(flight.mode==='separation'&&d<330){
      flight.mode='formation';flight.reunionUntil=performance.now()+1500;metrics.reunions++;metrics.reunionBonus+=150;ships[0].score+=75;ships[1].score+=75;flightStateEl.textContent='REUNITED +150';flightStateEl.className='flight-state reunion';notify(0,'REUNITED +75',1300);notify(1,'REUNITED +75',1300);burst(ships[0].x,ships[0].y,'#fff2a8',22);burst(ships[1].x,ships[1].y,'#fff2a8',22)
    }
    if(flight.mode==='formation'&&performance.now()>flight.reunionUntil){flightStateEl.textContent='FORMATION';flightStateEl.className='flight-state formation'}
  }
  function awardRescue(target,owner){var threatened=target.threatTargetIndex;if(threatened<0||target.targetIndex!==threatened||owner===threatened||target.rescuePilot!==owner||!target.rescueBroke)return;metrics.rescues++;ships[owner].score+=200;notify(threatened,'RESCUED +200',1200);notify(owner,'RESCUE +200',1200);logA('rescue · '+ships[owner].label+' broke formation to stop threat '+target.id);if(ships[1].coveringTarget===target.id){ships[1].coveringTarget=null;ships[1].coverBroke=false}}
  function coordinatedHit(target,owner){var now=performance.now(),bonus=0,threatened=target.threatTargetIndex;if(threatened>=0&&owner!==threatened){target.rescuePilot=owner;if(flight.mode==='separation')target.rescueBroke=true}if(flight.mode!=='formation'){target.lastHitBy=-1;target.lastHitAt=0;return 0}if(target.lastHitBy>=0&&target.lastHitBy!==owner&&now-target.lastHitAt<720&&now-(target.coordAt||0)>800){bonus=8;target.coordAt=now;metrics.coordinated++;ships[0].score+=25;ships[1].score+=25;notify(0,'COORDINATED STRIKE +50',650);notify(1,'COORDINATED STRIKE +50',650)}target.lastHitBy=owner;target.lastHitAt=now;return bonus}
  function steer(ship,tx,ty,dt,force){var dx=delta(tx,ship.x,WORLD.w),dy=delta(ty,ship.y,WORLD.h),l=Math.hypot(dx,dy)||1;ship.vx+=dx/l*force*dt;ship.vy+=dy/l*force*dt}
  function humanInput(i){var c=controls[i],x=0,y=0;if(i===0){x=(keys.KeyD?1:0)-(keys.KeyA?1:0);y=(keys.KeyS?1:0)-(keys.KeyW?1:0)}else{x=(keys.ArrowRight?1:0)-(keys.ArrowLeft?1:0);y=(keys.ArrowDown?1:0)-(keys.ArrowUp?1:0)}if(c.active){x+=c.x;y+=c.y}var l=Math.hypot(x,y);if(l>1){x/=l;y/=l}return {x:x,y:y,active:l>.06}}
  function aiPilot(dt){
    var s=ships[1], now=performance.now(),input=humanInput(1);if(!s.alive)return;
    var takeover=controls[1].active||input.active||controls[1].keyActive;
    if(takeover){drive(s,input,dt);return}
    if(controls[1].resumePending&&now-controls[1].lastRelease>850){controls[1].resumePending=false;notify(1,'AI RESUMED',900);logH('AI resumed Ship Two')}
    if(now-controls[1].lastRelease<850){s.vx*=Math.pow(.93,dt*60);s.vy*=Math.pow(.93,dt*60);return}
    var human=ships[0],threat=null,threatDistance=1e9;
    meteors.forEach(function(m){if(!m.resolved&&collisionCourse(m,human,2.1,32)){m.targetIndex=0;if(m.threatTargetIndex<0)m.threatTargetIndex=0;m.threat=true;var d=distObj(s,m);if(d<threatDistance){threat=m;threatDistance=d}}});
    enemies.forEach(function(e){if(e.hull>0&&e.threatTargetIndex===0&&distObj(e,human)<390){var d=distObj(s,e);if(d<threatDistance){threat=e;threatDistance=d}}});
    if(s.coveringTarget!==null){var still=enemies.concat(meteors).some(function(o){return o.id===s.coveringTarget&&!o.resolved&&o.hull>0});if(!still){s.coveringTarget=null;s.coverBroke=false}}
    var target=nearest(s,enemies,level===3||level===4?1150:850),power=nearest(s,pickups.filter(function(p){return !p.component||p.target===1}),520),ownRing=nearest(s,rings.filter(function(r){return r.owner===1}),900),fx=human.x,fy=human.y;
    if(threat){s.coveringTarget=threat.id;threat.rescuePilot=1;s.coverBroke=s.coverBroke||distObj(s,human)>470;if(s.coverBroke)threat.rescueBroke=true;fx=threat.x;fy=threat.y;if(now>s.coverNotice){notify(1,'COVERING YOU',900);notify(0,'AI COVERING YOU',900);s.coverNotice=now+1200}}
    else if(s.coveringTarget===null&&distObj(s,human)>260){fx=human.x;fy=human.y}
    else if((level===2||level===4)&&ownRing){fx=ownRing.x;fy=ownRing.y}
    else if(power){fx=power.x;fy=power.y}
    else if(target&&distObj(s,target)<(level>=3?620:340)){fx=target.x;fy=target.y}
    else{var side=Math.sin(elapsed*.7)*70;fx=human.x+Math.cos(human.a+1.57)*side+115;fy=human.y+Math.sin(human.a+1.57)*side}
    var dodgeX=0,dodgeY=0;
    meteors.concat(bolts).forEach(function(o){var d=distObj(s,o);if(d<150){var dx=delta(s.x,o.x,WORL