<!-- SOLID CITY · part 2007 -->
s):nearestEnemy(s)}
  function hitShard(sh,shot){sh.hp-=shot.damage;eventCounts.hit++;sparks.push({x:sh.x,y:sh.y,vx:rand()*60-30,vy:rand()*60-30,life:.3,color:'gold'});if(sh.hp<=0)sh.dead=true}
  function setSummitText(t){var el=document.getElementById('summitText');if(el)el.textContent=t}
  function beginUnion(winner){
    summit.active=true;summit.phase='union';summit.beaconHp=100;summit.hold=0;summit.wave=0;summit.spawnedThisWave=0;summit.spawnQueue=0;summit.spawnTimer=0;summit.shards=[];summit.choice=null;summit.anchor=null;summit.pulseR=0;summit.ignition=0;summit.choiceTimer=0;summit.lost=[];
    order.gold='form';order.teal='form';
    document.querySelectorAll('.orders button').forEach(function(b){b.classList.toggle('active',b.dataset.order==='form')});
    document.querySelectorAll('.objective span').forEach(function(el){el.textContent='UNION'});
    document.getElementById('missionLine').textContent='One constellation. Hold the octagon.';
    document.getElementById('summitBar').hidden=false;document.getElementById('summitChoices').hidden=true;
    setSummitText('FORM THE OCTAGON — 0/8 AT THEIR VERTICES');
    log('formation',(winner==='gold'?'Gold':'Teal')+' reached 100% · the Meridian Beacon answered','BEACON');
    toast('THE BEACON WAKES — FORM THE OCTAGON');
  }
  function ignite(){
    summit.phase='siege';summit.wave=0;summit.spawnedThisWave=0;summit.spawnQueue=WAVES[0];summit.spawnTimer=1.5;summit.ignition=0.0001;
    log('formation','Octagon locked · the beacon ignites · the storm answers','BEACON');
    toast('THE CONSTELLATION HOLDS — THE STORM COMES');
  }
  function spawnOne(){
    var i=summit.spawnedThisWave%8,a=((i*45+22.5)+summit.wave*11.25)*Math.PI/180;
    summit.shards.push({x:BEACON.x+580*Math.cos(a),y:BEACON.y+330*Math.sin(a),vx:0,vy:0,hp:14+summit.wave*5,spin:rand()*6,dead:false});
    summit.spawnedThisWave++;summit.spawnQueue--;
  }
  function updateShards(dt){
    var sp=95+summit.wave*14;
    for(var i=summit.shards.length-1;i>=0;i--){var sh=summit.shards[i];
      if(sh.dead){summit.shards.splice(i,1);continue}
      var dx=BEACON.x-sh.x,dy=BEACON.y-sh.y,d=Math.max(1,Math.hypot(dx,dy));
      sh.vx+=dx/d*70*dt;sh.vy+=dy/d*70*dt;
      var s2=Math.hypot(sh.vx,sh.vy);if(s2>sp){sh.vx=sh.vx/s2*sp;sh.vy=sh.vy/s2*sp}
      sh.x+=sh.vx*dt;sh.y+=sh.vy*dt;sh.spin+=dt*3;
      for(var q=0;q<ships.length;q++){var s=ships[q];if(s.disabled<=0&&dist(s,sh)<26){if(s.shieldWindow>0)s.shield=Math.max(0,s.shield-14*dt);else s.hull=Math.max(0,s.hull-14*dt);if(s.hull<=0){s.disabled=5;s.vx=s.vy=0;if(summit.phase==='final'){s.unionLost=true;summit.lost.push({name:s.name,reason:'held the octagon through the last wave'});log('hit',s.name+' lost in the last wave',s.id)}else log('hit',s.name+' disabled · rescue window opened',s.id)}}}
      if(d<BEACON.r+12){summit.shards.splice(i,1);summit.beaconHp=Math.max(0,summit.beaconHp-6);for(var k=0;k<6;k++)sparks.push({x:BEACON.x,y:BEACON.y,vx:rand()*120-60,vy:rand()*120-60,life:.4,color:'teal'});log('hit','Null shard struck the beacon · integrity '+Math.round(summit.beaconHp)+'%','BEACON')}
    }
    if(summit.beaconHp<=0){endSummit('fracture');return}
    if(ships.every(function(s){return s.disabled>0}))endSummit('fracture');
  }
  function openChoice(){
    summit.phase='choice';summit.choiceTimer=0;
    document.getElementById('summitChoices').hidden=false;
    setSummitText('THE BEACON OVERLOADS — ANCHOR ONE SHIP, OR HOLD THE OCTAGON');
    log('formation','The beacon overloads · the choice belongs to the commanders','BEACON');
    toast('THE BEACON OVERLOADS — CHOOSE');
  }
  function makeChoice(c){
    if(summit.phase!=='choice')return;
    summit.choice=c;document.getElementById('summitChoices').hidden=true;
    if(c==='anchor'){summit.phase='anchor-select';setSummitText('TAP THE SHIP THAT WILL HOLD THE CORE');toast('TAP A SHIP TO ANCHOR THE CORE');log('formation','Command chose the anchor · one ship for the storm','BEACON')}
    else{summit.phase='final';summit.wave=3;summit.spawnedThisWave=0;summit.spawnQueue=FINAL_WAVE;summit.spawnTimer=.8;setSummitText('THE LAST WAVE — NO SHIP RETURNS');toast('HOLD THE OCTAGON — THE LAST WAVE COMES');log('formation','The last wave · 21 shards · no ship returns','BEACON')}
  }
  function selectAnchor(s){
    if(summit.phase!=='anchor-select'||!s||s.disabled>0)return;
    release(s);
    summit.anchor=s;summit.phase='pulse';summit.pulseR=0;
    setSummitText('THE CORE TAKES ITS ANCHOR — '+s.name);
    log('formation',s.name+' flies for the core','BEACON');toast(s.name+' · TO THE CORE');
  }
  function updateSummit(dt){
    if(!summit.active||summit.phase==='done')return;
    if(summit.phase==='union'){
      var locked=0;ships.forEach(function(s){if(s.disabled<=0&&dist(s,unionVertex(s))<40)locked++});
      if(locked===8){summit.hold+=dt;if(summit.hold>=2*PHI)ignite()}else summit.hold=Math.max(0,summit.hold-dt*2);
      setSummitText('FORM THE OCTAGON — '+locked+'/8 AT THEIR VERT