<!-- SOLID CITY · part 2896 -->
the epoch immediately; commands from the previous owner are rejected. AI advisories remain separate from actuator commands, so the crew may warn you but cannot override you.</p>
        </div>
      </details>
    </div>
  </main>
  <div class="toast" id="toast" role="status"></div>

<script>
(function(){
  'use strict';
  const $ = (s,root=document)=>root.querySelector(s);
  const $$ = (s,root=document)=>Array.from(root.querySelectorAll(s));
  const canvas=$('#battle'), ctx=canvas.getContext('2d');
  const W=960,H=600;
  let running=false,paused=false,last=0,elapsed=0,adviceTimer=0,spawnTimer=0,finishTimer=0;
  let toastTimer=0;

  class ControlCoordination {
    constructor(tasks,onEvent){
      this.tasks={}; this.onEvent=onEvent; this.seen=new Set();
      tasks.forEach(name=>this.tasks[name]={owner:'ai',epoch:1,seq:0,connected:true});
    }
    owner(task){return this.tasks[task].owner}
    claim(task,owner){
      const t=this.tasks[task], started=performance.now();
      if(!t || t.owner===owner) return null;
      const from=t.owner; t.owner=owner; t.epoch++; t.seq=0;
      const latency=Math.max(0,performance.now()-started);
      this.onEvent('handoff',{task,from,to:owner,epoch:t.epoch,latency});
      return {task,owner,epoch:t.epoch,latency};
    }
    release(task){return this.claim(task,'ai')}
    command(task,owner,epoch,action){
      const t=this.tasks[task]; if(!t) return false;
      const key=task+':'+owner+':'+epoch+':'+action;
      if(t.owner!==owner || t.epoch!==epoch){this.onEvent('rejected',{task,owner,epoch,reason:'stale ownership'});return false}
      if(this.seen.has(key)){this.onEvent('rejected',{task,owner,epoch,reason:'duplicate command'});return false}
      this.seen.add(key); if(this.seen.size>160)this.seen.clear();
      return true;
    }
    snapshot(){return JSON.parse(JSON.stringify(this.tasks))}
  }

  let events=[], coordination;
  const state={};
  function resetState(){
    Object.assign(state,{
      ship:{x:118,y:310,vx:0,vy:0,angle:0,hp:100,shield:100,cargo:null,repairCd:0,pulse:0,fireCd:0},
      gate:{x:885,y:300,r:56},
      pods:[
        {id:'Aster',x:365,y:165,hp:100,status:'adrift',stabilize:0,story:'Pilot Aster · oxygen stable',lostReason:''},
        {id:'Morrow',x:555,y:410,hp:88,status:'adrift',stabilize:0,story:'Survey pilot Morrow · hull breach',lostReason:''},
        {id:'Kite',x:735,y:190,hp:66,status:'adrift',stabilize:0,story:'Courier Kite · power failing',lostReason:''}
      ],
      anchor:{x:785,y:300,integrity:100,phase:'hidden',scan:0,hold:0,retrieval:0,choice:null,lights:0,heartbeat:{id:'Iona',role:'Lantern keeper',status:'unknown',lostReason:''}},
      hazards:[{x:305,y:360,r:58,spin:0},{x:505,y:235,r:47,spin:1.7},{x:700,y:365,r:52,spin:2.4}],
      debris:Array.from({length:20},(_,i)=>({x:230+(i*197)%590,y:75+(i*83)%450,r:4+(i%5)*2,vx:-2+(i%4),vy:-1+(i%3)})),
      enemies:[],shots:[],rescued:0,lost:0,risk:0,missionEnd:false,extractHeld:false,stabilizeHeld:false,anchorHeld:false,retrievalHeld:false,triageTarget:null,keys:{},
      advisory:'Three clear life signs. A fourth echo flickers beneath the scanner noise.'
    });
    events=[]; elapsed=0; spawnTimer=0; finishTimer=0;
    coordination=new ControlCoordination(['navigation','extraction','protection','support'],recordEvent);
    updateStations(); updateCommand(); updateUI(); renderLog();
  }

  function recordEvent(type,data={}){
    const ev={type,data,t:elapsed,real:performance.now()}; events.push(ev);
    if(events.length>250)events.shift();
    if(type==='handoff'){
      const n=data.to==='human'?'You claimed '+pretty(data.task):'AI resumed '+pretty(data.task);
      showToast(n+' · epoch '+data.epoch); updateStations();
    }
    if(type==='rescue') showToast(data.pod+' is safe inside the rescue gate.');
    if(type==='lost') showToast(data.pod+' did not come home.');
    if(type==='anchorPhase') showToast(data.message);
    renderLog(); updateStats();
    if(['triage','lost','rescue','anchorPhase','choice','mission'].includes(type))updateCommand();
  }
  function pretty(s){return s.charAt(0).toUpperCase()+s.slice(1)}
  function showToast(text){const el=$('#toast');el.textContent=text;el.classList.add('show');clearTimeout(toastTimer);toastTimer=setTimeout(()=>el.classList.remove('show'),1800)}
  function setAdvice(text){state.advisory=text;$('#advisory').innerHTML='<strong>AI crew</strong> '+text}

  function chooseTriage(id){
    const pod=state.pods.find(p=>p.id===id&&p.status==='adrift');
    if(!pod||state.anchor.phase!=='hidden')return;
    state.triageTarget=id;recordEvent('triage',{pod:id});
    setAdvice('Triage received. AI pilot plotting '+id+'. You chose who we reach first.');
    updateCommand();
  }
  function humanHoldButton(button,key){
    button.setAttribute('aria-pressed',String(!!state[key]));
    button.addEventListener('click',e=>{e.preventDefault();state[key]=!state[key];button.classList.toggle('active',state[key]);bu