Files
Emil 89c53d7058 Expand material support and checkpoint the completed building toolkit
Expose the runtime block/item registry through compact material search and
single-material descriptions. Preserve private block-entity data through
checked edits and durable undo without sending payloads to model context.

Include the completed terrain tools, isolated world/map plugin, station lift,
ACP streaming and guidance fixes, local camera auto-connect, construction
scripts, and their public documentation, references and verification records.
Active station decoration and private runtime data remain outside this commit.

Validation: 145 Maven tests, 47 Bridge tests, successful camera Gradle build,
and isolated Paper verification of all 1,196 block defaults plus 5,392
independent property cases for placement, same-material edits and restoration.
2026-09-13 16:59:13 +03:00

178 lines
7.9 KiB
Python

"""Small, stateless voxel assets for the Shacraft arrival gardens.
Every public builder returns ``{(x, y, z): full_block_state}`` in local block
coordinates. Origin Y is the first air block above the paving/soil: translate
by Y96 for a paving block at Y95. No builder reads or changes a Minecraft world.
Trees need soil under their trunk; the composer owns foundations, collision
checks, leaf-distance propagation, block connection updates, and placement.
"""
from __future__ import annotations
import json
import math
import random
Position = tuple[int, int, int]
Asset = dict[Position, str]
_DIRECTIONS = ("north", "east", "south", "west")
def _state(material: str, **properties: object) -> str:
suffix = ",".join(
f"{key}={str(value).lower()}" for key, value in sorted(properties.items())
)
return f"minecraft:{material}" + (f"[{suffix}]" if suffix else "")
def _stair(material: str, facing: str) -> str:
return _state(material, facing=facing, half="bottom", shape="straight", waterlogged=False)
def _connections(material: str, *directions: str) -> str:
return _state(material, **{d: d in directions for d in _DIRECTIONS}, waterlogged=False)
def _rotate(asset: Asset, quarter_turns: int) -> Asset:
"""Rotate north to east, including stair backs and fence/bar connections."""
result: Asset = {}
direction = {d: _DIRECTIONS[(i + quarter_turns) % 4] for i, d in enumerate(_DIRECTIONS)}
for (x, y, z), state in asset.items():
for _ in range(quarter_turns):
x, z = -z, x
if "[" in state:
material, raw = state[:-1].split("[", 1)
properties = dict(pair.split("=", 1) for pair in raw.split(","))
properties = {direction.get(k, k): direction.get(v, v) for k, v in properties.items()}
if quarter_turns % 2 and properties.get("axis") in ("x", "z"):
properties["axis"] = "z" if properties["axis"] == "x" else "x"
state = _state(material.removeprefix("minecraft:"), **properties)
result[x, y, z] = state
return result
def conifer(height: int = 13, seed: int = 0) -> Asset:
"""A narrow spruce with irregular connected whorls and three clear trunk rows.
Height is the occupied block count, 11..15. The maximum canopy radius is
three blocks, and foliage begins at local Y3. Leaves deliberately start at
distance=7; recompute distances against the composed world before applying.
"""
if type(height) is not int or not 11 <= height <= 15:
raise ValueError("Conifer height must be an integer from 11 through 15")
if type(seed) is not int:
raise ValueError("Conifer seed must be an integer")
rng = random.Random(seed)
phase = rng.uniform(0, math.tau)
phase2 = rng.uniform(0, math.tau)
leaves = _state("spruce_leaves", distance=7, persistent=True, waterlogged=False)
asset: Asset = {}
for y in range(3, height):
progress = (y - 3) / (height - 4)
tier = (0.30, -0.30, -0.65)[(y - 3) % 3]
radius = max(0.0, 2.95 * (1.0 - progress) ** 0.85 + tier)
if y == height - 1:
radius = 0.0
for x in range(-3, 4):
for z in range(-3, 4):
angle = math.atan2(z, x)
edge = radius + 0.23 * math.cos(4 * angle + phase) + 0.16 * math.sin(3 * angle + phase2)
edge += rng.uniform(-0.10, 0.10)
if (x == 0 and z == 0) or math.hypot(x, z) <= edge:
asset[x, y, z] = leaves
# Leave a connected green leader above the last woody branch; overwriting
# these narrow upper layers with logs creates visible brown pegs at the tip.
for y in range(height - 5):
asset[0, y, 0] = _state("spruce_log", axis="y")
# Angular variation can leave a diagonal-only leaf at a narrow upper tier.
# Keep the face-connected canopy so no isolated foliage floats beside it.
connected = {(0, 0, 0)}
pending = [(0, 0, 0)]
while pending:
x, y, z = pending.pop()
for dx, dy, dz in ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)):
neighbor = (x + dx, y + dy, z + dz)
if neighbor in asset and neighbor not in connected:
connected.add(neighbor)
pending.append(neighbor)
return {position: state for position, state in asset.items() if position in connected}
def bench(length: int = 5, facing: str = "north") -> Asset:
"""A 3..5 block overall bench, including its two stone armrests.
``facing`` is the seated viewer's direction, not Minecraft's stair-facing
property: a north-looking seat has a south-facing stair/high back. The
default occupies X=-2..2, Z=0..1, Y=0; its front opens toward negative Z.
The fence back has Minecraft's 1.5-block collision height. This is decorative
seating and does not add a sit interaction.
"""
if type(length) is not int or not 3 <= length <= 5:
raise ValueError("Bench overall length must be an integer from 3 through 5")
if facing not in _DIRECTIONS:
raise ValueError("Bench facing must be north, east, south, or west")
first = -(length // 2)
last = first + length - 1
asset: Asset = {}
for x in range(first + 1, last):
asset[x, 0, 0] = _stair("spruce_stairs", "south")
asset[x, 0, 1] = _connections("spruce_fence", "north", "east", "west")
for x in (first, last):
asset[x, 0, 0] = _state("stone_bricks")
asset[x, 0, 1] = _state("stone_bricks")
return _rotate(asset, _DIRECTIONS.index(facing))
def lamp(height: int = 6) -> Asset:
"""A slender warm street lamp with a copper hood and a small brass collar.
Height is 6..8 occupied blocks. The stone/chain stem occupies one column;
the 3x3 cross-shaped housing begins at height-3, above pedestrian headroom.
Its hanging lantern has a full copper block immediately above it. The four
inward-facing copper stairs form the hood eaves; no trapdoors are used.
"""
if type(height) is not int or not 6 <= height <= 8:
raise ValueError("Lamp height must be an integer from 6 through 8")
collar_y = height - 3
light_y = height - 2
roof_y = height - 1
asset: Asset = {
(0, 0, 0): _state("chiseled_stone_bricks"),
(0, 1, 0): _state("stone_brick_wall", east="none", north="none", south="none", up=True, waterlogged=False, west="none"),
(0, collar_y, 0): _state("gold_block"),
(0, light_y, 0): _state("lantern", hanging=True, waterlogged=False),
(0, roof_y, 0): _state("waxed_oxidized_cut_copper"),
}
for y in range(2, collar_y):
asset[0, y, 0] = _state("iron_chain", axis="y", waterlogged=False)
for x, z, inward in ((-1, 0, "east"), (1, 0, "west"), (0, -1, "south"), (0, 1, "north")):
asset[x, collar_y, z] = _connections("iron_bars", inward)
asset[x, light_y, z] = _connections("iron_bars")
asset[x, roof_y, z] = _stair("waxed_oxidized_cut_copper_stairs", inward)
return asset
def describe(asset: Asset) -> dict:
"""Compact occupied bounds and ground contact cells for the layout composer."""
if not asset:
raise ValueError("Cannot describe an empty asset")
low = [min(p[i] for p in asset) for i in range(3)]
high = [max(p[i] for p in asset) for i in range(3)]
return {
"blocks": len(asset),
"min": dict(zip(("x", "y", "z"), low)),
"max": dict(zip(("x", "y", "z"), high)),
"size": dict(zip(("x", "y", "z"), (high[i] - low[i] + 1 for i in range(3)))),
"ground_contacts": sorted([x, z] for x, y, z in asset if y == 0),
"materials": sorted({state.split("[", 1)[0] for state in asset.values()}),
}
if __name__ == "__main__":
print(json.dumps({
"coordinate_convention": "Local Y0 is first air above paving; add Y96 over paving Y95.",
"conifer": describe(conifer()),
"bench_north": describe(bench()),
"lamp": describe(lamp()),
}, indent=2))