<!-- NEW7 — TEST 7 · THE WOVEN CITY · part 447 -->
-- region kits ----------
  const buckets = new Map();
  const bucket = (color) => { if (!buckets.has(color)) buckets.set(color, []); return buckets.get(color); };
  const treeGeos = [], trunkGeos = [];
  const tree = (x, z, s) => { trunkGeos.push(new THREE.CylinderGeometry(0.22 * s, 0.3 * s, 2.2 * s, 5).applyMatrix4(M4(THREE, x, 1.1 * s, z))); treeGeos.push(new THREE.ConeGeometry(1.9 * s, 5.2 * s, 7).applyMatrix4(M4(THREE, x, 2.2 * s + 2.6 * s, z))); counts.trees++; };
  const WALLS = [0xe8e2d4, 0xdfe5ea, 0xf0e0cc, 0xd8e2d0, 0xead9d9, 0xf3ead9], ROOFS = [0x6b5a4a, 0x596066, 0x746052, 0x4f5a60];
  for (const R of MAP.regions) {
    const rnd = mulberry32(R.seed || 7);
    if (R.kind === 'fashionblock') {
      if (!forge) { forge = makeFashionForge(THREE); forge.blocksLeft = MAP.regions.filter(q => q.kind === 'fashionblock').length; }
      const hardCap = MAP.fashionQuota || 800;
      const allow = Math.max(0, Math.ceil((hardCap - forge.total) / Math.max(1, forge.blocksLeft)));
      forge.blocksLeft--;
      counts.fashion += forge.buildBlock(R.x, R.z, rnd, R.peak != null ? R.peak : 0.5, allow, hardCap, buildings, MAP.busStops || []);
    } else if (R.kind === 'houseblock') {
      const slab = new THREE.Mesh(new THREE.BoxGeometry(92, 0.3, 92), new THREE.MeshLambertMaterial({ color: 0x66755e }));
      slab.position.set(R.x, 0.15, R.z); slab.name = 'HOUSEBLOCK_' + R.name; group.add(slab);
      for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) {
        const hx = R.x + (i - 1) * 29 + (rnd() - 0.5) * 3, hz = R.z + (j - 1) * 29 + (rnd() - 0.5) * 3;
        if ((MAP.busStops || []).some(s => Math.hypot(s.x - hx, s.z - hz) < 9)) { tree(hx, hz, 1); continue; }
        if (rnd() < 0.16) { tree(hx, hz, 0.9 + rnd() * 0.5); continue; }
        if (inWater(hx, hz)) continue;
        const w = 9 + rnd() * 3, d = 8 + rnd() * 3, h = 3.8 + rnd() * 1.8, wall = WALLS[Math.floor(rnd() * WALLS.length)], roof = ROOFS[Math.floor(rnd() * ROOFS.length)];
        bucket(wall).push(new THREE.BoxGeometry(w, h, d).applyMatrix4(M4(THREE, hx, h / 2 + 0.3, hz)));
        const rg = new THREE.ConeGeometry(Math.hypot(w, d) * 0.62, 2.8, 4); rg.applyMatrix4(M4(THREE, hx, h + 0.3 + 1.4, hz, Math.PI / 4)); bucket(roof).push(rg);
        buildings.push({ x: hx, z: hz, w: w + 1, d: d + 1, h }); counts.houses++;
      }
    } else if (R.kind === 'school') {
      const slab = new THREE.Mesh(new THREE.BoxGeometry(92, 0.3, 92), new THREE.MeshLambertMaterial({ color: 0x7a7f72 }));
      slab.position.set(R.x, 0.15, R.z); slab.name = 'SCHOOL_' + R.name; group.add(slab);
      bucket(0x4a7c43).push(new THREE.BoxGeometry(86, 0.14, 34).applyMatrix4(M4(THREE, R.x, 0.36, R.z + 26)));
      bucket(0xf0ead8).push(new THREE.BoxGeometry(80, 0.05, 1.2).applyMatrix4(M4(THREE, R.x, 0.45, R.z + 26)));
      bucket(0xcbb59a).push(new THREE.BoxGeometry(52, 13, 18).applyMatrix4(M4(THREE, R.x, 6.8, R.z - 22)));
      bucket(0x8a4f3a).push(new THREE.BoxGeometry(54, 1.4, 20).applyMatrix4(M4(THREE, R.x, 14, R.z - 22)));
      bucket(0x93a7b8).push(new THREE.BoxGeometry(46, 3.4, 0.8).applyMatrix4(M4(THREE, R.x, 8.6, R.z - 12.6)));
      bucket(0xd8cfc0).push(new THREE.BoxGeometry(10, 1, 6).applyMatrix4(M4(THREE, R.x, 0.8, R.z - 9)));
      trunkGeos.push(new THREE.CylinderGeometry(0.12, 0.12, 9, 5).applyMatrix4(M4(THREE, R.x + 32, 4.5, R.z - 22)));
      bucket(0xb03a30).push(new THREE.BoxGeometry(3.4, 2, 0.2).applyMatrix4(M4(THREE, R.x + 32 + 1.7, 8, R.z - 22)));
      buildings.push({ x: R.x, z: R.z - 22, w: 52, d: 18, h: 13 }); counts.schools++;
      tree(R.x - 38, R.z + 30, 1); tree(R.x + 38, R.z + 30, 1.1);
    } else if (R.kind === 'municipal') {
      const slab = new THREE.Mesh(new THREE.BoxGeometry(92, 0.3, 92), new THREE.MeshLambertMaterial({ color: 0x8a8578 }));
      slab.position.set(R.x, 0.15, R.z); slab.name = 'MUNICIPAL_' + R.name; group.add(slab);
      const sub = R.sub || 'hall';
      if (sub === 'hall') {
        bucket(0xd8cfc0).push(new THREE.BoxGeometry(44, 16, 20).applyMatrix4(M4(THREE, R.x, 8.3, R.z - 8)));
        bucket(0x8a8175).push(new THREE.BoxGeometry(46, 1.6, 22).applyMatrix4(M4(THREE, R.x, 17.1, R.z - 8)));
        for (let ci = 0; ci < 4; ci++) trunkGeos.push(new THREE.CylinderGeometry(0.7, 0.7, 10, 8).applyMatrix4(M4(THREE, R.x - 15 + ci * 10, 5.3, R.z + 4)));
        bucket(0xd8cfc0).push(new THREE.BoxGeometry(36, 0.8, 4).applyMatrix4(M4(THREE, R.x, 10.6, R.z + 4)));
        bucket(0xb0a890).push(new THREE.BoxGeometry(16, 1.2, 8).applyMatrix4(M4(THREE, R.x, 0.9, R.z + 10)));
        trunkGeos.push(new THREE.CylinderGeometry(0.12, 0.12, 11, 5).applyMatrix4(M4(THREE, R.x + 30, 5.5, R.z + 18)));
        bucket(0xb03a30).push(new THREE.BoxGeometry(3.8, 2.2, 0.2).applyMatrix4(M4(THREE, R.x + 30 + 1.9, 9.8, R.z + 18)));
        buildings.push({ x: R.x, z: R.z - 8, w: 44, d: 20, h: 16 });
      } else if (sub === 'fire') {
        bucket(0x9e4a3a).push(new THREE.BoxGeometry(3