← Back to Gallery

Loading 3D viewer…

Hakka tulou roundhouse (indexed)

by Jorge Barbero

Dimensions
80×40×80
Blocks
69,730
Compute Time
9.5s
Generation Time
3m 6s
Edits
1
Model
claude-opus-5
Created
August 25, 2026
autoevalindexedtulouarchitecture
Prompt

User Prompt

a Hakka tulou communal roundhouse in the Fujian hills: thick rammed-earth circular outer wall three storeys high with only small upper windows, one gated entrance, continuous grey clay tile roof sloping inward, open central courtyard with a small ancestral hall, wooden inner galleries and a staircase up to the upper floors

Photos

Exterior
Exteriorexterior
Patio y anillo interior
Patio y anillo interiorinterior
Sala ancestral
Sala ancestralinterior
Taller
Tallerinterior
Caja de escalera
Caja de escalerainterior
CraftScript Source
// === BUILD PLAN ===
// Style: Hakka tulou, CONCENTRIC (Chengqi-lou variant): 3-storey outer ring + 1-storey inner ring + centre shrine
// Footprint: outer wall BATTERED (base R24.5 -> top R22.5, inward slope ~1 per 8 height, per architecture-guide battered walls)
//            rooms band R14.0-19.5 | inner alley R11.5-13.5 | inner ring building R7.5-11.5 | shrine 5x5 at centre
// Height: walk1 Y1 / walk2 Y6 / walk3 Y11, inner wall top Y15, battered outer parapet Y17, ridge Y18
//
// PLAN:            ############
//               ###ooooooooooo###      # outer rammed-earth wall (battered, 5 thick at base)
//             ##ooo...........ooo##    o 3-storey ring rooms + timber gallery
//             #oo..  #######  ..oo#    . inner alley (paved)
//             #oo..  #  S  #  ..oo#    S centre shrine (5x5, pyramid tile roof)
//             #oo..  #######  ..oo#    inner 1-storey ring = kitchens/ancestral hall
//               ###ooooooooooo###
//                  #####G######        G single gate, south (+Z, faces the camera)
//
// SECTION (courtyard LEFT / outside RIGHT):
//        eave Y12 __/‾‾‾‾‾‾‾‾‾\__ Y15 eave + upside-down stair BRACKETS (roof overhang support)
//   rail | 3F Y11 |          |####    battered: each storey insets the outer face 1 block
//   rail | 2F Y6  |          |#####
//        | 1F Y1  |          |######
//
// PROCESS: LAYERED. (1) form in placeholders blue_wool (outer wall) / light_blue_wool (inner ring)
//          (2) carve gate + recessed windows (3) TEXTURE LAST with replaceMasked + gradientPalette/noisePalette.
//          Roof is built DIRECTLY with roofStair(mat(x,0,z)) - never replaceMasked over stairs (kills facing).
// MATERIALS: outer gradient mud_bricks/packed_mud (wet base) -> smooth_sandstone/sandstone (bleached top)
//            roof noisePalette deepslate_tile/cobbled_deepslate/tuff_brick stairs = grey clay tile
//            galleries spruce/oak timber; shrine dark oak + stone bricks
// STAIR PLAN: placeStairwell() U-turn wells (5x5) inside the ring: north walk1->f2, west walk2->f3.
// === END PLAN ===

var padY = 0, floorY = 0, walk1 = 1;
var f2Y = 5, walk2 = 6, f3Y = 10, walk3 = 11;
var innerTopY = 15, outerTopY = 17, ridgeY = 18;

var R_ROOM_IN = 14.0, R_ROOM_OUT = 19.5, R_ALLEY_IN = 11.5, R_INNER_OUT = 11.5, R_INNER_IN = 7.5;

function rr(x, z) { return Math.sqrt(x * x + z * z); }
// battered outer face: 24.5 at the base, 22.5 at the parapet
function outerFace(y) { return 24.5 - (y / outerTopY) * 2.0; }

var x, z, y, k, d;

// ============ 1. TERRAIN (hill terrace pad) ============
var ground = terrain({
    cx: 0, cz: 0, radius: 36, baseY: padY, amp: 5, scale: 0.05, seed: 41,
    surface: [{ block: 'grass_block', w: 60 }, { block: 'podzol', w: 22 }, { block: 'coarse_dirt', w: 18 }],
    sub: 'dirt', deep: 'stone', depth: 4,
    pads: [{ cx: 0, cz: 0, r: 28, y: padY, falloff: 10 }]
});

