<!-- SOLID CITY · part 39 -->
.holds) return;
  var t = G.holds; G.holds = null; G.repairing = false; G.repairProg = 0;
  var r = coord.release(t, 'YOU', 'SOLID');
  feed('YOU', 'Released ' + SYS[t].label + (r.ok ? ' — Solid resumes.' : ' (' + r.reason + ')'));
  $('repairbtn').style.display = 'none'; $('prompt').style.display = 'none';
  updateChips();
}
function promptRepair(taskId) {
  var s = SYS[taskId];
  if (s.dmg === 0) {
    $('prompt').style.display = 'block';
    $('prompt').textContent = s.label + ' NOMINAL — nothing to repair. Solid has the watch.';
    $('repairbtn').style.display = 'none';
  } else {
    $('prompt').style.display = 'block';
    $('prompt').textContent = 'HOLD REPAIR — ' + s.label + ' (' + DMG_NAMES[s.dmg] + ')';
    $('repairbtn').style.display = 'block';
  }
  updateChips();
}
function startRepair() {
  if (!G.holds || SYS[G.holds].dmg === 0) return;
  coord.claim(G.holds, 'YOU');  // re-affirm
  G.repairing = true;
}
function stopRepair() { G.repairing = false; }

function updateChips() {
  ['propulsion', 'shielding', 'comms'].forEach(function (id) {
    var el = $('chip-' + id); if (!el) return;
    var o = taskOwner(id);
    el.querySelector('.who').textContent = o === 'YOU' ? 'HELD BY YOU' : (o === 'SOLID' ? 'SOLID' : o);
    el.className = 'chip ' + (o === 'YOU' ? 'you' : 'solid');
    el.querySelector('.dmg').textContent = DMG_NAMES[SYS[id].dmg];
    el.querySelector('.dmg').style.color = DMG_CSS[SYS[id].dmg];
    var sb = document.getElementById('sb-' + id); if (sb) sb.style.color = DMG_CSS[SYS[id].dmg];
  });
}

/* ---------- Coordinator event wiring ---------- */
coord.subscribe(function (ev) {
  if (ev.type === 'ownership_changed' || ev.type.indexOf('handoff') === 0) updateChips();
  if (ev.type === 'advisory') feed(ev.operator, ev.text);
});
setInterval(function () {
  coord.heartbeat('YOU'); coord.heartbeat('SOLID'); coord.checkHeartbeats();
}, 2000);

/* ---------- Solid's voice ---------- */
var SOLID_LINES = {
  take: ['Taking {s}.', 'On {s}.', '{s} — mine.'],
  done: ['{s} stabilized.', '{s} holding.', 'Done. {s} back in the green.'],
  yield: ['Yours.', 'Take it. I\'ll cover the rest.'],
  idle: ['Watch is quiet.', 'Storm\'s teeth are out there. Stay sharp.', 'Rivet, keep those patches tight.']
};
var solidIdleT = 0;
function solidSays(text) { feed('SOLID', text); }
function solidLine(kind, sys) {
  var a = SOLID_LINES[kind]; var t = a[Math.floor(Math.random() * a.length)];
  solidSays(t.replace('{s}', sys ? SYS[sys].label.toLowerCase() : ''));
}
var RIVET_LINES = ['Rivet: hull patch holding on deck three.', 'Rivet: rerouting conduits, give me a minute.', 'Rivet: that last hit nearly took the array.'];
var SPARK_LINES = ['Spark: comms dish realigned — for now.', 'Spark: I can hear the storm coming. It sounds angry.', 'Spark: signal\'s clean. Say the word.'];
function helperChatter() {
  if (Math.random() < 0.5) feed('RIVET', RIVET_LINES[Math.floor(Math.random() * RIVET_LINES.length)].slice(7));
  else feed('SPARK', SPARK_LINES[Math.floor(Math.random() * SPARK_LINES.length)].slice(7));
}

/* ---------- SOLID the AI partner ---------- */
function solidTick(dt) {
  // finish an in-progress repair
  if (G.solidRepair) {
    var sr = G.solidRepair;
    if (taskOwner(sr.task) !== 'SOLID') { G.solidRepair = null; return; }  // yielded
    sr.prog += dt / 6;
    if (sr.prog >= 1) {
      SYS[sr.task].dmg = Math.max(0, SYS[sr.task].dmg - 1);
      refreshConsole(sr.task); updateChips();
      solidLine('done', sr.task);
      G.solidRepair = null;
    }
    return;
  }
  // pick the worst damaged system she still owns
  var worst = null, wd = 0;
  Object.keys(SYS).forEach(function (id) {
    if (taskOwner(id) === 'SOLID' && SYS[id].dmg > wd) { wd = SYS[id].dmg; worst = id; }
  });
  if (worst && wd > 0) {
    solidLine('take', worst);
    G.solidRepair = { task: worst, prog: 0 };
  } else {
    solidIdleT -= dt;
    if (solidIdleT <= 0) { solidIdleT = 25 + Math.random() * 20; solidLine('idle'); }
  }
}

/* ---------- Meteor storm ---------- */
function spawnMeteor() {
  var geo = new THREE.BoxGeometry(0.5, 0.5, 6);
  var mat = new THREE.MeshBasicMaterial({ color: 0xffb060 });
  var m = new THREE.Mesh(geo, mat);
  m.position.set((Math.random() - 0.5) * 60, 10 + Math.random() * 20, -55);
  scene.add(m); meteors.push({ mesh: m, v: 25 + Math.random() * 20 });
}
function meteorTick(dt) {
  for (var i = meteors.length - 1; i >= 0; i--) {
    var m = meteors[i];
    m.mesh.position.z += m.v * dt; m.mesh.position.y -= m.v * 0.25 * dt;
    if (m.mesh.position.z > -18) {
      scene.remove(m.mesh); meteors.splice(i, 1);
      impact();
    }
  }
  if (Math.random() < dt * 0.8) spawnMeteor();
}
function impact() {
  shake = 0.7;
  var ids = Object.keys(SYS);
  var id = ids[Math.floor(Math.random() * ids.length)];
  var before = SYS[id].dmg;
  SYS[id].dmg = Math.min(3, SYS[id].dmg + 1);
  refreshConsole(id); updateChips();
  feed('STORM', 'Impact — ' + SYS[id].label + ' ' + D