<!-- SOLID CITY · part 20 -->
t.position.set(sx, 4.5, sz); root.add(post);
      var plate = new T.Mesh(new T.PlaneGeometry(10, 2.5),
        new T.MeshBasicMaterial({ map: namePlate(T, name), side: T.DoubleSide }));
      plate.position.set(sx, 6.4, sz);
      plate.rotation.y = -sa + Math.PI / 2;
      root.add(plate); signs.push(plate);
      /* THE BATTENING (2026-10-14): the plate was single and
       * DoubleSide — from the road it read MIRRORED. A twin
       * plate now faces the road itself, 0.06 inward, so the
       * name reads true from the traveller's side too. */
      var twin = new T.Mesh(plate.geometry, plate.material);
      twin.position.set(sx - Math.cos(sa) * 0.06, 6.4, sz - Math.sin(sa) * 0.06);
      twin.rotation.y = -sa + Math.PI / 2 + Math.PI;
      root.add(twin); signs.push(twin);
      foot.push([sx, sz, 1.2, 1.2, 0, 7, 0]);
    }
    // thin gold ring marking the circle
    var ring = new T.Mesh(new T.TorusGeometry(rad, 0.25, 4, 96), new T.MeshBasicMaterial({ color: C.gold }));
    ring.rotation.x = Math.PI / 2; ring.position.y = 2.65; root.add(ring);
  }
  // Centre: the fire — a stone circle with an eternal flame for the ancestors
  var fireBase = new T.Mesh(new T.CylinderGeometry(14, 16, 3, 24), new T.MeshLambertMaterial({ color: C.basalt }));
  fireBase.position.y = 3.5; root.add(fireBase);
  var flame = new T.Mesh(new T.ConeGeometry(4, 12, 12),
    new T.MeshBasicMaterial({ color: 0xff9a30, transparent: true, opacity: 0.85 }));
  flame.position.y = 14; root.add(flame);
  var flameGlow = new T.PointLight(0xff9a30, 1.4, 120);
  flameGlow.position.y = 14; root.add(flameGlow);
  foot.push([0, 0, 32, 32, 0, 9, 0]);
  // North ramp: a stair from the terrace down toward the city
  var ramp = new T.Mesh(new T.BoxGeometry(40, 2, 120), brickMat);
  ramp.position.set(0, -18, 595); ramp.rotation.x = 0.32; root.add(ramp);
  // stepped collision for the ramp (AABBs cannot slope)
  foot.push([0, 550, 40, 30, -9, 1, 0]);
  foot.push([0, 580, 40, 30, -19, -9, 0]);
  foot.push([0, 610, 40, 30, -29, -19, 0]);
  foot.push([0, 640, 40, 30, -37, -29, 0]);
  root.userData = {
    footprint: foot, center: { x: 0, z: 0 }, radius: 560, height: 30,
    update: function (t) {
      flame.scale.y = 1 + Math.sin(t * 3.1) * 0.12;
      flameGlow.intensity = 1.3 + Math.sin(t * 3.1) * 0.25;
    }
  };
  return root;
}
export default createAncestorRoads;


===== FILE: public/city/ancestor-roads.js.bak-batten-20261014 =====
/* SOLID CITY — THE ANCESTRAL ROADS (2026-10-11, by order of the King)
 * "Give retry the road system of a Hawaiian Ancestry."
 * 55 brick roads for 55 ancestors, Maui-Potiki to Kamaile, in 5 rings.
 * Ring 0 (innermost) = the oldest; Ring 4 (outermost) = the newest, ending with
 * Wilcox, Kailiwai, Kamaile — his family, closest to the city and the future.
 * Self-contained ES module. Local frame: ground at y = 0 (platform top).
 *
 *   import { createAncestorRoads } from '/city/ancestor-roads.js';
 *   const g = createAncestorRoads({ THREE, position: {x, y, z} });
 *   // g.userData: footprint, update(t)
 */
export var ANCESTOR_COUNT = 55;
var NAMES = [
'Maui-Potiki','Hihiriotu','Maui-tikitiki-a-Taranga','Taharawa','Hekaponga',
'Toi-Kai-Rakau','Rua-rangi-takin','Whatoma','Poutiriao','Te Manutohikura-a-tane',
'Uenuku','Kamea','Pohukaina','Hua','Pau',
'Huanuikalalailai','Paumakua','Haho','Palena','Hanalaanui',
'Lanakawai','Pili','Koa','Loe','Kaniuhi',
'Kukohou','Kanipahu','Kalapanakuioiomoa','Kahaimoelea','Kalaunuiohua',
'Kuaiwa','Kahoukapu','Kauholanuimahu','Kiha-nui-lulu-moku','Liloa',
'Hakau','Kealiiokaloa','Umi-a-Liloa','Kukailani','Kapukamola',
'Makakaualii','Iwikauikaua','Kaneikaiwilani','Kalanikauleleiawi','Keawe',
'Kalaninuiiamamao','Keawemauhili','Mokulani','Papaikaniaunui','Ululani',
'Kaiwaokalani','Kanakaole','Wilcox','Kailiwai','Kamaile'
];
function brickTexture(T) {
  var c = document.createElement('canvas'); c.width = 256; c.height = 256;
  var g = c.getContext('2d');
  g.fillStyle = '#5a3a28'; g.fillRect(0, 0, 256, 256);
  var bh = 32, bw = 64;
  for (var y = 0; y < 256; y += bh) {
    var off = (y / bh) % 2 ? bw / 2 : 0;
    for (var x = -bw; x < 256 + bw; x += bw) {
      var v = 0.85 + Math.random() * 0.3;
      g.fillStyle = 'rgb(' + Math.floor(122*v) + ',' + Math.floor(78*v) + ',' + Math.floor(52*v) + ')';
      g.fillRect(x + off + 2, y + 2, bw - 4, bh - 4);
    }
  }
  var t = new T.CanvasTexture(c); t.encoding = T.sRGBEncoding;
  t.wrapS = t.wrapT = T.RepeatWrapping; return t;
}
function namePlate(T, name) {
  var c = document.createElement('canvas'); c.width = 512; c.height = 128;
  var g = c.getContext('2d');
  g.fillStyle = '#141114'; g.fillRect(0, 0, 512, 128);
  g.strokeStyle = '#d8aa48'; g.lineWidth = 6; g.strokeRect(6, 6, 500, 116);
  g.fillStyle = '#d8aa48'; g.textAlign = 'center'; g.textBaseline = 'middle';
  var fs = 44; g.font = 'bold ' + fs + 'px Georgia, serif';
  while (g.measureText(name).width > 460 && fs > 20) { fs -= 2; g.font = 'bold ' + fs +