<!-- RUBY-BACKUP · part 435 -->
;
  /* letterpress shadow for depth */
  ctx.fillStyle = 'rgba(0,0,0,0.7)';
  ctx.fillText(name, 258, 66);
  ctx.fillStyle = '#f0e0c0';
  ctx.fillText(name, 256, 64);
  var tex = new THREE.CanvasTexture(cv);
  tex.anisotropy = 4;
  return tex;
}
function buildStreetSign(name, x, z, rotationY){
  var g = new THREE.Group();
  /* post — dark metal */
  var post = new THREE.Mesh(
    new THREE.CylinderGeometry(0.06, 0.08, 2.2, 12),
    new THREE.MeshStandardMaterial({color: 0x2a2a3a, metalness: 0.8, roughness: 0.4})
  );
  post.position.y = 1.1;
  g.add(post);
  /* sign plate — double-sided */
  var tex = makeIntaglioSignTexture(name);
  var plateMat = new THREE.MeshStandardMaterial({
    map: tex, metalness: 0.3, roughness: 0.6,
    emissive: 0xffffff, emissiveMap: tex, emissiveIntensity: 0.15
  });
  var plate = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.45, 0.06), plateMat);
  plate.position.y = 2.0;
  g.add(plate);
  /* future glow — underlight */
  var glow = new THREE.Mesh(new THREE.SphereGeometry(0.04, 10, 10),
    new THREE.MeshStandardMaterial({color: 0x64c8ff, emissive: 0x64c8ff, emissiveIntensity: 2}));
  glow.position.y = 1.75;
  g.add(glow);
  g.position.set(x*0.13, 0, z*0.13);
  g.rotation.y = rotationY || 0;
  return g;
}
function placeAncestorStreets(){
  if(typeof scene==='undefined' || typeof mePos==='undefined') return;
  STREET_SIGNS.forEach(function(s){ try{ scene.remove(s); }catch(e){} });
  STREET_SIGNS = [];
  var px = mePos.x, pz = mePos.z;
  /* place along the street grid */
  ANCESTOR_STREETS.forEach(function(name, i){
    var side = i % 4;
    var idx = Math.floor(i/4);
    var x, z, rot;
    var spread = 24; /* expanded */
    if(side===0){ x = px + (idx-8)*spread; z = pz - 8; rot = 0; }
    else if(side===1){ x = px + (idx-8)*spread; z = pz + 8; rot = Math.PI; }
    else if(side===2){ x = px - 8; z = pz + (idx-8)*spread; rot = Math.PI/2; }
    else { x = px + 8; z = pz + (idx-8)*spread; rot = -Math.PI/2; }
    var sign = buildStreetSign(name, x, z, rot);
    scene.add(sign);
    STREET_SIGNS.push(sign);
  });
  /* save the names as the street list */
  setStreetNames(ANCESTOR_STREETS.map(function(n){
    return n.charAt(0)+n.slice(1).toLowerCase();
  }));
  codeStatus('ancestral streets — 15 signs, last names only. Intaglio and future.');
}
function clearAncestorStreets(){
  STREET_SIGNS.forEach(function(s){ try{ scene.remove(s); }catch(e){} });
  STREET_SIGNS = [];
  codeStatus('ancestor streets cleared');
}
function buildCity(){
  if(typeof scene==='undefined' || typeof mePos==='undefined') return;
  /* clear */
  CITY_TOWERS.forEach(function(t){ try{ scene.remove(t); }catch(e){} });
  CITY_TOWERS = [];
  /* grid of towers — streets between */
  var px = mePos.x, pz = mePos.z;
  var gridSize = 8, spacing = 12; /* expanded — room to breathe */
  for(var gx=-gridSize; gx<=gridSize; gx++){
    for(var gz=-gridSize; gz<=gridSize; gz++){
      if(gx===0 || gz===0) continue; /* streets */
      var x = px + gx*spacing, z = pz + gz*spacing;
      /* check if already persisted */
      var key = 'city-tower-'+x+'-'+z;
      var saved = null;
      try{ saved = localStorage.getItem(key); }catch(e){}
      var height, hue;
      if(saved){
        var d = JSON.parse(saved);
        height = d.h; hue = d.hue;
      }else{
        height = 3 + Math.random()*5;
        hue = 0.55 + Math.random()*0.15;
      }
      var tower = buildTower(x, z, height, hue);
      scene.add(tower);
      CITY_TOWERS.push(tower);
    }
  }
  CITY_BUILT = true;
  codeStatus('city built — '+CITY_TOWERS.length+' permanent towers. Streets await names.');
}
function setStreetNames(names){
  STREET_NAMES = names;
  try{ localStorage.setItem('city-streets', JSON.stringify(names)); }catch(e){}
  codeStatus('streets named: '+names.join(', '));
}
function getStreetNames(){
  try{
    var s = localStorage.getItem('city-streets');
    if(s) STREET_NAMES = JSON.parse(s);
  }catch(e){}
  return STREET_NAMES;
}

/* ANCESTRAL CITY — streets, towers, subdivisions, all intertwined */
var ANCESTRAL_CITY = null;
function buildSubdivisionHouse(x, z, hue){
  var g = new THREE.Group();
  var wallMat = new THREE.MeshStandardMaterial({
    color: new THREE.Color().setHSL(hue, 0.4, 0.55), roughness: 0.8
  });
  var roofMat = new THREE.MeshStandardMaterial({
    color: new THREE.Color().setHSL(hue+0.05, 0.5, 0.3), roughness: 0.7
  });
  /* house body */
  var body = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.8, 1.0), wallMat);
  body.position.y = 0.4; g.add(body);
  /* roof */
  var roof = new THREE.Mesh(new THREE.ConeGeometry(0.9, 0.5, 4), roofMat);
  roof.position.y = 1.05; roof.rotation.y = Math.PI/4; g.add(roof);
  /* door */
  var door = new THREE.Mesh(new THREE.PlaneGeometry(0.25, 0.5),
    new THREE.MeshStandardMaterial({color: 0x4a3220, roughness: 0.8}));
  door.position.set(0, 0.25, 0.51); g.add(door);
  /* window */
  var win = new THREE.Mesh(new THREE.PlaneGeometry(0.3, 0.25),
    new THREE.