<!-- SOLID CITY · part 1920 -->
;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,WORLD.w),dy=delta(s.y,o.y,WORLD.h),q=(150-d)/150;dodgeX+=dx/(d||1)*q*3;dodgeY+=dy/(d||1)*q*3}});
    steer(s,fx,fy,dt,threat?390:315);s.vx+=dodgeX*330*dt;s.vy+=dodgeY*330*dt;s.actionTick++;
  }
  function drive(s,input,dt){if(!s.alive)return;var accel=520+s.build.thruster*85;s.vx+=input.x*accel*dt;s.vy+=input.y*accel*dt;s.actionTick++;if(input.active&&Math.hypot(s.vx,s.vy)>8)s.a=Math.atan2(s.vy,s.vx)}
  function assist(dt){
    if(!armEl.checked||!ships[0].alive||!ships[1].alive)return;
    var h=ships[0],human=humanInput(0),now=performance.now();
    meteors.forEach(function(m){
      if(m.resolved)return;var rx=delta(m.x,h.x,WORLD.w),ry=delta(m.y,h.y,WORLD.h),rvx=m.vx-h.vx,rvy=m.vy-h.vy,rv2=rvx*rvx+rvy*rvy;if(rv2<1)return;
      var t=-(rx*rvx+ry*rvy)/rv2;var cx=rx+rvx*t,cy=ry+rvy*t,miss=Math.hypot(cx,cy);
      if(t>0&&t<1.65&&miss<m.r+26){
        if(!m.threat){m.threat=true;metrics.threats[m.id]={shipTicks:[ships[0].actionTick,ships[1].actionTick],time:elapsed};logA('threat detected · meteor '+m.id);notify(0,'THREAT LOCK',650)}
        if(m.action==='none'){
          var d=Math.hypot(rx,ry);
          if(t>.64&&d<600&&distObj(ships[1],m)<190){m.action='intercept';logA('action · intercept meteor '+m.id);notify(1,'INTERCEPT',700);setTimeout(function(){if(running&&!m.resolved&&meteors.indexOf(m)>=0){burst(m.x,m.y,'#ffc84a',16);m.resolved=true;metrics.avoided++;awardRescue(m,1);logA('outcome · collision avoided');checkContinuity(m);meteors.splice(meteors.indexOf(m),1)}},220)}
          else if(!human.active){var side=(rx*rvy-ry*rvx)>0?1:-1,mag=260;h.assistVX=-rvy/(Math.hypot(rvx,rvy)||1)*side*mag;h.assistVY=rvx/(Math.hypot(rvx,rvy)||1)*side*mag;h.assistUntil=now+420;m.action='nudge';logA('action · emergency thruster nudge');notify(0,'EVADE ASSIST',900)}
        }
      }
    })
  }
  function checkContinuity(m){var t=metrics.threats[m.id];if(t){metrics.assistChecks++;if(ships[0].actionTick>t.shipTicks[0]&&ships[1].actionTick>t.shipTicks[1])metrics.assistContinuity++}}
  function buildTotal(s){return s.build.hull+s.build.cannon+s.build.thruster+s.build.bio}
  function dropComponent(x,y){
    var types=['hull','cannon','thruster','bio'],target=buildTotal(ships[0])<=buildTotal(ships[1])?0:1,type=types[progression.componentIndex++%types.length];
    if(ships[target].build[type]>=2){for(var i=0;i<types.length;i++)if(ships[target].build[types[i]]<2){type=types[i];break}}
    pickups.push({id:nextId++,type:type,compo