<!-- SOLID CITY · part 19 -->
 * 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 + 'px Georgia, serif'; }
  g.fillText(name, 256, 66);
  var t = new T.CanvasTexture(c); t.encoding = T.sRGBEncoding; t.anisotropy = 4; return t;
}
export function createAncestorRoads(o) {
  var T = o.THREE, P = o.position || { x: 0, y: 0, z: 0 };
  var root = new T.Group(); root.name = 'ancestor-roads'; root.position.set(P.x, P.y, P.z);
  var foot = [];
  var C = { gold: 0xd8aa48, basalt: 0x141114, stone: 0x2a242a };
  // Platform: a great circular terrace; the roads rest on it.
  var plat = new T.Mesh(new T.CylinderGeometry(540, 560, 100, 64), new T.MeshLambertMaterial({ color: C.stone }));
  plat.position.y = -50; root.add(plat);
  var platTop = new T.Mesh(new T.CylinderGeometry(540, 540, 2, 64), new T.MeshLambertMaterial({ color: 0x3a3230 }));
  platTop.position.y = 1; root.add(platTop);
  var brick = brickTexture(T);
  var brickMat = new T.MeshLambertMaterial({ map: brick });
  var goldMat = new T.MeshLambertMaterial({ color: C.gold, emissive: 0x2a1a04 });
  var postGeo = new T.CylinderGeometry(0.35, 0.45, 5, 8);
  // 5 rings x 11 roads
  var RINGS = [130, 210, 290, 370, 450], ni = 0, r, i, a, x, z, segLen, seg;
  var signs = [];
  for (r = 0; r < 5; r++) {
    var rad = RINGS[r], circ = 2 * Math.PI * rad, arc = circ / 11;
    for (i = 0; i < 11; i++) {
      var name = NAMES[ni++];
      a = (i / 11) * Math.PI * 2;
      x = Math.cos(a) * rad; z = Math.sin(a) * rad;
      // road segment: brick box laid tangent to the ring
      segLen = arc * 0.92;
      seg = new T.Mesh(new T.BoxGeometry(segLen, 0.6, 16), brickMat);
      seg.position.set(x, 2.3, z); seg.rotation.y = -a + Math.PI / 2;
      root.add(seg);
      // THE ONE ROAD (2026-10-12): a flush brick paver fills the gap to the
      // next ancestor's road — same height, same brick, butt joint — so each
      // ring runs as one continuous smooth circle. The named segments stay
      // distinct by their signs; the surface no longer steps down to bare
      // terrace and back up between them.
      var ga2 = a + Math.PI / 11;
      var pav = new T.Mesh(new T.BoxGeometry(arc * 0.08, 0.6, 16), brickMat);
      pav.position.set(Math.cos(ga2) * rad, 2.3, Math.sin(ga2) * rad);
      pav.rotation.y = -ga2 + Math.PI / 2;
      root.add(pav);
      // gold sign post at the segment's start, name plate facing outward.
      // THE ONE ROAD: the post stands at the road's OUTER EDGE now, not on
      // its centreline — it names the road without standing in the traveller.
      var sa = a - (Math.PI / 11) * 0.92;
      var sx = Math.cos(sa) * (rad + 9.6), sz = Math.sin(sa) * (rad + 9.6);
      var post = new T.Mesh(postGeo, goldMat);
      pos