← Back to Gallery

Loading 3D viewer…

Hakka tulou roundhouse (guided)

by Jorge Barbero

Dimensions
72×40×72
Blocks
59,132
Compute Time
10.8s
Generation Time
5m 46s
Edits
2
Model
claude-opus-5
Created
August 25, 2026
autoevalguidedtulouarchitecture
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 sala ancestral
Patio y sala ancestralinterior
Estancia de planta baja
Estancia de planta bajainterior
Zaguán de entrada
Zaguán de entradainterior
Tercera planta
Tercera plantainterior
CraftScript Source
// === BUILD PLAN ===
// Style: Hakka tulou (Fujian rammed-earth communal roundhouse), rustic vernacular
// Footprint: RING (annulus). Outer wall R=22.5 (2 thick), rooms band R15.5-20.5,
//            inner gallery wall R=15, open courtyard R<=14, ancestral hall 7x7 at center.
// Height: 3 storeys (walk 1 / 6 / 11), inner wall top Y15, outer parapet Y17, roof ridge Y18.
//
// PLAN (top-down, 1 char = 1 block-ish):
//        ###########          # = rammed-earth outer wall (2 thick)
//      ##ooooooooooo##        o = ring rooms (5 deep) + wooden gallery
//     #ooo.........ooo#       . = open courtyard (packed earth + paving)
//     #oo...  A  ...oo#       A = ancestral hall (7x7, tiled gable roof)
//     #ooo.........ooo#       W = well
//      ##ooooooooooo##        S = tangential staircases inside the ring
//        #####G#####          G = single gated entrance (south)
//
// SECTION (radial slice, courtyard on the LEFT, outside on the RIGHT):
//     roof ridge Y18 over the outer wall, tiles slope INWARD to the courtyard eave Y13
//        __/‾‾‾‾\
//   rail |  3F   |#     3F walk Y11  (bedrooms, gallery + railing)
//   rail |  2F   |#     2F walk Y6   (bedrooms/looms, gallery + railing)
//        |  1F   |#     1F walk Y1   (kitchen, dining hall, workshop, granary)
//   ==== pad Y0 ==== flatten() pad inside organic terrain()
//
// MATERIALS PLAN:
//   plinth : cobblestone(35)+andesite(25)+mossy_cobblestone(20)+stone(20)  - WHY: wet stone base, grounds the mass
//   wall lo: packed_mud(35)+mud_bricks(25)+cut_sandstone(15)+smooth_sandstone(25) - WHY: darker, weathered rammed earth
//   wall hi: smooth_sandstone(40)+sandstone(25)+packed_mud(20)+yellow_terracotta(15) - WHY: sun-bleached ochre up top (vertical gradient = fake AO)
//   roof   : deepslate_tile / cobbled_deepslate / stone_brick STAIRS - WHY: grey clay tile, cool grey against warm earth = the tulou contrast
//   inner  : spruce planks + stripped_spruce_log posts + spruce fence rails - WHY: timber galleries read WARM against grey/ochre
// Rooms: 1F kitchen(W), dining hall(E), workshop(NW), granary(SE); 2F/3F bedrooms; ancestral hall with altar.
// STAIR PLAN: 2 tangential flights INSIDE the ring, walk1->walk2 (d=5 -> 6 cells, north, dir east)
//             and walk2->walk3 (south, dir east). staircase() computes treads/holes/landings.
// === END PLAN ===

// ---------- Y LEVELS ----------
var padY = 0;
var floorY = 0;
var walk1 = 1;
var f2Y = 5,  walk2 = 6;
var f3Y = 10, walk3 = 11;
var ceilY = 15;
var outerTopY = 17;
var ridgeY = 18;

// ---------- RADII ----------
var R_OUT = 22.5, R_WALL_IN = 20.4, R_ROOM_IN = 15.5, R_INNER = 14.4, R_BALC = 12.6;

