# Generate the furnished Lanternpine Inn, its terraces and surrounding conifer garden.
# Bounding box: X=0..54, Y=0..56, Z=0..46 (55 x 57 x 47 blocks).
# Reference reading: three timber-and-plaster storeys, one cathedral attic, concave steep
# charcoal tile roof, paired dormers, amber glazing, gold banners, ivy and stone approach.
# Run with the bundled Python in the generator container; no external assets or downloads.
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": (55, 57, 47), # width X, height Y, depth Z
    "minecraft_version": "1.21.1",
    "data_version": 3955,
    "output": "output.litematic",
    "name": "Lanternpine Inn",
    "author": "generator",
    "description": "Furnished timber inn with four connected levels, sweeping tiled roof, dormers and spruce garden",
}

FLOORS = (6, 12, 18, 24)
ROOF_PROFILE = (49, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 26, 25, 24, 24, 23, 23, 23, 23)
STONE = ('stone_bricks', 'stone_bricks', 'stone_bricks', 'andesite', 'cobblestone', 'mossy_stone_bricks')
PLASTER = ('smooth_sandstone', 'smooth_sandstone', 'smooth_sandstone', 'sandstone', 'stripped_birch_log')
WOOD_FLOOR = ('spruce_planks', 'spruce_planks', 'spruce_planks', 'dark_oak_planks')
ROOF = ('deepslate_tiles', 'deepslate_tiles', 'deepslate_tiles', 'deepslate_bricks', 'cobbled_deepslate')
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 vanilla block using its short name.

        Args:
            x (int): X coordinate.
            y (int): Y coordinate.
            z (int): Z coordinate.
            block (str): Vanilla block name.
            **properties (str): Blockstate properties.
        """
        self.put(x, y, z, 'minecraft:' + block, **properties)

    def fill(self, bounds: tuple, block: str, **properties: str) -> None:
        """Fill a box with a vanilla block.

        Args:
            bounds (tuple): Inclusive XYZ bounds.
            block (str): Short block name.
            **properties (str): Blockstate properties.
        """
        self.box(bounds, 'minecraft:' + block, **properties)

    def mix(self, bounds: tuple, palette: tuple) -> None:
        """Apply a seeded material palette to a box.

        Args:
            bounds (tuple): Inclusive XYZ bounds.
            palette (tuple): Weighted short block names.
        """
        self.texture(bounds, ['minecraft:' + b for b in palette])

    def stair(self, x: int, y: int, z: int, facing: str, block: str = 'spruce_stairs', half: str = 'bottom') -> None:
        """Place a stair with explicit geometry.

        Args:
            x (int): X coordinate.
            y (int): Y coordinate.
            z (int): Z coordinate.
            facing (str): Direction of ascent.
            block (str): Stair material.
            half (str): Bottom or top half.
        """
        self.p(x, y, z, block, facing=facing, half=half, shape='straight', waterlogged='false')

    def door(self, x: int, y: int, z: int, facing: str, hinge: str = 'left') -> None:
        """Install both halves of an operable wooden door.

        Args:
            x (int): X coordinate.
            y (int): Lower half height.
            z (int): Z coordinate.
            facing (str): Door direction.
            hinge (str): Hinge side.
        """
        for dy, half in enumerate(('lower', 'upper')):
            self.p(x, y + dy, z, 'spruce_door', facing=facing, hinge=hinge, half=half, open='false', powered='false')

    def bed(self, x: int, y: int, z: int, color: str = 'red') -> None:
        """Install a complete south-facing bed.

        Args:
            x (int): Bed X coordinate.
            y (int): Height above floor.
            z (int): Foot Z coordinate.
            color (str): Wool color.
        """
        self.p(x, y, z, color + '_bed', part='foot', facing='south', occupied='false')
        self.p(x, y, z + 1, color + '_bed', part='head', facing='south', occupied='false')

    def lamp(self, x: int, y: int, z: int) -> None:
        """Place a lantern on a bracket with a solid pedestal.

        Args:
            x (int): X coordinate.
            y (int): Lantern height.
            z (int): Z coordinate.
        """
        self.p(x, y - 1, z, 'dark_oak_planks')
        yy = y - 2
        while yy > 0 and self.get(x, yy, z) in (None, 'minecraft:air'):
            self.p(x, yy, z, 'spruce_fence', north='false', south='false', east='false', west='false', waterlogged='false')
            yy -= 1
        self.p(x, y, z, 'lantern', hanging='false', waterlogged='false')

    def build_terrain(self) -> None:
        """Build the irregular planted island, stone terrace and supported approach."""
        for x in range(1, 54):
            for z in range(1, 46):
                distance = ((x - 27) / 27)**2 + ((z - 24) / 24)**2
                if distance > 1.02 + self.rng.uniform(-0.045, 0.045):
                    continue
                h = 1 + int(z > 8) + int(z > 16)
                if 7 <= x <= 47 and 10 <= z <= 41:
                    h = 5
                self.ground[(x, z)] = h
                self.mix((x, 0, z, x, max(0, h - 2), z), ('stone', 'stone', 'andesite', 'cobblestone'))
                if h > 1:
                    self.fill((x, h - 1, z, x, h - 1, z), 'dirt')
                self.p(x, h, z, self.rng.choice(('grass_block', 'grass_block', 'moss_block', 'coarse_dirt')))
        self.mix((7, 5, 10, 47, 6, 41), STONE)
        # Deep foundation joins all wall feet to the terrace and terrain.
        self.mix((11, 0, 16, 43, 5, 38), STONE)
        self.mix((20, 0, 14, 34, 5, 15), STONE)
        for x in range(7, 48):
            if 22 <= x <= 32:
                continue
            self.mix((x, 2, 10, x, 6, 10), STONE)
        for x in (7, 47):
            self.mix((x, 2, 10, x, 6, 41), STONE)
        for step in range(6):
            z, y = 4 + step, 1 + step
            self.mix((22, 0, z, 32, y - 1, z), STONE)
            for x in range(22, 33):
                self.stair(x, y, z, 'south', self.rng.choice(('stone_brick_stairs', 'stone_brick_stairs', 'andesite_stairs')))
                self.approach.append((x, y, z))
            for x in (21, 33):
                self.mix((x, 0, z, x, y, z), STONE)
                self.p(x, y + 1, z, 'stone_brick_slab', type='bottom', waterlogged='false')
        self.mix((22, 6, 10, 32, 6, 14), ('stone_bricks', 'andesite', 'polished_andesite', 'mossy_stone_bricks'))
        for x in range(20, 35):
            for z in (2, 3):
                if (x, z) in self.ground:
                    self.p(x, 1, z, self.rng.choice(('dirt_path', 'gravel', 'coarse_dirt', 'andesite')))
                    self.ground[(x, z)] = 1
        for x in list(range(8, 21)) + list(range(34, 47)):
            self.p(x, 7, 10, 'cobblestone_wall', up='true', east='low', west='low', north='none', south='none', waterlogged='false')
        for x in (8, 14, 20, 34, 40, 46):
            self.mix((x, 6, 10, x, 8, 10), STONE)
            self.p(x, 9, 10, 'lantern', hanging='false', waterlogged='false')
        for x in (8, 46):
            for z in range(11, 41):
                if z % 6 == 0:
                    self.p(x, 7, z, 'stone_bricks')
                    self.p(x, 8, z, 'lantern', hanging='false', waterlogged='false')
                else:
                    self.p(x, 7, z, 'spruce_fence', north='true', south='true', east='false', west='false', waterlogged='false')
        for x, z, base in ((21, 4, 2), (33, 4, 2), (21, 7, 5), (33, 7, 5)):
            self.p(x, base, z, 'stone_bricks')
            self.p(x, base + 1, z, 'lantern', hanging='false', waterlogged='false')

    def roof_height(self, x: int) -> int:
        """Return the curved main-roof profile.

        Args:
            x (int): X coordinate.

        Returns:
            int: Roof surface height.
        """
        return ROOF_PROFILE[abs(x - 27)]

    def front_z(self, x: int) -> int:
        """Return the projecting front gable wall plane.

        Args:
            x (int): X coordinate.

        Returns:
            int: Front wall Z coordinate.
        """
        return 14 if 20 <= x <= 34 else 16

    def build_shell(self) -> None:
        """Raise the three timber storeys, gable walls and watertight tiled roof."""
        for f in FLOORS:
            self.mix((11, f, 16, 43, f, 38), WOOD_FLOOR)
            self.mix((20, f, 14, 34, f, 15), WOOD_FLOOR)
        for y in range(7, 25):
            for x in range(11, 44):
                self.p(x, y, self.front_z(x), self.rng.choice(PLASTER))
                self.p(x, y, 38, self.rng.choice(PLASTER))
            for z in range(16, 39):
                for x in (11, 43):
                    self.p(x, y, z, self.rng.choice(PLASTER))
            for x in (20, 34):
                self.fill((x, y, 14, x, y, 16), 'stripped_spruce_log', axis='y')
        for x in (11, 17, 20, 27, 34, 37, 43):
            self.fill((x, 6, self.front_z(x), x, 24, self.front_z(x)), 'spruce_log', axis='y')
            self.fill((x, 6, 38, x, 24, 38), 'spruce_log', axis='y')
        for z in (16, 22, 28, 33, 38):
            for x in (11, 43):
                self.fill((x, 6, z, x, 24, z), 'spruce_log', axis='y')
        for y in FLOORS:
            for x in range(11, 44):
                self.p(x, y, self.front_z(x), 'dark_oak_log', axis='x')
                self.p(x, y, 38, 'dark_oak_log', axis='x')
            for x in (11, 43):
                self.fill((x, y, 16, x, y, 38), 'dark_oak_log', axis='z')
        # Main gable infill follows the same concave profile as the roof.
        for x in range(11, 44):
            for z in (self.front_z(x), 38):
                self.mix((x, 25, z, x, max(25, self.roof_height(x)), z), PLASTER)
                if x % 3 == 0 or x == 27:
                    self.fill((x, 25, z, x, self.roof_height(x), z), 'stripped_spruce_log', axis='y')
        for x in (20, 34):
            self.fill((x, 25, 15, x, self.roof_height(x), 15), 'stripped_spruce_log', axis='y')
        for x in range(7, 48):
            outer = x - 1 if x < 27 else x + 1
            low = self.roof_height(outer) - 1 if 7 <= outer <= 47 else self.roof_height(x) - 1
            high = self.roof_height(x)
            z0 = self.front_z(x) - 2
            for z in range(z0, 41):
                self.mix((x, low, z, x, high, z), ROOF)
                if x != 27:
                    self.stair(x, high + 1, z, 'east' if x < 27 else 'west', self.rng.choice(('deepslate_tile_stairs',) * 5 + ('deepslate_brick_stairs',)))
                else:
                    self.p(x, high + 1, z, 'deepslate_tile_slab', type='bottom', waterlogged='false')
            # Thick dark verge and a warm rafter immediately beneath it.
            self.fill((x, low - 1, z0 + 1, x, high - 1, z0 + 1), 'spruce_log', axis='z')
            self.p(x, high, z0, 'cobbled_deepslate')
            if x % 3 == 0:
                self.p(x, high + 1, z0, 'deepslate_brick_slab', type='bottom', waterlogged='false')
        # Ridge crest, brass-colored finials and repeated rafter ties.
        for z in range(12, 41):
            self.p(27, 50, z, 'spruce_slab', type='bottom', waterlogged='false')
        for z in (12, 22, 32, 40):
            self.fill((27, 50, z, 27, 52, z), 'spruce_fence', north='false', south='false', east='false', west='false', waterlogged='false')
            self.p(27, 53, z, 'cut_copper_slab', type='bottom', waterlogged='false')
        for z in (19, 28, 36):
            self.fill((21, 35, z, 33, 35, z), 'spruce_log', axis='x')
            for x in (22, 32):
                self.fill((x, 25, z, x, 34, z), 'stripped_spruce_log', axis='y')
            self.fill((27, 29, z, 27, 34, z), 'chain', axis='y', waterlogged='false')
            self.p(27, 28, z, 'lantern', hanging='true', waterlogged='false')
        # Corbels beneath the projecting beams.
        for y in (11, 17, 23):
            for x in (11, 17, 20, 34, 37, 43):
                z = self.front_z(x) - 1
                self.stair(x, y, z, 'south', 'spruce_stairs', 'top')
            for z in (17, 22, 28, 33, 37):
                self.stair(44, y, z, 'west', 'spruce_stairs', 'top')
                self.stair(10, y, z, 'east', 'spruce_stairs', 'top')

    def window(self, x: int, y: int, z: int, width: int, height: int, facing: str) -> None:
        """Glaze an opening, then attach frame, shutters, sill and internal light.

        Args:
            x (int): First opening X coordinate.
            y (int): Sill-adjacent glass height.
            z (int): First opening Z coordinate.
            width (int): Opening width.
            height (int): Opening height.
            facing (str): Outward direction.
        """
        dx, dz = (1, 0) if facing in ('north', 'south') else (0, 1)
        ox, oz = {'north': (0, -1), 'south': (0, 1), 'east': (1, 0), 'west': (-1, 0)}[facing]
        for i in range(width):
            xx, zz = x + dx * i, z + dz * i
            for yy in range(y, y + height):
                self.p(xx, yy, zz, 'yellow_stained_glass')
                self.windows.append((xx, yy, zz, facing))
            self.p(xx, y - 1, zz, 'glowstone')
            self.p(xx + ox, y - 1, zz + oz, 'spruce_planks')
            self.p(xx + ox, y + height, zz + oz, 'dark_oak_planks')
        for i in (-1, width):
            xx, zz = x + dx * i, z + dz * i
            self.fill((xx, y - 1, zz, xx, y + height, zz), 'stripped_spruce_log', axis='y')
            for yy in range(y, y + height):
                self.p(xx + ox, yy, zz + oz, 'spruce_trapdoor', facing=facing, half='bottom', open='true', powered='false', waterlogged='false')
        # Fine center mullions are outside the glass, leaving its inner face clear.
        if width >= 3:
            for yy in range(y, y + height):
                self.p(x + dx * (width // 2) + ox, yy, z + dz * (width // 2) + oz, 'spruce_fence', north='false', south='false', east='false', west='false', waterlogged='false')

    def build_windows(self) -> None:
        """Install amber leaded windows on all four elevations."""
        for floor in FLOORS[:3]:
            for x in (13, 39):
                self.window(x, floor + 2, 16, 3, 3, 'north')
            if floor > 6:
                for x in (23, 29):
                    self.window(x, floor + 2, 14, 2, 3, 'north')
            for z in (18, 24, 30, 35):
                for x, facing in ((11, 'west'), (43, 'east')):
                    self.window(x, floor + 2, z, 2, 3, facing)
            for x in (13, 20, 27, 35, 40):
                self.window(x, floor + 2, 38, 2, 3, 'south')
        self.window(25, 27, 14, 5, 10, 'north')
        self.window(26, 39, 14, 3, 3, 'north')
        self.window(25, 28, 38, 5, 5, 'south')
        # Arched stone entrance, with a two-leaf wooden door and glazed fanlight.
        for x in (24, 29):
            self.fill((x, 7, 13, x, 10, 14), 'stone_bricks')
        self.fill((24, 11, 13, 29, 11, 14), 'stone_bricks')
        for x in (25, 26, 27, 28):
            self.fill((x, 7, 14, x, 10, 14), 'spruce_planks')
        for x, hinge in ((26, 'left'), (27, 'right')):
            self.door(x, 7, 14, 'north', hinge)
        self.fill((25, 9, 14, 28, 10, 14), 'yellow_stained_glass')
        self.stair(25, 10, 13, 'east', 'stone_brick_stairs', 'top')
        self.stair(28, 10, 13, 'west', 'stone_brick_stairs', 'top')
        for x in (22, 31):
            self.lamp(x, 9, 13)
        self.fill((24, 12, 12, 29, 12, 13), 'spruce_slab', type='bottom', waterlogged='false')

    def build_dormers(self) -> None:
        """Add two lit gabled dormers merged into the lower roof shoulders."""
        for cx in (14, 40):
            for x in range(cx - 3, cx + 4):
                top = 33 - abs(x - cx)
                self.fill((x, 24, 13, x, top, 23), 'spruce_planks')
                # Front and rear close each dormer, open its inner side into the attic.
                self.fill((x, 25, 14, x, top - 1, 22), 'air')
                self.fill((x, 25, 13, x, top - 1, 13), 'stripped_spruce_log', axis='y')
                self.mix((x, top, 12, x, top, 24), ROOF)
                for z in range(12, 25):
                    if x != cx:
                        self.stair(x, top + 1, z, 'east' if x < cx else 'west', 'deepslate_tile_stairs')
                    else:
                        self.p(x, top + 1, z, 'deepslate_tile_slab', type='bottom', waterlogged='false')
            for x in (cx - 3, cx + 3):
                self.fill((x, 25, 14, x, 29, 23), 'spruce_planks')
            inner = cx + 3 if cx < 27 else cx - 3
            self.fill((inner, 25, 18, inner, 28, 21), 'air')
            # Continue the portal through the old slope to the main attic.
            step = 1 if cx < 27 else -1
            for x in range(inner, inner + step * 3, step):
                self.fill((x, 25, 18, x, 28, 21), 'air')
                self.fill((x, 24, 18, x, 24, 21), 'spruce_planks')
            self.window(cx - 1, 26, 13, 3, 3, 'north')
            self.p(cx, 31, 12, 'stripped_oak_log', axis='y')
            self.fill((cx - 2, 25, 23, cx + 2, 28, 23), 'spruce_planks')
            self.p(cx - 1, 25, 21, 'barrel', facing='up', open='false')
            self.lamp(cx + 1, 26, 21)
            self.rooms.append((f'Attic dormer {cx}', (cx, 25, 18), (cx - 2, 25, 14, cx + 2, 29, 22)))

    def build_chimney(self) -> None:
        """Raise the kitchen chimney through the visible western roof shoulder."""
        self.mix((15, 7, 31, 17, 38, 33), ('stone_bricks', 'stone_bricks', 'andesite', 'cobblestone'))
        for y in (30, 34, 38):
            self.fill((14, y, 30, 18, y, 34), 'stone_bricks')
        self.p(16, 39, 32, 'campfire', facing='north', lit='true', signal_fire='true', waterlogged='false')
        for x in (15, 17):
            for z in (31, 33):
                self.fill((x, 39, z, x, 40, z), 'cobblestone_wall', up='true', east='none', west='none', north='none', south='none', waterlogged='false')
        self.fill((14, 41, 30, 18, 41, 34), 'stone_brick_slab', type='bottom', waterlogged='false')

    def room(self, name: str, point: tuple, bounds: tuple) -> None:
        """Register a furnished room and its reachable standing point.

        Args:
            name (str): Room purpose.
            point (tuple): XYZ standing position.
            bounds (tuple): Interior bounds.
        """
        self.rooms.append((name, point, bounds))

    def table(self, x: int, floor: int, z: int, length: int = 3) -> None:
        """Build a solid trestle table, facing benches and a candle.

        Args:
            x (int): Table X coordinate.
            floor (int): Floor height.
            z (int): First Z coordinate.
            length (int): Table length.
        """
        for zz in range(z, z + length):
            self.p(x, floor + 1, zz, 'spruce_planks')
            self.stair(x - 2, floor + 1, zz, 'east')
            self.stair(x + 2, floor + 1, zz, 'west')
        self.p(x, floor + 2, z + length // 2, 'candle', candles='3', lit='true', waterlogged='false')

    def build_interior(self) -> None:
        """Furnish a tavern, kitchen, guest rooms, library, studies and attic workshop."""
        # Public ground floor.
        self.table(17, 6, 20, 4)
        self.table(17, 6, 26, 2)
        self.table(36, 6, 21, 4)
        self.fill((24, 7, 18, 30, 7, 24), 'red_carpet')
        for x in range(24, 31):
            self.p(x, 7, 18, 'orange_carpet')
            self.p(x, 7, 24, 'orange_carpet')
        self.fill((12, 7, 29, 22, 11, 29), 'spruce_planks')
        self.door(20, 7, 29, 'north')
        self.fill((13, 7, 33, 13, 8, 35), 'smoker', facing='east', lit='true')
        self.fill((13, 7, 36, 21, 7, 36), 'spruce_planks')
        for x in (14, 16, 21):
            self.p(x, 8, 36, 'barrel', facing='up', open='false')
        self.p(18, 8, 36, 'water_cauldron', level='3')
        self.p(19, 8, 36, 'crafting_table')
        self.fill((15, 7, 30, 18, 7, 30), 'spruce_planks')
        self.p(17, 8, 30, 'cake', bites='0')
        self.fill((34, 7, 29, 41, 7, 29), 'barrel', facing='north', open='false')
        self.fill((34, 8, 29, 41, 8, 29), 'spruce_slab', type='bottom', waterlogged='false')
        for x in (34, 36, 40):
            self.stair(x, 7, 27, 'south')
        self.fill((40, 7, 35, 41, 8, 36), 'barrel', facing='north', open='false')
        self.p(40, 9, 36, 'brewing_stand', has_bottle_0='true', has_bottle_1='true', has_bottle_2='true')
        self.fill((37, 7, 31, 39, 10, 31), 'stone_bricks')
        self.p(38, 7, 30, 'magma_block')
        self.p(38, 8, 30, 'iron_bars', north='false', south='false', east='true', west='true', waterlogged='false')
        self.lamp(13, 10, 28)
        self.lamp(21, 10, 35)
        self.lamp(41, 10, 28)
        self.lamp(33, 10, 35)
        for z in (20, 26):
            self.fill((27, 10, z, 27, 11, z), 'chain', axis='y', waterlogged='false')
            self.p(27, 9, z, 'lantern', hanging='true', waterlogged='false')
        self.room('Taproom and dining hall', (24, 7, 26), (12, 7, 17, 42, 11, 28))
        self.room('Kitchen and pantry', (20, 7, 33), (12, 7, 30, 22, 11, 37))
        self.room('Bar and brewing hearth', (34, 7, 32), (33, 7, 30, 42, 11, 37))
        # Four side rooms per upper storey, opening into a broad central gallery.
        for floor in (12, 18):
            for x in (23, 33):
                self.fill((x, floor + 1, 16, x, floor + 5, 37), 'spruce_planks')
                for z in (22, 32):
                    self.door(x, floor + 1, z, 'west' if x == 23 else 'east')
            for x0, x1 in ((12, 22), (34, 42)):
                self.fill((x0, floor + 1, 27, x1, floor + 5, 27), 'stripped_spruce_log', axis='x')
            self.fill((26, floor + 1, 18, 28, floor + 1, 25), 'red_carpet')
            self.lamp(25, floor + 3, 26)
            self.lamp(31, floor + 3, 36)
            for side, xa, xb in (('West', 12, 22), ('East', 34, 42)):
                for section, za, zb in (('front', 17, 26), ('rear', 28, 37)):
                    is_study = floor == 18 and side == 'West'
                    if is_study:
                        for xx in range(14, 21):
                            self.fill((xx, floor + 1, zb - 1, xx, floor + 3, zb - 1), 'bookshelf')
                        desk_z = za + (1 if section == 'rear' else 3)
                        self.fill((14, floor + 1, desk_z, 16, floor + 1, desk_z), 'spruce_planks')
                        self.p(15, floor + 2, desk_z, 'lectern', facing='south', has_book='false', powered='false')
                        self.stair(18, floor + 1, desk_z, 'west')
                        self.p(19, floor + 1, za + 3, 'cartography_table')
                        purpose = 'Library' if section == 'front' else 'Map study'
                    else:
                        bedx = xa + 2
                        self.bed(bedx, floor + 1, za + 3, 'red' if side == 'West' else 'orange')
                        if section == 'front':
                            self.bed(bedx + 3, floor + 1, za + 3, 'white')
                        self.p(xa + 1, floor + 1, zb - 1, 'chest', facing='north', type='single', waterlogged='false')
                        self.fill((xb - 1, floor + 1, zb - 1, xb - 1, floor + 3, zb - 1), 'barrel', facing='north', open='false')
                        self.p(xa + 2, floor + 1, za + 1, 'spruce_planks')
                        self.stair(xa + 3, floor + 1, za + 1, 'west')
                        purpose = 'Guest bedroom'
                    self.lamp(xb - 1, floor + 3, za + 2)
                    self.room(f'{purpose} {floor} {side} {section}', (xb - 1 if side == 'West' else xa + 1, floor + 1, za + 5), (xa, floor + 1, za, xb, floor + 5, zb))
            self.stair(25, floor + 1, 16, 'south')
            self.stair(30, floor + 1, 16, 'south')
            self.room(f'Guest gallery {floor}', (30, floor + 1, 24), (24, floor + 1, 15, 32, floor + 5, 37))
        # Cathedral attic used as a makers' studio, with storage in the dormers.
        for x, block in ((20, 'loom'), (21, 'cartography_table'), (23, 'fletching_table')):
            self.p(x, 25, 25, block)
        self.fill((30, 25, 22, 33, 25, 24), 'spruce_planks')
        self.p(31, 26, 23, 'candle', candles='4', lit='true', waterlogged='false')
        self.p(32, 26, 24, 'flower_pot')
        self.stair(29, 25, 23, 'east')
        self.fill((24, 25, 36, 29, 27, 36), 'bookshelf')
        self.p(31, 25, 34, 'crafting_table')
        self.p(32, 25, 34, 'anvil', facing='north')
        self.fill((20, 25, 32, 21, 26, 35), 'barrel', facing='east', open='false')
        self.lamp(31, 27, 27)
        self.room('Cathedral attic workshop', (28, 25, 26), (21, 25, 15, 33, 34, 37))

    def build_staircase(self) -> None:
        """Connect all four storeys with three supported flights and open landings."""
        for floor in (12, 18, 24):
            self.fill((26, floor, 29, 28, floor, 34), 'air')
        for floor in (6, 12, 18):
            flight = []
            for step in range(6):
                z, y = 29 + step, floor + 1 + step
                for x in range(26, 29):
                    self.fill((x, y + 1, z, x, y + 3, z), 'air')
                    self.p(x, y - 1, z, 'spruce_planks')
                    self.stair(x, y, z, 'south')
                    self.walk_stairs.append((x, y, z, 'south'))
                flight.append((27, y, z))
            self.flights.append(flight)
        for floor in FLOORS:
            for z in range(29, 35):
                for x in (25, 29):
                    # Rails on the sides of the stair opening, with a clear return aisle.
                    self.p(x, floor + 1, z, 'spruce_fence', north='true', south='true', east='false', west='false', waterlogged='false')
            self.fill((30, floor + 1, 28, 32, floor + 2, 35), 'air')
            self.fill((26, floor + 1, 35, 30, floor + 2, 35), 'air')
            self.fill((26, floor + 1, 28, 30, floor + 2, 28), 'air')

    def build_details(self) -> None:
        """Dress the front with heraldic cloth, gable bracing, planters and ivy."""
        for cx in (19, 35):
            self.fill((cx - 1, 14, 12, cx + 1, 21, 12), 'orange_wool')
            for y in range(14, 22):
                for x in (cx - 1, cx + 1):
                    self.p(x, y, 12, 'yellow_terracotta')
            self.fill((cx, 16, 11, cx, 19, 11), 'yellow_wool')
            self.fill((cx - 1, 18, 11, cx + 1, 18, 11), 'yellow_wool')
            self.p(cx, 20, 11, 'yellow_wool')
            self.fill((cx - 2, 22, 12, cx + 2, 22, 12), 'spruce_log', axis='x')
            self.fill((cx, 22, 13, cx, 22, 16), 'spruce_log', axis='z')
            self.p(cx, 13, 12, 'orange_wool')
        for x in (22, 32):
            self.fill((x, 25, 13, x, 30, 13), 'stripped_oak_log', axis='y')
        for y, spread in ((25, 6), (38, 4), (43, 2)):
            self.fill((27 - spread, y, 13, 27 + spread, y, 13), 'spruce_log', axis='x')
        for side in (-1, 1):
            for k in range(4):
                self.p(27 + side * (6 - k), 26 + k, 13, 'stripped_spruce_log', axis='y')
        # Window boxes kept below the opening, with flowers on actual soil.
        for x0 in (13, 39):
            for floor in (6, 12, 18):
                for x in range(x0, x0 + 3):
                    self.p(x, floor + 1, 15, 'grass_block')
                    self.p(x, floor + 2, 15, 'poppy' if x % 2 else 'fern')
                    self.p(x, floor + 1, 14, 'spruce_trapdoor', facing='north', half='bottom', open='true', powered='false', waterlogged='false')
        # Leaf garlands adhere to exposed timber and avoid glass and doors.
        for x, z, bottom, top in ((20, 13, 7, 21), (34, 13, 7, 23), (11, 15, 7, 19), (43, 15, 7, 22)):
            for y in range(bottom, top + 1):
                if self.get(x, y, z) in (None, 'minecraft:air'):
                    self.p(x, y, z, 'oak_leaves', persistent='true', distance='1', waterlogged='false')
                if y % 3 == 0:
                    xx = x + (-1 if x <= 27 else 1)
                    if self.get(xx, y, z) in (None, 'minecraft:air'):
                        self.p(xx, y, z, 'oak_leaves', persistent='true', distance='1', waterlogged='false')
        for z in (21, 28, 37):
            for y in range(7, 22):
                if self.get(44, y, z) in (None, 'minecraft:air'):
                    self.p(44, y, z, 'vine', west='true', north='false', south='false', east='false', up='false')
        for x, z in ((9, 14), (45, 14), (9, 37), (45, 39)):
            self.p(x, 7, z, 'barrel', facing='up', open='false')
            self.p(x, 8, z, 'potted_azalea_bush')
        for x, z in ((18, 12), (36, 12), (9, 31), (45, 25)):
            if self.get(x, 7, z) in (None, 'minecraft:air'):
                self.p(x, 7, z, 'barrel', facing='up', open='false')
        self.stair(12, 7, 12, 'south')
        self.stair(13, 7, 12, 'south')
        self.stair(40, 7, 12, 'south')
        self.stair(41, 7, 12, 'south')

    def tree(self, x: int, z: int, height: int, radius: int) -> None:
        """Grow a layered spruce with a rooted trunk and attached branches.

        Args:
            x (int): Trunk X coordinate.
            z (int): Trunk Z coordinate.
            height (int): Trunk height.
            radius (int): Maximum canopy radius.
        """
        base = self.ground.get((x, z), 3)
        # Trees just outside the terrace stand on stones and soil, never in air.
        for y in range(base + 1, base + height + 1):
            if self.get(x, y, z) in (None, 'minecraft:air'):
                self.p(x, y, z, 'spruce_log', axis='y')
        for layer in range(4, height, 4):
            r = max(1, round(radius * (1 - layer / (height + 4))))
            for ox, oz in ((1, 0), (-1, 0), (0, 1), (0, -1)):
                for n in range(1, max(1, r - 1)):
                    xx, zz, yy = x + ox * n, z + oz * n, base + layer
                    if self.inside(xx, yy, zz) and self.get(xx, yy, zz) in (None, 'minecraft:air'):
                        self.p(xx, yy, zz, 'spruce_log', axis='x' if ox else 'z')
            for dx in range(-r, r + 1):
                for dz in range(-r, r + 1):
                    if abs(dx) + abs(dz) > r + 1 or (abs(dx) == r and abs(dz) == r):
                        continue
                    for dy in (0, 1, 2):
                        if dy == 2 and abs(dx) + abs(dz) > r - 1:
                            continue
                        xx, yy, zz = x + dx, base + layer + dy, z + dz
                        if self.inside(xx, yy, zz) and self.get(xx, yy, zz) in (None, 'minecraft:air'):
                            self.p(xx, yy, zz, 'spruce_leaves', persistent='true', distance='1', waterlogged='false')
        for dy in (-2, -1, 0, 1):
            self.p(x, base + height + dy, z, 'spruce_leaves', persistent='true', distance='1', waterlogged='false')

    def build_garden(self) -> None:
        """Plant dense conifers, ferns, flowers and mossy rubble around the stonework."""
        for spec in ((5, 22, 34, 5), (6, 37, 32, 5), (49, 32, 34, 5), (49, 18, 27, 4), (40, 44, 25, 4), (16, 43, 25, 4), (38, 6, 12, 3), (9, 7, 11, 3)):
            self.tree(*spec)
        for (x, z), y in list(self.ground.items()):
            if 7 <= x <= 47 and 10 <= z <= 41:
                continue
            if 20 <= x <= 34 and z <= 10:
                continue
            if self.get(x, y + 1, z) not in (None, 'minecraft:air'):
                continue
            chance = self.rng.random()
            if chance < 0.16:
                block = self.rng.choice(('mossy_cobblestone', 'mossy_stone_bricks', 'cobblestone'))
                self.p(x, y + 1, z, block)
                if self.rng.random() < 0.25:
                    self.p(x, y + 2, z, 'moss_carpet')
            elif chance < 0.56:
                self.p(x, y, z, 'grass_block')
                self.p(x, y + 1, z, self.rng.choice(('fern', 'fern', 'short_grass', 'sweet_berry_bush', 'poppy', 'oxeye_daisy', 'dandelion', 'azure_bluet')))
            elif chance < 0.66:
                self.p(x, y + 1, z, 'oak_leaves', persistent='true', distance='1', waterlogged='false')
        # Rubble buttresses break up the long retaining walls.
        for x in (6, 48):
            for z in (12, 20, 29, 39):
                for y in range(2, 6):
                    self.p(x, y, z, self.rng.choice(STONE))
        for x in (12, 18, 37, 43):
            self.p(x, 4, 9, 'mossy_cobblestone')
            self.p(x, 5, 9, 'moss_block')
            self.p(x, 6, 9, 'fern')

    def build(self) -> 'Build':
        """Build the reference-inspired Lanternpine Inn and record verification anchors.

        Returns:
            Build: Completed single-region build.
        """
        self.ground = {}
        self.windows = []
        self.walk_stairs = []
        self.approach = []
        self.flights = []
        self.rooms = []
        self.build_terrain()
        self.build_shell()
        self.build_windows()
        self.build_dormers()
        self.build_interior()
        self.build_chimney()
        self.build_staircase()
        self.build_details()
        self.build_garden()
        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)
        # Standard compressed NBT, with a fixed gzip timestamp for reproducible bytes.
        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()
