// === BUILD PLAN ===
// Style: Ottoman hammam (classical, 16th c. Sinan idiom)
// Footprint: linear 3-cell sequence on N-S axis, 25 wide x 62 deep + portico
// HARARET (hot room, OCTAGON) z15..37, center (40,26), R=11, big ribbed dome R=9
// SOGUKLUK (cool passage) z37..47, 13x11, small ribbed dome R=4
// CAMEKAN (changing hall) z47..71, 25x25, LARGEST ribbed dome R=10 + wooden gallery
// PORTICO z72..77, 3 arches, alternating red/cream voussoirs
//
// GROUND PLAN (top-down, z increases downward = toward the entrance):
// ....#######.... <- hararet octagon, 8 wall niches w/ basins
// ..## ..... ##.. central gobektasi (navel stone) platform
// .# [GOBEK] #.
// ..## ..... ##..
// ....###DDD###.... D = doorway
// ## ##
// # sogukluk # basins + benches, small dome
// ### DDD###
// #####################
// #G G# G = wooden gallery strip (upper level y13)
// #G ~~~~~ G# ~ = octagonal fountain pool
// #G ~~~~~ G#
// #G S G# S = staircase to gallery
// ##########DDD########
// | | | portico piers
// A A A A = arch (red/cream voussoirs)
//
// SECTION (front elevation):
// (.) <- ribbed lead dome, star oculi
// ___/ \___
// | drum |
// |#|#|#|#|#| <- pilastered limestone wall w/ string course
// | /^\ | <- portico arches
// =============
//
// MATERIALS PLAN (what goes where + WHY):
// walls : gradientPalette sandstone -> smooth_sandstone -> smooth_quartz (y8..y20)
// WHY: warm limestone that lightens with height = fake sun/AO, gives volume
// pilaster : cut_sandstone proud by 1 + cut_sandstone_stairs cornice
// WHY: kills flat_walls - vertical rhythm every 6 blocks, real shadow
// domes : gradientPalette deepslate_tiles -> gray_concrete -> light_gray_concrete
// WHY: lead sheeting reads darker at the springing, brighter at the crown
// ribs : polished_deepslate radial lines WHY: the "ribbed" signature, dark on grey
// oculi : light_blue/white stained glass rings WHY: star-lights, never a solid fire block
// voussoir : red_terracotta / smooth_quartz ALTERNATING WHY: the ablaq stripe is the look
// interior : quartz family marble noisePalette + spruce gallery WHY: cold marble vs warm wood
// Rooms: camekan(fountain, benches, rugs, upper gallery), sogukluk(basins, benches),
// hararet(gobektasi platform, 4 basin alcoves, oculi light)
// STAIR PLAN: camekan floor walkY=8 -> gallery walk 14, D=6 -> staircase() single run north
// along the west gallery strip at x=31, from z=68. Helper cuts the floor hole.
// === END PLAN ===
var padY = 6, floorY = 7, walkY = 8;
// ---------- palettes ----------
var LIME = gradientPalette(8, 20, [
{ block: 'sandstone', from: 0 },
{ block: 'smooth_sandstone', from: 0.35 },
{ block: 'smooth_quartz', from: 0.88 }
], { noise: 0.25, seed: 3 });
var LIMEFLAT = noisePalette([
{ block: 'smooth_sandstone', weight: 50 },
{ block: 'sandstone', weight: 25 },
{ block: 'cut_sandstone', weight: 25 }
], { seed: 7, freq: 0.1 });
var LEAD = gradientPalette(16, 32, [
{ block: 'gray_concrete', from: 0 },
{ block: 'light_gray_concrete', from: 0.3 },
{ block: 'light_gray_terracotta', from: 0.62 },
{ block: 'smooth_stone', from: 0.85 }
], { noise: 0.22, seed: 11 });
var MARBLE = noisePalette([
{ block: 'quartz_block', weight: 46 },
{ block: 'smooth_quartz', weight: 30 },
{ block: 'quartz_bricks', weight: 14 },
{ block: 'chiseled_quartz_block', weight: 10 }
], { seed: 9, freq: 0.16 });
var RIB = 'deepslate_tiles';
// ---------- 1. TERRAIN (superpowers, flat pad under the whole hammam) ----------
var land = blob(40, 42, 40, { irregularity: 0.35, seed: 12 });
var h = hills({ base: padY, amp: 3, scale: 0.035, seed: 5 });
var pad = flatten(h, { x0: 21, z0: 7, x1: 59, z1: 81 }, padY, 9);
var groundMat = patches(['grass_block', 'moss_block', 'coarse_dirt', 'sand'], 0.06, 4);
surface(land, pad, groundMat, { sub: 'dirt', deep: 'stone', depth: 4 });
// ---------- helpers ----------
function inOct(x, z, cx, cz, R, cut) {
var dx = x - cx, dz = z - cz;
if (dx < 0) dx = -dx;
if (dz < 0) dz = -dz;
return (dx <= R && dz <= R && (dx + dz) <= cut);
}
function dist2(x, z, cx, cz) {
var dx = x - cx, dz = z - cz;
return Math.sqrt(dx * dx + dz * dz);
}
// ribbed dome: stacked shrinking rings (watertight), radial ribs, oculi rings
function ribbedDome(cx, cz, baseY, R, shellPat, ribId, ribCount) {
for (var dy = 0; dy <= R; dy++) {
var r = Math.round(Math.sqrt(R * R - dy * dy));
var rn = (dy >= R) ? -1 : Math.round(Math.sqrt(R * R - (dy + 1) * (dy + 1)));
if (r <= 0) { pb(cx, baseY + dy, cz, ribId); continue; }
var start = rn + 1;
if (start > r) start = r;
if (start < 1) start = 1;
for (var rr = start; rr <= r; rr++) {
makeCylinder(cx, baseY + dy, cz, shellPat, rr, 1, true);
}
for (var k = 0; k < ribCount; k++) {
var a = (Math.PI * 2 * k) / ribCount;
pb(cx + Math.round(r * Math.cos(a)), baseY + dy, cz + Math.round(r * Math.sin(a)), ribId);
}
}
pb(cx, baseY + R + 1, cz, 'polished_deepslate');
pb(cx, baseY + R + 2, cz, 'lightning_rod');
}
// star oculi: single glass blocks ON the shell ring (computed from the ring radius -> never floating)
function domeOculi(cx, cz, baseY, R, count, level, glassA, glassB) {
var dy = Math.round(R * level);
var r = Math.round(Math.sqrt(R * R - dy * dy));
for (var k = 0; k < count; k++) {
var a = (Math.PI * 2 * (k + 0.5)) / count;
var gx = cx + Math.round(r * Math.cos(a));
var gz = cz + Math.round(r * Math.sin(a));
pb(gx, baseY + dy, gz, (k % 2 === 0) ? glassA : glassB);
pb(gx, baseY + dy - 1, gz, glassB);
}
}
// roof deck that turns a square/octagon room into a circular dome opening
function domeDeck(x0, z0, x1, z1, y0, y1, cx, cz, openR, pat, octR, octCut) {
for (var x = x0; x <= x1; x++) {
for (var z = z0; z <= z1; z++) {
if (octR > 0 && !inOct(x, z, cx, cz, octR, octCut)) continue;
if (dist2(x, z, cx, cz) <= openR - 1) continue;
fill(x, y0, z, x, y1, z, pat);
}
}
}
// ---------- 2. HARARET (hot room, octagon) ----------
var hcx = 40, hcz = 26, hR = 11, hCut = 15;
var hTop = walkY + 7; // y15
for (var x = hcx - hR; x <= hcx + hR; x++) {
for (var z = hcz - hR; z <= hcz + hR; z++) {
if (!inOct(x, z, hcx, hcz, hR, hCut)) continue;
fill(x, floorY, z, x, floorY, z, MARBLE); // marble floor
if (!inOct(x, z, hcx, hcz, hR - 2, hCut - 2)) {
fill(x, walkY, z, x, hTop, z, LIME); // 2-thick wall
} else {
fill(x, walkY, z, x, hTop + 6, z, 'air'); // clear interior
}
}
}
domeDeck(hcx - hR, hcz - hR, hcx + hR, hcz + hR, hTop + 1, hTop + 2, hcx, hcz, 9, LIMEFLAT, hR, hCut);
makeCylinder(hcx, hTop + 1, hcz, LIMEFLAT, 10, 2, true); // drum
ribbedDome(hcx, hcz, hTop + 3, 9, LEAD, RIB, 8);
domeOculi(hcx, hcz, hTop + 3, 9, 8, 0.45, 'light_blue_stained_glass', 'white_stained_glass');
// ---------- 3. SOGUKLUK (cool passage) ----------
var sx0 = 34, sx1 = 46, sz0 = 37, sz1 = 47, scx = 40, scz = 42;
var sTop = walkY + 5; // y13
fill(sx0, floorY, sz0, sx1, floorY, sz1, MARBLE);
makeWalls(sx0, walkY, sz0, sx1, sTop, sz1, LIME);
makeWalls(sx0 + 1, walkY, sz0 + 1, sx1 - 1, sTop, sz1 - 1, LIME);
fill(sx0 + 2, walkY, sz0 + 2, sx1 - 2, sTop + 5, sz1 - 2, 'air');
domeDeck(sx0, sz0, sx1, sz1, sTop + 1, sTop + 2, scx, scz, 4, LIMEFLAT, 0, 0);
makeCylinder(scx, sTop + 1, scz, LIMEFLAT, 5, 2, true);
ribbedDome(scx, scz, sTop + 3, 4, LEAD, RIB, 8);
domeOculi(scx, scz, sTop + 3, 4, 6, 0.4, 'light_blue_stained_glass', 'white_stained_glass');
// ---------- 4. CAMEKAN (changing hall) ----------
var cx0 = 28, cx1 = 52, cz0 = 47, cz1 = 71, ccx = 40, ccz = 59;
var cTop = walkY + 10; // y18
fill(cx0, floorY, cz0, cx1, floorY, cz1, MARBLE);
makeWalls(cx0, walkY, cz0, cx1, cTop, cz1, LIME);
makeWalls(cx0 + 1, walkY, cz0 + 1, cx1 - 1, cTop, cz1 - 1, LIME);
fill(cx0 + 2, walkY, cz0 + 2, cx1 - 2, cTop + 6, cz1 - 2, 'air');
domeDeck(cx0, cz0, cx1, cz1, cTop + 1, cTop + 2, ccx, ccz, 10, LIMEFLAT, 0, 0);
makeCylinder(ccx, cTop + 1, ccz, LIMEFLAT, 11, 2, true);
ribbedDome(ccx, ccz, cTop + 3, 10, LEAD, RIB, 12);
domeOculi(ccx, ccz, cTop + 3, 10, 10, 0.42, 'light_blue_stained_glass', 'white_stained_glass');
// ---------- 5. FACADE RELIEF: pilasters + string course + cornice (anti flat_walls) ----------
function pilaster(x, z, yTop, dir) {
fill(x, walkY, z, x, yTop - 1, z, 'cut_sandstone');
ps(x, yTop, z, 'cut_sandstone_stairs[facing=' + dir + ',half=top]');
pb(x, walkY - 1, z, 'chiseled_sandstone');
}
var pz;
for (pz = cz0 + 2; pz <= cz1 - 2; pz += 5) {
pilaster(cx0 - 1, pz, cTop, 'east');
pilaster(cx1 + 1, pz, cTop, 'west');
}
for (var px = cx0 + 3; px <= cx1 - 3; px += 5) {
pilaster(px, cz1 + 1, cTop, 'north');
}
for (pz = hcz - 8; pz <= hcz + 8; pz += 5) {
pilaster(hcx - hR - 1, pz, hTop, 'east');
pilaster(hcx + hR + 1, pz, hTop, 'west');
}
// string course band + top cornice around the camekan block
function bandRect(x0, z0, x1, z1, y, id) {
for (var x = x0; x <= x1; x++) { ps(x, y, z0, id); ps(x, y, z1, id); }
for (var z = z0; z <= z1; z++) { ps(x0, y, z, id); ps(x1, y, z, id); }
}
bandRect(cx0 - 1, cz0 - 1, cx1 + 1, cz1 + 1, walkY + 6, 'cut_sandstone_slab');
bandRect(cx0 - 1, cz0 - 1, cx1 + 1, cz1 + 1, cTop, 'cut_sandstone_slab');
bandRect(sx0 - 1, sz0, sx1 + 1, sz1, sTop, 'cut_sandstone_slab');
// ---------- 6. DOORWAYS + WINDOWS ----------
fill(39, walkY, 70, 41, walkY + 2, 71, 'air'); // portico -> camekan
fill(39, walkY, 46, 41, walkY + 2, 48, 'air'); // camekan -> sogukluk
fill(39, walkY, 36, 41, walkY + 2, 38, 'air'); // sogukluk -> hararet
// arched window with alternating red/cream voussoirs
function archWindow(x, z, y, axis) {
var i;
for (i = -1; i <= 1; i++) {
if (axis === 'x') {
fill(x, y, z + i, x + 1, y + 2, z + i, 'air');
fill(x, y, z + i, x + 1, y + 2, z + i, 'light_blue_stained_glass');
} else {
fill(x + i, y, z, x + i, y + 2, z + 1, 'air');
fill(x + i, y, z, x + i, y + 2, z + 1, 'light_blue_stained_glass');
}
}
for (i = -2; i <= 2; i++) {
var vy = y + 3 + (i === 0 ? 1 : (Math.abs(i) === 1 ? 1 : 0));
var vid = (Math.abs(i) % 2 === 0) ? 'smooth_quartz' : 'red_terracotta';
if (axis === 'x') { pb(x, vy, z + i, vid); pb(x + 1, vy, z + i, vid); }
else { pb(x + i, vy, z, vid); pb(x + i, vy, z + 1, vid); }
}
}
var wz;
for (wz = cz0 + 5; wz <= cz1 - 5; wz += 7) {
archWindow(cx0, wz, walkY + 2, 'x');
archWindow(cx1 - 1, wz, walkY + 2, 'x');
}
archWindow(36, cz1 - 1, walkY + 2, 'z');
archWindow(44, cz1 - 1, walkY + 2, 'z');
// ---------- 7. PORTICO with alternating red/cream voussoir arches ----------
var poZ = 75, piers = [31, 37, 43, 49];
var pi;
for (pi = 0; pi < piers.length; pi++) {
fill(piers[pi], walkY, poZ, piers[pi] + 1, walkY + 9, poZ + 1, LIMEFLAT);
pb(piers[pi], walkY - 1, poZ, 'chiseled_sandstone');
}
// three semicircular arches, r=3, springing y = walkY+7
function voussoirArch(cxA, yS, zA, r) {
for (var k = 0; k <= 24; k++) {
var a = Math.PI * k / 24;
var ax = cxA - Math.round(r * Math.cos(a));
var ay = yS + Math.round(r * Math.sin(a));
var id = ((ax + ay) % 2 === 0) ? 'red_terracotta' : 'smooth_quartz';
pb(ax, ay, zA, id);
pb(ax, ay, zA + 1, id);
}
}
for (pi = 0; pi < piers.length - 1; pi++) {
voussoirArch(piers[pi] + 4, walkY + 7, poZ, 3);
}
// portico roof: flat lead deck + cornice + small half domes
fill(31, walkY + 11, poZ - 1, 50, walkY + 11, poZ + 2, LIMEFLAT);
bandRect(30, poZ - 2, 51, poZ + 3, walkY + 12, 'cut_sandstone_slab');
for (pi = 0; pi < piers.length - 1; pi++) {
ribbedDome(piers[pi] + 4, poZ + 1, walkY + 12, 3, LEAD, RIB, 6);
}
// entrance steps
fill(37, walkY - 1, poZ + 2, 43, walkY - 1, poZ + 3, 'smooth_sandstone');
ps(37, walkY - 1, poZ + 4, 'sandstone_stairs[facing=south]');
fill(37, walkY - 2, poZ + 4, 43, walkY - 2, poZ + 4, 'smooth_sandstone');
// ---------- 8. INTERIOR: CAMEKAN (fountain, benches, gallery, light) ----------
// octagonal fountain pool
makeCylinder(ccx, walkY, ccz, 'quartz_bricks', 4, 1, true);
for (var fx = ccx - 3; fx <= ccx + 3; fx++) {
for (var fz = ccz - 3; fz <= ccz + 3; fz++) {
if (dist2(fx, fz, ccx, ccz) <= 3.2) { pb(fx, floorY, fz, 'water'); pb(fx, walkY, fz, 'water'); }
}
}
fill(ccx, floorY, ccz, ccx, walkY + 2, ccz, 'quartz_pillar');
pb(ccx, walkY + 3, ccz, 'chiseled_quartz_block');
pb(ccx, walkY + 4, ccz, 'sea_lantern');
// benches along the side walls (chairStair -> auto facing toward the room centre)
var bz;
for (bz = cz0 + 4; bz <= cz1 - 4; bz++) {
if (bz % 2 === 0) continue;
chairStair(cx0 + 2, walkY, bz, 'spruce_stairs', 'east');
chairStair(cx1 - 2, walkY, bz, 'spruce_stairs', 'west');
}
rug(34, walkY, 52, 5, 5, 'red', 'orange');
rug(44, walkY, 64, 5, 5, 'red', 'orange');
// ---- wooden upper gallery (y13 floor, walk 14) on 3 sides ----
var gFloor = 13;
fill(cx0 + 2, gFloor, cz0 + 2, cx0 + 4, gFloor, cz1 - 2, 'spruce_planks'); // west strip
fill(cx1 - 4, gFloor, cz0 + 2, cx1 - 2, gFloor, cz1 - 2, 'spruce_planks'); // east strip
fill(cx0 + 2, gFloor, cz0 + 2, cx1 - 2, gFloor, cz0 + 4, 'spruce_planks'); // north strip
// support posts + brackets so nothing floats
var gz;
for (gz = cz0 + 3; gz <= cz1 - 3; gz += 4) {
fill(cx0 + 4, walkY, gz, cx0 + 4, gFloor - 1, gz, 'stripped_spruce_log[axis=y]');
fill(cx1 - 4, walkY, gz, cx1 - 4, gFloor - 1, gz, 'stripped_spruce_log[axis=y]');
pb(cx0 + 4, gFloor, gz, 'spruce_planks');
pb(cx1 - 4, gFloor, gz, 'spruce_planks');
}
for (var gx = cx0 + 6; gx <= cx1 - 6; gx += 4) {
fill(gx, walkY, cz0 + 4, gx, gFloor - 1, cz0 + 4, 'stripped_spruce_log[axis=y]');
}
// railing
for (gz = cz0 + 2; gz <= cz1 - 2; gz++) {
pb(cx0 + 5, gFloor + 1, gz, 'spruce_fence');
pb(cx1 - 5, gFloor + 1, gz, 'spruce_fence');
}
for (gx = cx0 + 5; gx <= cx1 - 5; gx++) {
pb(gx, gFloor + 1, cz0 + 5, 'spruce_fence');
}
// gallery furnishing
rug(cx0 + 2, gFloor + 1, cz0 + 8, 3, 6, 'light_blue', 'blue');
rug(cx1 - 4, gFloor + 1, cz0 + 8, 3, 6, 'light_blue', 'blue');
chairStair(cx0 + 2, gFloor + 1, cz0 + 16, 'spruce_stairs', 'east');
chairStair(cx1 - 2, gFloor + 1, cz0 + 16, 'spruce_stairs', 'west');
// STAIR to the gallery (helper guarantees navigable run + floor hole)
staircase(cx0 + 3, cz1 - 3, walkY, gFloor + 1, 'north', { width: 2, material: 'spruce_stairs', rail: 'spruce_fence' });
// lighting
var lz;
for (lz = cz0 + 4; lz <= cz1 - 4; lz += 5) {
pb(cx0 + 2, walkY + 4, lz, 'lantern[hanging=false]');
pb(cx1 - 2, walkY + 4, lz, 'lantern[hanging=false]');
pb(cx0 + 5, gFloor + 2, lz, 'lantern[hanging=false]');
pb(cx1 - 5, gFloor + 2, lz, 'lantern[hanging=false]');
}
// ---------- 9. INTERIOR: SOGUKLUK ----------
chairStair(sx0 + 2, walkY, scz - 2, 'spruce_stairs', 'east');
chairStair(sx1 - 2, walkY, scz - 2, 'spruce_stairs', 'west');
chairStair(sx0 + 2, walkY, scz + 2, 'spruce_stairs', 'east');
chairStair(sx1 - 2, walkY, scz + 2, 'spruce_stairs', 'west');
function basin(bx, by, bz2, facingA, facingB) {
pb(bx, by - 1, bz2, 'quartz_bricks');
pb(bx, by, bz2, 'water');
ps(bx - 1, by, bz2, 'quartz_stairs[facing=' + facingA + ']');
ps(bx + 1, by, bz2, 'quartz_stairs[facing=' + facingB + ']');
pb(bx, by + 2, bz2, 'lantern[hanging=false]');
}
basin(scx - 3, walkY, sz0 + 2, 'east', 'west');
basin(scx + 3, walkY, sz0 + 2, 'east', 'west');
pb(scx, walkY + 4, scz, 'lantern[hanging=true]');
// ---------- 10. INTERIOR: HARARET (gobektasi + alcove basins) ----------
for (var gx2 = hcx - 3; gx2 <= hcx + 3; gx2++) {
for (var gz2 = hcz - 3; gz2 <= hcz + 3; gz2++) {
if (Math.abs(gx2 - hcx) + Math.abs(gz2 - hcz) > 4) continue;
fill(gx2, walkY, gz2, gx2, walkY + 1, gz2, 'smooth_quartz');
}
}
pb(hcx, walkY + 2, hcz, 'chiseled_quartz_block');
ps(hcx - 4, walkY, hcz, 'quartz_stairs[facing=west]');
ps(hcx + 4, walkY, hcz, 'quartz_stairs[facing=east]');
ps(hcx, walkY, hcz - 4, 'quartz_stairs[facing=north]');
ps(hcx, walkY, hcz + 4, 'quartz_stairs[facing=south]');
// 4 corner alcoves carved into the inner wall face, each with a basin
function alcove(ax, az, dirA, dirB) {
fill(ax - 1, walkY, az, ax + 1, walkY + 2, az, 'air');
basin(ax, walkY, az, dirA, dirB);
}
alcove(hcx - 7, hcz - 5, 'east', 'west');
alcove(hcx + 7, hcz - 5, 'east', 'west');
alcove(hcx - 7, hcz + 5, 'east', 'west');
alcove(hcx + 7, hcz + 5, 'east', 'west');
var hz;
for (hz = hcz - 6; hz <= hcz + 6; hz += 6) {
pb(hcx - 8, walkY + 3, hz, 'lantern[hanging=false]');
pb(hcx + 8, walkY + 3, hz, 'lantern[hanging=false]');
}
rug(hcx - 8, walkY, hcz - 2, 3, 5, 'red', 'orange');
rug(hcx + 6, walkY, hcz - 2, 3, 5, 'red', 'orange');
// ---------- 11. EXTERIOR: courtyard, cypresses, lamps, garden wall ----------
var court = noisePalette([
{ block: 'smooth_stone', weight: 40 },
{ block: 'polished_andesite', weight: 30 },
{ block: 'stone_bricks', weight: 20 },
{ block: 'cracked_stone_bricks', weight: 10 }
], { seed: 21, freq: 0.12 });
for (var qx = 25; qx <= 55; qx++) {
for (var qz = 72; qz <= 80; qz++) {
if (dist2(qx, qz, 40, 76) > 17) continue;
pb(qx, padY, qz, court);
}
}
function cypress(tx, tz) {
fill(tx, padY + 1, tz, tx, padY + 9, tz, 'spruce_log[axis=y]');
makeCone(tx, padY + 2, tz, noisePalette([{ block: 'spruce_leaves[persistent=true]', weight: 70 }, { block: 'azalea_leaves[persistent=true]', weight: 30 }], { seed: 4 }), 2, 9, false);
}
cypress(27, 74); cypress(53, 74); cypress(24, 66); cypress(56, 66);
cypress(24, 30); cypress(56, 30);
lampPost(33, padY + 1, 79, 4, 'cobblestone_wall');
lampPost(47, padY + 1, 79, 4, 'cobblestone_wall');
lampPost(26, padY + 1, 58, 4, 'cobblestone_wall');
lampPost(54, padY + 1, 58, 4, 'cobblestone_wall');
// low garden wall with planted beds
var wx;
for (wx = 22; wx <= 58; wx += 1) {
pb(wx, padY + 1, 82, 'sandstone_wall');
}
for (wx = 24; wx <= 56; wx += 8) {
pb(wx, padY + 2, 82, 'lantern[hanging=false]');
pb(wx, padY + 1, 81, 'flowering_azalea');
}