function rad(x, z) { return Math.sqrt(x * x + z * z); }
function wsel(list) {
    var t = 0, i;
    for (i = 0; i < list.length; i++) t += list[i][1];
    var r = Math.random() * t;
    for (i = 0; i < list.length; i++) { r -= list[i][1]; if (r <= 0) return list[i][0]; }
    return list[0][0];
}
var PLINTH = [['cobblestone', 35], ['andesite', 25], ['mossy_cobblestone', 20], ['stone', 20]];
var EARTH_LO = [['packed_mud', 35], ['mud_bricks', 25], ['cut_sandstone', 15], ['smooth_sandstone', 25]];
var EARTH_HI = [['smooth_sandstone', 40], ['sandstone', 25], ['packed_mud', 20], ['yellow_terracotta', 15]];
var TILE_ST = [['deepslate_tile_stairs', 45], ['cobbled_deepslate_stairs', 30], ['stone_brick_stairs', 25]];
var TILE_SOLID = [['deepslate_tiles', 45], ['cobbled_deepslate', 30], ['gray_terracotta', 25]];
var WOOD = [['spruce_planks', 60], ['oak_planks', 25], ['stripped_spruce_log', 15]];
var YARD = [['packed_mud', 30], ['coarse_dirt', 25], ['gravel', 20], ['cobblestone', 15], ['andesite', 10]];

function earth(y) { return y <= 6 ? wsel(EARTH_LO) : wsel(EARTH_HI); }

// gate corridor (south, x in [-1,1], z>0) is kept clear of walls/partitions
function inGate(x, z) { return (z > 13 && x >= -1 && x <= 1); }
// stair footprints inside the ring
function inStairN(x, z) { return (z <= -16 && z >= -20 && x >= -4 && x <= 4); }
function inStairS(x, z) { return (z >= 16 && z <= 20 && x >= -4 && x <= 4); }

// ===================== 1. TERRAIN =====================
var ground = terrain({
    cx: 0, cz: 0, radius: 34, baseY: padY, amp: 4, scale: 0.045, seed: 17,
    surface: [{ block: 'grass_block', w: 66 }, { block: 'coarse_dirt', w: 20 }, { block: 'podzol', w: 14 }],
    sub: 'dirt', deep: 'stone', depth: 4,
    pads: [{ cx: 0, cz: 0, r: 26, y: padY, falloff: 9 }]
});

var x, z, y, d, i, k;

// ===================== 2. GROUND SURFACES =====================
for (x = -26; x <= 26; x++) {
    for (z = -26; z <= 26; z++) {
        d = rad(x, z);
        if (d <= R_INNER) {                       // courtyard: packed earth + stone paving
            pb(x, floorY, z, wsel(YARD));
        } else if (d <= R_WALL_IN) {              // ring floor (rooms + gallery)
            pb(x, floorY, z, wsel(WOOD));
        }
    }
}

// ===================== 3. OUTER RAMMED-EARTH WALL =====================
for (x = -25; x <= 25; x++) {
    for (z = -25; z <= 25; z++) {
        d = rad(x, z);
        if (d > R_OUT || d <= R_WALL_IN) continue;
        if (inGate(x, z)) continue;
        for (y = 1; y <= outerTopY; y++) pb(x, y, z, earth(y));
    }
}
// stone plinth: 1 block PROUDER than the wall, 2 rows -> facade depth at the base
for (x = -25; x <= 25; x++) {
    for (z = -25; z <= 25; z++) {
        d = rad(x, z);
        if (d > 23.4 || d <= R_WALL_IN) continue;
        if (inGate(x, z)) continue;
        pb(x, 1, z, wsel(PLINTH));
        pb(x, 2, z, wsel(PLINTH));
        if (d > R_OUT) ps(x, 3, z, wsel(PLINTH) === 'stone' ? 'stone_slab' : 'cobblestone_slab');
    }
}
// buttress pilasters every 30 deg (skip the gate arc): 1 block proud, capped with a slab
for (k = 0; k < 12; k++) {
    var ang = k * 30 * Math.PI / 180;
    var cx = Math.cos(ang), cz = Math.sin(ang);
    if (cz > 0.90) continue;
    var rr;
    for (rr = 21.0; rr <= 23.4; rr += 0.45) {
        var px = Math.round(cx * rr), pz = Math.round(cz * rr);
        if (inGate(px, pz)) continue;
        for (y = 1; y <= 14; y++) pb(px, y, pz, earth(y));
        pb(px, 15, pz, 'smooth_sandstone');
        ps(px, 16, pz, 'smooth_sandstone_slab');
    }
}