// ============ 2. FORM IN PLACEHOLDERS ============
// outer battered wall
for (x = -27; x <= 27; x++) {
    for (z = -27; z <= 27; z++) {
        d = rr(x, z);
        if (d <= R_ROOM_OUT) continue;
        for (y = 1; y <= outerTopY; y++) {
            if (d <= outerFace(y)) pb(x, y, z, 'blue_wool');
        }
    }
}
// inner ring building (1 storey) + its own inner face
for (x = -13; x <= 13; x++) {
    for (z = -13; z <= 13; z++) {
        d = rr(x, z);
        if (d > R_INNER_IN && d <= R_INNER_OUT) {
            if (d > 10.4 || d <= 8.5) { for (y = 1; y <= 5; y++) pb(x, y, z, 'light_blue_wool'); }
            pb(x, floorY, z, 'light_gray_wool');
        }
    }
}
// centre shrine
for (x = -2; x <= 2; x++) {
    for (z = -2; z <= 2; z++) {
        pb(x, floorY, z, 'polished_andesite');
        if ((Math.abs(x) === 2 || Math.abs(z) === 2) && !(z === 2 && Math.abs(x) <= 1)) {
            for (y = 1; y <= 4; y++) pb(x, y, z, 'light_blue_wool');
        }
    }
}

// ============ 3. CARVE: gate, windows, doorways ============
// gate tunnel through the outer wall, south (+Z)
for (x = -1; x <= 1; x++) {
    for (z = 13; z <= 27; z++) {
        d = rr(x, z);
        if (d > 26) continue;
        for (y = 1; y <= 4; y++) b(x, y, z, 'air');
    }
}
// recessed windows: upper storeys only (a tulou has NO ground-floor openings)
for (k = 0; k < 26; k++) {
    var aw = (k * 13.85 + 6) * Math.PI / 180;
    var ux = Math.cos(aw), uz = Math.sin(aw);
    if (uz > 0.93) continue;
    var lv, ra;
    for (lv = 0; lv < 2; lv++) {
        var wy = (lv === 0 ? walk2 : walk3) + 1;
        var inner = null;
        for (ra = 19.2; ra <= 25.2; ra += 0.4) {
            var vx = Math.round(ux * ra), vz = Math.round(uz * ra);
            if (rr(vx, vz) > outerFace(wy) + 0.5) continue;
            b(vx, wy, vz, 'air'); b(vx, wy + 1, vz, 'air');
            if (inner === null) inner = [vx, vz];
        }
        if (inner) {
            pb(inner[0], wy, inner[1], 'brown_stained_glass_pane');
            pb(inner[0], wy + 1, inner[1], 'brown_stained_glass_pane');
            ps(inner[0], wy + 2, inner[1], 'dark_oak_slab[type=top]');
        }
    }
}

// ============ 4. TEXTURE LAST (masks + gradient/noise palettes) ============
var earthGrad = gradientPalette(1, outerTopY, [
    { block: 'mud_bricks', from: 0 }, { block: 'packed_mud', from: 0.18 },
    { block: 'cut_sandstone', from: 0.42 }, { block: 'smooth_sandstone', from: 0.58 },
    { block: 'sandstone', from: 0.8 }, { block: 'yellow_terracotta', from: 0.93 }
], { noise: 0.28 });
replaceMasked(-28, 0, -28, 28, outerTopY + 1, 28, earthGrad, onlyMaterial('blue_wool'));
var timberNoise = noisePalette([
    { block: 'spruce_planks', weight: 45 }, { block: 'oak_planks', weight: 25 },
    { block: 'stripped_spruce_log', weight: 18 }, { block: 'dark_oak_planks', weight: 12 }
], { freq: 0.18, seed: 5 });
replaceMasked(-14, 0, -14, 14, 6, 14, timberNoise, onlyMaterial('light_blue_wool'));
replaceMasked(-14, 0, -14, 14, 1, 14, noisePalette([
    { block: 'stone_bricks', weight: 40 }, { block: 'andesite', weight: 30 }, { block: 'cobblestone', weight: 30 }
], { freq: 0.2, seed: 9 }), onlyMaterial('light_gray_wool'));

