Files
minecraft-builder-mcp/scripts/verify-trapdoor-contact.py
T

120 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Read-only trapdoor panel/contact checks for actual or overlaid block readers.
Pass a ``state_reader(x, y, z)`` returning canonical block data. Each fixture is
``{'pos': [x,y,z], 'support': [x,y,z]}`` for an adjacent support cell, or uses
``'edge': 'south'`` for an intended edge within the trapdoor's own cell.
Support materials and partial support shapes must be validated by the caller;
this check measures contact with that cell's face and rejects an empty cell.
Shape source: pinned Paper 26.2 TrapDoorBlock.getShape / static initializer,
Block.boxZ(16,13,16), Shapes.rotateAll. An open NORTH trapdoor occupies the
SOUTH 3/16 strip. HALF affects closed panels only. No world/file/network writes.
>>> world = {(0, 0, 0): 'minecraft:spruce_trapdoor[facing=east,half=bottom,open=true]', (1, 0, 0): 'minecraft:spruce_planks'}
>>> read = lambda x, y, z: world.get((x,y,z), 'minecraft:air')
>>> check_contact(read, (0,0,0), support=(1,0,0))['gap_blocks']
0.8125
>>> world[(0,0,0)] = 'minecraft:spruce_trapdoor[open=true,facing=west,half=top]'
>>> check_contact(read, (0,0,0), support=(1,0,0))['passed']
True
>>> check_contact(read, (0,0,0), edge='north')['passed']
False
>>> world[(0,0,0)] = 'minecraft:iron_trapdoor[open=false,facing=south,half=top]'
>>> check_contact(read, (0,0,0), edge='up')['passed']
True
"""
NORMALS = {'west': (-1, 0, 0), 'east': (1, 0, 0),
'down': (0, -1, 0), 'up': (0, 1, 0),
'north': (0, 0, -1), 'south': (0, 0, 1)}
THICKNESS = 3 / 16
AIR = {'minecraft:air', 'minecraft:cave_air', 'minecraft:void_air'}
def panel_bounds(state):
"""Return local AABB minimum/maximum and its thickness axis (X=0,Y=1,Z=2)."""
name, separator, raw = state.partition('[')
if not name.startswith('minecraft:') or not name.endswith('_trapdoor') or not separator or not raw.endswith(']'):
raise ValueError('Expected explicit trapdoor block data')
values = dict(part.split('=', 1) for part in raw[:-1].split(','))
if values.get('open') == 'true':
# Bounds are in block units; the panel sits opposite its facing.
strips = {'north': (2, 1 - THICKNESS, 1), 'south': (2, 0, THICKNESS),
'east': (0, 0, THICKNESS), 'west': (0, 1 - THICKNESS, 1)}
if values.get('facing') not in strips:
raise ValueError('Open trapdoor needs a horizontal facing')
axis, start, end = strips[values['facing']]
elif values.get('open') == 'false' and values.get('half') in {'top', 'bottom'}:
axis = 1
start, end = (1 - THICKNESS, 1) if values['half'] == 'top' else (0, THICKNESS)
else:
raise ValueError('Trapdoor needs explicit open and valid half properties')
minimum, maximum = [0., 0., 0.], [1., 1., 1.]
minimum[axis], maximum[axis] = start, end
return minimum, maximum, axis
def _position(pos):
result = tuple(pos)
if len(result) != 3 or any(type(value) is not int for value in result):
raise ValueError('Position must contain three integer block coordinates')
return result
def check_contact(state_reader, pos, *, support=None, edge=None):
"""Measure a panel's gap/alignment to a requested edge or adjacent cell face.
``passed`` requires the broad panel face to be parallel and flush. Merely
touching with a thin side edge does not pass. A support fixture also requires
a nonair observed support; this is not a full support collision-shape audit.
"""
pos = _position(pos)
if (support is None) == (edge is None):
raise ValueError('Specify exactly one adjacent support or intended edge')
support_state = None
if support is not None:
support = _position(support)
delta = tuple(target - origin for origin, target in zip(pos, support))
edge = next((name for name, normal in NORMALS.items() if normal == delta), None)
if edge is None:
raise ValueError('Support must share one face with the trapdoor cell')
support_state = state_reader(*support)
if edge not in NORMALS:
raise ValueError('Unknown intended edge')
state = state_reader(*pos)
minimum, maximum, thickness_axis = panel_bounds(state)
normal = NORMALS[edge]
axis = next(i for i, value in enumerate(normal) if value)
gap = minimum[axis] if normal[axis] < 0 else 1 - maximum[axis]
parallel = axis == thickness_axis
support_present = support_state is None or support_state.partition('[')[0] not in AIR
result = {'pos': list(pos), 'state': state, 'edge': edge,
'panel_bounds': {'min': minimum, 'max': maximum},
'panel_parallel_to_face': parallel, 'gap_blocks': gap,
'passed': parallel and gap == 0 and support_present}
if support is not None:
result.update(support=list(support), support_state=support_state,
support_present=support_present)
return result
def audit_contacts(state_reader, fixtures):
"""Return a compact summary and per-fixture geometric evidence."""
checks = []
for fixture in fixtures:
result = check_contact(state_reader, fixture['pos'], support=fixture.get('support'), edge=fixture.get('edge'))
if 'name' in fixture:
result['name'] = fixture['name']
checks.append(result)
failures = [check for check in checks if not check['passed']]
return {'passed': not failures, 'checked': len(checks), 'failed': len(failures),
'max_gap_blocks': max((check['gap_blocks'] for check in checks), default=0),
'checks': checks}
if __name__ == '__main__':
import doctest
result = doctest.testmod()
raise SystemExit(bool(result.failed))