// ===================== 4. INNER GALLERY WALL + FLOORS =====================
for (x = -21; x <= 21; x++) {
    for (z = -21; z <= 21; z++) {
        d = rad(x, z);
        // inner wall band: ground floor solid timber, upper floors open (posts only)
        if (d > R_INNER && d <= R_ROOM_IN && !inGate(x, z)) {
            for (y = 1; y <= 4; y++) pb(x, y, z, wsel(WOOD));
        }
        // upper floor decks: ring rooms + cantilevered balcony over the courtyard
        if (d > R_BALC && d <= R_WALL_IN) {
            if (!inGate(x, z) || d > R_INNER) {
                pb(x, f2Y, z, wsel(WOOD));
                pb(x, f3Y, z, wsel(WOOD));
            }
        }
        // 3rd-floor ceiling over the rooms only (gallery stays open to the eaves)
        if (d > 16.3 && d <= R_WALL_IN) pb(x, ceilY, z, wsel(WOOD));
    }
}
// gallery posts + railings every ~12 deg, both upper floors
for (k = 0; k < 30; k++) {
    var a2 = k * 12 * Math.PI / 180;
    var qx = Math.round(Math.cos(a2) * 14.8), qz = Math.round(Math.sin(a2) * 14.8);
    for (y = walk2; y <= walk2 + 3; y++) pb(qx, y, qz, 'stripped_spruce_log');
    for (y = walk3; y <= walk3 + 3; y++) pb(qx, y, qz, 'stripped_spruce_log');
    var bx = Math.round(Math.cos(a2) * 13.2), bz = Math.round(Math.sin(a2) * 13.2);
    pb(bx, walk2, bz, 'spruce_fence'); pb(bx, walk3, bz, 'spruce_fence');
    if (k % 3 === 0) {
        pb(qx, walk2 + 3, qz, 'stripped_spruce_log');
        ps(qx, walk2 + 2, qz, 'lantern[hanging=false]');
        ps(qx, walk3 + 2, qz, 'lantern[hanging=false]');
    }
}
// continuous balcony railing ring
for (x = -15; x <= 15; x++) {
    for (z = -15; z <= 15; z++) {
        d = rad(x, z);
        if (d > 12.4 && d <= 13.5) {
            if (pb) { pb(x, walk2, z, 'spruce_fence'); pb(x, walk3, z, 'spruce_fence'); }
        }
    }
}

// ===================== 5. RADIAL PARTITION WALLS (rooms) =====================
for (k = 0; k < 24; k++) {
    var a3 = k * 15 * Math.PI / 180;
    var sx = Math.cos(a3), sz = Math.sin(a3);
    var rr2;
    for (rr2 = 15.6; rr2 <= 20.4; rr2 += 0.45) {
        var wx = Math.round(sx * rr2), wz = Math.round(sz * rr2);
        if (inGate(wx, wz) || inStairN(wx, wz) || inStairS(wx, wz)) continue;
        for (y = 1; y <= 4; y++) pb(wx, y, wz, wsel(WOOD));
        for (y = walk2; y <= walk2 + 3; y++) pb(wx, y, wz, wsel(WOOD));
        for (y = walk3; y <= walk3 + 3; y++) pb(wx, y, wz, wsel(WOOD));
    }
}

// ===================== 6. GATE (south) =====================
for (x = -1; x <= 1; x++) {
    for (z = 14; z <= 24; z++) {
        d = rad(x, z);
        if (d > R_OUT + 1.6) continue;
        for (y = 1; y <= 4; y++) b(x, y, z, 'air');
        pb(x, floorY, z, 'stone_bricks');
    }
}
for (x = -2; x <= 2; x++) {
    for (z = 19; z <= 23; z++) {
        d = rad(x, z);
        if (d > R_OUT || d <= R_WALL_IN) continue;
        pb(x, 5, z, 'stone_bricks');
        pb(x, 6, z, 'chiseled_sandstone');
    }
}
ps(-1, 4, 22, 'stone_brick_stairs[facing=east,half=top]');
ps(1, 4, 22, 'stone_brick_stairs[facing=west,half=top]');
pb(-2, 4, 23, 'lantern'); pb(2, 4, 23, 'lantern');
ps(-2, 3, 23, 'spruce_fence'); ps(2, 3, 23, 'spruce_fence');
// small tiled porch roof over the gate
for (x = -4; x <= 4; x++) {
    roofStair(x, 8, 24, wsel(TILE_ST), 'south');
    roofStair(x, 9, 23, wsel(TILE_ST), 'south');
    pb(x, 8, 23, wsel(TILE_SOLID));
}

