<!-- SOLID CITY · part 3719 -->
it.x,ty:summit.y,target:'MIRROR',life:1.6,offset:o/W,hit:false});emit('shot',{battery:name,owner,target:'MIRROR',shell:i+1});});emit('salvo',{battery:name,owner,target:'MIRROR'});for(let i=0;i<12;i++)particle(ship.x+(name==='batteryA'?-.018:.018),ship.y-.03,'muzzle');},id);return;}
  if(!live.length)return;const selected=name==='batteryA'?ship.targetA:ship.targetB;const target=live[selected%live.length];if(!target)return;const epoch=t.epoch,id='salvo-'+name+'-'+Math.floor(performance.now());coordination.command(name,owner,epoch,()=>{
    const offsets=[-10,-3,3,10];offsets.forEach((o,i)=>{shells.push({battery:name,owner,x:ship.x,y:ship.y-.035,tx:target.x,ty:target.y,target:target.id,life:1.4,offset:o/W,hit:false});emit('shot',{battery:name,owner,target:target.id,shell:i+1});});
    emit('salvo',{battery:name,owner,target:target.id});for(let i=0;i<12;i++)particle(ship.x+(name==='batteryA'?-.018:.018),ship.y-.03,'muzzle');
  },id);
}
function cycleTarget(name){const live=enemies.filter(e=>!e.dead);if(!live.length)return;if(name==='batteryA')ship.targetA=(ship.targetA+1)%live.length;else ship.targetB=(ship.targetB+1)%live.length;emit('target_selected',{battery:name,owner:'human',target:live[name==='batteryA'?ship.targetA:ship.targetB].id});}
function particle(x,y,type){particles.push({x,y,vx:(rand()-.5)*.14,vy:(rand()-.5)*.14,life:type==='boom'?1.2:.45,max:type==='boom'?1.2:.45,type});}
function hitEnemy(e,damage,s){e.hp-=damage;s.hit=true;emit('hit',{battery:s.battery,owner:s.owner,target:e.id,damage});for(let i=0;i<8;i++)particle(e.x,e.y,'hit');if(e.hp<=0&&!e.dead){e.dead=true;emit('threat_destroyed',{threat:e.id,battery:s.battery,owner:s.owner,kind:e.kind});for(let i=0;i<38;i++)particle(e.x,e.y,'boom');}}
const SHIELD_STRONG=.8,SHIELD_WEAK=.2;
function shieldProtection(facing,impactFace){return facing===impactFace?SHIELD_STRONG:SHIELD_WEAK;}
function projectileImpactFace(p){if(Math.abs(p.vx)>Math.abs(p.vy)*.55)return p.vx>0?'left':'right';return 'front';}
function relativeImpactFace(x){if(x<ship.x-.035)return 'left';if(x>ship.x+.035)return 'right';return 'front';}
function updateDefenseStatus(){
  const face=ship.shieldFace||'front',other=face==='front'?'sides':'other arcs',charge=Math.max(0,Math.round(ship.shield));
  $('defenseStatus').textContent=charge>0?'Facing '+face.toUpperCase()+' · rated 80% '+face+' / 20% '+other+' · charge '+charge+'%':'Facing '+face.toUpperCase()+' · 0% effective · shield empty';
  document.querySelectorAll('[data-station="defense"] [data-action^="shield"]').forEach(btn=>{const active=btn.dataset.action.toLowerCase()==='shield'+face;btn.classList.toggle('active',active);btn.setAttribute('aria-pressed',String(active));});
}
function damageShip(amount,source,impactFace){
  impactFace=impactFace||'front';
  const protection=shieldProtection(ship.shieldFace,impactFace),shieldAvailable=ship.shield>0;let absorbed=0;
  if(shieldAvailable){absorbed=Math.min(ship.shield,amount*protection);ship.shield-=absorbed;}
  const hull=Math.max(0,amount-absorbed);ship.hull-=hull;
  const absorbedPct=amount?Math.round(absorbed/amount*100):0;
  emit('damage_taken',{amount:+hull.toFixed(1),absorbed:+absorbed.toFixed(1),source,impactFace,shieldFace:ship.shieldFace,rating:+(protection*100).toFixed(0),absorbedPct});
  showWarning(shieldAvailable?impactFace.toUpperCase()+' IMPACT · '+absorbedPct+'% ABSORBED':impactFace.toUpperCase()+' HULL IMPACT · SHIELD EMPTY');
  for(let i=0;i<18;i++)particle(ship.x,ship.y,'hit');if(ship.hull<=0)finish(false);
}
function pointDefense(owner){if(ship.defCooldown>0)return;ship.defCooldown=.8;const before=enemyShots.length;enemyShots=enemyShots.filter(p=>{const d=Math.hypot(p.x-ship.x,p.y-ship.y);if(d<.18){emit('point_defense',{owner,projectile:p.id});for(let i=0;i<6;i++)particle(p.x,p.y,'muzzle');return false;}return true;});if(before!==enemyShots.length)emit('defense_intercept',{owner,count:before-enemyShots.length});}
function ai(dt){aiTick-=dt;if(aiTick>0)return;aiTick=.18;const nav=coordination.tasks.nav;if(nav.owner==='ai'){const ex=enemies.reduce((best,e)=>{const d=Math.hypot(e.x-ship.x,e.y-ship.y);return d<(best.d||9)?{d,e}:best;},{});const danger=enemyShots.find(p=>Math.hypot(p.x-ship.x,p.y-ship.y)<.24);let desired=.5;if(danger)desired=ship.x+(ship.x-danger.x>0?.2:-.2);else if(ex.e&&ex.d<.22)desired=ship.x+(ship.x-ex.e.x>0?.17:-.17);const epoch=nav.epoch;coordination.command('nav','ai',epoch,()=>{ship.navInput=Math.max(-1,Math.min(1,(desired-ship.x)*5));ship.boost=true;},'AI-nav-'+Math.floor(performance.now()/180));}
  if(summit)summitAI(dt);else{['batteryA','batteryB'].forEach((name,idx)=>{const t=coordination.tasks[name];if(t.owner==='ai'&&enemies.length){const live=enemies.filter(e=>!e.dead);if(live.length){if(name==='batteryA')ship.targetA=idx%live.length;else ship.targetB=(live.length-1);if(rand()<.72)fireBattery(name,'ai');}}});}
  const def=coordination.tasks.defense;if(def.owner==='ai'){const nearest=enemyShots.reduce((a,p)