// stone plinth course + banding on the battered wall (reads the batter)
for (x = -27; x <= 27; x++) {
    for (z = -27; z <= 27; z++) {
        d = rr(x, z);
        if (d <= R_ROOM_OUT) continue;
        if (x >= -1 && x <= 1 && z > 13) continue;
        if (d <= outerFace(1) + 0.5 && d > outerFace(2)) { pb(x, 1, z, 'cobblestone'); pb(x, 2, z, 'mossy_cobblestone'); }
        if (d <= outerFace(f2Y) + 0.5 && d > outerFace(f2Y) - 1.0) pb(x, f2Y, z, 'cut_sandstone');
        if (d <= outerFace(f3Y) + 0.5 && d > outerFace(f3Y) - 1.0) pb(x, f3Y, z, 'cut_sandstone');
    }
}

// ============ 5. RING FLOORS, GALLERIES, PARTITIONS ============
for (x = -21; x <= 21; x++) {
    for (z = -21; z <= 21; z++) {
        d = rr(x, z);
        if (d > R_ROOM_IN && d <= R_ROOM_OUT) {
            pb(x, floorY, z, timberNoise(x, 0, z));
            pb(x, f2Y, z, timberNoise(x, f2Y, z));
            pb(x, f3Y, z, timberNoise(x, f3Y, z));
            if (d > 18.1) pb(x, innerTopY, z, 'spruce_planks');
        }
        // cantilevered gallery deck over the alley
        if (d > 12.2 && d <= R_ROOM_IN) {
            pb(x, f2Y, z, timberNoise(x, f2Y, z));
            pb(x, f3Y, z, timberNoise(x, f3Y, z));
        }
        // paved alley between the rings
        if (d > R_INNER_OUT && d <= 13.6) pb(x, floorY, z, noisePalette([
            { block: 'stone_bricks', weight: 35 }, { block: 'andesite', weight: 25 },
            { block: 'cobblestone', weight: 25 }, { block: 'gravel', weight: 15 }], { freq: 0.25, seed: 3 })(x, 0, z));
    }
}
// ground-floor inner wall of the ring rooms, WITH a doorway per room
for (k = 0; k < 24; k++) {
    var ad = k * 15 * Math.PI / 180;
    var dx = Math.round(Math.cos(ad) * 14.2), dz = Math.round(Math.sin(ad) * 14.2);
    var doorx = Math.round(Math.cos((k * 15 + 7.5) * Math.PI / 180) * 14.2);
    var doorz = Math.round(Math.sin((k * 15 + 7.5) * Math.PI / 180) * 14.2);
    var rw;
    for (rw = 13.6; rw <= 14.6; rw += 0.5) {
        var sx = Math.round(Math.cos(ad) * rw), sz = Math.round(Math.sin(ad) * rw);
        for (y = 1; y <= 4; y++) pb(sx, y, sz, timberNoise(sx, y, sz));
    }
    // 2-high doorway from the alley into the room
    for (rw = 13.4; rw <= 14.8; rw += 0.4) {
        var ex = Math.round(Math.cos((k * 15 + 7.5) * Math.PI / 180) * rw);
        var ez = Math.round(Math.sin((k * 15 + 7.5) * Math.PI / 180) * rw);
        b(ex, walk1, ez, 'air'); b(ex, walk1 + 1, ez, 'air');
    }
    ps(doorx, walk1 + 2, doorz, 'dark_oak_slab[type=top]');
}
// veranda posts + rails on the gallery (ROOF OVERHANG SUPPORT — no floating eaves)
for (k = 0; k < 24; k++) {
    var ag = k * 15 * Math.PI / 180;
    var px = Math.round(Math.cos(ag) * 13.0), pz = Math.round(Math.sin(ag) * 13.0);
    for (y = walk1; y <= 4; y++) pb(px, y, pz, 'stripped_dark_oak_log');
    for (y = walk2; y <= walk2 + 3; y++) pb(px, y, pz, 'stripped_dark_oak_log');
    for (y = walk3; y <= walk3 + 3; y++) pb(px, y, pz, 'stripped_dark_oak_log');
    var qx = Math.round(Math.cos(ag) * 12.3), qz = Math.round(Math.sin(ag) * 12.3);
    pb(qx, walk2, qz, 'spruce_fence'); pb(qx, walk3, qz, 'spruce_fence');
    if (k % 2 === 0) { ps(px, walk2 + 2, pz, 'lantern[hanging=false]'); ps(px, walk3 + 2, pz, 'lantern[hanging=false]'); }
}
for (x = -14; x <= 14; x++) {
    for (z = -14; z <= 14; z++) {
        d = rr(x, z);
        if (d > 12.0 && d <= 12.9) { pb(x, walk2, z, 'spruce_fence'); pb(x, walk3, z, 'spruce_fence'); }
    }
}
// radial partitions on all three storeys
for (k = 0; k < 24; k++) {
    var ap = k * 15 * Math.PI / 180;
    var rp;
    for (rp = 14.6; rp <= 19.4; rp += 0.45) {
        var wx = Math.round(Math.cos(ap) * rp), wz = Math.round(Math.sin(ap) * rp);
        if (wx >= -3 && wx <= 3 && wz <= -14 && wz >= -21) continue;   // stairwell N
        if (wz >= -3 && wz <= 3 && wx <= -14 && wx >= -21) continue;   // stairwell W
        if (wx >= -1 && wx <= 1 && wz > 13) continue;                   // gate
        for (y = 1; y <= 4; y++) pb(wx, y, wz, timberNoise(wx, y, wz));
        for (y = walk2; y <= walk2 + 3; y++) pb(wx, y, wz, timberNoise(wx, y, wz));
        for (y = walk3; y <= walk3 + 3; y++) pb(wx, y, wz, timberNoise(wx, y, wz));
    }
}