// ===================== 7. WINDOWS (upper floors only, deeply recessed) =====================
for (k = 0; k < 24; k++) {
    var a4 = (k * 15 + 7.5) * Math.PI / 180;
    var ux = Math.cos(a4), uz = Math.sin(a4);
    if (uz > 0.93) continue;
    var lvl, rr3;
    for (lvl = 0; lvl < 2; lvl++) {
        var wy = (lvl === 0 ? walk2 : walk3) + 1;
        var innermost = null;
        for (rr3 = 20.2; rr3 <= 23.4; rr3 += 0.4) {
            var vx = Math.round(ux * rr3), vz = Math.round(uz * rr3);
            b(vx, wy, vz, 'air'); b(vx, wy + 1, vz, 'air');
            if (innermost === null) innermost = [vx, vz];
        }
        if (innermost) {
            pb(innermost[0], wy, innermost[1], 'brown_stained_glass_pane');
            pb(innermost[0], wy + 1, innermost[1], 'brown_stained_glass_pane');
        }
    }
}

// ===================== 8. STAIRCASES (tangential, inside the ring) =====================
staircase(-3, -18, walk1, walk2, 'east', { width: 2, material: 'spruce_stairs', rail: true, support: 'spruce_planks' });
staircase(-3, 18, walk2, walk3, 'east', { width: 2, material: 'spruce_stairs', rail: true, support: 'spruce_planks' });
// keep the stair wells clear of partition leftovers
for (x = -4; x <= 4; x++) {
    for (z = -20; z <= -16; z++) { b(x, walk2 + 1, z, 'air'); b(x, walk2 + 2, z, 'air'); }
}

// ===================== 9. RING ROOF (tiles slope INWARD to the courtyard) =====================
function roofYFor(dd) {
    if (dd > 24.6) return -1;
    if (dd > 23.5) return 16;
    if (dd > 22.5) return 17;
    if (dd > 20.5) return ridgeY;      // ridge band, sits over the outer wall
    if (dd > 19.0) return 17;
    if (dd > 17.5) return 16;
    if (dd > 16.0) return 15;
    if (dd > 14.5) return 14;
    if (dd > 13.2) return 13;          // eave overhangs the courtyard gallery
    return -1;
}
for (x = -26; x <= 26; x++) {
    for (z = -26; z <= 26; z++) {
        d = rad(x, z);
        var ry = roofYFor(d);
        if (ry < 0) continue;
        if (d > 20.5 && d <= 22.5) {                    // solid ridge cap - never a sky gap
            pb(x, ry, z, wsel(TILE_SOLID));
            continue;
        }
        var edge;
        if (d > 22.5) {                                  // outward slope: downhill = away from centre
            edge = (Math.abs(x) > Math.abs(z)) ? (x > 0 ? 'east' : 'west') : (z > 0 ? 'south' : 'north');
        } else {                                         // inward slope: downhill = toward centre
            edge = (Math.abs(x) > Math.abs(z)) ? (x > 0 ? 'west' : 'east') : (z > 0 ? 'north' : 'south');
        }
        roofStair(x, ry, z, wsel(TILE_ST), edge);
        if (ry - 1 >= 16) pb(x, ry - 1, z, wsel(TILE_SOLID));   // underlayment above the attic only
    }
}

