// === BUILD PLAN ===
// Style: late-Victorian fairground carousel (merry-go-round) in a municipal park
// Footprint: REGULAR DODECAGON (12-sided), never a square:
// deck r=13 at Y2 on a panelled skirt Y0-Y1, 3-step entry on the south side
// Height: deck Y2 | mounts Y3-Y8 | valance ring Y15-16 | striped cone Y16-25 | crown+flag Y26-29
//
// PLAN (top-down, X right, Z down) - 12 sectors, 8 mounts + 12 rim rods:
// _ _ _ _ _
// / h R h \ h = horse (cardinal, body along an axis)
// / R R \ H = horse (diagonal, body drawn on the diagonal)
// | H ### H | ### = mirrored centre column (r2, sea-lantern core)
// | h ##### h | R = rim rod carrying the canopy (12 of them)
// \ H H / S = entry steps, south
// \_ _ R h R _ _/
// S
//
// ELEVATION:
// ^ flag canopy = 12 SECTORS, alternating cream / crimson
// /\|/\ stripes run UP the slope; gold trim ring at the rim
// /=========\ <- gold ring and a gold crown at the apex
// /||||||||||\\ <- striped cone
// ~^~^~^~^~^~^~ <- SCALLOPED VALANCE: upside-down stairs + slabs + hanging lanterns
// | ## | <- mirrored column, horses on brass poles between deck and canopy
// ============== <- deck Y2
// |____________| <- panelled skirt
//
// MATERIALS PLAN (what, where, WHY):
// deck : noisePalette spruce/dark_oak/stripped_spruce planks - WHY: worn fairground boards
// skirt : gradientPalette Y0->Y1 deepslate_bricks -> polished_andesite - WHY: shadow at ground = AO
// canopy : SECTOR stripes, cream = bone_block/white_terracotta/smooth_quartz,
// crimson = red_terracotta/red_concrete/red_nether? no -> red_terracotta ramp
// WHY: the stripe IS the silhouette-reading feature of a carousel
// trim : gold_block + waxed copper accents on the rim ring, crown and pole caps
// column : quartz + white_stained_glass mirror panels over a sea_lantern core - WHY: it glows
// horses : bone_block/white_terracotta body, gold mane, red_wool saddle, chain pole
// "Rooms": the covered deck IS the interior - centre column, 8 mounts, 2 chariot benches,
// lantern ring, ticket kiosk outside. Every mount reachable from the entry steps.
// === END PLAN ===
var floorY = 0;
var deckY = 2, walkY = 3;
var rimY = 16, capH = 9;
var R_DECK = 13, R_CANOPY = 14;
function polyR(theta, R0, n) {
var seg = Math.PI * 2 / n;
var a = theta % seg;
if (a < 0) a += seg;
return R0 * Math.cos(Math.PI / n) / Math.cos(a - Math.PI / n);
}
function ang(x, z) { var t = Math.atan2(z, x); if (t < 0) t += Math.PI * 2; return t; }
// ---------- PARK GROUND ----------
var park = terrain({
cx: 0, cz: 0, radius: 32, baseY: -1, amp: 2, scale: 0.05, seed: 91,
irregularity: 0.35,
surface: [{ block: "grass_block", w: 74 }, { block: "moss_block", w: 12 }, { block: "coarse_dirt", w: 8 }, { block: "podzol", w: 6 }],
sub: "dirt", deep: "stone", depth: 4,
pads: [{ cx: 0, cz: 0, r: 17, y: floorY, falloff: 9 }]
});
// ---------- SKIRT + DECK ----------
var skirtMat = gradientPalette(floorY, deckY, [
{ block: "deepslate_bricks", from: 0 },
{ block: "polished_deepslate", from: 0.35 },
{ block: "polished_andesite", from: 0.7 }
], { noise: 0.2, seed: 3 });
var deckMat = noisePalette([{ block: "spruce_planks", weight: 50 }, { block: "dark_oak_planks", weight: 28 }, { block: "stripped_spruce_wood", weight: 22 }], { seed: 12, freq: 0.22 });
var x, z, t, rr, dd;
for (x = -R_CANOPY - 2; x <= R_CANOPY + 2; x++) {
for (z = -R_CANOPY - 2; z <= R_CANOPY + 2; z++) {
dd = Math.sqrt(x * x + z * z);
if (dd < 0.5) continue;
t = ang(x, z);
rr = polyR(t, R_DECK, 12);
if (dd <= rr) {
pb(x, floorY, z, skirtMat(x, floorY, z));
pb(x, floorY + 1, z, skirtMat(x, floorY + 1, z));
pb(x, deckY, z, deckMat(x, deckY, z));
}
}
}
pb(0, floorY, 0, "polished_deepslate"); pb(0, floorY + 1, 0, "polished_deepslate"); pb(0, deckY, 0, "polished_andesite");
// decorative skirt panelling: painted pilaster every sector edge
var si;
for (si = 0; si < 12; si++) {
var a0 = (Math.PI * 2 / 12) * si;
var pr = polyR(a0, R_DECK, 12);
var px = Math.round(Math.cos(a0) * pr), pz = Math.round(Math.sin(a0) * pr);
pb(px, floorY, pz, "red_terracotta");
pb(px, floorY + 1, pz, "gold_block");
ps(px, deckY, pz, "polished_andesite_slab[type=bottom]");
}
// deck edge kerb + gold studs
for (x = -R_CANOPY; x <= R_CANOPY; x++) {
for (z = -R_CANOPY; z <= R_CANOPY; z++) {
dd = Math.sqrt(x * x + z * z);
t = ang(x, z);
rr = polyR(t, R_DECK, 12);
if (dd <= rr && dd > rr - 1.0) {
ps(x, deckY + 1, z, "dark_oak_slab[type=bottom]");
if ((x + z) % 5 === 0) pb(x, deckY + 1, z, "waxed_cut_copper_slab[type=bottom]");
}
}
}
// entry steps, south
var st;
for (st = 0; st < 3; st++) {
var sz = R_DECK + 1 + st;
var sxx;
for (sxx = -3; sxx <= 3; sxx++) {
roofStair(sxx, deckY - st, sz, "polished_andesite_stairs", "south");
if (st === 2) pb(sxx, deckY - st - 1, sz, "polished_andesite");
}
}
pb(-4, deckY, R_DECK + 1, "gold_block"); pb(4, deckY, R_DECK + 1, "gold_block");
pb(-4, deckY + 1, R_DECK + 1, "lantern"); pb(4, deckY + 1, R_DECK + 1, "lantern");
// ---------- MIRRORED CENTRE COLUMN ----------
makeCylinder(0, walkY, 0, "sea_lantern", 1, rimY - walkY, false);
var colMat = noisePalette([{ block: "quartz_block", weight: 40 }, { block: "smooth_quartz", weight: 25 }, { block: "white_stained_glass", weight: 25 }, { block: "gold_block", weight: 10 }], { seed: 21, freq: 0.3 });
makeCylinder(0, walkY, 0, colMat, 2.4, rimY - walkY, true);
// gold banding on the column
var cy;
for (cy = walkY + 2; cy < rimY; cy += 4) makeCylinder(0, cy, 0, "gold_block", 2.6, 1, true);
makeCylinder(0, walkY - 1, 0, "waxed_cut_copper", 3.4, 1, true);
makeCylinder(0, rimY - 1, 0, "gold_block", 3.2, 1, true);
// ---------- 12 RIM RODS carrying the canopy ----------
var ri;
for (ri = 0; ri < 12; ri++) {
var ra = (Math.PI * 2 / 12) * ri + (Math.PI / 12);
var rrad = polyR(ra, R_DECK - 1, 12);
var rx = Math.round(Math.cos(ra) * rrad), rz = Math.round(Math.sin(ra) * rrad);
var yy;
for (yy = walkY; yy <= rimY - 1; yy++) pb(rx, yy, rz, "chain[axis=y]");
pb(rx, walkY - 1, rz, "waxed_cut_copper");
pb(rx, rimY - 1, rz, "gold_block");
}
// ---------- SCALLOPED VALANCE ----------
for (x = -R_CANOPY - 1; x <= R_CANOPY + 1; x++) {
for (z = -R_CANOPY - 1; z <= R_CANOPY + 1; z++) {
dd = Math.sqrt(x * x + z * z);
t = ang(x, z);
rr = polyR(t, R_CANOPY, 12);
if (dd <= rr && dd > rr - 1.2) {
var sector = Math.floor(t / (Math.PI * 2 / 12));
pb(x, rimY, z, (sector % 2 === 0) ? "bone_block[axis=y]" : "red_terracotta");
// scallop: upside-down stair skirt hanging one below the rim
if (Math.abs(x) >= Math.abs(z)) roofStair(x, rimY - 1, z, (sector % 2 === 0) ? "smooth_quartz_stairs" : "red_sandstone_stairs", x > 0 ? "east" : "west");
else roofStair(x, rimY - 1, z, (sector % 2 === 0) ? "smooth_quartz_stairs" : "red_sandstone_stairs", z > 0 ? "south" : "north");
if ((x + z) % 4 === 0) { pb(x, rimY - 2, z, "lantern[hanging=true]"); }
}
}
}
// ---------- STRIPED 12-SECTOR CONICAL CANOPY ----------
var ki;
for (ki = 0; ki <= capH; ki++) {
var y = rimY + 1 + ki;
var shrink = 1 - (ki + 1) / (capH + 1.5);
var Rl = R_CANOPY * shrink;
if (Rl < 1.1) Rl = 1.1;
var RR = Math.ceil(Rl) + 1;
for (x = -RR; x <= RR; x++) {
for (z = -RR; z <= RR; z++) {
dd = Math.sqrt(x * x + z * z);
t = ang(x, z);
rr = polyR(t, Rl, 12);
if (dd <= rr && dd > rr - 2.4) {
var sec = Math.floor(t / (Math.PI * 2 / 12));
var block;
if (sec % 2 === 0) block = (((x + z + ki) % 7) === 0) ? "smooth_quartz" : (((x * 3 + z) % 5) === 0 ? "white_terracotta" : "bone_block[axis=y]");
else block = (((x + z + ki) % 6) === 0) ? "red_concrete" : (((x + z * 2) % 5) === 0 ? "terracotta" : "red_terracotta");
pb(x, y, z, block);
}
}
}
}
// gold rib on every sector seam
var gi;
for (gi = 0; gi < 12; gi++) {
var ga = (Math.PI * 2 / 12) * gi;
for (ki = 0; ki <= capH; ki++) {
var yg = rimY + 1 + ki;
var shr = 1 - (ki + 1) / (capH + 1.5);
var Rg = R_CANOPY * shr;
if (Rg < 1.1) Rg = 1.1;
var rg = polyR(ga, Rg, 12);
pb(Math.round(Math.cos(ga) * rg), yg, Math.round(Math.sin(ga) * rg), "gold_block");
}
}
// crown + flag
fill(-1, rimY + capH + 1, -1, 1, rimY + capH + 1, 1, "gold_block");
pb(0, rimY + capH + 2, 0, "gold_block");
pb(0, rimY + capH + 3, 0, "chain[axis=y]");
pb(0, rimY + capH + 4, 0, "chain[axis=y]");
ps(1, rimY + capH + 4, 0, "red_wall_banner[facing=east]");
pb(0, rimY + capH + 5, 0, "end_rod[facing=up]");
// ---------- HORSES ON BRASS POLES ----------
var horseBody = noisePalette([{ block: "bone_block[axis=y]", weight: 48 }, { block: "white_terracotta", weight: 30 }, { block: "smooth_quartz", weight: 22 }], { seed: 77, freq: 0.35 });
function carouselHorse(hx, hz, dx, dz, saddle) {
var bodyY = walkY + 3;
// barrel of the body
drawLine(hx - dx * 2, bodyY, hz - dz * 2, hx + dx * 2, bodyY, hz + dz * 2, "bone_block[axis=y]", 1.6);
// neck rising forward + head
drawLine(hx + dx * 1.6, bodyY, hz + dz * 1.6, hx + dx * 3, bodyY + 2, hz + dz * 3, "bone_block[axis=y]", 1.1);
// dapple the barrel so the horse is not one flat block
var qi, qx, qz;
for (qi = -2; qi <= 2; qi++) {
qx = Math.round(hx + dx * qi); qz = Math.round(hz + dz * qi);
if ((qi + 3) % 2 === 0) pb(qx, bodyY, qz, horseBody(qx, bodyY, qz));
pb(Math.round(qx - dz * 1), bodyY, Math.round(qz + dx * 1), horseBody(qx, bodyY, qz));
}
pb(Math.round(hx + dx * 3.6), bodyY + 2, Math.round(hz + dz * 3.6), "bone_block[axis=y]");
pb(Math.round(hx + dx * 4.2), bodyY + 2, Math.round(hz + dz * 4.2), "white_terracotta");
pb(Math.round(hx + dx * 3), bodyY + 3, Math.round(hz + dz * 3), "gold_block"); // ear/plume
// gold mane down the neck
pb(Math.round(hx + dx * 2.2), bodyY + 2, Math.round(hz + dz * 2.2), "gold_block");
pb(Math.round(hx + dx * 1.4), bodyY + 1, Math.round(hz + dz * 1.4), "gold_block");
// legs down to the deck
var px = -dz, pz = dx;
var li, lj;
for (li = -1; li <= 1; li += 2) {
for (lj = -1; lj <= 1; lj += 2) {
var lx = Math.round(hx + dx * 1.5 * li + px * 0.9 * lj);
var lz = Math.round(hz + dz * 1.5 * li + pz * 0.9 * lj);
var yy2;
for (yy2 = walkY; yy2 < bodyY; yy2++) pb(lx, yy2, lz, "white_terracotta");
}
}
// tail
pb(Math.round(hx - dx * 2.6), bodyY, Math.round(hz - dz * 2.6), "gold_block");
pb(Math.round(hx - dx * 2.9), bodyY - 1, Math.round(hz - dz * 2.9), "gold_block");
// saddle + bridle
pb(hx, bodyY + 1, hz, saddle);
pb(Math.round(hx - dx * 0.8), bodyY + 1, Math.round(hz - dz * 0.8), saddle);
// brass pole through the saddle up to the canopy
var yy3;
for (yy3 = bodyY + 2; yy3 <= rimY - 1; yy3++) pb(hx, yy3, hz, "chain[axis=y]");
pb(hx, rimY - 1, hz, "gold_block");
}
var mounts = [
[0, -9, 1, 0], [0, 9, -1, 0], [9, 0, 0, 1], [-9, 0, 0, -1],
[6.4, -6.4, 0.7071, 0.7071], [-6.4, 6.4, -0.7071, -0.7071],
[6.4, 6.4, -0.7071, 0.7071], [-6.4, -6.4, 0.7071, -0.7071]
];
var mi;
var saddles = ["red_wool", "blue_wool", "green_wool", "purple_wool", "red_wool", "orange_wool", "blue_wool", "magenta_wool"];
for (mi = 0; mi < mounts.length; mi++) {
carouselHorse(Math.round(mounts[mi][0]), Math.round(mounts[mi][1]), mounts[mi][2], mounts[mi][3], saddles[mi]);
}
// two chariot benches between mounts (classic carousel seating)
function chariot(bx, bz, faceDir) {
fill(bx - 1, walkY, bz - 1, bx + 1, walkY, bz + 1, "dark_oak_planks");
chairStair(bx - 1, walkY + 1, bz, "spruce_stairs", faceDir);
chairStair(bx, walkY + 1, bz, "spruce_stairs", faceDir);
chairStair(bx + 1, walkY + 1, bz, "spruce_stairs", faceDir);
pb(bx - 2, walkY + 1, bz, "gold_block"); pb(bx + 2, walkY + 1, bz, "gold_block");
pb(bx - 2, walkY + 2, bz, "red_terracotta"); pb(bx + 2, walkY + 2, bz, "red_terracotta");
}
chariot(0, -12, "north");
chariot(0, 11, "south");
// ---------- PARK SURROUNDINGS ----------
var pathMat = noisePalette([{ block: "gravel", weight: 38 }, { block: "cobblestone", weight: 26 }, { block: "andesite", weight: 22 }, { block: "coarse_dirt", weight: 14 }], { seed: 44, freq: 0.25 });
var pz4;
for (pz4 = R_DECK + 4; pz4 <= 30; pz4++) {
var pw = (pz4 > 22) ? 3 : 2;
var pxx;
for (pxx = -pw; pxx <= pw; pxx++) pb(pxx, floorY, pz4, pathMat(pxx, floorY, pz4));
}
// ring path around the carousel
for (x = -22; x <= 22; x++) {
for (z = -22; z <= 22; z++) {
dd = Math.sqrt(x * x + z * z);
if (dd <= 18.5 && dd > 16.0) pb(x, floorY, z, pathMat(x, floorY, z));
}
}
// park benches + lamp posts around the ring
function bench(bx, bz, dir) {
pb(bx - 1, floorY + 1, bz, "dark_oak_fence");
chairStair(bx, floorY + 1, bz, "spruce_stairs", dir);
chairStair(bx + 1, floorY + 1, bz, "spruce_stairs", dir);
pb(bx + 2, floorY + 1, bz, "dark_oak_fence");
}
bench(-19, 6, "east"); bench(16, -8, "west"); bench(-8, -19, "south"); bench(7, 18, "north");
function lamp(lx, lz) {
pb(lx, floorY + 1, lz, "waxed_cut_copper");
pb(lx, floorY + 2, lz, "dark_oak_fence");
pb(lx, floorY + 3, lz, "dark_oak_fence");
pb(lx, floorY + 4, lz, "dark_oak_fence");
ps(lx + 1, floorY + 4, lz, "dark_oak_fence");
pb(lx + 1, floorY + 3, lz, "lantern[hanging=true]");
pb(lx, floorY + 5, lz, "gold_block");
}
lamp(-20, -4); lamp(20, 4); lamp(-5, 20); lamp(5, -20);
// striped ticket kiosk
var kx = -17, kz = 14;
fill(kx, floorY + 1, kz, kx + 3, floorY + 4, kz + 3, "red_terracotta");
fill(kx + 1, floorY + 1, kz + 1, kx + 2, floorY + 4, kz + 2, "air");
var kk;
for (kk = 0; kk <= 3; kk++) {
pb(kx + kk, floorY + 2, kz, (kk % 2 === 0) ? "bone_block[axis=y]" : "red_terracotta");
pb(kx + kk, floorY + 3, kz, (kk % 2 === 0) ? "red_terracotta" : "bone_block[axis=y]");
}
fill(kx + 1, floorY + 2, kz, kx + 2, floorY + 3, kz, "air");
ps(kx + 1, floorY + 2, kz, "dark_oak_trapdoor[facing=north,half=top,open=false]");
pb(kx + 1, floorY + 1, kz, "dark_oak_slab[type=top]"); pb(kx + 2, floorY + 1, kz, "dark_oak_slab[type=top]");
pb(kx + 2, floorY + 1, kz + 2, "barrel[facing=up]");
pb(kx + 1, floorY + 3, kz + 2, "lantern[hanging=true]");
var kr;
for (kr = -1; kr <= 4; kr++) {
roofStair(kx + kr, floorY + 5, kz - 1, "red_sandstone_stairs", "north");
roofStair(kx + kr, floorY + 5, kz + 4, "red_sandstone_stairs", "south");
pb(kx + kr, floorY + 6, kz + 1, "smooth_quartz_slab[type=bottom]");
pb(kx + kr, floorY + 6, kz + 2, "smooth_quartz_slab[type=bottom]");
}
ps(kx + 1, floorY + 4, kz - 1, "oak_wall_sign[facing=north]");
// trees + flowerbeds
function tree(tx, tz, th, sd) {
var i3;
for (i3 = 0; i3 < th; i3++) pb(tx, floorY + 1 + i3, tz, "oak_log[axis=y]");
makeSphere(tx, floorY + th, tz, noisePalette([{ block: "oak_leaves[persistent=true]", weight: 60 }, { block: "birch_leaves[persistent=true]", weight: 22 }, { block: "azalea_leaves[persistent=true]", weight: 18 }], { seed: sd, freq: 0.3 }), 3.4, false);
pb(tx, floorY + th + 3, tz, "oak_leaves[persistent=true]");
}
tree(-24, -12, 5, 2); tree(23, -16, 5, 8); tree(-22, 20, 4, 14); tree(24, 17, 5, 19); tree(2, -26, 5, 23);
// flower beds in the four quadrants of the ring
var fb2;
for (fb2 = 0; fb2 < 4; fb2++) {
var fa = Math.PI / 4 + (Math.PI / 2) * fb2;
var fcx = Math.round(Math.cos(fa) * 21), fcz = Math.round(Math.sin(fa) * 21);
var ax2, az2;
for (ax2 = -2; ax2 <= 2; ax2++) {
for (az2 = -2; az2 <= 2; az2++) {
if (Math.abs(ax2) + Math.abs(az2) > 3) continue;
pb(fcx + ax2, floorY, fcz + az2, "rooted_dirt");
pb(fcx + ax2, floorY + 1, fcz + az2, ((ax2 + az2) % 3 === 0) ? "red_tulip" : (((ax2 + az2) % 3 === 1) ? "orange_tulip" : "oxeye_daisy"));
}
}
var bd;
for (bd = -3; bd <= 3; bd++) {
pb(fcx + bd, floorY, fcz - 3, "polished_andesite");
pb(fcx + bd, floorY, fcz + 3, "polished_andesite");
}
}
// scattered park grass and flowers
var fi;
for (fi = 0; fi < 110; fi++) {
var fx = Math.floor(noise2d(fi * 2.3, 1.1) * 58) - 29;
var fz = Math.floor(noise2d(0.7, fi * 3.7) * 58) - 29;
var dcen = Math.sqrt(fx * fx + fz * fz);
if (dcen < 19) continue;
pb(fx, floorY + 1, fz, (fi % 4 === 0) ? "short_grass" : (fi % 4 === 1 ? "dandelion" : (fi % 4 === 2 ? "cornflower" : "short_grass")));
}