<!-- SOLID CITY · part 1850 -->
eHUD();
  }
  function moveProjectiles(a,dt){a.forEach(function(p){p.x=wrapX(p.x+p.vx*dt);p.y=wrapY(p.y+p.vy*dt);p.life-=dt})}
  function friendlyHit(source,owner,allyIndex,amount,kind,x,y){var key=String(source&&source.id||source&&source.fid||kind)+'-'+owner+'-'+allyIndex;if(friendlyHitGuard[key])return false;friendlyHitGuard[key]=performance.now();metrics.friendlyFire++;damage(ships[allyIndex],allyIndex,amount,'friendly');notify(allyIndex,'FRIENDLY FIRE'+(kind==='blast'?' · BLAST':''),750);notify(owner,'FRIENDLY FIRE'+(kind==='blast'?' · BLAST':''),750);burst(x,y,'#d38cff',kind==='blast'?16:12);logA('friendly fire · '+ships[owner].label+' '+(kind==='blast'?'blast caught ':'struck ')+ships[allyIndex].label);return true}
  function collisions(){
    shots.forEach(function(s){
      if(level===4){ships.forEach(function(ally,ai){if(ai!==s.owner&&s.life>0&&ally.alive&&distObj(s,ally)<18){s.life=0;friendlyHit(s,s.owner,ai,10,'direct',s.x,s.y)}})}
      enemies.forEach(function(e){if(s.life>0&&e.hull>0&&distObj(s,e)<18){s.life=0;e.hull-=12+ships[s.owner].build.cannon*4+coordinatedHit(e,s.owner);burst(s.x,s.y,'#ffb36a',6);if(e.hull<=0){e.hull=0;metrics.kills++;progression.levelKills++;ships[s.owner].score+=100;awardRescue(e,s.owner);burst(e.x,e.y,'#ff5b66',14);if(level===1)dropComponent(e.x,e.y);dropPower(e.x,e.y)}}});
      meteors.forEach(function(m){if(s.life>0&&!m.resolved&&distObj(s,m)<m.r+4){s.life=0;m.hull-=1+coordinatedHit(m,s.owner)/8;burst(s.x,s.y,'#ffc84a',5);if(m.hull<=0){m.resolved=true;ships[s.owner].score+=25;awardRescue(m,s.owner);burst(m.x,m.y,'#ffc84a',13);if(level===4){ships.forEach(function(ally,ai){if(ai!==s.owner&&ally.alive&&distObj(m,ally)<72){friendlyHit(m,s.owner,ai,15,'blast',ally.x,ally.y)}})}if(m.threat){metrics.avoided++;logA('outcome · meteor destroyed');checkContinuity(m)}}}})
    });
    bolts.forEach(function(b){ships.forEach(function(s,i){if(b.life>0&&s.alive&&distObj(b,s)<17){b.life=0;damage(s,i,15,'bolt')}})});
    meteors.forEach(function(m){ships.forEach(function(s,i){if(!m.resolved&&s.alive&&distObj(m,s)<m.r+19){m.resolved=true;damage(s,i,38,'meteor');burst(m.x,m.y,'#ff8c24',20);if(i===0){metrics.meteorHits++;if(m.threat){logA('outcome · human ship hit');checkContinuity(m)}}}})});
    enemies.forEach(function(e){ships.forEach(function(s,i){if(e.hull>0&&s.alive&&distObj(e,s)<32){e.hull=0;metrics.kills++;progression.levelKills++;if(level===1)dropComponent(e.x,e.y);dropPower(e.x,e.y);damage(s,i,30,'collision');burst(e.x,e.y,'#ff5b66',14)}})})
  }
  function breakShield(s){if(!s.shieldCharges)return;s.shieldCharges=0;s.shieldUntil=0;s.shieldShatterUntil=performance.now()+500;burst(s.x,s.y,'#6ef2ff',34)}
  function damage(s,i,n,why){if(!s||!s.alive||!Number.isFinite(n)||n<=0)return;var now=performance.now();if(s.shieldCharges>0&&now<s.shieldUntil){s.shieldCharges--;s.shieldHitUntil=now+260;metrics.shieldHits++;notify(i,'SHIELD ABSORBED',420);burst(s.x,s.y,'#d8fbff',12);if(s.shieldCharges<=0){s.shieldCharges=1;breakShield(s);notify(i,'SHIELD SHATTERED',700)}return}if(flight.mode==='formation'&&ships[0].alive&&ships[1].alive){var saved=n*.18;n*=.82;metrics.defenseSaved+=saved;notify(i,'FORMATION DEFENSE',420)}s.hull-=n;notify(i,flight.mode==='formation'?'FORMATION DEFENSE':'HULL HIT',350);if(s.hull<=0){s.hull=0;s.alive=false;s.respawn=2.4;s.vx=0;s.vy=0;burst(s.x,s.y,s.color,28);notify(i,'RESPAWNING',1600);if(!runEnding&&ships.every(function(q){return !q.alive})){runEnding=true;setTimeout(function(){endRun('defeat')},240)}}}
  function burst(x,y,color,n){for(var i=0;i<n;i++){var a=Math.random()*6.28,sp=40+Math.random()*180;sparks.push({x:x,y:y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,life:.3+Math.random()*.55,color:color})}}
  function updateHUD(){
    var now=performance.now();ships.forEach(function(s,i){document.querySelector('#hull'+i).style.width=Math.max(0,s.hull/s.maxHull*100)+'%';document.querySelector('#score'+i).textContent=String(s.score).padStart(4,'0');document.querySelector('#build'+i).innerHTML='<b>H'+s.build.hull+'</b> C'+s.build.cannon+' T'+s.build.thruster+' B'+s.build.bio;var items=[];if(s.shieldCharges>0&&s.shieldUntil>now)items.push('<span class="power shield on">SH '+Math.max(0,(s.shieldUntil-now)/1000).toFixed(1)+'</span>');if(s.spreadUntil>now)items.push('<span class="power spread on">SP '+((s.spreadUntil-now)/1000).toFixed(1)+'</span>');if(s.rapidUntil>now)items.push('<span class="power rapid on">RP '+((s.rapidUntil-now)/1000).toFixed(1)+'</span>');if(s.repairUntil>now)items.push('<span class="power repair on">R+</span>');document.querySelector('#powers'+i).innerHTML=items.join('');var band=altitudeBand(s.altitude);document.querySelectorAll('#alt'+i+' span').forEach(function(el){el.classList.toggle('active',Number(el.dataset.band)===band)})});
    var speed=level===5?Math.min(599,390+Math.round(levelElapsed*4)):Math.round((Math.hypot(ships[0].vx,ships[0].vy)+Math.hypot(ships[1].vx,ships[1].vy))/2);speedValueEl.textContent=String(s