// ===================== 10. ANCESTRAL HALL + COURTYARD =====================
for (x = -3; x <= 3; x++) {
    for (z = -3; z <= 3; z++) {
        pb(x, floorY, z, 'polished_andesite');
        if ((Math.abs(x) === 3 || Math.abs(z) === 3) && !(z === 3 && Math.abs(x) <= 1)) {
            for (y = 1; y <= 4; y++) {
                if (y <= 2) pb(x, y, z, 'stone_bricks');
                else pb(x, y, z, wsel([['dark_oak_planks', 60], ['spruce_planks', 25], ['stripped_dark_oak_log', 15]]));
            }
        }
    }
}
for (x = -3; x <= 3; x += 6) { for (z = -3; z <= 3; z += 6) { for (y = 1; y <= 4; y++) pb(x, y, z, 'dark_oak_log'); } }
placeGableRoofNS(-3, 3, -3, 3, 5, 'deepslate_tile_stairs', 'deepslate_tile_slab', 'dark_oak_planks');
// altar
pb(0, walk1, -2, 'smooth_stone'); ps(0, walk1 + 1, -2, 'lectern[facing=south]');
pb(-1, walk1, -2, 'stone_brick_wall'); pb(-1, walk1 + 1, -2, 'lantern');
pb(1, walk1, -2, 'stone_brick_wall'); pb(1, walk1 + 1, -2, 'lantern');
ps(-2, walk1, -1, 'red_carpet'); ps(2, walk1, -1, 'red_carpet');
rug(-1, walk1, 0, 3, 3, 'red', 'orange');
ps(0, walk1, 2, 'oak_pressure_plate');
ps(-2, walk1, 1, 'flower_pot'); ps(2, walk1, 1, 'flower_pot');
ps(-1, 4, 3, 'red_wall_banner[facing=south]'); ps(1, 4, 3, 'red_wall_banner[facing=south]');

// courtyard: paving ring, well, plants
for (k = 0; k < 60; k++) {
    var a5 = k * 6 * Math.PI / 180;
    var yx = Math.round(Math.cos(a5) * 9), yz = Math.round(Math.sin(a5) * 9);
    pb(yx, floorY, yz, wsel([['stone_bricks', 40], ['andesite', 30], ['cobblestone', 30]]));
}
// well
for (x = 7; x <= 9; x++) { for (z = 6; z <= 8; z++) { pb(x, walk1, z, 'cobblestone'); } }
pb(8, walk1, 7, 'water');
pb(7, walk1 + 1, 6, 'oak_fence'); pb(9, walk1 + 1, 6, 'oak_fence');
pb(7, walk1 + 1, 8, 'oak_fence'); pb(9, walk1 + 1, 8, 'oak_fence');
for (x = 7; x <= 9; x++) { for (z = 6; z <= 8; z++) ps(x, walk1 + 2, z, 'dark_oak_slab[type=bottom]'); }
ps(8, walk1 + 1, 7, 'iron_chain[axis=y]');
// plants & props in the courtyard
ps(-8, walk1, 6, 'flower_pot'); ps(-9, walk1, 7, 'flower_pot');
pb(-8, floorY, 8, 'farmland'); pb(-7, floorY, 8, 'farmland'); pb(-9, floorY, 8, 'farmland');
ps(-8, walk1, 8, 'wheat[age=6]'); ps(-7, walk1, 8, 'wheat[age=5]'); ps(-9, walk1, 8, 'wheat[age=7]');
pb(-6, walk1, -7, 'barrel'); pb(-7, walk1, -7, 'barrel'); pb(-6, walk1 + 1, -7, 'barrel');
pb(6, walk1, -7, 'cauldron'); pb(5, walk1, -8, 'composter');
lampPost(-10, walk1, -3, 3, 'spruce_fence');
lampPost(10, walk1, -3, 3, 'spruce_fence');
lampPost(-10, walk1, 10, 3, 'spruce_fence');
lampPost(10, walk1, 10, 3, 'spruce_fence');

