// === BUILD PLAN ===
// Style: Ruined copper-dome observatory on a sea stack + rope bridge to a hermit chapel on a 2nd stack
// Footprint: big stack r~10 @ (30,40) H36 | small stack r~6 @ (57,20) H26 | sea level Y8
// Height: big pad Y36 → observatory walls Y37-46 → cornice Y47 → copper dome Y47-58
//
// TOP VIEW (1 char = 4 blocks, Z down):
// ~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~o~~~~~~ o = tide-pool rock
// ~~~~~~~~~~~~~~~CC~~~ C = chapel stack (57,20)
// ~~~~~~~~~~~~~~==~~~~ = = rope bridge (sags)
// ~~~~~OOOO~~~==~~~~~~
// ~~~~OOOOOO~=~~~~~~~~ O = observatory stack (30,40)
// ~~~~OOOOOO~~~~~~~~~~
// ~~~o~OOOO~~~~o~~~~~~
// ~~~~~~~~~~~~~~~~~~~~
//
// SIDE ELEVATION (observatory):
// _.-'|
// .' | ← dome with north-facing slit, telescope inside
// |______| Y47 cornice
// | W W | Y37-46 stone drum, arched openings
// __|______|__ Y36 mossy pad
// / ROCK \
// / ROCK \ ~~~~ Y8 sea
//
// MATERIALS PLAN:
// rock (wet band Y<=10): tuff / deepslate / andesite — WHY: dark = wet stone at waterline
// rock (mid): stone / andesite / cobblestone / tuff — WHY: cohesive grey ramp
// rock (top Y>=28): moss_block / mossy_cobble / stone — WHY: vegetation creeps down from summit
// observatory walls: gradient deepslate_bricks→stone_bricks→mossy — WHY: darker base = weight, moss = age
// dome: gradient exposed→weathered→oxidized copper — WHY: verdigris grows toward the top (rain)
// accents: dark_oak (floor, bridge, dead tree), iron bars, cobwebs, lanterns
// Rooms: observatory hall (telescope mount, lectern, barrels, cobwebs); chapel (bell beam, candles, lectern)
// === END PLAN ===
var seed = 1337;
function rnd() { seed = (seed * 1103515245 + 12345) % 2147483648; return seed / 2147483648; }
function ri(a, b) { return a + Math.floor(rnd() * (b - a + 1)); }
var SEA = 8;
// === SEABED + SEA ===
var bed = patches(['sand', 'sand', 'gravel', 'stone'], 0.07, 4);
fill(0, 0, 0, 79, 2, 79, function (x, y, z) { return bed(x, z); });
fill(0, 3, 0, 79, SEA, 79, 'water');
for (var k = 0; k < 40; k++) {
var kx = ri(2, 77), kz = ri(2, 77);
var kh = ri(1, 4);
for (var ky = 3; ky < 3 + kh; ky++) pb(kx, ky, kz, 'kelp_plant');
pb(kx, 3 + kh, kz, 'kelp');
}
for (var s = 0; s < 60; s++) pb(ri(1, 78), 3, ri(1, 78), 'seagrass');
checkpoint('SEA');
// === ROCK PALETTES ===
var wetPal = noisePalette([{block:'tuff',weight:40},{block:'deepslate',weight:30},{block:'andesite',weight:20},{block:'cobbled_deepslate',weight:10}], {seed:3, freq:0.18});
var rockPal = noisePalette([{block:'stone',weight:40},{block:'andesite',weight:25},{block:'cobblestone',weight:20},{block:'tuff',weight:15}], {seed:5, freq:0.14});
var mossPal = noisePalette([{block:'moss_block',weight:35},{block:'mossy_cobblestone',weight:30},{block:'stone',weight:20},{block:'andesite',weight:15}], {seed:9, freq:0.16});
function rockMat(y) { return y <= 10 ? wetPal : (y < 28 ? rockPal : mossPal); }
// === BIG SEA STACK (observatory) ===
var BX = 30, BZ = 40, BTOP = 36;
for (var y = 0; y <= BTOP; y++) {
var r = 10.5 - 0.08 * y + 1.2 * Math.sin(y * 0.45 + 1) + (y < 5 ? (5 - y) * 0.6 : 0) + (y > 28 ? 0.8 : 0);
var cx = BX + 1.0 * Math.sin(y * 0.35);
var cz = BZ + 1.0 * Math.cos(y * 0.28);
makeCylinder(cx, y, cz, rockMat(y), r, 1, true);
}
// summit pad: perfectly flat mossy top
makeCylinder(BX, BTOP, BZ, noisePalette([{block:'moss_block',weight:55},{block:'mossy_cobblestone',weight:25},{block:'stone',weight:20}], {seed:12, freq:0.2}), 9.3, 1, true);
makeCylinder(BX, BTOP + 1, BZ, 'air', 9.3, 30, true);
for (var m = 0; m < 40; m++) {
var a = rnd() * 6.283, d = 6.4 + rnd() * 2.6;
var mx = Math.round(BX + d * Math.cos(a)), mz = Math.round(BZ + d * Math.sin(a));
var top = String(b(mx, BTOP, mz));
if (top.indexOf('air') < 0) pb(mx, BTOP + 1, mz, rnd() < 0.5 ? 'moss_carpet' : (rnd() < 0.5 ? 'short_grass' : 'fern'));
}
// boulders fused into the pillar to break the stacked-ring look
for (var bo = 0; bo < 22; bo++) {
var ba = rnd() * 6.283, by = ri(4, 30);
var br = 1.6 + rnd() * 2.2;
var bd = (10.5 - 0.08 * by) + br * 0.35;
var bx = BX + 1.0 * Math.sin(by * 0.35) + bd * Math.cos(ba);
var bz = BZ + 1.0 * Math.cos(by * 0.28) + bd * Math.sin(ba);
makeSphere(bx, by, bz, rockMat(by), br, false);
}
checkpoint('BIG_STACK');
// === SMALL SEA STACK (chapel) ===
var SX = 57, SZ = 20, STOP = 26;
for (var y2 = 0; y2 <= STOP; y2++) {
var r2 = 6.5 - 0.05 * y2 + 0.8 * Math.sin(y2 * 0.6) + (y2 < 4 ? (4 - y2) * 0.5 : 0);
var cx2 = SX + 0.7 * Math.cos(y2 * 0.4);
var cz2 = SZ + 0.7 * Math.sin(y2 * 0.33);
makeCylinder(cx2, y2, cz2, rockMat(y2 + 10), r2, 1, true);
}
makeCylinder(SX, STOP, SZ, noisePalette([{block:'moss_block',weight:40},{block:'cobblestone',weight:30},{block:'mossy_cobblestone',weight:30}], {seed:21, freq:0.2}), 5.4, 1, true);
makeCylinder(SX, STOP + 1, SZ, 'air', 5.4, 12, true);
for (var bo2 = 0; bo2 < 10; bo2++) {
var ba2 = rnd() * 6.283, by2 = ri(3, 22);
var br2 = 1.3 + rnd() * 1.6;
var bd2 = (6.5 - 0.05 * by2) + br2 * 0.3;
makeSphere(SX + bd2 * Math.cos(ba2), by2, SZ + bd2 * Math.sin(ba2), rockMat(by2 + 10), br2, false);
}
checkpoint('SMALL_STACK');
// === TIDE POOLS ===
var pools = [[16, 52], [44, 57], [20, 25], [66, 40]];
for (var p = 0; p < pools.length; p++) {
var px = pools[p][0], pz = pools[p][1];
makeCylinder(px, 5, pz, wetPal, 3.2 + rnd(), 4, true); // rock up to Y8
makeCylinder(px, 9, pz, wetPal, 2.4 + rnd(), 1, true); // rim at Y9
pb(px, 9, pz, 'water');
if (rnd() < 0.7) pb(px + 1, 9, pz, 'water');
pb(px - 1, 10, pz - 1, 'sea_pickle[pickles=' + ri(1, 4) + ']');
if (rnd() < 0.6) pb(px + 1, 10, pz + 1, 'dead_bush');
}
checkpoint('POOLS');
// === OBSERVATORY: DRUM ===
var floorY = BTOP, walkY = BTOP + 1; // floor Y36, walk Y37
var wallPal = gradientPalette(walkY, walkY + 9, [{block:'deepslate_bricks',from:0},{block:'stone_bricks',from:0.35},{block:'mossy_stone_bricks',from:0.8}], {noise:0.25, seed:7});
makeCylinder(BX, floorY, BZ, noisePalette([{block:'dark_oak_planks',weight:70},{block:'spruce_planks',weight:20},{block:'moss_carpet',weight:0},{block:'stripped_dark_oak_log',weight:10}], {seed:31, freq:0.3}), 5.5, 1, true);
makeCylinder(BX, walkY, BZ, wallPal, 6, 10, false); // walls Y37..46
makeCylinder(BX, walkY, BZ, 'air', 5, 10, true); // guarantee hollow interior
// cornice ring
makeCylinder(BX, 47, BZ, 'stone_brick_slab', 7, 1, false);
// arched openings (N,S,W,E) at Y40-41, some with iron bars
fill(30, 40, 34, 30, 41, 34, 'air'); pb(30, 42, 34, 'stone_brick_slab[type=top]');
fill(30, 40, 46, 30, 41, 46, 'iron_bars'); pb(30, 42, 46, 'stone_brick_slab[type=top]');
fill(24, 40, 40, 24, 41, 40, 'air'); pb(24, 42, 40, 'stone_brick_slab[type=top]');
fill(36, 40, 40, 36, 41, 40, 'iron_bars'); pb(36, 42, 40, 'stone_brick_slab[type=top]');
// ruined entrance arch toward the bridge (SE)
fill(34, walkY, 35, 35, walkY + 2, 37, 'air');
pb(35, walkY + 3, 36, 'mossy_stone_bricks');
pb(34, walkY + 3, 37, 'mossy_stone_bricks');
checkpoint('OBS_WALLS');
// === OBSERVATORY: COPPER DOME (north slit) ===
var domePal = gradientPalette(47, 58, [{block:'exposed_copper',from:0},{block:'weathered_copper',from:0.4},{block:'oxidized_copper',from:0.78}], {noise:0.3, seed:13});
makeCylinder(BX, 47, BZ, 'cut_copper', 6, 2, false); // drum Y47-48
var domeR = [6, 5.8, 5.5, 5.0, 4.4, 3.6, 2.7, 1.7, 1.0];
for (var i = 0; i < domeR.length; i++) {
makeCylinder(BX, 49 + i, BZ, domePal, domeR[i], 1, i === domeR.length - 1);
}
pb(BX, 58, BZ, 'oxidized_cut_copper');
pb(BX, 59, BZ, 'lightning_rod');
// slit facing north
fill(29, 49, 33, 31, 57, 39, 'air');
pb(29, 49, 34, 'oxidized_cut_copper_stairs[facing=east]');
pb(31, 49, 34, 'oxidized_cut_copper_stairs[facing=west]');
checkpoint('DOME');
// === OBSERVATORY: INTERIOR ===
fill(29, walkY, 39, 31, walkY, 41, 'polished_deepslate');
fill(30, walkY + 1, 40, 30, walkY + 3, 40, 'deepslate_tiles');
pb(29, walkY + 1, 39, 'polished_deepslate_wall'); pb(31, walkY + 1, 41, 'polished_deepslate_wall');
drawLine(30, walkY + 4, 40, 30, 52, 35, 'copper_block', 1); // telescope tube aimed at the slit
pb(30, walkY + 4, 41, 'weathered_copper'); // eyepiece
pb(30, 53, 34, 'tinted_glass'); // lens
// clutter of an abandoned astronomer
pb(26, walkY, 42, 'lectern[facing=east]');
pb(27, walkY, 43, 'barrel'); pb(27, walkY + 1, 43, 'barrel');
pb(33, walkY, 43, 'crafting_table');
pb(34, walkY, 42, 'cobweb'); pb(26, walkY + 3, 38, 'cobweb'); pb(33, walkY + 6, 37, 'cobweb');
pb(25, walkY, 39, 'flower_pot');
pb(28, walkY, 36, 'candle[candles=4,lit=true]');
pb(33, walkY + 4, 36, 'lantern[hanging=true]'); // will be replaced by chain below
pb(33, walkY + 5, 36, 'iron_chain');
pb(30, walkY + 7, 44, 'lantern[hanging=true]');
pb(30, walkY + 8, 44, 'iron_chain');
pb(26, walkY, 40, 'moss_carpet');
pb(33, walkY, 39, 'moss_carpet');
checkpoint('OBS_INTERIOR');
// === VINES ON THE OBSERVATORY ===
for (var v = 0; v < 26; v++) {
var ang = rnd() * 6.283;
var vx = Math.round(BX + 7 * Math.cos(ang)), vz = Math.round(BZ + 7 * Math.sin(ang));
var vy0 = ri(42, 46), vlen = ri(2, 6);
for (var vy = vy0; vy > vy0 - vlen && vy > walkY; vy--) {
if (String(b(vx, vy, vz)).indexOf('air') < 0) break;
var st = null;
if (String(b(vx - 1, vy, vz)).indexOf('air') < 0) st = 'west=true';
else if (String(b(vx + 1, vy, vz)).indexOf('air') < 0) st = 'east=true';
else if (String(b(vx, vy, vz - 1)).indexOf('air') < 0) st = 'north=true';
else if (String(b(vx, vy, vz + 1)).indexOf('air') < 0) st = 'south=true';
if (st === null) break;
pb(vx, vy, vz, 'vine[' + st + ']');
}
}
checkpoint('VINES');
// === DEAD TREE ON THE SUMMIT ===
fill(23, walkY, 44, 23, walkY + 6, 44, 'dark_oak_log');
drawLine(23, walkY + 6, 44, 20, walkY + 9, 45, 'dark_oak_log', 1);
drawLine(23, walkY + 5, 44, 24, walkY + 9, 47, 'dark_oak_log', 1);
drawLine(23, walkY + 4, 44, 21, walkY + 6, 42, 'dark_oak_log', 1);
pb(23, walkY, 45, 'brown_mushroom'); pb(22, walkY, 44, 'brown_mushroom');
checkpoint('TREE');
// === HERMIT CHAPEL (ruined) on the small stack ===
var cw = walkY; // reuse name? no — chapel walk level:
var chW = STOP + 1; // Y27
var chapPal = noisePalette([{block:'cobblestone',weight:35},{block:'mossy_cobblestone',weight:30},{block:'stone_bricks',weight:20},{block:'mossy_stone_bricks',weight:15}], {seed:44, freq:0.2});
fill(55, STOP, 17, 59, STOP, 23, noisePalette([{block:'cobblestone',weight:50},{block:'moss_block',weight:25},{block:'gravel',weight:25}], {seed:45, freq:0.25}));
// perimeter columns of variable (ruined) height; back wall intact
for (var wx = 55; wx <= 59; wx++) {
for (var wz = 17; wz <= 23; wz++) {
var onEdge = (wx === 55 || wx === 59 || wz === 17 || wz === 23);
if (!onEdge) continue;
var hgt = (wz <= 19) ? 4 : ri(1, 4);
if (wx === 55 && (wz === 20 || wz === 21)) hgt = 0; // doorway, west side
if (hgt > 0) fill(wx, chW, wz, wx, chW + hgt - 1, wz, chapPal);
}
}
// partial gable roof over the back third (ridge along Z, slopes E/W), roofBaseY = Y31
var rbY = chW + 4;
for (var rz = 16; rz <= 19; rz++) {
for (var step = 0; step < 3; step++) {
roofStair(54 + step, rbY + step, rz, 'dark_oak_stairs', 'west');
roofStair(60 - step, rbY + step, rz, 'dark_oak_stairs', 'east');
}
pb(57, rbY + 2, rz, 'dark_oak_log[axis=z]');
}
for (var step2 = 0; step2 < 3; step2++) {
for (var gx = 55 + step2; gx <= 59 - step2; gx++) pb(gx, rbY + step2, 17, chapPal(gx, rbY + step2, 17));
}
// broken rafters over the open part
pb(56, rbY + 1, 21, 'dark_oak_fence'); pb(58, rbY, 22, 'dark_oak_fence');
// bell beam + bell, candles, lectern, moss
fill(56, chW + 2, 18, 58, chW + 2, 18, 'dark_oak_log[axis=x]');
pb(57, chW + 1, 18, 'bell[attachment=ceiling]');
pb(57, chW, 19, 'lectern[facing=south]');
pb(58, chW, 22, 'candle[candles=3,lit=true]');
pb(56, chW, 22, 'candle[candles=2,lit=true]');
pb(56, chW, 20, 'moss_carpet'); pb(58, chW, 21, 'moss_carpet');
pb(59, chW + 3, 18, 'vine[west=true]'); pb(59, chW + 2, 18, 'vine[west=true]');
pb(59, chW, 24, 'dead_bush'); pb(54, chW, 18, 'fern');
pb(56, chW, 24, 'lantern');
checkpoint('CHAPEL');
// === ROPE BRIDGE (sagging) ===
var deck = [{x:37, y:36, z:35}, {x:45, y:27, z:29}, {x:53, y:26, z:23}];
var rail = [{x:37, y:37, z:35}, {x:45, y:28, z:29}, {x:53, y:27, z:23}];
drawSpline(deck, 'dark_oak_planks', 1);
drawSpline(rail, 'dark_oak_fence', 1);
// posts + lanterns at both ends
fill(36, 37, 34, 36, 38, 34, 'dark_oak_fence'); pb(36, 39, 34, 'lantern');
fill(38, 37, 36, 38, 38, 36, 'dark_oak_fence');
fill(52, 27, 24, 52, 28, 24, 'dark_oak_fence'); pb(52, 29, 24, 'lantern');
fill(54, 27, 22, 54, 28, 22, 'dark_oak_fence');
checkpoint('BRIDGE');