# Starting point for a Minecraft build generator. Copy into the workspace and fill in build().
# Everything that has to be right for the harness is already right here: bounds checked placement,
# one seeded random source, and a byte reproducible export.
from __future__ import annotations

import argparse
import gzip
import random
import sys
from pathlib import Path
from typing import Dict, Iterable, Optional, Tuple

sys.path.insert(0, "/work/generator/MCIO")

from mcio.schematic import load_schematic # noqa: E402
from mcio.sketch import LitematicCanvas, Material, parse_blockstate # noqa: E402

CONFIG = {
    "seed": 20260923,
    "size_xyz": (42, 29, 42), # width X, height Y, depth Z
    "minecraft_version": "1.21.1",
    "data_version": 3955,
    "output": "output.litematic",
    "name": "Red Roof Farmstead",
    "author": "generator",
    "description": "Timber farmhouse, furnished loft, well, wheat garden and four-sail windmill. Java 1.21.1.",
}

Position = Tuple[int, int, int]


class Build:
    """Hold a block volume and write it out as a Litematica schematic."""

    def __init__(self, size_xyz: Tuple[int, int, int] = CONFIG["size_xyz"], seed: int = CONFIG["seed"]) -> None:
        """Create an empty volume with one seeded random source.

        Args:
            size_xyz (Tuple[int, int, int]): Volume size as width X, height Y, depth Z.
            seed (int): Seed for every random decision in this build.
        """
        self.size_x, self.size_y, self.size_z = size_xyz
        self.rng = random.Random(seed)
        self.canvas = LitematicCanvas((self.size_y, self.size_z, self.size_x))
        self.placed: Dict[Position, str] = {}

    def inside(self, x: int, y: int, z: int) -> bool:
        """Report whether a coordinate is inside the volume.

        Args:
            x (int): Width coordinate.
            y (int): Height coordinate.
            z (int): Depth coordinate.

        Returns:
            bool: True when every axis is within range.
        """
        return 0 <= x < self.size_x and 0 <= y < self.size_y and 0 <= z < self.size_z

    def put(self, x: int, y: int, z: int, block: str | Material, **properties: str) -> None:
        """Place one block, in XYZ order, with an immediate bounds check.

        Args:
            x (int): Width coordinate.
            y (int): Height coordinate.
            z (int): Depth coordinate.
            block (str | Material): Namespaced block name, a full blockstate string, or a Material.
            **properties: Blockstate properties such as facing or half.

        Raises:
            AssertionError: When the coordinate is outside the volume, naming the coordinate.
        """
        assert self.inside(x, y, z), f"out of bounds: (x={x}, y={y}, z={z}) in {self.size_x}x{self.size_y}x{self.size_z}"
        material = self.material(block, **properties) if isinstance(block, str) else block
        self.canvas.block((y, z, x), material)
        self.placed[(x, y, z)] = material.blockstate

    def material(self, name: str, **properties: str) -> Material:
        """Build a Material from a block name and its properties.

        Args:
            name (str): Namespaced block name, or a full blockstate string.
            **properties: Blockstate properties.

        Returns:
            Material: Material ready for placement.
        """
        if "[" in name:
            return parse_blockstate(name)
        return self.canvas.material(name, **dict(sorted(properties.items())))

    def box(self, bounds: Tuple[int, int, int, int, int, int], block: str | Material, **properties: str) -> None:
        """Fill an inclusive box.

        Args:
            bounds (Tuple[int, int, int, int, int, int]): x0, y0, z0, x1, y1, z1, inclusive on both ends.
            block (str | Material): Block to place.
            **properties: Blockstate properties.
        """
        x0, y0, z0, x1, y1, z1 = bounds
        for x in range(min(x0, x1), max(x0, x1) + 1):
            for y in range(min(y0, y1), max(y0, y1) + 1):
                for z in range(min(z0, z1), max(z0, z1) + 1):
                    self.put(x, y, z, block, **properties)

    def shell(self, bounds: Tuple[int, int, int, int, int, int], block: str | Material, **properties: str) -> None:
        """Fill only the faces of a box, leaving the inside empty.

        Args:
            bounds (Tuple[int, int, int, int, int, int]): x0, y0, z0, x1, y1, z1, inclusive.
            block (str | Material): Block to place.
            **properties: Blockstate properties.
        """
        x0, y0, z0, x1, y1, z1 = bounds
        for x in range(min(x0, x1), max(x0, x1) + 1):
            for y in range(min(y0, y1), max(y0, y1) + 1):
                for z in range(min(z0, z1), max(z0, z1) + 1):
                    on_face = x in (x0, x1) or y in (y0, y1) or z in (z0, z1)
                    if on_face:
                        self.put(x, y, z, block, **properties)

    def texture(self, bounds: Tuple[int, int, int, int, int, int], palette: Iterable[str]) -> None:
        """Fill a box with a weighted mix of related blocks, so the surface is not flat.

        Args:
            bounds (Tuple[int, int, int, int, int, int]): x0, y0, z0, x1, y1, z1, inclusive.
            palette (Iterable[str]): Block names. Repeat a name to make it more common.
        """
        choices = list(palette)
        x0, y0, z0, x1, y1, z1 = bounds
        for x in range(min(x0, x1), max(x0, x1) + 1):
            for y in range(min(y0, y1), max(y0, y1) + 1):
                for z in range(min(z0, z1), max(z0, z1) + 1):
                    self.put(x, y, z, self.rng.choice(choices))

    def get(self, x: int, y: int, z: int) -> Optional[str]:
        """Read back what was placed at a coordinate.

        Args:
            x (int): Width coordinate.
            y (int): Height coordinate.
            z (int): Depth coordinate.

        Returns:
            Optional[str]: Blockstate string, or None when the cell is empty.
        """
        return self.placed.get((x, y, z))

    def p(self, x: int, y: int, z: int, block: str, **properties: str) -> None:
        """Place a short vanilla block name at XYZ, forwarding its properties."""
        self.put(x, y, z, 'minecraft:' + block, **properties)

    def fill(self, bounds: Tuple[int, int, int, int, int, int], block: str, **properties: str) -> None:
        """Fill inclusive XYZ bounds using a short vanilla block name."""
        self.box(bounds, 'minecraft:' + block, **properties)

    def mix(self, bounds: Tuple[int, int, int, int, int, int], palette: Iterable[str]) -> None:
        """Texture inclusive XYZ bounds from short vanilla block names."""
        self.texture(bounds, ['minecraft:' + name for name in palette])

    @staticmethod
    def roof_height(z: int) -> int:
        """Return the stepped main roof height at a north/south coordinate."""
        return 9 + min(z - 5, 24 - z)

    def terrain(self) -> None:
        """Build the flat square meadow, stone plinth, and branching farm track."""
        self.mix((0, 0, 0, 41, 0, 41), ['stone', 'stone', 'andesite', 'cobblestone'])
        self.fill((0, 1, 0, 41, 1, 41), 'dirt')
        self.fill((0, 2, 0, 41, 2, 41), 'grass_block')
        for x in range(42):
            for z in range(42):
                if x in (0, 41) or z in (0, 41):
                    self.p(x, 1, z, self.rng.choice(['stone_bricks', 'stone_bricks', 'mossy_stone_bricks']))
                    self.p(x, 2, z, 'stone_brick_slab', type='bottom')
        # Widened yard narrows into a slightly meandering approach to the gate.
        self.path_cells = set()
        for z in range(23, 42):
            center = 18 if z < 31 else 19
            width = 4 if z < 29 else 3
            for x in range(center - width // 2, center + width // 2 + 2):
                self.path_cells.add((x, z))
        for x in range(8, 29):
            for z in range(30, 33):
                self.path_cells.add((x, z))
        for x, z in sorted(self.path_cells):
            self.p(x, 2, z, self.rng.choice(['dirt_path'] * 7 + ['coarse_dirt'] * 3 + ['packed_mud', 'rooted_dirt']))
        # Gate posts and the low front boundary keep the square plot legible.
        for x0, x1 in [(2, 15), (24, 39)]:
            for x in range(x0, x1 + 1):
                self.p(x, 3, 39, 'spruce_fence')
            for x in (x0, x1):
                self.fill((x, 3, 39, x, 4, 39), 'oak_log', axis='y')
                self.p(x, 5, 39, 'spruce_slab', type='bottom')
        for x in (17, 22):
            self.fill((x, 3, 39, x, 4, 39), 'oak_log', axis='y')
            self.p(x, 5, 39, 'lantern', hanging='false')
        # An open four-block gate leaves the farm lane traversable.
        for x in (18, 19, 20, 21):
            self.p(x, 3, 39, 'spruce_fence_gate', facing='south', open='true', in_wall='false')

    def house(self) -> None:
        """Build the timber shell, sealed red roof, glazed dormer, and cupola."""
        self.mix((9, 3, 7, 28, 3, 22), ['stone_bricks'] * 5 + ['cobblestone', 'mossy_stone_bricks'])
        self.mix((10, 3, 8, 27, 3, 21), ['oak_planks'] * 8 + ['spruce_planks'])
        for x in range(9, 29):
            for z in range(7, 23):
                if x in (9, 28) or z in (7, 22):
                    for y in range(4, self.roof_height(z)):
                        self.p(x, y, z, self.rng.choice(['oak_planks'] * 8 + ['stripped_oak_log', 'spruce_planks']))
        for x in (9, 28):
            for z in (7, 14, 15, 22):
                self.fill((x, 4, z, x, self.roof_height(z) - 1, z), 'spruce_log', axis='y')
        for z in (7, 22):
            for x in (13, 16, 21, 24):
                self.fill((x, 4, z, x, 10, z), 'oak_log', axis='y')
            self.fill((9, 8, z, 28, 8, z), 'spruce_log', axis='x')
            self.fill((9, 10, z, 28, 10, z), 'spruce_log', axis='x')
        for x in (9, 28):
            self.fill((x, 8, 7, x, 8, 22), 'spruce_log', axis='z')
            self.fill((x, 10, 7, x, 10, 22), 'spruce_log', axis='z')
        # Double full-block roof backing seals the staircase profile against weather.
        for z in range(5, 25):
            h = self.roof_height(z)
            for x in range(7, 31):
                self.p(x, h - 1, z, 'red_terracotta')
                self.p(x, h, z, self.rng.choice(['mangrove_planks'] * 8 + ['red_nether_bricks', 'red_terracotta']))
                if z not in (14, 15):
                    self.p(x, h + 1, z, 'mangrove_stairs' if self.rng.random() < .90 else 'red_nether_brick_stairs', facing='south' if z < 14 else 'north', half='bottom', shape='straight')
                else:
                    self.p(x, h + 1, z, 'red_nether_brick_slab', type='bottom')
            for x in (7, 30):
                self.p(x, h, z, 'smooth_sandstone')
                self.p(x, h + 1, z, 'smooth_sandstone_stairs', facing='south' if z < 14 else 'north', half='bottom', shape='straight')
        # Timber eaves and corbels ground the wide overhang.
        for z in (5, 24):
            self.fill((8, 8, z, 29, 8, z), 'spruce_planks')
        for x in (9, 13, 16, 21, 24, 28):
            self.p(x, 8, 23, 'spruce_stairs', facing='north', half='top', shape='straight')
            self.p(x, 8, 6, 'spruce_stairs', facing='south', half='top', shape='straight')
        self.fill((10, 9, 8, 27, 9, 21), 'spruce_planks')
        # Glazed, inset openings stay clear of the fireplace and the ladder.
        for z in (7, 22):
            for x0 in (11, 25):
                self.fill((x0, 5, z, x0 + 1, 7, z), 'gray_stained_glass')
                self.fill((x0, 4, z, x0 + 1, 4, z), 'stripped_oak_log', axis='x')
        for x in (9, 28):
            for z0 in (10, 16):
                self.fill((x, 5, z0, x, 7, z0 + 1), 'gray_stained_glass')
                self.fill((x, 4, z0, x, 4, z0 + 1), 'stripped_oak_log', axis='z')
            self.fill((x, 12, 14, x, 14, 15), 'gray_stained_glass')
            self.fill((x, 11, 13, x, 11, 16), 'stripped_oak_log', axis='z')
        # Tall shadowed surround and complete paired doors.
        for x in (17, 20):
            self.fill((x, 4, 22, x, 7, 22), 'spruce_log', axis='y')
        self.fill((17, 7, 22, 20, 7, 22), 'dark_oak_log', axis='x')
        for x, hinge in [(18, 'left'), (19, 'right')]:
            for y, half in [(4, 'lower'), (5, 'upper')]:
                self.p(x, y, 22, 'dark_oak_door', facing='south', half=half, hinge=hinge, open='false', powered='false')
            self.p(x, 6, 22, 'dark_oak_planks')
        self.fill((17, 3, 23, 20, 3, 23), 'stone_brick_stairs', facing='north', half='bottom', shape='straight')
        # Dormer projects from the front slope, centered over the doorway.
        self.fill((15, 9, 18, 22, 9, 23), 'spruce_planks')
        for x in range(15, 23):
            h = 12 + min(x - 14, 23 - x)
            self.fill((x, 10, 23, x, h - 1, 23), 'oak_planks')
            if x in (15, 22):
                self.fill((x, 10, 18, x, h - 1, 23), 'oak_log', axis='y')
        for x in range(14, 24):
            h = 12 + min(x - 14, 23 - x)
            for z in range(18, 25):
                if h < self.roof_height(z):
                    continue
                self.fill((x, h - 1, z, x, h, z), 'red_nether_bricks')
                self.p(x, h + 1, z, 'red_nether_brick_stairs', facing='east' if x < 19 else 'west', half='bottom', shape='straight')
            self.p(x, h, 24, 'smooth_sandstone')
            self.p(x, h + 1, 24, 'smooth_sandstone_stairs', facing='east' if x < 19 else 'west', half='bottom', shape='straight')
        # Carve only the inner dormer union, preserving its two-block roof backing.
        for x in range(16, 22):
            h = 12 + min(x - 14, 23 - x)
            self.fill((x, 10, 18, x, h - 2, 22), 'air')
        self.fill((17, 11, 23, 20, 13, 23), 'gray_stained_glass')
        self.fill((16, 10, 23, 21, 10, 23), 'stripped_oak_log', axis='x')
        for x in (15, 22):
            self.fill((x, 8, 23, x, 12, 23), 'oak_log', axis='y')
        # Decorative cupola is solid-cored, glazed, and lit, with a layered pyramid cap.
        self.fill((16, 17, 12, 21, 18, 17), 'spruce_planks')
        self.fill((16, 19, 12, 21, 20, 17), 'gray_stained_glass')
        self.fill((17, 19, 13, 20, 20, 16), 'glowstone')
        for x in (16, 21):
            for z in (12, 17):
                self.fill((x, 18, z, x, 20, z), 'oak_log', axis='y')
        self.fill((15, 21, 11, 22, 21, 18), 'smooth_sandstone')
        for x in range(15, 23):
            for z in range(11, 19):
                if x in (15, 22) or z in (11, 18):
                    self.p(x, 22, z, 'smooth_sandstone_slab', type='bottom')
        for y, inset in [(22, 0), (23, 1), (24, 2)]:
            self.fill((16 + inset, y, 12 + inset, 21 - inset, y, 17 - inset), 'red_nether_bricks')
        self.fill((18, 25, 14, 19, 25, 15), 'red_nether_brick_slab', type='bottom')

    def furnishings(self) -> None:
        """Furnish both accessible floors with farm work, eating, and sleeping areas."""
        # Hearth opens south. Stone cheeks and a hood carry the exterior flue.
        self.mix((25, 3, 18, 27, 3, 20), ['stone_bricks', 'andesite'])
        self.fill((25, 4, 18, 27, 6, 18), 'stone_bricks')
        for x in (25, 27):
            self.fill((x, 4, 19, x, 6, 20), 'stone_bricks')
        self.fill((25, 7, 18, 27, 7, 20), 'stone_bricks')
        self.p(26, 4, 19, 'campfire', facing='south', lit='true', signal_fire='false')
        self.p(26, 4, 20, 'iron_bars', east='true', west='true', north='false', south='false')
        for y in range(8, 22):
            self.p(26, y, 19, self.rng.choice(['andesite', 'stone_bricks', 'cobblestone']))
        self.p(26, 21, 19, 'chiseled_stone_bricks')
        self.p(26, 22, 19, 'campfire', facing='east', lit='true', signal_fire='false')
        # Six-seat farmhouse table with grounded legs and a sturdy tabletop.
        for x in (17, 20):
            for z in (14, 16):
                self.p(x, 4, z, 'spruce_fence')
        self.fill((17, 5, 14, 20, 5, 16), 'oak_slab', type='top')
        for x, face in [(16, 'east'), (21, 'west')]:
            for z in (14, 16):
                self.p(x, 4, z, 'spruce_stairs', facing=face, half='bottom', shape='straight')
        self.p(18, 4, 13, 'spruce_stairs', facing='south', half='bottom', shape='straight')
        self.p(19, 4, 17, 'spruce_stairs', facing='north', half='bottom', shape='straight')
        self.p(18, 6, 15, 'candle', candles='3', lit='true')
        self.p(20, 6, 15, 'flower_pot')
        for x in range(17, 21):
            for z in range(18, 21):
                self.p(x, 4, z, 'red_carpet' if (x + z) % 3 else 'brown_carpet')
        # Harvest stores, tools, and a kitchen/work bench around the open room.
        for x, z in [(10, 19), (10, 20), (12, 20), (13, 20), (10, 9), (10, 10), (24, 9)]:
            self.p(x, 4, z, 'barrel', facing='up', open='false')
        self.p(10, 5, 20, 'barrel', facing='south', open='false')
        self.fill((10, 4, 17, 11, 4, 18), 'hay_block', axis='y')
        self.p(10, 5, 18, 'hay_block', axis='x')
        self.p(11, 4, 8, 'crafting_table')
        self.p(12, 4, 8, 'smoker', facing='south', lit='false')
        self.p(13, 4, 8, 'chest', facing='south', type='single')
        self.p(14, 4, 8, 'furnace', facing='south', lit='true')
        self.p(15, 4, 8, 'water_cauldron', level='3')
        self.fill((13, 7, 8, 15, 7, 8), 'spruce_slab', type='top')
        self.p(13, 8, 8, 'flower_pot')
        for x, z in [(13, 20), (24, 9)]:
            self.p(x, 5, z, 'lantern', hanging='false')
        for x, z in [(12, 11), (22, 13), (15, 17)]:
            self.p(x, 8, z, 'chain', axis='y')
            self.p(x, 7, z, 'lantern', hanging='true')
        # Ladder has continuous wall backing and a two-block clear landing.
        self.p(10, 9, 14, 'air')
        for y in range(4, 11):
            self.p(10, y, 14, 'ladder', facing='east')
        for z in (13, 15):
            self.p(11, 10, z, 'spruce_fence')
        for x in (12, 13, 24):
            for z, part in [(13, 'foot'), (12, 'head')]:
                self.p(x, 10, z, 'red_bed', facing='north', part=part, occupied='false')
        for x in range(14, 17):
            for z in range(12, 16):
                self.p(x, 10, z, 'white_carpet' if (x + z) % 4 else 'red_carpet')
        self.p(11, 10, 10, 'chest', facing='south', type='single')
        self.p(11, 10, 17, 'barrel', facing='up', open='false')
        self.p(11, 11, 17, 'lantern', hanging='false')
        self.p(23, 10, 12, 'barrel', facing='up', open='false')
        self.p(23, 11, 12, 'lantern', hanging='false')
        self.p(25, 10, 17, 'barrel', facing='up', open='false')
        self.p(25, 11, 17, 'lantern', hanging='false')
        self.fill((10, 10, 11, 10, 11, 12), 'bookshelf')
        self.p(10, 12, 11, 'lantern', hanging='false')
        self.fill((18, 10, 22, 19, 10, 22), 'oak_planks')
        self.p(18, 10, 20, 'spruce_stairs', facing='south', half='bottom', shape='straight')
        self.p(19, 11, 22, 'candle', candles='2', lit='true')
        self.p(18, 11, 22, 'flower_pot')
        self.p(20, 10, 20, 'barrel', facing='up', open='false')
        self.p(20, 11, 20, 'lantern', hanging='false')
        self.p(17, 10, 10, 'barrel', facing='up', open='false')
        self.p(17, 11, 10, 'lantern', hanging='false')
        # Two sconces flank the doorway without occupying its passage.
        for x in (16, 21):
            self.p(x, 5, 23, 'spruce_slab', type='top')
            self.p(x, 6, 23, 'lantern', hanging='false')

    def well(self) -> None:
        """Place a stone water well beneath an open timber shelter."""
        self.mix((3, 2, 27, 9, 2, 33), ['cobblestone'] * 4 + ['andesite', 'mossy_cobblestone'])
        self.mix((4, 3, 28, 8, 4, 32), ['stone_bricks'] * 3 + ['cobblestone', 'mossy_stone_bricks'])
        self.fill((5, 3, 29, 7, 4, 31), 'water', level='0')
        for x in range(4, 9):
            for z in range(28, 33):
                if x in (4, 8) or z in (28, 32):
                    self.p(x, 5, z, 'stone_brick_slab', type='bottom')
        for x in (3, 9):
            for z in (27, 33):
                self.p(x, 3, z, 'cobblestone')
                self.fill((x, 4, z, x, 8, z), 'spruce_fence')
                self.p(x, 9, z, 'oak_log', axis='y')
        for z in (27, 33):
            self.fill((3, 8, z, 9, 8, z), 'spruce_log', axis='x')
        self.fill((3, 8, 27, 3, 8, 33), 'spruce_log', axis='z')
        self.fill((9, 8, 27, 9, 8, 33), 'spruce_log', axis='z')
        self.mix((2, 9, 26, 10, 9, 34), ['oak_planks'] * 5 + ['spruce_planks'])
        self.fill((3, 10, 27, 9, 10, 33), 'oak_slab', type='bottom')
        for x in (2, 10):
            for z in (26, 34):
                self.p(x, 10, z, 'spruce_fence')
        self.fill((3, 7, 30, 9, 7, 30), 'oak_log', axis='x')
        self.p(6, 6, 30, 'chain', axis='y')
        self.p(6, 5, 30, 'lantern', hanging='true')
        self.p(10, 3, 32, 'water_cauldron', level='2')

    def field_and_mill(self) -> None:
        """Build the irrigated wheat patch, harvest yard, and four-armed mill."""
        for x in range(26, 38):
            for z in range(28, 38):
                if x in (26, 37) or z in (28, 37):
                    if (x, z) not in [(26, 31), (26, 32)]:
                        self.p(x, 3, z, self.rng.choice(['cobblestone', 'andesite', 'stone_bricks']))
                else:
                    self.p(x, 2, z, 'farmland', moisture='7')
                    self.p(x, 3, z, 'wheat', age='7' if self.rng.random() < .86 else '5')
        for z in range(29, 37):
            self.p(31, 2, z, 'water', level='0')
            self.p(31, 3, z, 'air')
        for z in (30, 35):
            self.fill((30, 3, z, 32, 3, z), 'oak_slab', type='bottom')
        self.p(36, 2, 33, 'water', level='0')
        self.p(36, 3, 33, 'oak_slab', type='bottom')
        for x, z in [(26, 28), (37, 28), (37, 37), (26, 37)]:
            self.p(x, 4, z, 'cobblestone_slab', type='bottom')
            self.p(x + (-1 if x == 26 else 1), 3, z, 'andesite_stairs', facing='east' if x == 26 else 'west', half='bottom', shape='straight')
        # Paddock fence and logs, with gateways to mill and crop bed.
        for z in range(20, 38):
            self.p(39, 3, z, 'spruce_fence')
        for x in range(32, 40):
            self.p(x, 3, 20, 'spruce_fence')
        for x, z in [(32, 20), (39, 20), (39, 27), (39, 37)]:
            self.fill((x, 3, z, x, 4, z), 'oak_log', axis='y')
            self.p(x, 5, z, 'spruce_slab', type='bottom')
        self.p(35, 3, 20, 'spruce_fence_gate', facing='south', open='true', in_wall='false')
        self.p(36, 3, 20, 'spruce_fence_gate', facing='south', open='true', in_wall='false')
        self.fill((30, 3, 23, 32, 3, 25), 'hay_block', axis='y')
        self.fill((31, 4, 23, 32, 4, 24), 'hay_block', axis='x')
        self.p(32, 5, 23, 'hay_block', axis='z')
        self.p(30, 3, 26, 'barrel', facing='up', open='false')
        self.p(30, 4, 26, 'lantern', hanging='false')
        # Solid tapering timber tower stands five blocks clear of the house wall.
        self.mix((34, 3, 10, 37, 3, 13), ['stone_bricks', 'cobblestone'])
        self.fill((34, 4, 10, 37, 7, 13), 'spruce_planks')
        for x in (34, 37):
            for z in (10, 13):
                self.fill((x, 4, z, x, 9, z), 'oak_log', axis='y')
        self.fill((34, 8, 10, 37, 8, 13), 'oak_log', axis='x')
        self.fill((35, 9, 11, 36, 14, 12), 'stripped_spruce_log', axis='y')
        self.fill((34, 15, 10, 37, 15, 13), 'spruce_slab', type='bottom')
        self.fill((35, 13, 12, 35, 13, 15), 'oak_log', axis='z')
        # Four offset lattice paddles create a clearly readable pinwheel silhouette.
        for d in range(-5, 6):
            if d:
                self.p(35 + d, 13, 15, 'stripped_oak_log', axis='x')
                self.p(35, 13 + d, 15, 'stripped_oak_log', axis='y')
        for d in range(3, 6):
            for w in (1, 2):
                for x, y in [(35 + w, 13 + d), (35 + d, 13 - w), (35 - w, 13 - d), (35 - d, 13 + w)]:
                    self.p(x, y, 15, 'oak_fence')
        self.p(35, 13, 16, 'stripped_oak_log', axis='z')
        self.p(35, 4, 14, 'grindstone', face='floor', facing='south')
        self.p(35, 3, 14, 'cobblestone')
        self.p(36, 3, 15, 'barrel', facing='up', open='false')
        self.p(36, 4, 15, 'lantern', hanging='false')

    def greenery(self) -> None:
        """Scatter supported meadow plants, flower patches, and low berry hedges."""
        patches = [(3, 4), (5, 18), (3, 36), (12, 35), (28, 39), (39, 5), (38, 24), (22, 4)]
        for cx, cz in patches:
            for dx, dz in [(-1, 0), (0, 0), (1, 0), (0, 1), (1, 1)]:
                x, z = cx + dx, cz + dz
                if self.get(x, 3, z) in (None, 'minecraft:air') and self.get(x, 2, z) == 'minecraft:grass_block':
                    self.p(x, 3, z, 'oak_leaves', persistent='true', distance='1')
            for dx, dz in [(-1, 1), (0, -1), (2, 0), (0, 2)]:
                x, z = cx + dx, cz + dz
                if self.get(x, 3, z) in (None, 'minecraft:air') and self.get(x, 2, z) == 'minecraft:grass_block':
                    self.p(x, 3, z, self.rng.choice(['poppy', 'oxeye_daisy', 'cornflower', 'dandelion']))
        for x in range(2, 40):
            for z in range(2, 40):
                if self.get(x, 2, z) != 'minecraft:grass_block' or self.get(x, 3, z) not in (None, 'minecraft:air'):
                    continue
                # Exclude circulation strips around every built feature.
                if (7 <= x <= 30 and 5 <= z <= 25) or (1 <= x <= 11 and 25 <= z <= 35) or (32 <= x <= 38 and 9 <= z <= 21):
                    continue
                t = self.rng.random()
                if t < .18:
                    self.p(x, 3, z, 'short_grass')
                elif t < .225 and self.get(x, 4, z) is None:
                    self.p(x, 3, z, 'tall_grass', half='lower')
                    self.p(x, 4, z, 'tall_grass', half='upper')
                elif t < .245:
                    self.p(x, 3, z, self.rng.choice(['poppy', 'oxeye_daisy', 'cornflower']))
        for x, z in [(5, 7), (4, 20), (23, 36), (38, 7)]:
            if self.get(x, 3, z) in (None, 'minecraft:air', 'minecraft:short_grass'):
                self.p(x, 3, z, 'mossy_cobblestone')

    def connect_fences(self) -> None:
        """Resolve all lattice and railing connections explicitly for schematic rendering."""
        for (x, y, z), state in list(self.placed.items()):
            if state.split('[')[0] not in ('minecraft:spruce_fence', 'minecraft:oak_fence'):
                continue
            properties = {}
            for face, dx, dz in [('east', 1, 0), ('west', -1, 0), ('south', 0, 1), ('north', 0, -1)]:
                neighbor = self.get(x + dx, y, z + dz) or 'minecraft:air'
                name = neighbor.split('[')[0]
                connect = any(token in name for token in ('_fence', '_log', '_planks', 'stone_bricks', 'cobblestone'))
                properties[face] = str(connect).lower()
            self.put(x, y, z, state.split('[')[0], **properties)

    def build(self) -> 'Build':
        """Assemble the reference farmstead with deterministic detail and usable interiors."""
        self.terrain()
        self.house()
        self.furnishings()
        self.well()
        self.field_and_mill()
        self.greenery()
        self.connect_fences()
        return self

    def export(self, path: Path) -> Path:
        """Write the schematic so that two runs produce identical bytes.

        Args:
            path (Path): Destination .litematic path.

        Returns:
            Path: The path written.
        """
        schematic = self.canvas.to_litematica(
            name=CONFIG["name"],
            author=CONFIG["author"],
            description=CONFIG["description"],
            minecraft_data_version=CONFIG["data_version"],
        )
        # The metadata otherwise records the current wall clock, which changes the bytes on every run.
        schematic.metadata.time_created = 0
        schematic.metadata.time_modified = 0
        path.parent.mkdir(parents=True, exist_ok=True)
        schematic.save(path)
        # Java Litematica consumes compressed NBT. Pin gzip time as well.
        path.write_bytes(gzip.compress(path.read_bytes(), mtime=0))
        return path

    def verify(self, path: Path) -> Dict[str, object]:
        """Reload the written file and confirm it matches what was placed.

        Args:
            path (Path): Schematic to reload.

        Returns:
            Dict[str, object]: Size, block count and palette size of the reloaded file.

        Raises:
            AssertionError: When a reloaded cell differs from what was placed.
        """
        loaded = load_schematic(path)
        size_y, size_z, size_x = loaded.size_yzx
        assert (size_x, size_y, size_z) == (self.size_x, self.size_y, self.size_z), "size changed on reload"
        ids = loaded.read_flat(0, loaded.volume).reshape(loaded.size_yzx)
        for (x, y, z), state in self.placed.items():
            assert loaded.palette[int(ids[y, z, x])] == state, f"cell (x={x}, y={y}, z={z}) changed on reload"
        return {
            "size_xyz": [size_x, size_y, size_z],
            "placed_blocks": len(self.placed),
            "palette_states": len(loaded.palette),
        }


def main() -> None:
    """Generate the build, export it, and verify the exported file."""
    parser = argparse.ArgumentParser(description="Generate a Minecraft schematic.")
    parser.add_argument("--output", type=Path, default=Path(CONFIG["output"]))
    parser.add_argument("--seed", type=int, default=CONFIG["seed"])
    args = parser.parse_args()

    build = Build(seed=args.seed).build()
    path = build.export(args.output)
    report = build.verify(path)
    print(f"wrote {path}: {report}")


if __name__ == "__main__":
    main()
