<!-- SOLID CITY · part 2927 -->
x=mv.x*130,targetVy=mv.y*190;state.player.vx+=(targetVx-state.player.vx)*Math.min(1,dt*4.2);state.player.vy+=(targetVy-state.player.vy)*Math.min(1,dt*5.5);
    state.player.y=Math.max(42,Math.min(H-42,state.player.y+state.player.vy*dt));state.player.x=Math.max(W*.14,Math.min(W*.50,state.player.x+state.player.vx*dt*.18));
    var crawl=state.summit.phase==='mirror'?0.08:1;
    state.progress+=Math.max(18,90+state.player.vx*.36)*crawl*dt;
    if(state.summit.phase==='mirror')state.progress=Math.min(state.progress,state.target*0.618+150);
    if(!state.summit.active&&state.progress>=state.target*0.618)startSummit();
    state.wing.x+=(state.player.x-54-state.wing.x)*dt*3.2;state.wing.y+=(state.player.y+42-state.wing.y)*dt*3.2;
    state.spawn-=dt;if(state.spawn<=0){spawnEnemy();state.spawn=(Math.max(.55,1.35-state.elapsed*.008)+Math.random()*.5)*(state.summit.phase==='mirror'?2.4:1)}
    state.shot-=dt;if(wp.fire&&state.shot<=0){state.shot=.15;state.bullets.push({x:state.player.x+wp.x*18,y:state.player.y+wp.y*18,vx:wp.x*520,vy:wp.y*520,life:1.2,ai:owners.weapon==='ai'});particle(state.player.x+wp.x*18,state.player.y+wp.y*18,colors.gold2,2)}
    state.enemies.forEach(function(e){e.phase+=dt*2;e.x+=e.vx*dt;e.y+=Math.sin(e.phase)*15*dt;e.shot-=dt;if(e.shot<0){e.shot=1.4+Math.random();var dx=state.player.x-e.x,dy=state.player.y-e.y,m=Math.hypot(dx,dy)||1;state.enemyBullets.push({x:e.x,y:e.y,vx:dx/m*210,vy:dy/m*210,life:3})}});
    state.rocks.forEach(function(r){r.x+=r.vx*dt;r.rot+=dt*1.8});
    summitTick(dt);
    state.bullets.forEach(function(b){b.x+=b.vx*dt;b.y+=b.vy*dt;b.life-=dt;state.enemies.forEach(function(e){if(e.hp>0&&hitCircle(b,e,18)){e.hp--;b.life=0;if(e.hp<=0){state.kills++;particle(e.x,e.y,colors.teal,15)}}});var tws=state.summit.twin;if(tws&&tws.hp>0&&b.life>0&&hitCircle(b,tws,23)){b.life=0;if(b.ai)twinFizzle(b.x,b.y);else twinHit(b.x,b.y)}});
    state.enemyBullets.forEach(function(b){b.x+=b.vx*dt;b.y+=b.vy*dt;b.life-=dt;if(b.life>0&&hitCircle(b,state.player,17)){b.life=0;damage(7)}});
    state.rocks.forEach(function(r){if(r.r>0&&hitCircle(r,state.player,r.r+14)){r.r=0;damage(13)}});
    state.enemies.forEach(function(e){if(e.hp>0&&hitCircle(e,state.player,30)){e.hp=0;damage(16)}});
    state.enemies=state.enemies.filter(function(e){return e.x>-80&&e.hp>0});state.rocks=state.rocks.filter(function(r){return r.x>-80&&r.r>0});state.bullets=state.bullets.filter(function(b){return b.life>0&&b.x<W+30&&b.x>-30&&b.y>-30&&b.y<H+30});state.enemyBullets=state.enemyBullets.filter(function(b){return b.life>0});
    state.particles.forEach(function(p){p.x+=p.vx*dt;p.y+=p.vy*dt;p.vx*=.97;p.vy*=.97;p.life-=dt});state.particles=state.particles.filter(function(p){return p.life>0});
    var th=nearestThreat(),warn=th.obj&&th.d<145;if(warn&&!state.warningOn&&state.warningCooldown<=0){state.warnings++;state.warningCooldown=1.5;log('HAZ','Collision vector · advisory only','');}state.warningOn=!!warn;state.warningCooldown=Math.max(0,state.warningCooldown-dt);
    if(state.progress>=state.target)finish(true);updateHud();
  }
  function updateHud(){if(!state)return;els.hullBar.style.width=state.hull+'%';els.hullText.textContent=Math.round(state.hull)+'%';els.distanceText.textContent=Math.max(0,Math.round(state.target-state.progress)).toLocaleString()+' m';els.killsText.textContent=state.kills+' DEFENDED';els.handoffCount.textContent=state.handoffs;els.conflictCount.textContent=state.conflicts;els.warningCount.textContent=state.warnings;var sa=state.summitAlert,saOn=!!(sa&&now()<sa.until);els.warning.textContent=saOn?sa.text:'COLLISION VECTOR · ADVISORY ONLY';els.warning.classList.toggle('show',!!(state.warningOn||saOn))}
  function finish(success){if(!state.running)return;state.running=false;state.paused=false;els.pauseBtn.hidden=true;els.pauseOverlay.hidden=true;els.stage.classList.remove('is-running');els.debrief.hidden=false;
    var sm=state.summit,inS=sm.active&&sm.phase!=='done';
    if(inS){if(success&&sm.phase==='choice'&&sm.core){sm.core=null;sm.wing='lost'}sm.phase='done'}
    els.mirrorWrap.hidden=true;
    if(success){
      var reclaimed=sm.active&&sm.wing==='formation';
      els.debriefEyebrow.textContent=reclaimed?'ROUTE COMPLETE · ONE SHIP':'ROUTE COMPLETE';
      els.debriefTitle.textContent=!sm.active?'Aster Gate reached.':reclaimed?'Aster Gate reached. The wing came home.':sm.wing==='lost'?'Aster Gate reached. The wing stayed in the dark.':'Aster Gate reached. The Mirror is broken.';
    }else{
      els.debriefEyebrow.textContent='MISSION INTERRUPTED';
      els.debriefTitle.textContent=sm.active?(sm.wing==='formation'?'The Mirror is broken. The Rider fell.':'The Mirror held the gate.'):'Rider lost before the gate.';
    }
    els.scoreHull.textContent=Math.round(state.hull)+'%';els.scoreHandoffs.textContent=state.handoffs;els.scoreTime.textContent=Math.round(state.elapsed)+'s';}

  function ship(x,y,angle,color,scale){ctx.save();ctx.translate(x,y);ctx.rotate(angle);