// ===================== 11. INTERIOR - GROUND FLOOR ROOMS =====================
// KITCHEN (west, x ~ -20..-17)
ps(-20, walk1, -1, 'furnace[facing=east,lit=true]');
pb(-20, walk1, 0, 'stripped_spruce_log'); ps(-20, walk1 + 1, 0, 'spruce_stairs[facing=east,half=top]');
ps(-20, walk1, 1, 'water_cauldron[level=3]');
pb(-20, walk1, -2, 'barrel'); pb(-19, walk1, -3, 'smoker[facing=east]');
pb(-17, walk1, 0, 'spruce_fence'); ps(-17, walk1 + 1, 0, 'spruce_pressure_plate');
chairStair(-17, walk1, -1, 'spruce_stairs', 'south');
chairStair(-17, walk1, 1, 'spruce_stairs', 'north');
pb(-18, walk1 + 3, 0, 'spruce_planks'); ps(-18, walk1 + 2, 0, 'lantern[hanging=true]');
rug(-19, walk1, 2, 3, 2, 'yellow', 'brown');
// DINING HALL (east, x ~ 17..20)
for (z = -2; z <= 2; z++) { pb(19, walk1, z, 'dark_oak_fence'); ps(19, walk1 + 1, z, 'dark_oak_pressure_plate'); }
for (z = -2; z <= 2; z++) { chairStair(18, walk1, z, 'spruce_stairs', 'east'); chairStair(20, walk1, z, 'spruce_stairs', 'west'); }
pb(17, walk1, 0, 'barrel'); pb(17, walk1, 2, 'chest');
pb(19, walk1 + 3, 0, 'spruce_planks'); ps(19, walk1 + 2, 0, 'lantern[hanging=true]');
rug(18, walk1, -3, 3, 2, 'red', 'orange');
ps(17, walk1, -2, 'flower_pot');
// WORKSHOP (north-west)
ps(-14, walk1, -13, 'loom[facing=south]'); ps(-15, walk1, -13, 'crafting_table');
ps(-13, walk1, -14, 'smithing_table'); pb(-14, walk1, -15, 'barrel');
pb(-15, walk1 + 3, -14, 'spruce_planks'); ps(-15, walk1 + 2, -14, 'lantern[hanging=true]');
// GRANARY (south-east)
for (x = 13; x <= 16; x++) { pb(x, walk1, 13, 'hay_block'); pb(x, walk1, 14, 'barrel'); }
pb(14, walk1 + 1, 13, 'hay_block'); pb(15, walk1 + 1, 13, 'hay_block');
ps(16, walk1 + 2, 14, 'lantern[hanging=false]');

// ===================== 12. INTERIOR - UPPER BEDROOMS =====================
placeBed(-18, walk2, 2, 'red', 'north');
pb(-20, walk2, 2, 'spruce_fence'); ps(-20, walk2 + 1, 2, 'spruce_pressure_plate'); pb(-20, walk2 + 2, 2, 'lantern');
pb(-18, walk2, 4, 'chest'); rug(-19, walk2, 0, 3, 2, 'blue', 'light_blue');
shelfWall(-20, walk2, -1, 'north', 3, 2);
placeBed(18, walk2, -2, 'blue', 'south');
pb(20, walk2, -2, 'spruce_fence'); ps(20, walk2 + 1, -2, 'spruce_pressure_plate'); pb(20, walk2 + 2, -2, 'lantern');
pb(18, walk2, -4, 'barrel'); rug(18, walk2, 0, 3, 2, 'green', 'lime');
placeBed(-18, walk3, 2, 'brown', 'north');
pb(-20, walk3, 2, 'spruce_fence'); pb(-20, walk3 + 1, 2, 'lantern');
rug(-19, walk3, 0, 3, 2, 'orange', 'yellow');
placeBed(18, walk3, -2, 'white', 'south');
pb(20, walk3, -2, 'spruce_fence'); pb(20, walk3 + 1, -2, 'lantern');
pb(18, walk3, -4, 'chest');
shelfWall(20, walk3, 1, 'south', 3, 2);

