<!-- NEW6 — TEST 6 · THE FIVE SHORES · part 1 -->
===== FILE: public/new6/buildings.js =====
/* buildings.js — THE CITYWRIGHT's forge for TEST 2 at /new2.
 *
 * The King's word: "Only city. A district of buildings bigger than
 * what this image shows — keep each duplicate different on its face
 * for every building but built in the same architectural fashion.
 * I want 800 buildings."
 *
 * ONE FASHION, defined once below and never left: a family of
 * stone-and-glass towers — the same window proportions, the same
 * dark ground band with its door, the same parapet crown-line, the
 * same roof gray — varied the way real districts vary: rhythm,
 * tint, height, setbacks, crown. No building outside the fashion;
 * no two buildings the same face inside it.
 *
 * THE COMPRESSION AT THE CORE: the fashion is a shared wardrobe —
 * 16 drawn facade plates and one stone material — and the per-
 * building differences are baked into merged geometry (UV scale,
 * vertex tint, tier stack, crown). Eight hundred buildings stand
 * in a handful of meshes, not eight hundred copies of copies.
 *
 * Contract: buildDistrict(THREE) -> { group, meta }
 *   meta.count       — 800, exactly, by the King's count
 *   meta.uniqueFaces — 800: every face signature distinct
 *   meta.buildings[] — { x, z, w, d, h, floors, variant, crown, tint }
 *   meta.streets     — { xs, zs } street center lines
 *   meta.district    — { w: 1100, d: 990 }
 */

'use strict';

/* ================= THE FASHION =================
 * One architectural language. Everything a building may wear is
 * named here; the list below only ever draws from this table. */
const FASHION = {
  seed: 20261016,
  floorH: 3.1,          // one true floor height, everywhere
  tileRows: 8,          // floors drawn in one facade plate
  windowPitch: 2.4,     // one true window width, everywhere
  frame: '#b3a894',     // stone mullion tone (drawn into the plates)
  parapet: '#8a8175',   // the crown-line band atop every plate
  roofGray: '#77726c',  // THE roof patch color — flat, known corner
  groundBand: '#35312c',// the dark street band on every face
  door: '#17150f',      // the recessed door, drawn dark
  litPane: '#ffd9a0',   // warm light in a few windows
  // the glass family — slate, bronze, pale; variants stay inside it
  glass: ['#5d6d7a', '#77828c', '#7a6c54', '#8a7a5e', '#93a3ab', '#a7b2b4'],
  // vertex tints — warm stone to cool glass, one family of light
  tints: [0xffffff, 0xf5eeda, 0xe8e2d4, 0xdfe5e8, 0xcfd8de,
          0xf0e2cc, 0xe6d9c3, 0xd8dee2, 0xf7f3ea, 0xe2d5bd],
  crownStone: [0xb8ae9c, 0xa89e8a, 0x9aa0a2, 0xc2bba9],
  crowns: ['lip', 'cap', 'step', 'spire'],
  variants: 16,
};

/* the sixteen plates: grid rhythm x glass x lit ratio x band style,
 * all combinations inside the fashion, none outside it */
function variantSpec(v) {
  return {
    cols: [5, 6, 7][v % 3],                        // window rhythm
    glass: FASHION.glass[v % FASHION.glass.length],
    lit: 0.04 + (v % 4) * 0.035,                   // lit-window ratio
    band: v % 3,                                   // ground-band styling
    shade: 0.92 + ((v * 37) % 10) * 0.016,         // plate's own light
  };
}

function mulberry32(seed) {
  let a = seed >>> 0;
  return function () {
    a |= 0; a = (a + 0x6D2B79F5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

/* ================= THE PLATES =================
 * Each plate is a full facade strip of TILE_ROWS floors: a top
 * floor under the parapet band, window floors, and the dark
 * ground band with its door at the foot. The roof patch — flat
 * ROOF GRAY — sits at the TOP-LEFT corner (canvas 0,0, 30x30);
 * roof faces point their UVs there and read as plain roof. */
const ROOF_UV = { u: 15 / 256, v: 1 - 15 / 512 };
function drawPlate(THREE, v) {
  const spec = variantSpec(v);
  const rng = mulberry32(FASHION.seed + v * 7919);
  const cv = document.createElement('canvas');
  cv.width = 256; cv.height = 512;
  const g = cv.getContext('2d');
  const rows = FASHION.tileRows, rh = 512 / rows, cw = 256 / spec.cols;

  g.fillStyle = FASHION.frame; g.fillRect(0, 0, 256, 512); // stone field

  for (let r = 0; r < rows - 1; r++) {           // window floors (ground row last)
    for (let c = 0; c < spec.cols; c++) {
      const x = c * cw, y = r * rh;
      const lit = rng() < spec.lit;
      const jitter = 0.9 + rng() * 0.2;
      g.fillStyle = lit ? FASHION.litPane : spec.glass;
      g.globalAlpha = lit ? 0.95 : Math.min(1, spec.shade * jitter);
      g.fillRect(x + 5, y + 9, cw - 10, rh - 18);  // the pane, inset in stone
      g.globalAlpha = 0.16;                        // sky caught in the upper glass
      g.fillStyle = '#ffffff';
      g.fillRect(x + 5, y + 9, cw - 10, (rh - 18) * 0.42);
      g.globalAlpha = 1;
    }
  }
  // parapet band across the top floor's head
  g.fillStyle = FASHION.parapet; g.fillRect(