<!-- NEW6 — TEST 6 · THE FIVE SHORES · part 442 -->
Stage };
}
if (typeof window !== 'undefined') {
  window.makeStage = makeStage;
}
export { makeStage };
export default makeStage;


===== FILE: public/new6/worldmap.js =====
// worldmap.js — ONE RECIPE, MANY MAPS. The shared engine for the investigation
// maps (new3-new6). A map is DATA: { water, streets, regions, spawn } and this
// engine renders it whole: rivers and lakes with banks, streets that become
// bridges where they cross water (deck at 4.5, piers, rails, ramps), region
// kits (fashion districts of unique buildings, suburbs of houses, markets,
// parks, waterfronts, canal rows). meta carries the census, the colliders,
// deckAt(x,z) and inWater(x,z) — the ground law for the King's feet.
import { buildDistrict } from './buildings.js';

// mergeGeometries, written for this engine: concatenates indexed geometries
// that all carry position/normal/uv (boxes, cones, cylinders, circles, and
// this file's ribbons) into one geometry — the recipe's merging hand.
function mergeGeos(THREE, geos) {
  let vTotal = 0, iTotal = 0;
  for (const g of geos) { vTotal += g.attributes.position.count; iTotal += g.index.count; }
  const pos = new Float32Array(vTotal * 3), nor = new Float32Array(vTotal * 3), uv = new Float32Array(vTotal * 2);
  const idx = vTotal > 65535 ? new Uint32Array(iTotal) : new Uint16Array(iTotal);
  let vo = 0, io = 0;
  for (const g of geos) {
    pos.set(g.attributes.position.array, vo * 3);
    nor.set(g.attributes.normal.array, vo * 3);
    if (g.attributes.uv) uv.set(g.attributes.uv.array, vo * 2);
    const gi = g.index.array;
    for (let i = 0; i < gi.length; i++) idx[io + i] = gi[i] + vo;
    vo += g.attributes.position.count; io += gi.length;
  }
  const out = new THREE.BufferGeometry();
  out.setAttribute('position', new THREE.BufferAttribute(pos, 3));
  out.setAttribute('normal', new THREE.BufferAttribute(nor, 3));
  out.setAttribute('uv', new THREE.BufferAttribute(uv, 2));
  out.setIndex(new THREE.BufferAttribute(idx, 1));
  return out;
}

export function line(a, b, n) { const p = []; for (let i = 0; i <= n; i++) { const t = i / n; p.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]); } return p; }
export function arc(cx, cz, r, a0, a1, n) { const p = []; for (let i = 0; i <= n; i++) { const a = a0 + (a1 - a0) * (i / n); p.push([cx + Math.cos(a) * r, cz + Math.sin(a) * r]); } return p; }
export function join() { const out = []; for (const s of arguments) { for (const p of s) { const l = out[out.length - 1]; if (!l || Math.hypot(l[0] - p[0], l[1] - p[1]) > 0.01) out.push(p); } } return out; }

function mulberry32(a) { 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; }; }
const M4 = (THREE, x, y, z, ry, sx, sy, sz) => { const m = new THREE.Matrix4(); const q = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, ry || 0, 0)); m.compose(new THREE.Vector3(x, y, z), q, new THREE.Vector3(sx || 1, sy || 1, sz || 1)); return m; };

// ---------- towers kit (secondary districts: waterfront rows, canal rows) --
// One shared window plate, tinted stone boxes — kin to the fashion, humbler.
function towerPlate(THREE) {
  const cv = document.createElement('canvas'); cv.width = 128; cv.height = 256; const g = cv.getContext('2d');
  g.fillStyle = '#a99c86'; g.fillRect(0, 0, 128, 256);
  for (let r = 0; r < 8; r++) for (let c = 0; c < 4; c++) { g.fillStyle = (r * 4 + c) % 7 === 0 ? '#ffd9a0' : '#66788c'; g.fillRect(10 + c * 30, 12 + r * 26, 20, 16); }
  g.fillStyle = '#35312c'; g.fillRect(0, 224, 128, 32);
  g.fillStyle = '#77726c'; g.fillRect(0, 0, 16, 16);
  const tx = new THREE.CanvasTexture(cv); tx.wrapS = tx.wrapT = THREE.RepeatWrapping; tx.colorSpace = THREE.SRGBColorSpace;
  return tx;
}
function towersKit(THREE, group, R, rnd, buildings, counts, inWater) {
  const tex = towerPlate(THREE);
  const geos = [];
  const cols = Math.max(2, Math.floor(R.w / 64)), rows = Math.max(1, Math.floor(R.d / 64));
  for (let i = 0; i < cols; i++) for (let j = 0; j < rows; j++) {
    if (rnd() < 0.18) continue;
    const x = R.x - R.w / 2 + (i + 0.5) * (R.w / cols) + (rnd() - 0.5) * 8, z = R.z - R.d / 2 + (j + 0.5) * (R.d / rows) + (rnd() - 0.5) * 8;
    if (inWater && inWater(x, z)) continue;
    const w = 20 + rnd() * 10, d = 20 + rnd() * 10, floors = 5 + Math.floor(rnd() * (R.floors || 14)), h = floors * 3.1;
    const geo = new THREE.BoxGeometry(w, h, d); const uv = geo.attributes.uv;
    for (let f = 0; f < 6; f++) for (let k = 0; k < 4; k++) { const ix = f * 4 + k; if (f === 2 || f === 3) uv.setXY(ix, 8 / 128, 1 - 8 / 256); else uv.setXY(ix, uv.getX(ix) * Math.max(1, Math.round(w / 12)), uv.getY(ix) * Math.max(1, Math.round(floors / 8))); }
    geo.applyMatrix4(M4(THREE, x, h / 2, z));
    geos.push(geo); buildings.push({ x, z, w, d, h }); counts.towers++;
  }
  if (geos.length) { const m = new THREE.Mesh(