<!-- SOLID CITY · part 3164 -->
L.x + 10.5, F, z - 8, 1); lamp(GAL.x + 10.5, F, z + 8, 1);
  })();
  /* city name signs at the gallery mouths — read both ways */
  sign(2613, A_C.f + 4.6, 1410, Math.PI * 0.94, 8.6, 1.6, ['THE SERVITUDE', 'THE FIRST HOLLOW'], 96, 4.6);
  sign(2587, B_C.f + 4.6, 1530, Math.PI * 0.06, 8.6, 1.6, ['THE SERVITUDE', 'THE DEEP HOLLOW'], 96, 4.6);

  /* ================= THE ENTREE =================
   * The gate in the First Hollow's south-western face: two
   * towers, a lintel, and the name of the place lettered
   * PROUD over the way in — with a lamp-lit shelf running
   * out to the brow of the mountain. */
  (function buildGate() {
    var u = { x: -0.7071, z: 0.7071 }, perp = { x: 0.7071, z: 0.7071 };
    var G = { x: A_C.x + u.x * 150, z: A_C.z + u.z * 150 }, F = A_C.f;
    var ry = Math.atan2(u.x, u.z);
    [-9.5, 9.5].forEach(function (off) {
      var tx = G.x + perp.x * off, tz = G.z + perp.z * off;
      box(solid, 3.4, 17, 3.4, mat4(tx, F + 8.5, tz, 0, ry, 0), C.stone);
      box(solid, 4.4, 1, 4.4, mat4(tx, F + 17.4, tz, 0, ry, 0), C.stoneDark);
      sph(glow, 0.55, mat4(tx, F + 18.3, tz, 0, 0, 0), C.lamp);
      footprints.push({ x: tx, z: tz, w: 4, d: 4, a: ry, y0: F, y1: F + 17.9 });
    });
    box(solid, 22.5, 2.8, 3.8, mat4(G.x, F + 15.4, G.z, 0, ry, 0), C.stoneDark);
    box(glow, 22.5, 0.4, 0.5, mat4(G.x + u.x * 1.95, F + 13.85, G.z + u.z * 1.95, 0, ry, 0), C.gold);
    sign(G.x + u.x * 2.15, F + 15.4, G.z + u.z * 2.15, ry, 13, 2.1, ['THE SERVITUDE'], 120, 0);
    /* the shelf processional: gold edges + lamps to the brow */
    for (var s2 = 158; s2 <= 242; s2 += 21) {
      var px = A_C.x + u.x * s2, pz = A_C.z + u.z * s2, py = H(px, pz);
      [-13, 13].forEach(function (off) { box(glow, 0.5, 0.25, 19, mat4(px + perp.x * off, py + 0.2, pz + perp.z * off, 0, ry, 0), C.gold); });
      if ((s2 - 158) % 42 === 0) { lamp(px + perp.x * 11, py, pz + perp.z * 11, 1); lamp(px - perp.x * 11, py, pz - perp.z * 11, 1); }
    }
  })();

  group.add(solid.build(new T.MeshLambertMaterial({ vertexColors: true })));
  group.add(glow.build(new T.MeshBasicMaterial({ vertexColors: true })));
  if (glassB.n) group.add(glassB.build(new T.MeshLambertMaterial({ vertexColors: true, transparent: true, opacity: 0.6 })));
  group.userData = {
    footprints: footprints, layers: layers,
    centers: { a: A_C, b: B_C }, gallery: GAL,
    texTier: opts.texTier || null,
    molded: molded,
    counts: { footprints: footprints.length, layers: layers.length },
    update: function (dt, t) {
      t = t || 0;
      crystals.forEach(function (c, i) { c.rotation.y = t * (0.14 + i * 0.03); });
      if (lavaClones.length) {
        var p = 0.9 + 0.1 * Math.sin(t * 0.7);
        for (var i = 0; i < lavaMats.length; i++) lavaMats[i].color.setRGB(p, p * 0.97, p * 0.95);
        for (var j = 0; j < lavaClones.length; j++) { lavaClones[j].offset.x = (t * 0.004) % 1; lavaClones[j].offset.y = (t * 0.006) % 1; }
      }
    }
  };
  return group;
}
export { createServitude, A_C as SERV_A, B_C as SERV_B };
export default createServitude;


===== FILE: public/city/siege/index.html =====
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no" />
  <meta name="color-scheme" content="light dark" />
  <link rel="icon" href="data:," />
  <title>Siege Breaker</title>
  <style>
    :root{
      color-scheme:light dark;
      --bg:#eef1f1;--panel:#ffffff;--panel2:#e2e7e7;--ink:#10191d;--muted:#4c5d63;
      --line:#9aa9ad;--hot:#d02e1c;--gold:#a66a00;--teal:#007c78;--good:#087b4f;--shadow:0 14px 40px rgba(17,32,38,.14);
      --canvas:#050a0e;--canvasInk:#e7f5f4;
    }
    @media(prefers-color-scheme:dark){:root{
      --bg:#070b0e;--panel:#0e1519;--panel2:#152026;--ink:#e9f2f2;--muted:#9aa9ad;
      --line:#2d4149;--hot:#ff4b35;--gold:#f0ae31;--teal:#25c7bd;--good:#42d392;--shadow:0 16px 48px rgba(0,0,0,.45);
      --canvas:#030609;--canvasInk:#e7f5f4;
    }}
    *{box-sizing:border-box}
    html,body{margin:0;min-height:100%;background:var(--bg);color:var(--ink);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overscroll-behavior:none}
    body{min-height:100dvh;overflow-x:hidden;user-select:none;-webkit-user-select:none}
    button{font:inherit;color:inherit}
    button:focus-visible{outline:3px solid var(--teal);outline-offset:2px}
    .app{min-height:100dvh;display:grid;grid-template-rows:auto minmax(0,1fr) auto;padding:max(10px,env(safe-area-inset-top)) max(10px,env(safe-area-inset-right)) max(10px,env(safe-area-inset-bottom)) max(10px,env(safe-area-inset-left));gap:10px}
    .hud{display:grid;grid-template-columns:minmax(0,1.6fr) repeat(3,minmax(100px,.7fr));gap:8px}
    .readout{background:var(--panel);border:1px solid var(--line);padding:9px 12px;min-height:58px;display:flex;flex-direction:column;justify-content:center}
    .readout:first-child{border-left:4px solid v