// === BUILD PLAN ===
// Subject: GIANT HERMIT CRAB (colossal ~40x34x26) hauling a spiral conch shell across a tide pool
// Orientation: walking toward +Z / +X (camera side) — oversized claw raised on +X.
//
// SIDE SILHOUETTE (Y up, Z right = forward):
// ..@@@@.. @ = conch spiral (whorls grow toward the aperture)
// .@@@@@@@@@. oo o = eye stalks on stalks
// .@@@@@@@@@@@. ..####.. # = cephalothorax (orange-red armour)
// '@@@@@@@@' .########.
// \\ / ## ## \ / = jointed legs, knee ABOVE the body
// \\____/ /| |\ \
// ^^ ^^ ^ ^ ^^ ^
// ~~~~ tide pool ~~~~ mossy rocks ~~~~
//
// FRONT SILHOUETTE (Y up, X across):
// o o
// .###########. big claw raised right (+X), small claw tucked left
// / ######### \_
// /_/ ##### \_\ \
// ^ ^ ^ ^ ^ ^ ^
//
// MATERIALS PLAN:
// carapace : noisePalette orange_terracotta38/red_terracotta24/orange_concrete22/terracotta16
// limbs : red/orange terracotta + polished_blackstone chitin tips (dark tips = readable joints)
// shell : ALTERNATING BANDS pale (quartz/calcite/bone) vs warm (terracotta/pink) + ridge knobs
// ground : blob + hills + flatten pad Y10, carved tide pool (sand/prismarine/water), moss + gravel shore
// NOTE: thickLine() = own ellipsoid-swept limb (the prelude drawLine rejects palette functions).
// === END PLAN ===
var padY = 10;
var groundY = padY + 1;
// === TERRAIN ===
var land = blob(34, 32, 33, {irregularity: 0.42, seed: 9});
var h = hills({base: 9, amp: 3, scale: 0.05, seed: 11});
h = flatten(h, {cx: 34, cz: 34, r: 22}, padY, 10);
var pool = blob(24, 48, 14, {irregularity: 0.55, seed: 21});
var shoreMat = function (x, z) {
var n = noise2d('perlin', x, z, {seed: 33});
if (pool(x, z)) return 'sand';
if (n > 0.78) return 'stone';
if (n > 0.6) return 'moss_block';
if (n > 0.42) return 'gravel';
return 'sand';
};
surface(land, h, shoreMat, {sub: 'sand', deep: 'stone', depth: 4});
for (var wx = 6; wx <= 44; wx++) {
for (var wz = 32; wz <= 62; wz++) {
if (pool(wx, wz)) {
fill(wx, padY - 2, wz, wx, padY + 4, wz, 'air');
pb(wx, padY - 3, wz, noise2d('perlin', wx, wz, {seed: 5}) > 0.5 ? 'sand' : 'prismarine');
fill(wx, padY - 2, wz, wx, padY, wz, 'water');
}
}
}
// === PALETTES ===
var shellA = noisePalette([
{block: 'smooth_quartz', weight: 44}, {block: 'calcite', weight: 28},
{block: 'white_terracotta', weight: 18}, {block: 'bone_block', weight: 10}
], {seed: 4, freq: 0.16});
var shellB = noisePalette([
{block: 'terracotta', weight: 40}, {block: 'pink_terracotta', weight: 28},
{block: 'brown_terracotta', weight: 20}, {block: 'red_sand', weight: 12}
], {seed: 6, freq: 0.18});
var shellRidge = noisePalette([
{block: 'brown_terracotta', weight: 55}, {block: 'granite', weight: 25}, {block: 'terracotta', weight: 20}
], {seed: 7, freq: 0.2});
var carapace = noisePalette([
{block: 'orange_terracotta', weight: 38}, {block: 'red_terracotta', weight: 24},
{block: 'orange_concrete', weight: 22}, {block: 'terracotta', weight: 16}
], {seed: 13, freq: 0.13});
var limbPal = noisePalette([
{block: 'red_terracotta', weight: 42}, {block: 'orange_terracotta', weight: 33},
{block: 'orange_concrete', weight: 25}
], {seed: 17, freq: 0.22});
function ellipsoid(cx, cy, cz, rx, ry, rz, pat, squashBelly) {
var dx, dy, dz;
if (rx < 1) rx = 1; if (ry < 1) ry = 1; if (rz < 1) rz = 1;
for (dy = -ry; dy <= ry; dy++) {
var fy = dy / ry;
var k = Math.sqrt(Math.max(0, 1 - fy * fy));
var ax = rx * k, az = rz * k;
if (squashBelly && dy < 0) { ax = ax * 0.9; az = az * 0.92; }
if (ax < 0.6 || az < 0.6) continue;
for (dx = -Math.ceil(ax); dx <= Math.ceil(ax); dx++) {
for (dz = -Math.ceil(az); dz <= Math.ceil(az); dz++) {
var v = (dx * dx) / (ax * ax + 0.001) + (dz * dz) / (az * az + 0.001);
if (v <= 1.02) pb(cx + dx, cy + dy, cz + dz, pat);
}
}
}
}
// own swept-ellipsoid limb (prelude drawLine will not take a palette function)
function thickLine(x1, y1, z1, x2, y2, z2, pat, r1, r2) {
var dx = x2 - x1, dy = y2 - y1, dz = z2 - z1;
var n = Math.ceil(Math.max(Math.abs(dx), Math.max(Math.abs(dy), Math.abs(dz))) * 1.4) + 1;
for (var i = 0; i <= n; i++) {
var f = i / n;
var rr = r1 + (r2 - r1) * f;
ellipsoid(Math.round(x1 + dx * f), Math.round(y1 + dy * f), Math.round(z1 + dz * f),
Math.round(rr), Math.round(rr), Math.round(rr), pat, false);
}
}
// === CONCH SHELL ===
var shCx = 32, shCz = 20;
var steps = 30;
for (var t = 0; t < steps; t++) {
var f = t / (steps - 1);
var ang = 2.2 + f * 5.0;
var d = 1.5 + f * f * 13.5;
var rr2 = 1.0 + f * 6.4;
var sx = Math.round(shCx + Math.cos(ang) * d);
var sz = Math.round(shCz + Math.sin(ang) * d);
var sy = Math.round(14 + f * 9);
var pal = (t % 5 < 3) ? shellA : shellB;
ellipsoid(sx, sy, sz, Math.round(rr2), Math.round(rr2 * 0.92), Math.round(rr2), pal, false);
if (t > 10 && t % 3 === 0) {
var kx = Math.round(sx + Math.cos(ang) * (rr2 + 1));
var kz = Math.round(sz + Math.sin(ang) * (rr2 + 1));
var ky2 = sy + Math.round(rr2 * 0.35);
ellipsoid(kx, ky2, kz, 2, 2, 2, shellRidge, false);
pb(kx + 1, ky2 + 2, kz, 'brown_terracotta');
pb(kx, ky2 + 2, kz + 1, 'brown_terracotta');
}
}
ellipsoid(30, 13, 12, 2, 2, 2, shellB, false);
pb(30, 12, 10, 'brown_terracotta');
var apX = Math.round(shCx + Math.cos(7.2) * 15);
var apZ = Math.round(shCz + Math.sin(7.2) * 15);
ellipsoid(apX, 22, apZ, 8, 7, 8, shellA, false);
ellipsoid(apX, 22, apZ + 2, 6, 5, 6, 'air', false);
ellipsoid(apX + 1, 22, apZ + 5, 6, 6, 2, shellB, false);
// === BODY ===
var bx = 34, by = 19, bz = 37;
ellipsoid(bx, by, bz, 10, 6, 8, carapace, true);
thickLine(bx, by, bz - 6, apX, 21, apZ + 3, carapace, 5, 4); // abdomen into the shell
for (var rx2 = -8; rx2 <= 8; rx2++) {
pb(bx + rx2, by + 6, bz + Math.round(Math.abs(rx2) * 0.2), 'red_terracotta');
if (rx2 % 3 === 0) ps(bx + rx2, by + 7, bz - 1, 'polished_blackstone_slab[type=bottom]');
}
for (var mx = -5; mx <= 5; mx++) {
pb(bx + mx, by - 1, bz + 8, 'brown_terracotta');
if (mx % 2 === 0) pb(bx + mx, by - 2, bz + 7, 'polished_blackstone');
}
ps(bx - 1, by, bz + 9, 'polished_blackstone_stairs[facing=south,half=bottom]');
ps(bx + 1, by, bz + 9, 'polished_blackstone_stairs[facing=south,half=bottom]');
// === EYE STALKS + ANTENNAE ===
function eyeStalk(ex, ez, lean) {
thickLine(ex, by + 5, ez, ex + lean, by + 11, ez + 2, 'orange_concrete', 1, 1);
ellipsoid(ex + lean, by + 12, ez + 2, 2, 2, 2, 'black_concrete', false);
pb(ex + lean + 1, by + 13, ez + 3, 'white_concrete');
pb(ex + lean - 1, by + 12, ez + 3, 'white_concrete');
}
eyeStalk(bx - 4, bz + 5, -1);
eyeStalk(bx + 4, bz + 5, 1);
thickLine(bx - 2, by + 3, bz + 8, bx - 8, by + 9, bz + 17, 'brown_terracotta', 1, 1);
thickLine(bx + 2, by + 3, bz + 8, bx + 9, by + 8, bz + 18, 'brown_terracotta', 1, 1);
// === LEGS ===
function leg(side, idx) {
var ax = bx + side * 8;
var az = bz + 4 - idx * 6;
var ay = by + 1;
var kx = bx + side * (15 + idx * 2);
var ky = by + 6 - idx;
var kz = az + 2 - idx;
var fx = bx + side * (17 + idx * 3);
var fz = az + 5 - idx * 3;
var fy = groundY;
thickLine(ax, ay, az, kx, ky, kz, limbPal, 3, 2);
thickLine(kx, ky, kz, fx, fy + 1, fz, limbPal, 2, 1);
pb(fx, fy, fz, 'polished_blackstone');
pb(fx, fy - 1, fz, 'polished_blackstone');
pb(kx, ky + 2, kz, 'polished_blackstone');
pb(Math.round((ax + kx) / 2), ky + 1, Math.round((az + kz) / 2), 'polished_blackstone');
if (pool(fx, fz)) { pb(fx + 1, fy, fz, 'water'); pb(fx, fy, fz + 1, 'water'); }
}
leg(1, 0); leg(1, 1); leg(1, 2);
leg(-1, 0); leg(-1, 1); leg(-1, 2);
// === BIG CLAW (raised, +X) ===
thickLine(bx + 8, by + 2, bz + 4, bx + 15, by + 8, bz + 8, limbPal, 4, 3);
thickLine(bx + 15, by + 8, bz + 8, bx + 19, by + 11, bz + 13, limbPal, 4, 3);
ellipsoid(bx + 21, by + 12, bz + 16, 5, 4, 6, limbPal, false);
thickLine(bx + 21, by + 10, bz + 21, bx + 24, by + 9, bz + 26, 'orange_concrete', 2, 1);
pb(bx + 24, by + 9, bz + 27, 'polished_blackstone');
pb(bx + 25, by + 9, bz + 26, 'polished_blackstone');
thickLine(bx + 21, by + 14, bz + 20, bx + 25, by + 16, bz + 25, 'orange_concrete', 2, 1);
pb(bx + 25, by + 16, bz + 26, 'polished_blackstone');
pb(bx + 22, by + 12, bz + 23, 'bone_block');
pb(bx + 23, by + 13, bz + 24, 'bone_block');
// === SMALL CLAW (left) ===
thickLine(bx - 8, by, bz + 5, bx - 13, by - 1, bz + 10, limbPal, 3, 2);
ellipsoid(bx - 15, by - 1, bz + 13, 3, 3, 4, limbPal, false);
thickLine(bx - 15, by - 2, bz + 16, bx - 17, by - 2, bz + 20, 'orange_concrete', 1, 1);
thickLine(bx - 15, by, bz + 16, bx - 18, by + 1, bz + 20, 'orange_concrete', 1, 1);
pb(bx - 17, by - 2, bz + 21, 'polished_blackstone');
pb(bx - 18, by + 1, bz + 21, 'polished_blackstone');
// === SCENE ===
var rockPal = noisePalette([
{block: 'mossy_cobblestone', weight: 34}, {block: 'stone', weight: 26},
{block: 'andesite', weight: 22}, {block: 'cobblestone', weight: 18}
], {seed: 3, freq: 0.15});
var rocks = [[14, 26], [18, 56], [52, 46], [46, 16], [58, 30], [10, 40], [40, 60], [60, 54]];
for (var i = 0; i < rocks.length; i++) {
var rgx = rocks[i][0], rgz = rocks[i][1];
var ry3 = Math.floor(h(rgx, rgz));
ellipsoid(rgx, ry3 + 1, rgz, 3 + (i % 3), 2 + (i % 2), 3 + ((i + 1) % 3), rockPal, false);
pb(rgx, ry3 + 3 + (i % 2), rgz, 'moss_carpet');
}
for (var kx2 = 6; kx2 <= 44; kx2++) {
for (var kz2 = 32; kz2 <= 62; kz2++) {
if (!pool(kx2, kz2)) continue;
var kn = noise2d('perlin', kx2 * 2.1, kz2 * 2.1, {seed: 44});
if (kn > 0.9) {
pb(kx2, padY - 2, kz2, 'kelp_plant');
pb(kx2, padY - 1, kz2, 'kelp_plant');
pb(kx2, padY, kz2, 'kelp');
} else if (kn > 0.85) {
ps(kx2, padY - 2, kz2, 'sea_pickle[pickles=3,waterlogged=true]');
} else if (kn > 0.82) {
pb(kx2, padY - 3, kz2, 'tube_coral_block');
}
}
}
function starfish(sx2, sz2, block) {
var sy2 = Math.floor(h(sx2, sz2)) + 1;
pb(sx2, sy2, sz2, block);
pb(sx2 + 1, sy2, sz2, block); pb(sx2 - 1, sy2, sz2, block);
pb(sx2, sy2, sz2 + 1, block); pb(sx2, sy2, sz2 - 1, block);
pb(sx2 + 2, sy2, sz2 + 1, block); pb(sx2 - 2, sy2, sz2 - 1, block);
}
starfish(16, 34, 'fire_coral_block');
starfish(50, 52, 'horn_coral_block');
starfish(54, 24, 'fire_coral_block');
thickLine(50, groundY, 56, 57, groundY + 1, 60, 'stripped_oak_log[axis=x]', 1, 1);
pb(53, groundY + 1, 58, 'stripped_oak_log[axis=z]');
ellipsoid(24, groundY + 1, 24, 2, 1, 2, shellB, false);
ellipsoid(46, groundY + 1, 12, 2, 1, 2, shellA, false);
for (var gx = 4; gx <= 66; gx++) {
for (var gz = 4; gz <= 62; gz++) {
var gn = noise2d('perlin', gx * 1.8, gz * 1.8, {seed: 61});
if (gn > 0.9 && !pool(gx, gz)) {
var gy3 = Math.floor(h(gx, gz));
pb(gx, gy3 + 1, gz, gn > 0.95 ? 'dead_bush' : 'moss_carpet');
}
}
}