<!-- SOLID CITY · part 36 -->
, presentedEpoch, commandId) {
      emit(type, { task, operator, currentEpoch, presentedEpoch, commandId, cause });
      return { ok: false, reason: cause };
    }

    function snapshot() {
      return { operators: [...operators.values()].map(clone), tasks: [...tasks.values()].map(clone), config: { ...config, onExecute: undefined, onSafeBehavior: undefined }, sequence };
    }

    function subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); }
    return Object.freeze({ registerOperator, registerTask, claim, release, command, advisory, heartbeat, disconnect, checkHeartbeats, snapshot, subscribe });
  }
  return Object.freeze({ create });
})();
/* ============================================================
   AXIOM — THE LIVING FRONTIER (vertical slice)
   Single-file game. No network except the Three.js CDN script tag.
   ============================================================ */
'use strict';

/* ---------- Consequence ledger (localStorage) — the world remembers ---------- */
var LEDGER_KEY = 'axiom-frontier-ledger';
function loadLedger() {
  try { var l = JSON.parse(localStorage.getItem(LEDGER_KEY)); return Array.isArray(l) ? l : []; }
  catch (e) { return []; }
}
function saveRun(rec) {
  var l = loadLedger(); l.push(rec);
  try { localStorage.setItem(LEDGER_KEY, JSON.stringify(l)); } catch (e) {}
  return l;
}
function clearLedger() { try { localStorage.removeItem(LEDGER_KEY); } catch (e) {} }

/* ---------- Game state ---------- */
var DMG_NAMES = ['NOMINAL', 'DEGRADED', 'CRITICAL', 'FAILED'];
var DMG_COLORS = [0x51d39b, 0xf2ad42, 0xff7a3c, 0xff3b4e];
var DMG_CSS = ['#51d39b', '#f2ad42', '#ff7a3c', '#ff3b4e'];
var SYS = {
  propulsion: { label: 'PROPULSION', dmg: 0 },
  shielding:  { label: 'SHIELDING',  dmg: 0 },
  comms:      { label: 'COMMS',      dmg: 0 }
};
var STORM_SECONDS = 240;
var G = {
  mode: 'brief',      // brief | play | pause | over
  stormT: STORM_SECONDS,
  pausedAccum: 0, pausedAt: 0,
  holds: null,        // taskId the human currently holds
  repairProg: 0, repairing: false,
  solidRepair: null,  // {task, prog}
  nextStrike: 14,
  over: false, result: null,
  elapsed: 0
};

/* ---------- Coordinator ---------- */
var coord = ControlCoordination.create({
  onExecute: function () {},
  onSafeBehavior: function (task, cause) { feed('SYS', task.label + ' safe behavior: ' + cause); }
});
coord.registerOperator({ id: 'YOU', kind: 'HUMAN', label: 'YOU' });
coord.registerOperator({ id: 'SOLID', kind: 'AI', label: 'SOLID' });
Object.keys(SYS).forEach(function (id) {
  coord.registerTask({ id: id, label: SYS[id].label, ownerId: 'SOLID' });
});
function taskOwner(id) {
  var s = coord.snapshot();
  for (var i = 0; i < s.tasks.length; i++) if (s.tasks[i].id === id) return s.tasks[i].ownerId;
  return '?';
}

/* ---------- DOM helpers ---------- */
function $(id) { return document.getElementById(id); }
function feed(who, text) {
  var ul = $('feed'); if (!ul) return;
  var li = document.createElement('li');
  var b = document.createElement('b'); b.textContent = who + ' ';
  li.appendChild(b); li.appendChild(document.createTextNode(text));
  ul.insertBefore(li, ul.firstChild);
  while (ul.children.length > 28) ul.removeChild(ul.lastChild);
}
function advisory(text) { coord.advisory({ operatorId: 'SOLID', taskId: 'propulsion', text: text }); }

/* ---------- Three.js scene ---------- */
var scene, camera, renderer, clock;
var consoles = {};   // taskId -> {group, ring, screen, screenCtx, screenTex, hit}
var colliders = [];  // {x,z,hw,hd}
var meteors = [];
var starPts = null;
var shake = 0;

function canvasTex(w, h, draw) {
  var c = document.createElement('canvas'); c.width = w; c.height = h;
  draw(c.getContext('2d'), w, h);
  var t = new THREE.CanvasTexture(c); t.encoding = THREE.sRGBEncoding; return t;
}

function initThree() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x04070c);
  scene.fog = new THREE.Fog(0x04070c, 30, 90);
  camera = new THREE.PerspectiveCamera(70, innerWidth / innerHeight, 0.1, 400);
  camera.position.set(0, 1.7, 18); camera.rotation.y = Math.PI;
  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(innerWidth, innerHeight);
  renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
  renderer.outputEncoding = THREE.sRGBEncoding;
  $('stage').appendChild(renderer.domElement);
  clock = new THREE.Clock();

  scene.add(new THREE.AmbientLight(0x8fa6b0, 0.55));
  var hemi = new THREE.HemisphereLight(0x9fd8e8, 0x1a1210, 0.35); scene.add(hemi);

  // Floor: dark metal grid
  var floorTex = canvasTex(512, 512, function (g) {
    g.fillStyle = '#14181d'; g.fillRect(0, 0, 512, 512);
    g.strokeStyle = '#232b33'; g.lineWidth = 2;
    for (var i = 0; i <= 8; i++) {
      g.beginPath(); g.moveTo(i * 64, 0); g.lineTo(i * 64, 512); g.stroke();
      g.beginPath(); g.moveTo(0, i * 64); g.lineTo(512, i * 64); g.stroke();
    }
    g.strokeStyle = '#2e3d47'; g.line