# Generate and verify a furnished Amberstone Lodge for vanilla Java 1.21.1.
from __future__ import annotations
import argparse
from collections import Counter
import gzip
import io
import json
from pathlib import Path
import random
import sys
import uuid
import numpy as np
CONFIG = {
'seed': 731942,
'size_xyz': (47, 43, 37),
'data_version': 3955,
'name': 'Amberstone Lodge',
'library': Path('/work/generator/MCIO'),
'registry': Path('/work/generator/MCRender/resources/1.21.1-blocks.json'),
'epoch_ms': 1750000000000,
'palettes': {
'stone': ('stone_bricks', 'stone_bricks', 'stone_bricks', 'cobblestone', 'mossy_stone_bricks'),
'plaster': ('smooth_sandstone', 'smooth_sandstone', 'smooth_sandstone', 'sandstone', 'end_stone_bricks'),
'roof': ('deepslate_tiles', 'deepslate_tiles', 'deepslate_tiles', 'deepslate_bricks', 'cobbled_deepslate'),
'wood': ('spruce_planks', 'spruce_planks', 'spruce_planks', 'spruce_planks', 'spruce_planks', 'oak_planks', 'dark_oak_planks'),
'path': ('gravel', 'coarse_dirt', 'dirt_path', 'cobblestone', 'mossy_cobblestone'),
'terrain': ('grass_block', 'grass_block', 'grass_block', 'coarse_dirt', 'moss_block'),
},
'layout': {
'low': (10, 27, 10, 26),
'tower': (27, 39, 9, 27),
'floors': (3, 10, 16, 22),
'tower_roof': {
25: 18,
26: 19,
27: 20,
28: 23,
29: 26,
30: 29,
31: 32,
32: 35,
33: 37,
34: 35,
35: 32,
36: 29,
37: 26,
38: 23,
39: 20,
40: 19,
41: 18
},
'front_door': (24, 4, 10),
'dormer': (27, 31, 23, 30, 14, 20),
'stairs': ((3, 10), (10, 16), (16, 22)),
'trees': ((6, 29, 23, 4), (18, 31, 28, 4), (42, 31, 17, 3)),
'rooms': {
'entrance': {
'target': (25, 4, 13),
'bounds': (21, 27, 4, 9, 11, 22)
},
'workshop': {
'target': (16, 4, 17),
'bounds': (11, 20, 4, 9, 11, 25)
},
'store_hall': {
'target': (33, 4, 17),
'bounds': (28, 38, 4, 9, 10, 26)
},
'living': {
'target': (17, 11, 18),
'bounds': (11, 26, 11, 16, 11, 25)
},
'dining': {
'target': (33, 11, 18),
'bounds': (28, 38, 11, 15, 10, 26)
},
'bedroom': {
'target': (33, 17, 18),
'bounds': (28, 38, 17, 21, 10, 26)
},
'attic_study': {
'target': (33, 23, 18),
'bounds': (29, 37, 23, 28, 10, 26)
},
'dormer_nook': {
'target': (29, 24, 17),
'bounds': (28, 30, 24, 29, 15, 19)
},
},
'cutaways': {
'ground': (9, 40, 3, 9, 9, 27),
'living': (9, 40, 10, 15, 9, 27),
'bedroom': (27, 39, 16, 21, 9, 27),
'attic': (27, 37, 22, 28, 9, 27),
'bedroom_detail': (27, 39, 16, 21, 9, 21),
'forge_detail': (1, 10, 3, 7, 10, 23),
},
},
}
sys.path.insert(0, str(CONFIG['library']))
from mcio.sketch import LitematicCanvas
from mcio.schematic import load_schematic
from mcio.nbt.nbt import File as NBTFile
from mcio.nbt.tag import Byte, Compound, Double, Float, Int, IntArray, List as NBTList, Long, Short, String
DIRECTIONS = {'north': (0, 0, -1), 'south': (0, 0, 1), 'east': (1, 0, 0), 'west': (-1, 0, 0)}
AIR = 'minecraft:air'
def split_state(state: str) -> tuple[str, dict[str, str]]:
"""Split a canonical block state into its name and property map.
Args:
state: Namespaced state string.
Returns:
Block name and properties.
"""
if '[' not in state:
return state.removeprefix('minecraft:'), {}
name, properties = state[:-1].split('[')
return name.removeprefix('minecraft:'), dict(p.split('=') for p in properties.split(','))
class Lodge:
"""Hold deterministic XYZ placements, entity data, and design metadata."""
def __init__(self, seed: int = CONFIG['seed']):
self.seed = seed
self.rng = random.Random(seed)
self.registry = json.loads(CONFIG['registry'].read_text())
self.blocks = {}
self.tile_entities = {}
self.entities = []
self.windows = set()
self.window_outsides = {}
self.banners = []
self.stair_routes = []
def state(self, name: str, **properties: str) -> str:
"""Validate properties and fill every vanilla default.
Args:
name: Vanilla block ID without namespace.
properties: Explicit state overrides.
Returns:
Complete, sorted state string.
"""
name = name.removeprefix('minecraft:')
allowed, defaults = self.registry[name]
result = defaults.copy()
for key, value in properties.items():
if key not in allowed or str(value) not in allowed[key]:
raise ValueError((name, key, value))
result[key] = str(value)
return 'minecraft:' + name + ('[' + ','.join(f'{k}={v}' for k, v in sorted(result.items())) + ']' if result else '')
def put(self, x: int, y: int, z: int, name: str, **properties: str) -> None:
"""Place one validated block, using XYZ coordinates.
Args:
x, y, z: Local block coordinates.
name: Block ID.
properties: State overrides.
"""
sx, sy, sz = CONFIG['size_xyz']
if not (0 <= x < sx and 0 <= y < sy and 0 <= z < sz):
raise ValueError(('out of bounds', x, y, z))
pos = x, y, z
state = self.state(name, **properties)
if state == AIR:
self.blocks.pop(pos, None)
else:
self.blocks[pos] = state
def get(self, x: int, y: int, z: int) -> str:
"""Return a block state, treating unplaced cells as air.
Args:
x, y, z: Local coordinates.
Returns:
Canonical state.
"""
return self.blocks.get((x, y, z), AIR)
def box(self, bounds: tuple, name: str, **properties: str) -> None:
"""Fill inclusive bounds with one material or a configured palette.
Args:
bounds: x0, x1, y0, y1, z0, z1.
name: Material or palette prefixed by @.
properties: State overrides.
"""
x0, x1, y0, y1, z0, z1 = bounds
for y in range(y0, y1 + 1):
for z in range(z0, z1 + 1):
for x in range(x0, x1 + 1):
block = self.rng.choice(CONFIG['palettes'][name[1:]]) if name.startswith('@') else name
self.put(x, y, z, block, **properties)
def timber(self, bounds: tuple, axis: str = 'y') -> None:
"""Place stripped spruce framing.
Args:
bounds: Inclusive XYZ box.
axis: Timber grain direction.
"""
self.box(bounds, 'stripped_spruce_log', axis=axis)
def door(self, x: int, y: int, z: int, facing: str, hinge: str = 'left', wood: str = 'spruce') -> None:
"""Place complete door halves.
Args:
x, y, z: Bottom half.
facing: Outward direction.
hinge: Door hinge side.
wood: Vanilla door family.
"""
for dy, half in enumerate(('lower', 'upper')):
self.put(x, y + dy, z, wood + '_door', facing=facing, hinge=hinge, half=half, open='false')
def tile(self, x: int, y: int, z: int, kind: str, **tags) -> None:
"""Attach a generated vanilla block entity to a canvas placement.
Args:
x, y, z: Block entity coordinates.
kind: Block entity ID.
tags: Typed NBT payload.
"""
self.tile_entities[x, y, z] = Compound({'id': String('minecraft:' + kind), 'x': Int(x), 'y': Int(y), 'z': Int(z), **tags})
def frame(self, x: int, y: int, z: int, facing: str, item: str) -> None:
"""Create a fixed vanilla item frame with deterministic UUID.
Args:
x, y, z: Air block occupied by the frame.
facing: Face normal.
item: Displayed item ID.
"""
dx, _, dz = DIRECTIONS[facing]
ident = uuid.uuid5(uuid.NAMESPACE_URL, f'amberstone:{self.seed}:{x},{y},{z}')
ids = [int.from_bytes(ident.bytes[i:i + 4], 'big', signed=True) for i in range(0, 16, 4)]
self.entities.append(Compound({
'id': String('minecraft:item_frame'),
'UUID': IntArray(ids),
'Pos': NBTList[Double]([x + .5 - dx * .46875, y + .5, z + .5 - dz * .46875]),
'Motion': NBTList[Double]([0, 0, 0]),
'Rotation': NBTList[Float]([0, 0]),
'TileX': Int(x),
'TileY': Int(y),
'TileZ': Int(z),
'Facing': Byte({
'north': 2,
'south': 3,
'west': 4,
'east': 5
}[facing]),
'Fixed': Byte(1),
'Invisible': Byte(0),
'ItemRotation': Byte(0),
'Item': Compound({
'id': String('minecraft:' + item),
'count': Int(1)
}),
}))
def lantern(self, x: int, y: int, z: int, hanging: bool = False) -> None:
"""Place a lantern whose support is supplied by its caller.
Args:
x, y, z: Lantern position.
hanging: Whether mounted to the ceiling.
"""
self.put(x, y, z, 'lantern', hanging=str(hanging).lower())
def window(self, axis: str, plane: int, start: int, width: int, bottom: int, height: int, outside: int) -> None:
"""Insert framed glass windows with sill and open shutters.
Args:
axis: z for front/rear walls, x for side walls.
plane: Wall coordinate.
start: First horizontal window coordinate.
width, bottom, height: Opening dimensions.
outside: Direction from wall, -1 or +1.
"""
facing = ('north' if outside < 0 else 'south') if axis == 'z' else ('west' if outside < 0 else 'east')
for t in range(start - 1, start + width + 1):
for y in (bottom - 1, bottom + height):
x, z = (t, plane) if axis == 'z' else (plane, t)
self.put(x, y, z, 'stripped_dark_oak_log', axis='x' if axis == 'z' else 'z')
for t in (start - 1, start + width):
for y in range(bottom, bottom + height):
x, z = (t, plane) if axis == 'z' else (plane, t)
self.put(x, y, z, 'stripped_spruce_log')
ox, oz = (x, z + outside) if axis == 'z' else (x + outside, z)
self.put(ox, y, oz, 'spruce_trapdoor', facing=facing, open='true')
for t in range(start, start + width):
for y in range(bottom, bottom + height):
x, z = (t, plane) if axis == 'z' else (plane, t)
self.put(x, y, z, 'light_gray_stained_glass')
self.windows.add((x, y, z))
self.window_outsides[x, y, z] = (x, y, z + outside) if axis == 'z' else (x + outside, y, z)
x, z = (t, plane + outside) if axis == 'z' else (plane + outside, t)
self.put(x, bottom - 1, z, 'spruce_stairs', facing=facing, half='top')
def landscape(self) -> None:
"""Build an irregular terrain island, terrace, and curving approach."""
for x in range(1, 46):
for z in range(1, 36):
edge = ((x - 23) / 24)**6 + ((z - 18) / 19)**6
if edge < 1.28 and not (x < 4 and z < 6):
self.put(x, 0, z, 'dirt')
self.put(x, 1, z, self.rng.choice(CONFIG['palettes']['terrain']))
self.box((7, 41, 1, 2, 6, 29), '@stone')
self.box((8, 40, 3, 3, 7, 28), '@stone')
for x in range(8, 41):
if x not in range(22, 28):
self.put(x, 3, 6, 'stone_brick_slab')
for z in range(7, 30):
self.put(41, 3, z, 'stone_brick_slab')
for z, y in ((3, 1), (4, 2), (5, 3)):
self.box((22, 27, 1, y - 1, z, z), '@stone')
for x in range(22, 28):
self.put(x, y, z, 'stone_brick_stairs', facing='south')
for z in range(0, 7):
center = 24 - (6 - z) // 2
for x in range(center - 3, center + 4):
if z < 3 or not 22 <= x <= 27:
if self.rng.random() < .87:
self.put(x, 0, z, 'dirt')
self.put(x, 1, z, self.rng.choice(CONFIG['palettes']['path']))
for x in range(9, 41):
for z in (7, 28):
if x % 4 == 1:
self.put(x, 3, z, 'chiseled_stone_bricks')
for x, z in ((9, 7), (38, 7), (40, 28), (8, 28)):
self.put(x, 4, z, 'cobblestone_wall')
self.lantern(x, 5, z)
def shell(self) -> None:
"""Build both sealed wings, timber grids, floors, and joined roofs."""
low = CONFIG['layout']['low']
tower = CONFIG['layout']['tower']
for x0, x1, z0, z1 in (low, tower):
self.box((x0, x1, 3, 3, z0, z1), '@stone')
for x in range(x0, x1 + 1):
for z in range(z0, z1 + 1):
if x in (x0, x1) or z in (z0, z1):
self.box((x, x, 4, 9, z, z), '@stone')
self.box((x, x, 11, 17 if (x0, x1) == low[:2] else 19, z, z), '@plaster')
self.box((x0, x1, 10, 10, z0, z1), '@wood')
for y in (9, 10, 16):
self.timber((x0, x1, y, y, z0, z0), 'x')
self.timber((x0, x1, y, y, z1, z1), 'x')
self.timber((x0, x0, y, y, z0, z1), 'z')
self.timber((x1, x1, y, y, z0, z1), 'z')
for x in range(x0, x1 + 1):
if x in (x0, x1) or (x - x0) % 5 == 0:
for z in (z0, z1):
self.timber((x, x, 4, 17, z, z))
for z in range(z0, z1 + 1):
if z in (z0, z1) or (z - z0) % 5 == 0:
for x in (x0, x1):
self.timber((x, x, 4, 17, z, z))
for y in (16, 22):
self.box((28, 38, y, y, 10, 26), '@wood')
self.parquet()
# Exposed lower-wing gable ends.
for z in range(10, 27):
h = 23 - int(abs(z - 18) * .7)
self.box((10, 10, 18, h - 1, z, z), '@plaster')
if z in (13, 18, 23):
self.timber((10, 10, 17, h - 1, z, z))
# Low roof overlaps only the shared structural wall.
for x in range(8, 28):
for z in range(8, 29):
h = 23 - int(abs(z - 18) * .7)
self.put(x, h, z, self.rng.choice(CONFIG['palettes']['roof']))
if z != 18:
self.put(x, h + 1, z, 'deepslate_tile_stairs', facing='south' if z < 18 else 'north')
else:
self.put(x, h + 1, z, 'deepslate_tile_slab')
if x in (8, 27):
self.put(x, h, z, 'deepslate_bricks')
roof = CONFIG['layout']['tower_roof']
for x in range(27, 40):
for z in (9, 27):
self.box((x, x, 20, roof[x] - 1, z, z), '@plaster')
if x in (28, 31, 33, 35, 38):
self.timber((x, x, 17, roof[x] - 1, z, z))
for y in (22, 28, 33):
if y < roof[x]:
self.put(x, y, z, 'stripped_spruce_log', axis='x')
for x, h in roof.items():
inner = roof.get(x + (1 if x < 33 else -1), h) if x != 33 else h
for z in range(7, 30):
self.box((x, x, h, max(h, inner - 1), z, z), '@roof')
self.put(x, max(h, inner - 1) + 1, z, 'deepslate_tile_stairs', facing='east' if x < 33 else 'west')
if z in (7, 29):
self.box((x, x, h, max(h, inner - 1), z, z), 'deepslate_bricks')
for z in range(7, 30):
self.put(33, 38, z, 'dark_oak_slab', type='bottom')
for z in (7, 29):
self.put(33, 39, z, 'dark_oak_fence')
self.put(33, 40, z, 'dark_oak_fence')
self.put(33, 41, z, 'lightning_rod')
# Wide interior openings through the shared wall.
self.box((27, 27, 4, 7, 13, 18), 'air')
self.box((27, 27, 11, 14, 13, 18), 'air')
for y in (8, 15):
self.timber((27, 27, y, y, 12, 19), 'z')
for z in (12, 19):
self.timber((27, 27, 4, 15, z, z))
# Low-wing stair brackets and eave details.
for x in (10, 15, 20, 25):
self.put(x, 16, 9, 'spruce_stairs', facing='south', half='top')
self.put(x, 17, 8, 'spruce_stairs', facing='south', half='top')
for z in (11, 16, 21, 26):
self.put(40, 18, z, 'spruce_stairs', facing='west', half='top')
def parquet(self) -> None:
"""Lay coherent timber borders and end-grain accents in upper rooms."""
for y in CONFIG['layout']['floors'][1:]:
bounds = [(28, 38, 10, 26)]
if y == 10:
bounds.append((11, 26, 11, 25))
for x0, x1, z0, z1 in bounds:
for x in range(x0, x1 + 1):
for z in range(z0, z1 + 1):
material = 'dark_oak_planks' if x in (x0, x1) or z in (z0, z1) else 'stripped_spruce_log' if x % 3 == z % 3 == 0 else 'spruce_planks'
self.put(x, y, z, material)
def dormer(self) -> None:
"""Add a timber dormer opening into the tower study."""
x0, x1, base, peak, z0, z1 = CONFIG['layout']['dormer']
self.box((x0, x1 - 2, base, base, z0, z1), 'spruce_planks')
# Union the dormer roof with the original slope at every column.
main_roof = CONFIG['layout']['tower_roof']
for z in range(z0, z1 + 1):
dormer_h = peak - abs(z - (z0 + z1) // 2)
for x in range(x0, x1 + 1):
h = max(main_roof[x], dormer_h)
inner = max(main_roof[x + 1], dormer_h)
self.box((x, x, base + 1, h - 1, z, z), 'air')
if x == x0:
self.box((x, x, base + 1, h - 1, z, z), '@plaster')
elif z in (z0, z1):
self.box((x, x, max(base + 1, main_roof[x]), h - 1, z, z), '@plaster')
self.box((x, x, h, max(h, inner - 1), z, z), '@roof')
self.put(x, max(h, inner - 1) + 1, z, 'deepslate_tile_stairs', facing='east' if inner > h else 'south' if z < 17 else 'north')
for z in (z0, z1):
self.timber((x0, x0, base, peak - 4, z, z))
self.timber((x0, x1, base, base, z, z), 'x')
self.window('x', x0, 16, 3, 25, 2, -1)
for z in (16, 17, 18):
self.put(30, 23, z, 'spruce_stairs', facing='west')
self.put(28, 24, 19, 'barrel', facing='north')
self.put(28, 25, 19, 'potted_blue_orchid')
self.ceiling_lamp(28, 17, 30, 4)
def openings(self) -> None:
"""Fit all windows and doors without intersecting structural chimneys."""
self.door(24, 4, 10, 'north', 'left')
self.door(25, 4, 10, 'north', 'right')
self.timber((23, 26, 6, 6, 10, 10), 'x')
for x in (23, 26):
self.timber((x, x, 4, 5, 10, 10))
for x in range(23, 27):
self.put(x, 7, 9, 'deepslate_tile_stairs', facing='south')
self.door(10, 4, 15, 'west')
# Concealed rear service entry completes the reconstructed elevation.
self.door(30, 4, 27, 'south')
for start in (12, 18):
self.window('z', 10, start, 2, 5, 2, -1)
self.window('z', 10, start, 2, 12, 3, -1)
self.window('z', 26, start, 2, 12, 3, 1)
self.window('x', 10, 12, 3, 13, 2, -1)
self.window('x', 10, 15, 3, 19, 2, -1)
for b, height in ((5, 2), (12, 3), (18, 3), (24, 3), (30, 2)):
self.window('z', 9, 32, 3 if b < 30 else 2, b, height, -1)
for b in (5, 12, 18, 24):
self.window('z', 27, 32, 3, b, 2, 1)
for start in (12, 19):
self.window('x', 39, start, 3, 5, 2, 1)
self.window('x', 39, start, 3, 12, 3, 1)
# Entry lamps attached to timber brackets.
for x in (22, 28):
self.put(x, 7, 8, 'spruce_fence', south='true')
self.put(x, 7, 9, 'spruce_fence', north='true', south='true')
self.lantern(x, 6, 8, hanging=True)
def stairs(self) -> None:
"""Install three two-block-wide flights with cleared two-block headroom."""
for floor, top in CONFIG['layout']['stairs']:
route = [(28, floor + 1, 24)]
for i in range(top - floor):
x, y = 29 + i, floor + i + 1
for z in (24, 25):
self.box((x, x, floor + 1, y - 1, z, z), 'spruce_planks')
self.put(x, y, z, 'spruce_stairs', facing='east')
self.box((x, x, y + 1, y + 2, z, z), 'air')
route.append((x, y + 1, 24))
route.append((29 + top - floor, top + 1, 24))
self.stair_routes.append(route)
# Clear flights after every upper support has been installed.
for route in self.stair_routes:
for x, y, z in route:
self.box((x, x, y, y + 2, 24, 25), 'air')
for y in (10, 16, 22):
for x in range(31, 36):
self.put(x, y + 1, 23, 'spruce_fence', east='true', west='true')
# Supported ladder reaches a small service shelf above the ground workshop.
self.box((11, 12, 7, 7, 23, 25), 'spruce_slab', type='top')
self.put(12, 8, 24, 'chest', facing='north')
for y in range(4, 8):
self.put(11, y, 22, 'ladder', facing='north')
self.put(11, y, 23, 'spruce_planks')
def forge(self) -> None:
"""Build the covered smithy, fueled furnace, open hearth, and flue."""
self.box((2, 9, 2, 2, 11, 22), '@stone')
self.box((2, 9, 3, 3, 11, 22), 'stone_bricks')
for x, z in ((2, 11), (2, 22), (9, 11), (9, 22)):
self.box((x, x, 4, 8, z, z), 'spruce_log')
self.put(x, 8, z + (1 if z == 11 else -1), 'spruce_stairs', facing='south' if z == 11 else 'north', half='top')
for x in range(1, 10):
h = 8 + (x // 3)
for z in range(10, 24):
self.put(x, h, z, 'deepslate_tiles')
self.put(x, h + 1, z, 'deepslate_tile_stairs', facing='east')
# Fireplace is outside the house wall, with its flue safely behind windows.
self.box((7, 9, 4, 7, 18, 21), '@stone')
self.put(7, 4, 19, 'blast_furnace', facing='west', lit='true')
self.tile(7, 4, 19, 'blast_furnace', BurnTime=Short(20000), CookTime=Short(0), CookTimeTotal=Short(100), Items=NBTList[Compound]([
Compound({
'Slot': Byte(0),
'id': String('minecraft:raw_iron'),
'count': Int(64)
}),
Compound({
'Slot': Byte(1),
'id': String('minecraft:coal'),
'count': Int(64)
}),
]))
self.put(7, 5, 20, 'campfire', facing='west', lit='true')
self.put(6, 5, 20, 'iron_bars', north='true', south='true')
self.box((8, 9, 8, 26, 19, 20), '@stone')
self.box((7, 10, 27, 27, 18, 21), 'stone_brick_slab', type='top')
self.put(8, 28, 19, 'campfire', lit='true', signal_fire='true')
self.put(9, 28, 20, 'campfire', lit='true', signal_fire='true')
for x, z in ((8, 20), (9, 19)):
self.put(x, 28, z, 'cobblestone_wall')
self.box((7, 10, 29, 29, 18, 21), 'stone_brick_slab')
self.put(4, 4, 16, 'anvil', facing='north')
self.put(4, 4, 20, 'smithing_table')
self.put(3, 4, 12, 'barrel', facing='up')
self.put(4, 4, 12, 'barrel', facing='up')
self.put(3, 5, 12, 'chest', facing='west')
self.put(8, 4, 12, 'water_cauldron', level='3')
self.lantern(3, 7, 11, hanging=True)
self.put(3, 8, 11, 'spruce_fence')
self.lantern(6, 4, 22)
self.put(6, 3, 22, 'stone_bricks')
def carpet(self, x0: int, x1: int, z0: int, z1: int, y: int, color: str) -> None:
"""Place a bordered rug on an existing solid floor.
Args:
x0, x1, z0, z1: Rug bounds.
y: Carpet height.
color: Center color.
"""
for x in range(x0, x1 + 1):
for z in range(z0, z1 + 1):
self.put(x, y, z, ('brown' if x in (x0, x1) or z in (z0, z1) else color) + '_carpet')
def ceiling_lamp(self, x: int, z: int, ceiling: int, drop: int = 1) -> None:
"""Hang a lantern from a supported ceiling beam.
Args:
x, z: Horizontal coordinates.
ceiling: Existing ceiling height.
drop: Distance below the ceiling.
"""
for y in range(ceiling - drop + 1, ceiling):
self.put(x, y, z, 'chain', axis='y')
self.lantern(x, ceiling - drop, z, hanging=True)
def cabinet(self, x: int, y: int, z: int, facing: str = 'north') -> None:
"""Place a barrel cupboard with an upper cabinet.
Args:
x, y, z: Cabinet base.
facing: Front face.
"""
self.put(x, y, z, 'barrel', facing=facing)
self.put(x, y + 2, z, 'barrel', facing=facing)
self.put(x, y + 3, z, 'spruce_slab')
def furnished_ground(self) -> None:
"""Furnish the entrance hall, internal workshop, and storage room."""
# Workshop partition with an oak glazed door.
self.box((21, 21, 4, 8, 11, 25), 'spruce_planks')
self.box((21, 21, 6, 7, 13, 16), 'glass')
self.door(21, 4, 18, 'east', wood='oak')
self.carpet(23, 25, 12, 17, 4, 'red')
self.carpet(29, 35, 15, 20, 4, 'red')
for x in (12, 14, 16, 18):
self.cabinet(x, 4, 25)
self.frame(x, 6, 24, 'north', 'iron_ingot' if x % 4 else 'coal')
self.put(12, 4, 12, 'crafting_table')
self.put(13, 4, 12, 'smithing_table')
self.put(14, 4, 12, 'barrel', facing='south')
self.put(15, 4, 12, 'stonecutter', facing='south')
self.put(17, 4, 12, 'grindstone', face='floor', facing='south')
self.put(13, 5, 12, 'potted_blue_orchid')
self.put(17, 4, 20, 'anvil')
for z in (13, 16, 19, 22):
self.put(38, 4, z, 'barrel', facing='west')
self.put(38, 5, z, 'chest', facing='west')
for x in (29, 30, 31):
self.put(x, 4, 11, 'spruce_stairs', facing='north')
self.put(36, 4, 11, 'barrel')
self.put(36, 5, 11, 'potted_fern')
self.box((28, 28, 4, 7, 20, 21), 'bookshelf')
self.put(22, 4, 12, 'barrel')
self.put(22, 5, 12, 'candle', candles='3', lit='true')
for x, z in ((14, 16), (18, 22), (24, 19), (32, 13), (36, 21)):
self.ceiling_lamp(x, z, 10, 2)
def furnished_living(self) -> None:
"""Furnish a lounge, hearth, dining room, and pantry."""
self.carpet(13, 20, 16, 22, 11, 'green')
for x in (14, 15, 16, 17):
self.put(x, 11, 24, 'spruce_stairs', facing='south')
for z in (17, 18, 19):
self.put(12, 11, z, 'dark_oak_stairs', facing='west')
for x in (15, 16, 17):
self.put(x, 11, 19, 'dark_oak_slab', type='top')
self.put(16, 12, 19, 'candle', candles='3', lit='true')
self.put(19, 11, 24, 'barrel')
self.put(19, 12, 24, 'potted_azalea_bush')
# A lined hearth against the party wall is capped beneath the ceiling.
self.box((24, 26, 11, 15, 21, 23), 'stone_bricks')
self.put(24, 11, 22, 'campfire', facing='west', lit='true')
self.put(23, 11, 22, 'iron_bars', north='true', south='true')
self.box((24, 26, 16, 17, 21, 23), 'andesite')
self.put(23, 14, 22, 'stone_brick_stairs', facing='east', half='top')
for x in (12, 13, 14):
self.cabinet(x, 11, 11, 'south')
self.put(13, 12, 11, 'potted_blue_orchid')
for z in (12, 13, 14):
self.put(25, 11, z, 'bookshelf')
self.put(25, 12, z, 'bookshelf')
self.put(25, 13, z, 'spruce_slab')
self.put(23, 11, 13, 'lectern', facing='west', has_book='true')
self.book(23, 11, 13, 'The Lodge Guestbook')
self.carpet(30, 36, 13, 19, 11, 'red')
for x in (32, 33, 34):
for z in (15, 16):
self.put(x, 11, z, 'spruce_fence', east='true', west='true')
self.put(x, 12, z, 'spruce_pressure_plate')
for x in (32, 34):
self.put(x, 11, 13, 'dark_oak_stairs', facing='north')
self.put(x, 11, 18, 'dark_oak_stairs', facing='south')
self.put(30, 11, 15, 'dark_oak_stairs', facing='west')
self.put(36, 11, 15, 'dark_oak_stairs', facing='east')
# Candles stand on full barrel sideboards, avoiding floating tabletop props.
for z in (12, 14, 16, 18, 20):
self.put(38, 11, z, 'barrel', facing='west')
self.put(38, 12, 14, 'candle', candles='3', lit='true')
self.put(38, 12, 18, 'potted_fern')
self.put(29, 11, 11, 'smoker', facing='south', lit='true')
self.put(30, 11, 11, 'water_cauldron', level='3')
self.put(29, 12, 11, 'stone_bricks')
self.put(29, 13, 11, 'stone_brick_wall')
for x, z in ((32, 12), (35, 21)):
self.ceiling_lamp(x, z, 16, 2)
# Low-wing rafters support the lanterns below a tall vaulted ceiling.
for z in (14, 20, 25):
self.timber((10, 27, 17, 17, z, z), 'x')
for x, z in ((17, 14), (20, 20), (14, 25)):
self.ceiling_lamp(x, z, 17, 2)
def book(self, x: int, y: int, z: int, title: str) -> None:
"""Put a real readable book on a generated lectern.
Args:
x, y, z: Lectern coordinates.
title: Book title.
"""
content = Compound({'title': Compound({'raw': String(title)}), 'author': String('Amberstone Lodge'), 'generation': Int(0), 'resolved': Byte(1), 'pages': NBTList[Compound]([Compound({'raw': String(json.dumps({'text': 'Welcome to Amberstone Lodge. The pines shelter travelers, and the forge keeps the hearth warm.'}))})])})
self.tile(x, y, z, 'lectern', Book=Compound({'id': String('minecraft:written_book'), 'count': Int(1), 'components': Compound({'minecraft:written_book_content': content})}), Page=Int(0))
def furnished_bedroom(self) -> None:
"""Reconstruct the raised green bed, curtains, cabinetry, and window seat."""
self.carpet(30, 36, 17, 21, 17, 'green')
# Raised platform and broad approach step.
self.box((34, 37, 17, 17, 12, 15), 'dark_oak_planks')
for x in (34, 35, 36, 37):
self.put(x, 17, 16, 'dark_oak_stairs', facing='north')
for x in (35, 36):
self.put(x, 18, 14, 'green_bed', part='foot', facing='north')
self.put(x, 18, 13, 'green_bed', part='head', facing='north')
for x in (34, 37):
self.put(x, 18, 12, 'barrel', facing='south')
self.put(x, 19, 12, 'potted_fern' if x == 34 else 'candle', **({} if x == 34 else {'candles': '2', 'lit': 'true'}))
for x in (35, 36):
self.put(x, 18, 15, 'dark_oak_trapdoor', facing='south', open='true')
self.put(x, 20, 12, 'bookshelf')
self.put(x, 21, 12, 'spruce_slab')
self.box((35, 36, 18, 19, 12, 12), 'dark_oak_planks')
# Curtains stand beside glass, attached to actual solid side mullions.
for x in (31, 35):
self.put(x, 20, 10, 'white_wall_banner', facing='south')
self.tile(x, 20, 10, 'banner', patterns=NBTList[Compound]([Compound({'color': String('green'), 'pattern': String('minecraft:small_stripes')})]))
self.banners.append((x, 20, 10))
for x in (32, 33, 34):
self.put(x, 17, 10, 'dark_oak_stairs', facing='north')
self.put(x, 18, 10, 'white_carpet')
self.put(30, 17, 10, 'barrel', facing='south')
self.put(30, 18, 10, 'potted_blue_orchid')
for z in (17, 19, 21):
self.cabinet(28, 17, z, 'east')
self.put(28, 18, 19, 'chest', facing='east')
self.put(37, 17, 20, 'barrel', facing='west')
self.put(37, 18, 20, 'candle', candles='3', lit='true')
self.put(37, 17, 21, 'spruce_stairs', facing='east')
self.frame(29, 19, 19, 'east', 'clock')
for x, z in ((31, 14), (34, 20)):
self.ceiling_lamp(x, z, 22, 1)
def furnished_attic(self) -> None:
"""Furnish the vaulted study with an open book and storage cabinets."""
self.carpet(31, 35, 15, 21, 23, 'green')
# Backing is inside the sealed sloping roof, with usable center aisle.
for z in (12, 14, 20):
self.put(30, 23, z, 'barrel', facing='east')
self.put(30, 24, z, 'bookshelf')
self.put(30, 25, z, 'barrel', facing='east')
self.put(30, 26, z, 'spruce_slab')
for z in (12, 13, 14):
self.put(36, 23, z, 'spruce_planks')
self.put(36, 24, 13, 'lectern', facing='west', has_book='true')
self.book(36, 24, 13, 'Pines and Iron')
self.put(36, 24, 12, 'candle', candles='3', lit='true')
self.put(36, 24, 14, 'potted_fern')
self.put(34, 23, 13, 'dark_oak_stairs', facing='west')
for z in (18, 20):
self.put(36, 23, z, 'barrel', facing='west')
self.put(36, 24, z, 'chest', facing='west')
self.put(34, 23, 20, 'dark_oak_stairs', facing='south')
self.put(34, 23, 18, 'dark_oak_slab', type='top')
self.put(34, 24, 18, 'candle', candles='2', lit='true')
self.frame(31, 25, 12, 'east', 'compass')
for z in (12, 19, 26):
self.timber((30, 36, 29, 29, z, z), 'x')
for z in (12, 19):
self.ceiling_lamp(33, z, 29, 3)
# Service ledge stays clear of the main stairwell and roof.
for y in range(23, 30):
self.put(35, y, 26, 'ladder', facing='north')
self.box((33, 34, 28, 28, 25, 26), 'spruce_slab', type='top')
self.box((33, 34, 29, 30, 25, 26), 'air')
self.put(33, 29, 26, 'barrel', facing='north')
self.lantern(33, 30, 26)
def greenery(self) -> None:
"""Build hand-shaped pines and supported climbing greenery."""
for x, z, height, radius in CONFIG['layout']['trees']:
self.box((x, x, 2, height, z, z), 'spruce_log')
for y in range(5, height, 3):
r = max(1, round(radius * (height - y) / (height - 5)))
for dx, dz in ((1, 0), (-1, 0), (0, 1), (0, -1)):
for i in range(1, r + 1):
self.put(x + dx * i, y, z + dz * i, 'spruce_log', axis='x' if dx else 'z')
for dx in range(-r - 1, r + 2):
for dz in range(-r - 1, r + 2):
if abs(dx) + abs(dz) <= r + 1 and not (dx == dz == 0):
if self.get(x + dx, y + 1, z + dz) == AIR and (x + dx, y + 1, z + dz) not in self.window_outsides.values():
self.put(x + dx, y + 1, z + dz, 'spruce_leaves', persistent='true', distance='1')
if abs(dx) + abs(dz) <= r and self.get(x + dx, y, z + dz) == AIR and (x + dx, y, z + dz) not in self.window_outsides.values():
self.put(x + dx, y, z + dz, 'spruce_leaves', persistent='true', distance='1')
self.put(x, height + 1, z, 'spruce_leaves', persistent='true', distance='1')
self.put(x, height + 2, z, 'spruce_leaves', persistent='true', distance='1')
for x, z in ((28, 8), (29, 8), (37, 8), (40, 23), (40, 24)):
self.put(x, 4, z, 'mossy_cobblestone')
for y in range(5, 15 if x < 31 else 10):
support = self.get(x, y, z + 1) if z == 8 else self.get(x - 1, y, z)
if 'glass' not in support and support != AIR and self.get(x, y, z) == AIR:
self.put(x, y, z, 'vine', **({'south': 'true'} if z == 8 else {'west': 'true'}))
# Potted climbing plant in the entry hall, rooted on a cabinet.
self.put(29, 4, 21, 'barrel')
self.put(29, 5, 21, 'flower_pot')
self.put(29, 6, 21, 'azalea_leaves', persistent='true', distance='1')
self.put(28, 6, 21, 'azalea_leaves', persistent='true', distance='1')
self.put(28, 7, 21, 'azalea_leaves', persistent='true', distance='1')
# Natural scatter avoids the pathway and the terrace.
for _ in range(150):
x, z = self.rng.randrange(2, 45), self.rng.randrange(2, 35)
if self.get(x, 2, z) == AIR and 'grass_block' in self.get(x, 1, z):
self.put(x, 2, z, self.rng.choice(('short_grass', 'fern', 'poppy', 'azure_bluet')))
def build(self) -> 'Lodge':
"""Run deterministic construction passes and return this lodge.
Returns:
Populated generator instance.
"""
self.landscape()
self.shell()
self.openings()
self.dormer()
self.forge()
self.furnished_ground()
self.furnished_living()
self.furnished_bedroom()
self.furnished_attic()
self.stairs()
self.greenery()
return self
def export(self, path: Path, crop: tuple | None = None, cut_front: bool = True) -> None:
"""Export canvas blocks and generated entity payloads to one region.
Args:
path: Destination litematic.
crop: Optional cutaway bounds, used only for preview exports.
cut_front: Remove north/west walls, or south/east for a reverse view.
"""
if crop is None:
sx, sy, sz = CONFIG['size_xyz']
x0, x1, y0, y1, z0, z1 = 0, sx - 1, 0, sy - 1, 0, sz - 1
else:
x0, x1, y0, y1, z0, z1 = crop
canvas = LitematicCanvas((y1 - y0 + 1, z1 - z0 + 1, x1 - x0 + 1))
kept = set()
for (x, y, z), state in self.blocks.items():
if x0 <= x <= x1 and y0 <= y <= y1 and z0 <= z <= z1:
# Remove front and west walls above the sill to expose interiors.
if crop and y > y0 + 1:
if cut_front and (z <= z0 + 1 or x <= x0):
continue
if not cut_front and (z >= z1 - 1 or x >= x1):
continue
canvas.block((y - y0, z - z0, x - x0), state)
kept.add((x, y, z))
schematic = canvas.to_litematica(name=CONFIG['name'], author='Amberstone workshop', description='Furnished lodge, seed ' + str(self.seed), minecraft_data_version=CONFIG['data_version'])
schematic.metadata.time_created = CONFIG['epoch_ms']
schematic.metadata.time_modified = CONFIG['epoch_ms']
nbt = schematic.write_to_nbt()
region = nbt['Regions'][CONFIG['name']]
tiles = []
for pos, tile in self.tile_entities.items():
if pos in kept:
tile = Compound(tile)
for key, delta in (('x', x0), ('y', y0), ('z', z0)):
tile[key] = Int(int(tile[key]) - delta)
tiles.append(tile)
region['TileEntities'] = NBTList[Compound](tiles)
region['Entities'] = NBTList[Compound](self.entities if crop is None else [])
path.parent.mkdir(parents=True, exist_ok=True)
buffer = io.BytesIO()
NBTFile(nbt).write(buffer)
path.write_bytes(gzip.compress(buffer.getvalue(), mtime=0))
def verify_roundtrip(self, path: Path) -> dict:
"""Reload and compare every cell, complete state, dimensions, and counts.
Args:
path: Exported schematic.
Returns:
JSON-safe verification evidence.
"""
loaded = load_schematic(path)
sx, sy, sz = CONFIG['size_xyz']
if loaded.size_yzx != (sy, sz, sx):
raise AssertionError(('dimensions', loaded.size_yzx))
actual = np.array(loaded.palette, dtype=object)[loaded.read_flat(0, loaded.volume)].reshape(loaded.size_yzx)
expected = np.full((sy, sz, sx), AIR, dtype=object)
for (x, y, z), state in self.blocks.items():
expected[y, z, x] = state
mismatches = np.argwhere(actual != expected)
if mismatches.size:
raise AssertionError(('state roundtrip', mismatches[:10]))
counts = Counter(actual.ravel())
if loaded.warnings:
raise AssertionError(loaded.warnings)
return {'seed': self.seed, 'dimensions_xyz': [sx, sy, sz], 'region_count': 1, 'minecraft_data_version': CONFIG['data_version'], 'total_cells_compared': loaded.volume, 'non_air_blocks': len(self.blocks), 'unique_non_air_states': len(counts) - 1, 'full_state_mismatches': 0, 'sha256': loaded.sha256, 'tile_entities': len(self.tile_entities), 'item_frames': len(self.entities), 'counts_by_block': dict(sorted(Counter(split_state(s)[0] for s in self.blocks.values()).items())), 'counts_by_full_state': dict(sorted(counts.items()))}
def main() -> None:
"""Generate output and review cutaways from a clean working directory."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', type=Path, default=Path('output.litematic'))
parser.add_argument('--seed', type=int, default=CONFIG['seed'])
parser.add_argument('--no-previews', action='store_true')
args = parser.parse_args()
lodge = Lodge(args.seed).build()
lodge.export(args.output)
evidence = lodge.verify_roundtrip(args.output)
evidence_path = args.output.parent / 'validation.json'
evidence_path.write_text(json.dumps(evidence, indent=2) + '\n')
if not args.no_previews:
for name, crop in CONFIG['layout']['cutaways'].items():
lodge.export(args.output.parent / 'previews' / (name + '.litematic'), crop, cut_front=name != 'bedroom_detail')
print(json.dumps({k: v for k, v in evidence.items() if not k.startswith('counts')}, indent=2))
if __name__ == '__main__':
main()