<!-- SOLID CITY · part 1229 -->
),
  gun: curve(new T.MeshLambertMaterial({ color: 0x2a2a30 })),
  gold: curve(new T.MeshLambertMaterial({ color: 0xc99a3a, emissive: 0x100a02 })),
  red: curve(new T.MeshBasicMaterial({ color: 0xff2a40 })),
  redDim: curve(new T.MeshLambertMaterial({ color: 0x5a0410, emissive: 0x3a020a })),
  glowGold: curve(new T.MeshBasicMaterial({ color: 0xffd27a })),
  board: curve(new T.MeshLambertMaterial({ map: TEX_BOARD, color: 0xffffff, emissive: 0x030001 })),
  road: curve(new T.MeshLambertMaterial({ color: 0x0b0a0c })),
  bldg: curve(new T.MeshLambertMaterial({ map: TEX_BLDG, emissiveMap: TEX_WIN, emissive: 0xffffff }))
};
function box(w, h, d, mat, x, y, z, solid) {
  var m = new T.Mesh(new T.BoxGeometry(w, h, d), mat); m.position.set(x, y + h / 2, z); world.add(m);
  if (solid !== false) solidBox(x, z, w, d, y, y + h);
  return m;
}
function cyl(rt, rb, h, mat, x, y, z, seg, solid) {
  var m = new T.Mesh(new T.CylinderGeometry(rt, rb, h, seg || 16), mat); m.position.set(x, y + h / 2, z); world.add(m);
  if (solid !== false) solidBox(x, z, Math.max(rt, rb) * 1.6, Math.max(rt, rb) * 1.6, y, y + h);
  return m;
}
function sign(text, sub, x, y, z, w, rotY) {
  var m = new T.Mesh(new T.PlaneGeometry(w, w / 4), curve(new T.MeshBasicMaterial({ map: labelTex(text, sub), side: T.DoubleSide, fog: true })));
  m.position.set(x, y, z); m.rotation.y = rotY || 0; world.add(m); return m;
}
var animated = [];   // {obj, fn}
var NP = { draw: null, playing: false }, RADIO_MASTS = [];

/* districts (absolute coords, city ground at CITY_Y) */
var D = {
  plaza:  { name: 'FORUM PLAZA', x: 0, z: 0, url: '/', label: 'The Forums', text: 'The boards and threads of epistles — the real Forum lives here.' },
  studio: { name: 'MUSIC STUDIO TOWER', x: 430, z: -170, url: '/ss', label: 'Music Studio', title: 'Open the full Music Studio', go: 'OPEN THE FULL MUSIC STUDIO', text: 'Solid Sound — write, record, sing and sell your songs. (Opens /ss; the city music stops when you leave.)' },
  soul:   { name: 'SOUL PRINT TOWER', x: 210, z: 170, label: 'Soul Print Tower' },
  port:   { name: 'ORBITAL SPACEPORT', x: -380, z: 330, url: '/warpspeed', label: 'Warp Speed', text: 'The Warp Speed arcade run — wormholes and Soliditronium. (Mini-game, opens a new page.)' }
};
var portals = [];
function portal(dist, x, z, rotY) {
  var gY = CITY_Y, g = new T.Group(); g.position.set(x, gY, z); g.rotation.y = rotY || 0; world.add(g);
  var lp = new T.Mesh(new T.BoxGeometry(1.4, 9, 1.4), M.black); lp.position.set(-4, 4.5, 0); g.add(lp);
  var rp = lp.clone(); rp.position.x = 4; g.add(rp);
  var top = new T.Mesh(new T.BoxGeometry(10, 1.2, 1.6), M.gold); top.position.y = 9.4; g.add(top);
  var ring = new T.Mesh(new T.TorusGeometry(3.1, 0.18, 8, 40), M.red); ring.position.y = 4.6; g.add(ring);
  var disc = new T.Mesh(new T.CircleGeometry(3, 32), curve(new T.MeshBasicMaterial({ color: 0xff2a40, transparent: true, opacity: 0.22, side: T.DoubleSide, depthWrite: false }))); disc.position.y = 4.6; g.add(disc);
  var c = Math.cos(rotY || 0), s = Math.sin(rotY || 0);
  solidBox(x - 4 * c, z + 4 * s, 1.6, 1.6, gY, gY + 9); solidBox(x + 4 * c, z - 4 * s, 1.6, 1.6, gY, gY + 9);
  animated.push(function (t) { ring.rotation.z = t * 0.8; disc.material.opacity = 0.16 + 0.1 * Math.sin(t * 3); });
  portals.push({ d: dist, x: x, z: z });
  var sg = sign(dist.label.toUpperCase(), 'WALK THROUGH →', 0, 12.6, 0, 10); world.remove(sg); g.add(sg);
}

function buildCity() {
  var Y = CITY_Y;
  // city base: a dark disc so the capital sits on solid ground
  var base = new T.Mesh(new T.CircleGeometry(800, 64), M.road); base.rotation.x = -Math.PI / 2; base.position.set(0, Y + 0.02, 0); world.add(base);
  // roads (red-lit) from plaza to districts — dashes instanced (one draw call)
  var dashes = new T.InstancedMesh(new T.PlaneGeometry(0.5, 7), M.red, 200), dashO = new T.Object3D(), nd = 0;
  [D.studio, D.port, { x: 0, z: -700 }, { x: -700, z: -80 }].forEach(function (t) {
    var L = Math.hypot(t.x, t.z), a = Math.atan2(t.x, t.z);
    var road = new T.Mesh(new T.PlaneGeometry(14, L), curve(new T.MeshLambertMaterial({ color: 0x151216 }))); road.rotation.x = -Math.PI / 2; road.rotation.z = a; road.position.set(t.x / 2, Y + 0.06, t.z / 2); world.add(road);
    for (var k = 30; k < L - 10; k += 26) { dashO.position.set(Math.sin(a) * k, Y + 0.1, Math.cos(a) * k); dashO.rotation.set(-Math.PI / 2, 0, a); dashO.updateMatrix(); dashes.setMatrixAt(nd++, dashO.matrix); }
  });
  dashes.count = nd; dashes.instanceMatrix.needsUpdate = true; world.add(dashes);
  // ---- FORUM PLAZA ----
  var floor = new T.Mesh(new T.CircleGeometry(110, 64), M.board); floor.rotation.x = -Math.PI / 2; floor.position.set(0, Y + 0.12, 0); world.add(floor);
  var rim = new T.Mesh(new T.RingGeometry(108, 111, 64), M.glowGold); rim.rotation.x = -Math.PI / 2; rim.position.set(0, Y + 0.15, 0); world.add(rim);
  // open pavilions (Solidivism: no walls — posts + canopy)
  for (var p = 0; p < 6; p+