<!-- NEW7 — TEST 7 · THE WOVEN CITY · part 449 -->
oor(R.d / pz);
      for (let i = 0; i < cols; i++) for (let j = 0; j < rows; j++) {
        if (rnd() < 0.14) { tree(R.x - R.w / 2 + (i + 0.5) * px, R.z - R.d / 2 + (j + 0.5) * pz, 0.9 + rnd() * 0.5); continue; }
        const hx = R.x - R.w / 2 + (i + 0.5) * px + (rnd() - 0.5) * 3, hz = R.z - R.d / 2 + (j + 0.5) * pz + (rnd() - 0.5) * 3;
        if (inWater(hx, hz)) continue;
        const w = 8 + rnd() * 3, d = 7 + rnd() * 3, h = 3.6 + rnd() * 1.6, 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.6, 4); rg.applyMatrix4(M4(THREE, hx, h + 0.3 + 1.3, 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 === 'island') {
      const disc = new THREE.Mesh(new THREE.CircleGeometry(R.r, 36), new THREE.MeshLambertMaterial({ color: 0x4a7c43 }));
      disc.rotation.x = -Math.PI / 2; disc.position.set(R.x, 0.95, R.z); disc.name = 'ISLAND_' + R.name; group.add(disc);
      const rim = new THREE.Mesh(new THREE.CircleGeometry(R.r + 6, 36), new THREE.MeshLambertMaterial({ color: 0x6b6257 }));
      rim.rotation.x = -Math.PI / 2; rim.position.set(R.x, 0.7, R.z); group.add(rim);
      for (let i = 0; i < 12; i++) { const a = (i / 12) * Math.PI * 2, rr = R.r * (0.45 + rnd() * 0.4); tree(R.x + Math.cos(a) * rr, R.z + Math.sin(a) * rr, 1 + rnd() * 0.6); }
      tree(R.x, R.z, 1.5);
    } else if (R.kind === 'market') {
      const slab = new THREE.Mesh(new THREE.BoxGeometry(R.w, 0.3, R.d), new THREE.MeshLambertMaterial({ color: 0x8a7f6d }));
      slab.position.set(R.x, 0.15, R.z); slab.name = 'MARKET_GROUND_' + R.name; group.add(slab);
      const rowsN = Math.max(2, Math.floor(R.d / 44));
      for (let j = 0; j < rowsN; j++) {
        const mz = R.z - R.d / 2 + (j + 0.5) * (R.d / rowsN);
        bucket(0xd8cfc0).push(new THREE.BoxGeometry(R.w * 0.86, 7.5, 16).applyMatrix4(M4(THREE, R.x, 4.05, mz)));
        bucket(0x9e3f36).push(new THREE.BoxGeometry(R.w * 0.88, 1.6, 18).applyMatrix4(M4(THREE, R.x, 8.6, mz)));
        buildings.push({ x: R.x, z: mz, w: R.w * 0.86, d: 16, h: 8 });
      }
    } else if (R.kind === 'park') {
      const slab = new THREE.Mesh(new THREE.BoxGeometry(R.w, 0.3, R.d), new THREE.MeshLambertMaterial({ color: 0x4a7c43 }));
      slab.position.set(R.x, 0.15, R.z); slab.name = 'PARK_GROUND_' + R.name; group.add(slab);
      bucket(0x9a8f7c).push(new THREE.BoxGeometry(R.w * 0.9, 0.14, 5).applyMatrix4(M4(THREE, R.x, 0.36, R.z)));
      bucket(0x9a8f7c).push(new THREE.BoxGeometry(5, 0.14, R.d * 0.9).applyMatrix4(M4(THREE, R.x, 0.36, R.z)));
      const n = Math.floor(R.w * R.d / 2600);
      for (let i = 0; i < n; i++) { const tx = R.x + (rnd() - 0.5) * (R.w - 16), tz = R.z + (rnd() - 0.5) * (R.d - 16); if (Math.abs(tx - R.x) < 6 || Math.abs(tz - R.z) < 6) continue; tree(tx, tz, 1 + rnd() * 0.7); }
    }
  }
  if (forge) forge.finalize(group);
  // ---------- bus stops, planted proper ----------
  for (const S of (MAP.busStops || [])) {
    const cos = Math.cos(S.rot), sin = Math.sin(S.rot);
    const P = (lx, lz) => [S.x + lx * cos + lz * sin, S.z - lx * sin + lz * cos];
    let p = P(0, 0);
    bucket(0xb9bec4).push(new THREE.BoxGeometry(5.4, 0.18, 2.8).applyMatrix4(M4(THREE, p[0], 0.12, p[1], S.rot)));
    for (const sx of [-1.9, 1.9]) { p = P(sx, -0.8); trunkGeos.push(new THREE.CylinderGeometry(0.09, 0.09, 2.6, 5).applyMatrix4(M4(THREE, p[0], 1.3, p[1]))); }
    p = P(0, -0.5); bucket(0x596066).push(new THREE.BoxGeometry(4.4, 0.15, 1.9).applyMatrix4(M4(THREE, p[0], 2.68, p[1], S.rot)));
    p = P(0, -1.05); bucket(0x9fc4d8).push(new THREE.BoxGeometry(4.2, 1.5, 0.08).applyMatrix4(M4(THREE, p[0], 1.35, p[1], S.rot)));
    p = P(0, -0.72); bucket(0x8a6a4a).push(new THREE.BoxGeometry(3.6, 0.12, 0.5).applyMatrix4(M4(THREE, p[0], 0.78, p[1], S.rot)));
    p = P(2.7, 0.9); trunkGeos.push(new THREE.CylinderGeometry(0.07, 0.07, 3, 5).applyMatrix4(M4(THREE, p[0], 1.5, p[1])));
    p = P(2.7, 0.9); bucket(0xf2c14e).push(new THREE.BoxGeometry(0.95, 0.65, 0.08).applyMatrix4(M4(THREE, p[0], 2.95, p[1], S.rot)));
  }
  for (const [color, arr] of buckets) { if (arr.length) { const m = new THREE.Mesh(mergeGeometries(arr), new THREE.MeshLambertMaterial({ color })); m.name = 'KIT_' + color.toString(16); group.add(m); } }
  if (trunkGeos.length) { const m = new THREE.Mesh(mergeGeometries(trunkGeos), new THREE.MeshLambertMaterial({ color: 0x5d4630 })); m.name = 'TRUNKS'; group.add(m); }
  if (treeGeos.length) { const m = new THREE.Mesh(mergeGeometries(treeGeos), new THREE.MeshLambertMaterial({ color: 0x3d6b34 })); m.name = 'TREES'; group.add(m); }
  const deckAt = (x, z) => { let best = 0; for (const dk of decks) { const h = deckProfileAt(dk, x, z); if (