<!-- SOLID CITY · part 2949 -->
e.player.y+wp.y*18,vx:wp.x*520,vy:wp.y*520,life:1.2});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});
    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)}}})});
    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;els.warning.classList.toggle('show',state.warningOn)}
  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;els.debriefEyebrow.textContent=success?'ROUTE COMPLETE':'MISSION INTERRUPTED';els.debriefTitle.textContent=success?'Aster Gate reached.':'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);ctx.scale(scale,scale);ctx.shadowBlur=12;ctx.shadowColor=color;ctx.strokeStyle=color;ctx.fillStyle='rgba(6,16,15,.95)';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(22,0);ctx.lineTo(-12,-10);ctx.lineTo(-6,0);ctx.lineTo(-12,10);ctx.closePath();ctx.fill();ctx.stroke();ctx.beginPath();ctx.moveTo(-9,-5);ctx.lineTo(-22,-3);ctx.moveTo(-9,5);ctx.lineTo(-22,3);ctx.stroke();ctx.restore()}
  function draw(){
    ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,W,H);var g=ctx.createLinearGradient(0,0,W,H);g.addColorStop(0,'#06100f');g.addColorStop(1,'#0b1d1a');ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
    state.stars.forEach(function(s){s.x-=s.z*(.3+state.player.vx/160);if(s.x<0)s.x=W;ctx.fillStyle='rgba(183,231,220,'+(.2+s.z*.5)+')';ctx.fillRect(s.x,s.y,s.z*1.4,s.z*1.4)});
    ctx.strokeStyle='rgba(65,199,187,.08)';ctx.lineWidth=1;for(var i=0;i<7;i++){var yy=(i+1)*H/8;ctx.beginPath();ctx.moveTo(0,yy);ctx.lineTo(W,yy);ctx.stroke()}
    var remain=state.target-state.progress;if(remain<800){var gx=W-(remain/800)*(W*.55);ctx.save();ctx.translate(gx,H*.5);ctx.strokeStyle=colors.teal;ctx.shadowBlur=18;ctx.shadowColor=colors.teal;ctx.lineWidth=2;ctx.beginPath();ctx.ellipse(0,0,28,74,0,0,6.28);ctx.stroke();ctx.strokeStyle='rgba(67,216,207,.3)';ctx.beginPath();ctx.ellipse(0,0,46,102,0,0,6.28);ctx.stroke();ctx.restore()}
    state.rocks.forEach(function(r){ctx.save();ctx.translate(r.x,r.y);ctx.rotate(r.rot);ctx.fillStyle='#22302d';ctx.strokeStyle='#69817b';ctx.lineWidth=1;ctx.beginPath();for(var i=0;i<7;i++){var a=i/7*6.28,rr=r.r*(.77+(i%3)*.11);ctx.lineTo(Math.cos(a)*rr,Math.sin(a)*rr)}ctx.closePath();ctx.fill();ctx.stroke();ctx.restore()});
    state.enemies.forEach(function(e){ship(e.x,e.y,Math.PI,'#ff6f66',.8)});
    state.bullets.forEach(function(b){ctx.strokeStyle=colors.gold2;ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(b.x,b.y);ctx.lineTo(b.x-b.vx*.025,b.y-b.vy*.025);ctx.stroke()});
    state.enemyBullets.forEach(function(b){ctx.fillStyle=colors.red;ctx.beginPath();ctx.ar