// ============ 6. STAIRWELLS (U-turn, 5x5) ============
placeStairwell(0, -17, walk1, f2Y, 'spruce_stairs', 'spruce_fence', 'spruce_planks', 'east');
placeStairwell(-17, 0, walk2, f3Y, 'spruce_stairs', 'spruce_fence', 'spruce_planks', 'south');

// ============ 7. RING ROOF (grey tile, slopes inward; braced eaves) ============
var tileMat = noisePalette([
    { block: 'deepslate_tile_stairs', weight: 40 }, { block: 'cobbled_deepslate_stairs', weight: 28 },
    { block: 'tuff_brick_stairs', weight: 18 }, { block: 'stone_brick_stairs', weight: 14 }
], { freq: 0.16, seed: 13 });
var tileSolid = noisePalette([
    { block: 'deepslate_tiles', weight: 40 }, { block: 'cobbled_deepslate', weight: 30 },
    { block: 'tuff_bricks', weight: 18 }, { block: 'gray_terracotta', weight: 12 }
], { freq: 0.16, seed: 13 });
function roofYFor(dd) {
    if (dd > 26.2) return -1;
    if (dd > 25.0) return 15;
    if (dd > 24.0) return 16;
    if (dd > 23.0) return 17;
    if (dd > 21.0) return ridgeY;
    if (dd > 19.5) return 17;
    if (dd > 18.0) return 16;
    if (dd > 16.5) return 15;
    if (dd > 15.0) return 14;
    if (dd > 13.4) return 13;
    if (dd > 12.0) return 12;
    return -1;
}
for (x = -27; x <= 27; x++) {
    for (z = -27; z <= 27; z++) {
        d = rr(x, z);
        var ry = roofYFor(d);
        if (ry < 0) continue;
        if (d > 21.0 && d <= 23.0) { pb(x, ry, z, tileSolid(x, ry, z)); continue; }
        var edge;
        if (d > 23.0) edge = (Math.abs(x) > Math.abs(z)) ? (x > 0 ? 'east' : 'west') : (z > 0 ? 'south' : 'north');
        else edge = (Math.abs(x) > Math.abs(z)) ? (x > 0 ? 'west' : 'east') : (z > 0 ? 'north' : 'south');
        roofStair(x, ry, z, tileMat(x, ry, z), edge);
        if (ry - 1 >= 16) pb(x, ry - 1, z, tileSolid(x, ry - 1, z));
    }
}
// upside-down stair BRACKETS under the outer eave (roof overhang support, build-rules #22)
for (k = 0; k < 48; k++) {
    var ab = k * 7.5 * Math.PI / 180;
    var bx = Math.round(Math.cos(ab) * 24.2), bz = Math.round(Math.sin(ab) * 24.2);
    var fdir = (Math.abs(bx) > Math.abs(bz)) ? (bx > 0 ? 'west' : 'east') : (bz > 0 ? 'north' : 'south');
    ps(bx, 15, bz, 'dark_oak_stairs[facing=' + fdir + ',half=top]');
    var cx2 = Math.round(Math.cos(ab) * 23.2), cz2 = Math.round(Math.sin(ab) * 23.2);
    ps(cx2, 14, cz2, 'dark_oak_stairs[facing=' + fdir + ',half=top]');
}

