<!-- SOLID CITY · part 3699 -->
x=e.x*W,y=e.y*H,s=e.kind==='cruiser'?18:11;ctx.save();ctx.translate(x,y);ctx.fillStyle=e.kind==='cruiser'?'#c6503d':'#8f4051';ctx.strokeStyle='#ff9b78';ctx.lineWidth=1.5;ctx.beginPath();ctx.moveTo(0,s);ctx.lineTo(-s*.8,-s*.7);ctx.lineTo(0,-s*.25);ctx.lineTo(s*.8,-s*.7);ctx.closePath();ctx.fill();ctx.stroke();ctx.restore();ctx.fillStyle='rgba(0,0,0,.7)';ctx.fillRect(x-18,y-s-9,36,3);ctx.fillStyle='#ff6358';ctx.fillRect(x-18,y-s-9,36*Math.max(0,e.hp/e.maxHp),3);}
function drawShip(){const x=ship.x*W,y=ship.y*H,s=Math.max(22,Math.min(34,W*.035));ctx.save();ctx.translate(x,y);ctx.fillStyle='rgba(35,199,217,'+(0.12+ship.shield/450)+')';ctx.strokeStyle='#36d9e8';ctx.lineWidth=2;ctx.beginPath();const a=ship.shieldFace==='left'?Math.PI*.55:ship.shieldFace==='right'?-Math.PI*.05:Math.PI*1.05;ctx.arc(0,0,s*1.7,a,a+Math.PI*.9);ctx.stroke();ctx.fillStyle='#b9d5df';ctx.strokeStyle='#effcff';ctx.lineWidth=1.5;ctx.beginPath();ctx.moveTo(0,-s*1.25);ctx.lineTo(-s*.72,s*.8);ctx.lineTo(-s*.28,s*.56);ctx.lineTo(0,s*.9);ctx.lineTo(s*.28,s*.56);ctx.lineTo(s*.72,s*.8);ctx.closePath();ctx.fill();ctx.stroke();ctx.fillStyle='#0c5967';ctx.fillRect(-s*.18,-s*.55,s*.36,s*.65);ctx.fillStyle='#58e5ef';ctx.fillRect(-s*.45,s*.65,s*.22,s*.22);ctx.fillRect(s*.23,s*.65,s*.22,s*.22);ctx.fillStyle='#ffbd59';[-.55,-.3,.3,.55].forEach(q=>{ctx.fillRect(q*s,-s*.05,3,8)});ctx.restore();}
function drawParticle(p){const a=Math.max(0,p.life/p.max);ctx.globalAlpha=a;ctx.fillStyle=p.type==='boom'?'#ff8a3d':p.type==='muzzle'?'#7bf5ff':'#ffd27b';ctx.beginPath();ctx.arc(p.x*W,p.y*H,(p.type==='boom'?10:4)*(1-a*.3),0,Math.PI*2);ctx.fill();ctx.globalAlpha=1;}
function updateHUD(){$('progressBar').style.width=(ship.progress*100).toFixed(1)+'%';$('rangeText').textContent=Math.max(0,Math.round(8000*(1-ship.progress))).toLocaleString()+' KM';$('hullBar').style.width=Math.max(0,ship.hull)+'%';$('hullText').textContent=Math.max(0,Math.round(ship.hull))+'%';$('shieldBar').style.width=Math.max(0,ship.shield)+'%';$('shieldText').textContent=Math.max(0,Math.round(ship.shield))+'%';$('waveChip').textContent=warden?'FINAL PASSAGE · WARDEN':'WAVE '+wave+' · CONTACTS '+enemies.length;updateDefenseStatus();}
function loop(t){const dt=Math.min(.034,(t-last)/1000||.016);last=t;update(dt);draw();raf=requestAnimationFrame(loop);}
function finish(win){if(ended)return;ended=true;running=false;paused=false;$('pauseBtn').hidden=true;$('pauseOverlay').hidden=true;document.body.classList.remove('game-active');emit(win?'mission_arrival':'mission_failed',{progress:+(ship.progress*100).toFixed(1),hull:+Math.max(0,ship.hull).toFixed(1)});const stats=deriveStats();$('resultKicker').textContent=win?'MISSION COMPLETE':'WARSHIP LOST';$('resultTitle').textContent=win?'The Warden yields. Meridian Gate reached.':'The blockade held.';$('resultText').textContent=win?'The crew aligned the helm, redirected the shield, synchronized both batteries, and restored the bridge by hand. The gate was won by command, not brute force.':'The route remains open for another attempt. Your station ownership and combat contributions were preserved in the mission ledger.';$('resultStats').innerHTML=statCards(stats,true);$('debrief').hidden=false;}
function deriveStats(){const shots=events.filter(e=>e.type==='shot'),hits=events.filter(e=>e.type==='hit'),hands=events.filter(e=>e.type==='handoff'),destroyed=events.filter(e=>e.type==='threat_destroyed'),damage=events.filter(e=>e.type==='damage_taken').reduce((a,e)=>a+e.amount,0),reject=events.filter(e=>e.type==='command_rejected');const handAvg=hands.length?hands.reduce((a,e)=>a+e.latency,0)/hands.length:0;return {shots:shots.length,hits:hits.length,destroyed:destroyed.length,damage:+damage.toFixed(1),handoffs:hands.length,handAvg:+handAvg.toFixed(2),rejected:reject.length,events:events.length,shotsA:shots.filter(e=>e.battery==='batteryA').length,hitsA:hits.filter(e=>e.battery==='batteryA').length,shotsB:shots.filter(e=>e.battery==='batteryB').length,hitsB:hits.filter(e=>e.battery==='batteryB').length,humanHits:hits.filter(e=>e.owner==='human').length,aiHits:hits.filter(e=>e.owner==='ai').length};}
function statCards(s,small){const arr=[['THREATS',s.destroyed],['HITS',s.hits],['HANDOFFS',s.handoffs],['HULL DAMAGE',s.damage+'%']];if(!small)arr.push(['LEDGER EVENTS',s.events],['STALE / JAM REJECTS',s.rejected],['AVG HANDOFF',s.handAvg+' ms'],['HUMAN / AI HITS',s.humanHits+' / '+s.aiHits]);return arr.map(x=>'<div class="stat"><b>'+x[1]+'</b><span>'+x[0]+'</span></div>').join('');}
function renderStats(){const s=deriveStats();$('statsGrid').innerHTML=statCards(s,false);$('batteryStats').innerHTML='<div class="log-row"><time>BATTERY A</time><b>'+s.hitsA+' hits / '+s.shotsA+' shells · '+(s.shotsA?Math.round(s.hitsA/s.shotsA*100):0)+'%</b></div><div class="log-row"><time>BATTERY B</time><b>'+s.hitsB+' hits / '+s.shotsB+' shells · '+(s.shotsB?Math.round(s.hitsB/s.shotsB*100):0)+'%</b></div>';$('statsLog').innerHTML=eventRows(events.slice(-28).rev