// ===================== 13. EXTERIOR: PATH, LIGHTS, VEGETATION =====================
for (z = 24; z <= 33; z++) {
    for (x = -2; x <= 2; x++) {
        var gy = Math.round(ground(x, z));
        pb(x, gy, z, wsel([['dirt_path', 55], ['gravel', 25], ['cobblestone', 20]]));
    }
}
lampPost(-4, Math.round(ground(-4, 26)) + 1, 26, 4, 'spruce_fence');
lampPost(4, Math.round(ground(4, 26)) + 1, 26, 4, 'spruce_fence');
lampPost(-4, Math.round(ground(-4, 31)) + 1, 31, 4, 'spruce_fence');
lampPost(4, Math.round(ground(4, 31)) + 1, 31, 4, 'spruce_fence');
for (k = 0; k < 26; k++) {
    var a6 = k * 13.8 * Math.PI / 180;
    var tx = Math.round(Math.cos(a6) * (26 + (k % 4) * 2.0));
    var tz = Math.round(Math.sin(a6) * (26 + (k % 3) * 2.5));
    if (tz > 20 && Math.abs(tx) < 6) continue;
    var ty = Math.round(ground(tx, tz)) + 1;
    if (k % 3 === 0) placeTree(tx, ty, tz, 'oak');
    else if (k % 3 === 1) {
        pb(tx, ty, tz, wsel([['grass_block', 1]]));
        ps(tx, ty, tz, k % 2 === 0 ? 'fern' : 'short_grass');
        ps(tx + 1, ty, tz, 'dandelion');
    } else {
        ps(tx, ty, tz, 'poppy'); ps(tx - 1, ty, tz, 'azure_bluet');
    }
}
// terraced vegetable plots + fence outside the gate
for (x = 8; x <= 16; x++) {
    for (z = 24; z <= 29; z++) {
        var fy = Math.round(ground(x, z));
        if ((x + z) % 2 === 0) pb(x, fy, z, 'farmland'); else pb(x, fy, z, 'coarse_dirt');
        if ((x + z) % 2 === 0) ps(x, fy + 1, z, 'carrots[age=5]');
    }
}
for (x = 7; x <= 17; x++) { var qy = Math.round(ground(x, 30)) + 1; pb(x, qy, 30, 'oak_fence'); }
for (z = 24; z <= 30; z++) { var qy2 = Math.round(ground(17, z)) + 1; pb(17, qy2, z, 'oak_fence'); }


// ===================== 14. FIX PASS (vanos + sellado del faldon) =====================
// (a) un VANO por habitacion desde el patio: la planta baja era inaccesible
for (k = 0; k < 24; k++) {
    var afx = (k * 15 + 7.5) * Math.PI / 180;
    var rfx;
    for (rfx = 13.9; rfx <= 15.9; rfx += 0.35) {
        var fx = Math.round(Math.cos(afx) * rfx), fz = Math.round(Math.sin(afx) * rfx);
        if (inStairN(fx, fz) || inStairS(fx, fz)) continue;
        b(fx, walk1, fz, 'air'); b(fx, walk1 + 1, fz, 'air');
    }
    var lxx = Math.round(Math.cos(afx) * 15.0), lzz = Math.round(Math.sin(afx) * 15.0);
    ps(lxx, walk1 + 2, lzz, 'dark_oak_slab[type=top]');
    var pxx = Math.round(Math.cos(afx) * 13.4), pzz = Math.round(Math.sin(afx) * 13.4);
    if (k % 4 === 0) { pb(pxx, walk1, pzz, 'stripped_spruce_log'); ps(pxx, walk1 + 1, pzz, 'lantern[hanging=false]'); }
}
// (b) SELLAR el faldon: donde la hilada de fuera sube un nivel, tapar el escalon con teja maciza
for (x = -26; x <= 26; x++) {
    for (z = -26; z <= 26; z++) {
        d = rad(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, wsel(TILE_SOLID));
        if (rIn > r0 && d < 20.0) pb(x, r0 + 1, z, wsel(TILE_SOLID));
    }
}
// (c) dos habitaciones mas amuebladas en planta baja (norte y sur)
ps(3, walk1, -18, 'loom[facing=south]'); pb(2, walk1, -19, 'barrel'); pb(4, walk1, -19, 'chest');
pb(3, walk1 + 3, -18, 'spruce_planks'); ps(3, walk1 + 2, -18, 'lantern[hanging=true]');
rug(2, walk1, -17, 3, 2, 'purple', 'magenta');
pb(-4, walk1, 17, 'cauldron'); pb(-5, walk1, 18, 'barrel'); pb(-3, walk1, 18, 'composter');
chairStair(-4, walk1, 19, 'spruce_stairs', 'north');
pb(-4, walk1 + 3, 18, 'spruce_planks'); ps(-4, walk1 + 2, 18, 'lantern[hanging=true]');

Static Renders

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