// ============ 8. INNER RING ROOF + SHRINE ROOF ============
for (x = -13; x <= 13; x++) {
    for (z = -13; z <= 13; z++) {
        d = rr(x, z);
        if (d > 11.9 || d <= 6.4) continue;
        var iy;
        if (d > 10.6) iy = 6; else if (d > 9.2) iy = 7; else if (d > 7.8) iy = 7; else iy = 6;
        if (d > 9.2 && d <= 10.6) { pb(x, 7, z, tileSolid(x, 7, z)); continue; }
        var iedge;
        if (d > 10.0) iedge = (Math.abs(x) > Math.abs(z)) ? (x > 0 ? 'east' : 'west') : (z > 0 ? 'south' : 'north');
        else iedge = (Math.abs(x) > Math.abs(z)) ? (x > 0 ? 'west' : 'east') : (z > 0 ? 'north' : 'south');
        roofStair(x, iy, z, tileMat(x, iy, z), iedge);
    }
}
makePyramid(0, 5, 0, tileSolid, 3, true);
pb(0, 8, 0, 'gold_block');

// ============ 9. INTERIORS ============
// -- inner ring: ancestral hall (south segment) + communal kitchen (north segment)
rug(-2, walk1, 9, 5, 3, 'red', 'orange');
pb(0, walk1, 10, 'smooth_stone'); ps(0, walk1 + 1, 10, 'lectern[facing=north]');
pb(-1, walk1, 10, 'stone_brick_wall'); pb(-1, walk1 + 1, 10, 'lantern');
pb(1, walk1, 10, 'stone_brick_wall'); pb(1, walk1 + 1, 10, 'lantern');
ps(-2, 4, 11, 'red_wall_banner[facing=north]'); ps(2, 4, 11, 'red_wall_banner[facing=north]');
chairStair(-2, walk1, 8, 'dark_oak_stairs', 'north'); chairStair(2, walk1, 8, 'dark_oak_stairs', 'north');
ps(-3, walk1, 9, 'flower_pot'); ps(3, walk1, 9, 'flower_pot');
// kitchen (north segment of the inner ring)
ps(0, walk1, -10, 'smoker[facing=south]'); ps(-1, walk1, -10, 'furnace[facing=south,lit=true]');
pb(1, walk1, -10, 'stripped_spruce_log'); ps(1, walk1 + 1, -10, 'spruce_stairs[facing=south,half=top]');
ps(2, walk1, -10, 'water_cauldron[level=3]'); pb(-2, walk1, -10, 'barrel');
pb(0, walk1, -8, 'spruce_fence'); ps(0, walk1 + 1, -8, 'spruce_pressure_plate');
chairStair(-1, walk1, -8, 'spruce_stairs', 'east'); chairStair(1, walk1, -8, 'spruce_stairs', 'west');
pb(0, 5, -9, 'spruce_planks'); ps(0, 4, -9, 'lantern[hanging=true]');
rug(-2, walk1, -7, 5, 2, 'brown', 'yellow');
// -- outer ring, ground floor: workshop (east) + granary (west)
ps(18, walk1, 0, 'loom[facing=west]'); ps(18, walk1, -1, 'crafting_table');
ps(18, walk1, 1, 'smithing_table'); pb(17, walk1, 0, 'barrel'); pb(16, walk1, 1, 'chest');
pb(16, walk1 + 3, 0, 'spruce_planks'); ps(16, walk1 + 2, 0, 'lantern[hanging=true]');
rug(15, walk1, -1, 2, 3, 'green', 'lime');
for (z = -2; z <= 2; z++) { pb(-18, walk1, z, 'hay_block'); pb(-17, walk1, z, 'barrel'); }
pb(-18, walk1 + 1, -1, 'hay_block'); pb(-18, walk1 + 1, 1, 'hay_block');
pb(-16, walk1 + 3, 0, 'spruce_planks'); ps(-16, walk1 + 2, 0, 'lantern[hanging=true]');
// -- upper storeys: bedrooms
placeBed(17, walk2, -1, 'red', 'north'); pb(19, walk2, -1, 'spruce_fence'); pb(19, walk2 + 1, -1, 'lantern');
pb(17, walk2, 2, 'chest'); rug(16, walk2, 0, 3, 2, 'blue', 'light_blue'); shelfWall(19, walk2, 1, 'south', 3, 2);
placeBed(-17, walk2, 1, 'blue', 'south'); pb(-19, walk2, 1, 'spruce_fence'); pb(-19, walk2 + 1, 1, 'lantern');
pb(-17, walk2, -2, 'barrel'); rug(-18, walk2, -1, 2, 3, 'yellow', 'orange');
placeBed(1, walk3, 17, 'brown', 'north'); pb(1, walk3, 19, 'spruce_fence'); pb(1, walk3 + 1, 19, 'lantern');
rug(-1, walk3, 16, 3, 2, 'orange', 'red');
placeBed(17, walk3, -1, 'white', 'north'); pb(19, walk3, -1, 'spruce_fence'); pb(19, walk3 + 1, -1, 'lantern');
pb(17, walk3, 2, 'chest'); ps(16, walk3, 1, 'loom[facing=west]');

