<!-- SOLID CITY · part 2026 -->
s.vy+=ay*dt;var sp=Math.hypot(s.vx,s.vy),max=130;if(sp>max){s.vx=s.vx/sp*max;s.vy=s.vy/sp*max}s.a=Math.atan2(s.vy,s.vx)}
  function fire(s,forced){if(s.disabled>0||s.cool>0)return;var e=nearestEnemy(s);if(!e)return;var d=dist(s,e);if(!forced&&d>370)return;var lead={x:e.x+e.vx*d/420,y:e.y+e.vy*d/420},a=Math.atan2(lead.y-s.y,lead.x-s.x);shots.push({x:s.x+Math.cos(a)*18,y:s.y+Math.sin(a)*18,vx:Math.cos(a)*420,vy:Math.sin(a)*420,team:s.team,life:1.2,from:s.id,damage:7+(s.slot%2)*2});s.cool=.55+rand()*.35;for(var i=0;i<3;i++)sparks.push({x:s.x,y:s.y,vx:-Math.cos(a)*(20+rand()*50),vy:-Math.sin(a)*(20+rand()*50),life:.25,color:s.team})}
  function hitShip(target,shot){if(target.owners.defense==='AI'&&target.shield>10){target.shield=Math.max(0,target.shield-shot.damage*1.2);target.hull-=shot.damage*.28}else target.hull-=shot.damage;target.hull=Math.max(0,target.hull);eventCounts.hit++;if(target.hull<=0){target.disabled=5;target.vx=target.vy=0;log('hit',target.name+' disabled · rescue window opened',target.id)}else if(target.hull<30&&target.task.indexOf('Rescue')<0){log('hit',target.name+' critical at '+Math.round(target.hull)+'% hull',target.id)}}
  function update(dt){if(!running)return;for(var r of rocks){r.x+=r.vx*dt;r.y+=r.vy*dt;r.spin+=dt;if(r.x<280)r.x=920;if(r.x>920)r.x=280;if(r.y<20)r.y=700;if(r.y>700)r.y=20}
    for(var s of ships){s.cool=Math.max(0,s.cool-dt);s.shield=Math.min(100,s.shield+6*dt);if(s.disabled>0){s.disabled-=dt;if(s.disabled<=0){s.hull=45;s.shield=50;s.x=s.team==='gold'?145:1055;s.y=170+s.slot*115;log('rescue',s.name+' recovered by squadron support',s.id)}continue}
      if(s.owners.movement==='AI')steer(s,desired(s),dt);else if(s.humanAim)steer(s,s.humanAim,dt);
      if(s.owners.weapons==='AI')fire(s,false);
      s.x=clamp(s.x+s.vx*dt,25,W-25);s.y=clamp(s.y+s.vy*dt,28,H-28);s.vx*=Math.pow(.9,dt);s.vy*=Math.pow(.9,dt);
      for(var r2 of rocks){if(dist(s,r2)<r2.r+12){s.hull=Math.max(0,s.hull-22*dt);s.vx+=(s.x-r2.x)*dt*5;s.vy+=(s.y-r2.y)*dt*5}}
      var des=desired(s),fd=dist(s,des);s.formation=fd<65?'locked':fd<140?'recovering':'broken';if(s.rescueTarget&&dist(s,s.rescueTarget)<70){s.rescueTarget.hull=Math.min(55,s.rescueTarget.hull+18*dt);s.rescueTarget.disabled=Math.max(0,s.rescueTarget.disabled-dt*2)}}
    for(var i=shots.length-1;i>=0;i--){var p=shots[i];p.x+=p.vx*dt;p.y+=p.vy*dt;p.life-=dt;var targets=teamShips(enemyTeam(p.team));var hit=targets.find(function(s){return s.disabled<=0&&dist(s,p)<15});if(hit){hitShip(hit,p);shots.splice(i,1);continue}if(p.life<=0||p.x<0||p.x>W||p.y<0||p.y>H)shots.splice(i,1)}
    for(var j=sparks.length-1;j>=0;j--){var q=sparks[j];q.x+=q.vx*dt;q.y+=q.vy*dt;q.life-=dt;if(q.life<=0)sparks.splice(j,1)}
    var inGold=teamShips('gold').filter(function(s){return s.disabled<=0&&dist(s,BEACON)<180}).length,inTeal=teamShips('teal').filter(function(s){return s.disabled<=0&&dist(s,BEACON)<180}).length;
    if(inGold>inTeal)progress.gold=clamp(progress.gold+dt*(1.2+inGold*.35),0,100);if(inTeal>inGold)progress.teal=clamp(progress.teal+dt*(1.2+inTeal*.35),0,100);
    if(progress.gold>=100)end('gold');if(progress.teal>=100)end('teal')
  }
  function end(team){running=false;var d=Math.round((performance.now()-missionStart)/1000),resc=events.filter(function(e){return e.type==='rescue'}).length,hand=events.filter(function(e){return e.type==='handoff'}).length;document.getElementById('winner').textContent=(team==='gold'?'Gold':'Teal')+' secures the Meridian Beacon.';document.getElementById('summary').textContent='Mission time '+Math.floor(d/60)+'m '+d%60+'s · '+hand+' control handoffs · '+resc+' rescues · '+eventCounts.hit+' confirmed hits. Every figure is drawn from this battle ledger.';document.getElementById('debrief').classList.add('open')}
  function shipColor(team){return team==='gold'?'#ffc952':'#38e3d1'}
  function draw(ctx,focusTeam,w,h){ctx.clearRect(0,0,w,h);ctx.save();ctx.scale(w/W,h/H);var g=ctx.createRadialGradient(600,360,10,600,360,650);g.addColorStop(0,'#0c2420');g.addColorStop(1,'#010504');ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
    ctx.fillStyle='#b9ffef';for(var k=0;k<70;k++){var sx=(k*193%W),sy=(k*97%H),sz=(k%3)+1;ctx.globalAlpha=.22+(k%5)*.08;ctx.fillRect(sx,sy,sz,sz)}ctx.globalAlpha=1;
    ctx.strokeStyle='#28443e';ctx.lineWidth=2;ctx.setLineDash([7,10]);ctx.beginPath();ctx.arc(BEACON.x,BEACON.y,BEACON.r,0,Math.PI*2);ctx.stroke();ctx.setLineDash([]);ctx.fillStyle='#9bcac0';ctx.font='700 13px sans-serif';ctx.textAlign='center';ctx.fillText('MERIDIAN',600,356);ctx.font='10px sans-serif';ctx.fillText('CONTROL BEACON',600,372);
    for(var r of rocks){ctx.save();ctx.translate(r.x,r.y);ctx.rotate(r.spin);ctx.fillStyle='#26302e';ctx.strokeStyle='#52605d';ctx.lineWidth=2;ctx.beginPath();for(var v=0;v<8;v++){var a=v/8*Math.PI*2,rr=r.r*(.75+(v%3)*.12);ctx.lineTo(Math.cos(a)*rr,Math.sin(a)*rr)}ctx.closePath();ctx.fill();ctx.stroke();ctx.restore()}
    for(var p of shots){ctx.strokeStyle=shipColor(p.team);ctx.lineWidth=3;ctx