<!-- NEW7 — TEST 7 · THE WOVEN CITY · part 439 -->
pe, UnsignedInt101111Type, UnsignedInt248Type, UnsignedInt5999Type, UnsignedIntType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShortType, VSMShadowMap, Vector2, Vector3, Vector4, WebGLCoordinateSystem, WebGLCubeRenderTarget, WebGLRenderTarget, WebGLRenderer, WebGLUtils, WebXRController, ZeroFactor, createCanvasElement, error, log, warn, warnOnce };


===== FILE: public/new7/mapdata7.js =====
// mapdata7.js — TEST 7 · THE WOVEN CITY. No districts, no tiles: a fabric
// of blocks, each block a different use from its neighbors — tower blocks
// against house blocks, schools and city buildings woven between, gas on
// the corners, a river through it, and two bus routes with their stops
// planted far-side at researched spacing. The generator enforces the
// weave law itself: no two blocks of a kind stand side by side.
import { line, join } from './worldmap.js';

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 rng = mulberry32(20261017);

const COLS = 13, ROWS = 11, PITCH = 110;
const cellX = c => -660 + c * PITCH;
const cellZ = r => -550 + r * PITCH;

const riverPts = join(
  line([-790, 260], [-420, 185], 6),
  line([-420, 185], [-110, 245], 6),
  line([-110, 245], [210, 70], 6),
  line([210, 70], [480, -30], 6),
  line([480, -30], [790, -120], 6)
);
function distToSeg(px, pz, a, b) { const vx = b[0] - a[0], vz = b[1] - a[1], wx = px - a[0], wz = pz - a[1]; const c1 = vx * wx + vz * wz, c2 = vx * vx + vz * vz; const t = c2 ? Math.max(0, Math.min(1, c1 / c2)) : 0; return Math.hypot(wx - vx * t, wz - vz * t); }
function distToRiver(x, z) { let d = 1e9; for (let i = 0; i < riverPts.length - 1; i++) d = Math.min(d, distToSeg(x, z, riverPts[i], riverPts[i + 1])); return d; }

// ---------- the weave ----------
const WEIGHTS = [
  ['tower', 0.50], ['house', 0.26], ['park', 0.065], ['market', 0.055],
  ['school', 0.04], ['municipal', 0.055], ['gas', 0.015], ['parking', 0.01],
];
const CAPS = { school: 6, municipal: 9, gas: 5 };
const MUNI_SUBS = ['hall', 'fire', 'library', 'police', 'fire', 'library', 'police', 'fire', 'library'];
const cells = [];
const counts = {};
let muniIdx = 0;
for (let r = 0; r < ROWS; r++) {
  for (let c = 0; c < COLS; c++) {
    const x = cellX(c), z = cellZ(r);
    const left = c > 0 ? cells[cells.length - 1].use : null;
    const above = r > 0 ? cells[(r - 1) * COLS + c].use : null;
    let use;
    const rd = distToRiver(x, z);
    if (rd < 58) use = 'water';
    else if (rd < 104) use = 'park';
    else {
      const cand = [];
      for (const [u, w] of WEIGHTS) {
        if (u === left || u === above) continue;
        if (CAPS[u] && (counts[u] || 0) >= CAPS[u]) continue;
        if (u === 'gas' && !(c % 3 === 0 && r % 3 === 0)) continue;
        if (u === 'gas' && (left === 'school' || above === 'school')) continue;
        if (u === 'school' && (left === 'gas' || above === 'gas')) continue;
        cand.push([u, u === 'gas' ? 0.12 : w]);
      }
      if (!cand.length) cand.push([left === 'house' ? 'tower' : 'house', 1]);
      let sum = 0; for (const [, w] of cand) sum += w;
      let pick = rng() * sum; use = cand[cand.length - 1][0];
      for (const [u, w] of cand) { pick -= w; if (pick <= 0) { use = u; break; } }
    }
    counts[use] = (counts[use] || 0) + 1;
    const cell = { c, r, x, z, use };
    if (use === 'municipal') cell.sub = MUNI_SUBS[Math.min(muniIdx++, MUNI_SUBS.length - 1)];
    cells.push(cell);
  }
}
// Guarantee the fashion quota is reachable: tower blocks hold ~16 faces;
// if the weave came up short, flip house blocks that keep the law — a flip
// happens after every assignment, so it must respect ALL FOUR neighbors.
let towerBlocks = cells.filter(cl => cl.use === 'tower').length;
for (const cl of cells) {
  if (towerBlocks * 15 >= 845) break;
  if (cl.use !== 'house' && cl.use !== 'parking' && cl.use !== 'market') continue;
  const around = [[-1, 0], [1, 0], [0, -1], [0, 1]].map(([dc, dr]) => {
    const cc = cl.c + dc, rr = cl.r + dr;
    return (cc < 0 || rr < 0 || cc >= COLS || rr >= ROWS) ? null : cells[rr * COLS + cc].use;
  });
  if (around.some(u => u === 'tower')) continue;
  cl.use = 'tower'; towerBlocks++;
}

// ---------- regions from cells ----------
const KIND_OF = { tower: 'fashionblock', house: 'houseblock', park: 'park', market: 'market', school: 'school', municipal: 'municipal', gas: 'gas', parking: 'parking' };
const NAME_OF = { tower: 'City Block', house: 'House Block', park: 'Park Block', market: 'Market Block', school: 'School', municipal: 'Municipal Block', gas: 'Gas Station', parking: 'Parking Block' };
const kindCount = {};
const regions = [];
for (const cl of cells) {
  if (cl.use === 'water') continue;
  kindCount[cl.use] = (kindCount[cl.use] || 0) + 1;
  const reg = {
    kind: KIND_OF[cl.use], name: N