// ============ 10. EXTERIOR: terraces, path, bamboo ============
for (z = 27; z <= 35; z++) {
    for (x = -2; x <= 2; x++) {
        var gy = Math.round(ground(x, z));
        pb(x, gy, z, noisePalette([{ block: 'dirt_path', weight: 55 }, { block: 'gravel', weight: 25 }, { block: 'cobblestone', weight: 20 }], { freq: 0.3, seed: 8 })(x, gy, z));
    }
}
lampPost(-4, Math.round(ground(-4, 28)) + 1, 28, 4, 'dark_oak_fence');
lampPost(4, Math.round(ground(4, 28)) + 1, 28, 4, 'dark_oak_fence');
lampPost(-4, Math.round(ground(-4, 34)) + 1, 34, 4, 'dark_oak_fence');
lampPost(4, Math.round(ground(4, 34)) + 1, 34, 4, 'dark_oak_fence');
// tea terraces to the east, retained by low stone walls
for (k = 0; k < 4; k++) {
    for (z = -12; z <= 12; z++) {
        var tx = 29 + k * 2;
        var ty = Math.round(ground(tx, z));
        pb(tx, ty, z, 'farmland'); ps(tx, ty + 1, z, 'sweet_berry_bush[age=3]');
        pb(tx + 1, ty, z, 'stone_bricks');
    }
}
// bamboo + trees ring
for (k = 0; k < 30; k++) {
    var at = k * 12 * Math.PI / 180;
    var bxx = Math.round(Math.cos(at) * (29 + (k % 4) * 1.8));
    var bzz = Math.round(Math.sin(at) * (29 + (k % 3) * 2.2));
    if (bzz > 24 && Math.abs(bxx) < 6) continue;
    if (bxx > 26) continue;
    var byy = Math.round(ground(bxx, bzz)) + 1;
    if (k % 3 === 0) placeTree(bxx, byy, bzz, 'oak');
    else if (k % 3 === 1) { for (y = 0; y < 5; y++) pb(bxx, byy + y, bzz, 'bamboo'); ps(bxx + 1, byy, bzz, 'fern'); }
    else { ps(bxx, byy, bzz, 'short_grass'); ps(bxx - 1, byy, bzz, 'poppy'); ps(bxx + 1, byy, bzz, 'azure_bluet'); }
}


// ============ 11. FIX PASS: sellar el faldon (se veia el cielo desde dentro) ============
for (x = -27; x <= 27; x++) {
    for (z = -27; z <= 27; z++) {
        d = rr(x, z);
        var r0 = roofYFor(d);
        if (r0 < 0) continue;
        var rOut = roofYFor(d + 1.0);
        var rIn = roofYFor(d - 1.0);
        if (rOut > r0) pb(x, r0 + 1, z, tileSolid(x, r0 + 1, z));
        if (rIn > r0 && d < 20.0) pb(x, r0 + 1, z, tileSolid(x, r0 + 1, z));
    }
}
// techo sobre los huecos de escalera (los pozos quedaban abiertos al cielo)
for (x = -21; x <= 21; x++) {
    for (z = -21; z <= 21; z++) {
        d = rr(x, z);
        if (d <= 14.6 || d > 18.2) continue;
        var inN = (x >= -3 && x <= 3 && z <= -13 && z >= -22);
        var inW = (z >= -3 && z <= 3 && x <= -13 && x >= -22);
        if (inN || inW) pb(x, innerTopY, z, 'spruce_planks');
    }
}

Static Renders

Hakka tulou roundhouse (indexed) isometric viewisometric
Hakka tulou roundhouse (indexed) front viewfront
Hakka tulou roundhouse (indexed) side viewside
Hakka tulou roundhouse (indexed) back viewback
Hakka tulou roundhouse (indexed) top viewtop