Files
minecraft-builder-mcp/scripts/verify-zone-containment.py
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

385 lines
20 KiB
Python

#!/usr/bin/env python3
"""Read-only proof of a closed, observed full-cube Minecraft zone enclosure.
The membrane is derived independently from an extruded XZ mask minus explicit
excluded voxels: every six-neighbor exterior shell voxel must be a full cube. An
exterior flood treats every unknown/partial block as empty, overestimating escape
routes. Swept 0.6 x 1.8 player AABB probes additionally test cardinal/diagonal
crossings, half-block stair rises, floor drops and roof ascent. These sample
probes supplement the complete membrane proof; they are not the proof itself.
This verifies static continuous movement, not spectator, commands, breaking,
plugin teleportation, or arbitrary discontinuous teleport/pearl behavior.
"""
import argparse
from collections import Counter, deque
import hashlib
import importlib.util
import itertools
import json
import math
from pathlib import Path
import unittest
ROOT = Path(__file__).resolve().parents[1]
SPEC = importlib.util.spec_from_file_location('containment_base', ROOT / 'scripts/verify-balustrade.py')
base = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(base)
survey, plaza = base.survey, base.plaza
FULL = plaza.STATIC_CUBES | {'barrier'}
N2 = ((1, 0), (-1, 0), (0, 1), (0, -1))
N3 = ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1))
WIDTH, HEIGHT = .6, 1.8
def full_cube(state):
return plaza.parse(state)[0] in FULL
def mask_geometry(metadata):
raw = metadata['interior_columns']
if (not isinstance(raw, list) or not raw or any(not isinstance(p, list) or len(p) != 2
or any(type(v) is not int for v in p) for p in raw)):
raise ValueError('interior_columns must be nonempty integer [x,z] pairs')
interior = set(map(tuple, raw))
if len(interior) != len(raw):
raise ValueError('Duplicate interior column')
floor, roof = metadata['floor_y'], metadata['roof_y']
if type(floor) is not int or type(roof) is not int or roof - floor < 4:
raise ValueError('Integer floor_y and roof_y need at least three free interior rows')
shell = {(x + dx, z + dz) for x, z in interior for dx, dz in N2} - interior
footprint = interior | shell
reached, queue = set(), deque([next(iter(interior))])
while queue:
p = queue.popleft()
if p in reached or p not in interior:
continue
reached.add(p)
queue.extend((p[0] + dx, p[1] + dz) for dx, dz in N2)
if reached != interior:
raise ValueError('Interior mask must be one cardinally connected component')
for key in ('wall_columns', 'shell_columns'):
if ('interior_excluded_voxels' not in metadata and key in metadata
and set(map(tuple, metadata[key])) != shell):
raise ValueError(f'Claimed {key} differ from the independently derived complete shell')
bounds = ((min(x for x, z in footprint) - 1, floor - 1, min(z for x, z in footprint) - 1),
(max(x for x, z in footprint) + 1, roof + 1, max(z for x, z in footprint) + 1))
if math.prod(hi - lo + 1 for lo, hi in zip(*bounds)) > survey.MAX_VOXELS:
raise ValueError('Enclosure verification exceeds the bounded snapshot voxel limit')
raw_excluded = metadata.get('interior_excluded_voxels', [])
if (not isinstance(raw_excluded, list) or any(not isinstance(p, list) or len(p) != 3
or any(type(v) is not int for v in p) for p in raw_excluded)):
raise ValueError('interior_excluded_voxels must contain integer [x,y,z] triples')
excluded = set(map(tuple, raw_excluded))
if len(excluded) != len(raw_excluded):
raise ValueError('Duplicate excluded interior voxel')
volume = {(x, y, z) for x, z in interior for y in range(floor + 1, roof)}
if not excluded <= volume:
raise ValueError('Excluded voxel is outside the base extruded interior')
volume -= excluded
if not volume:
raise ValueError('Excluded voxels remove the whole enclosure interior')
membrane_voxels = {(x + dx, y + dy, z + dz) for x, y, z in volume for dx, dy, dz in N3} - volume
return interior, shell, footprint, floor, roof, bounds, volume, membrane_voxels, excluded
def membrane(reader, geometry):
interior, _, _, floor, roof, _ = geometry[:6]
failures, materials, counts = [], Counter(), Counter()
for x, y, z in sorted(geometry[7]):
kind = ('floor' if y == floor else 'roof' if y == roof else
'wall' if (x, z) not in interior else 'folded_boundary')
counts[kind] += 1
state = reader.state(x, y, z)
materials[plaza.parse(state)[0]] += 1
if not full_cube(state):
failures.append({'at': [x, y, z], 'part': kind, 'actual': state})
return {'passed': not failures, 'required_voxels': sum(counts.values()), 'parts': dict(counts),
'observed_materials': dict(materials), 'nonfull_voxels': len(failures), 'examples': failures[:30],
'method': 'Every voxel in N6(interior volume) minus interior volume must be an observed full collision cube.'}
def conservative_exterior_flood(reader, geometry, source):
lo, hi = geometry[5]
blocked, passable, unknown = set(), set(), Counter()
boundary = []
for x in range(lo[0], hi[0] + 1):
for y in range(lo[1], hi[1] + 1):
for z in range(lo[2], hi[2] + 1):
p = x, y, z
state = reader.state(*p)
if full_cube(state):
blocked.add(p)
continue
passable.add(p)
if state not in survey.AIR:
unknown[plaza.parse(state)[0]] += 1
if any(v in (low, high) for v, low, high in zip(p, lo, hi)):
boundary.append(p)
reached, queue = set(boundary), deque(boundary)
while queue:
x, y, z = queue.popleft()
for dx, dy, dz in N3:
p = x + dx, y + dy, z + dz
if p in passable and p not in reached:
reached.add(p)
queue.append(p)
leaks = sorted(reached & geometry[6])
source_cell = tuple(math.floor(v) for v in source)
return {'passed': not leaks, 'observed_voxels': len(blocked) + len(passable),
'exterior_reached_voxels': len(reached), 'interior_reached_from_exterior': len(leaks),
'source_reached_from_exterior': source_cell in reached,
'unknown_or_partial_blocks_treated_as_empty': dict(unknown), 'leak_examples': leaks[:30],
'method': 'Six-neighbor free-voxel exterior flood; only known full collision cubes obstruct it.'}
def segment_box(start, finish, low, high):
"""Slab intersection with the interior of an expanded block AABB.
Face contact is legal: a player's feet resting exactly on paving must not
make every horizontal probe appear blocked before reaching the guard.
"""
enter, leave = 0., 1.
for a, b, lo, hi in zip(start, finish, low, high):
lo, hi = lo + 1e-9, hi - 1e-9
delta = b - a
if abs(delta) < 1e-12:
if a < lo or a > hi:
return False
continue
first, last = sorted(((lo - a) / delta, (hi - a) / delta))
enter, leave = max(enter, first), min(leave, last)
if enter > leave:
return False
return True
def swept_player_hits_full_cube(reader, start, finish):
radius = WIDTH / 2
low = [math.floor(min(start[i], finish[i]) - (radius if i != 1 else 0)) for i in range(3)]
high = [math.floor(max(start[i], finish[i]) + (radius if i != 1 else HEIGHT)) for i in range(3)]
for x in range(low[0], high[0] + 1):
for y in range(low[1], high[1] + 1):
for z in range(low[2], high[2] + 1):
if full_cube(reader.state(x, y, z)) and segment_box(
start, finish, (x - radius, y - HEIGHT, z - radius),
(x + 1 + radius, y + 1, z + 1 + radius)):
return True
return False
def player_probes(reader, geometry, source):
interior, _, _, floor, roof, _ = geometry[:6]
counts, missed = Counter(), []
source_clear = all(reader.state(x, y, z) in survey.AIR
for x in range(math.floor(source[0] - WIDTH / 2), math.floor(source[0] + WIDTH / 2) + 1)
for y in range(math.floor(source[1]), math.ceil(source[1] + HEIGHT))
for z in range(math.floor(source[2] - WIDTH / 2), math.floor(source[2] + WIDTH / 2) + 1))
# Cross each boundary in cardinal and diagonal directions, including half-
# block stair rises/drops. Fixed-height samples also cover free flight rows.
heights = sorted({float(floor + 1), float(source[1]), roof - HEIGHT - .05})
for x, z in sorted(interior):
for dx, dz in itertools.product((-1, 0, 1), repeat=2):
if (dx == dz == 0) or (x + dx, z + dz) in interior:
continue
for y in heights:
for dy in (-.5, 0., .5):
start = (x + .5, y, z + .5)
finish = (x + dx + .5, y + dy, z + dz + .5)
kind = 'diagonal' if dx and dz else 'cardinal'
counts[kind] += 1
if not swept_player_hits_full_cube(reader, start, finish):
missed.append({'kind': kind, 'from': start, 'to': finish})
# Test the locally folded boundary as well as the original outer perimeter.
# The complete membrane check covers every height even when a sample starts
# in an already obstructed partial-height pocket.
for x, y, z in sorted(geometry[8] & geometry[7]):
for dx, dy, dz in N3:
p = x + dx, y + dy, z + dz
if p not in geometry[6]:
continue
start, finish = (p[0] + .5, float(p[1]), p[2] + .5), (x + .5, float(y), z + .5)
counts['folded_boundary'] += 1
if not swept_player_hits_full_cube(reader, start, finish):
missed.append({'kind': 'folded_boundary', 'from': start, 'to': finish})
for kind, start, finish in [('floor', (source[0], floor + 1.1, source[2]),
(source[0], floor - .5, source[2])),
('roof', (source[0], roof - HEIGHT - .1, source[2]),
(source[0], roof - .5, source[2]))]:
counts[kind] += 1
if not swept_player_hits_full_cube(reader, start, finish):
missed.append({'kind': kind, 'from': start, 'to': finish})
return {'passed': source_clear and not missed, 'player_width': WIDTH, 'player_height': HEIGHT,
'source_headroom_observed_air': source_clear, 'swept_crossing_probes': dict(counts),
'unblocked_probes': len(missed), 'examples': missed[:30],
'note': 'Supplementary swept-AABB boundary probes; the independently derived full membrane is the complete static containment criterion.'}
def volume_components(reader, geometry, source):
"""Report geometric components; every one is still checked for containment."""
pending, components = set(geometry[6]), []
source_cell = tuple(math.floor(v) for v in source)
while pending:
queue, count, nonfull, contains_source = deque([next(iter(pending))]), 0, 0, False
while queue:
p = queue.popleft()
if p not in pending:
continue
pending.remove(p)
count += 1
nonfull += not full_cube(reader.state(*p))
contains_source |= p == source_cell
queue.extend((p[0] + dx, p[1] + dy, p[2] + dz) for dx, dy, dz in N3)
components.append({'voxels': count, 'nonfull_voxels': nonfull, 'contains_source': contains_source})
return {'components': sorted(components, key=lambda c: c['voxels'], reverse=True),
'source_in_volume': any(c['contains_source'] for c in components),
'note': 'Disconnected sealed components are reported, not treated as escapes. The entire required membrane and exterior flood are checked.'}
def preserved_decor(before, reader, desired):
protected, failures = 0, []
for at, state in base.captured_voxels(before):
name, _ = plaza.parse(state)
if (name not in plaza.FLOWERS | {'lantern', 'spruce_fence', 'spruce_stairs', 'iron_chain'}
and not any(suffix in name for suffix in ('_leaves', '_log', 'waxed_oxidized_cut_copper'))):
continue
protected += 1
if reader.state(*at) != state:
failures.append({'at': list(at), 'before': state, 'after': reader.state(*at), 'in_plan': at in desired})
return {'passed': not failures, 'protected_observed_states': protected, 'changed_states': len(failures),
'examples': failures[:30]}
def audit(before, layout, metadata, after=None):
scope = survey.scope_of(layout['scope'])
if scope != before.scope or scope != metadata['scope'] or (after and after.scope != scope):
raise ValueError('Project/world/epoch differs between containment inputs')
desired = base.coordinates(layout)
for row in layout['blocks']:
p = tuple(row[a] for a in ('x', 'y', 'z'))
if before.state(*p) != row['expected']:
raise ValueError(f'Expected plan state differs from observed baseline at {p}')
source = tuple(metadata['source'][axis] for axis in ('x', 'y', 'z'))
if any(type(v) not in (int, float) or not math.isfinite(v) for v in source):
raise ValueError('Source must contain finite x/y/z player feet coordinates')
geometry = mask_geometry(metadata)
if tuple(math.floor(v) for v in source) not in geometry[6] or not geometry[3] < source[1] < geometry[4] - HEIGHT:
raise ValueError('Source is outside the enclosure interior')
reader = plaza.Reader(after or before, None if after else desired)
closed = membrane(reader, geometry)
flood = conservative_exterior_flood(reader, geometry, source)
probes = player_probes(reader, geometry, source)
components = volume_components(reader, geometry, source)
decor = preserved_decor(before, reader, desired)
volume = base.compare_volume(before, after, desired) if after else None
return {'version': 1, 'scope': scope, 'world_edits': 0,
'mode': 'actual_after_snapshot' if after else 'candidate_overlay',
'desired_blocks': len(desired), 'interior_columns': len(geometry[0]),
'independently_derived_wall_columns': len(geometry[1]),
'interior_voxels': len(geometry[6]), 'interior_excluded_voxels': len(geometry[8]),
'volume_components': components,
'membrane': closed, 'exterior_flood': flood, 'player_probes': probes,
'preserved_decor': decor, 'volume': volume,
'passed': all(r['passed'] for r in (closed, flood, probes, decor)) and (volume is None or volume['passed']),
'limitations': 'Static continuous collision containment for normal nonspectator players only. No protection against spectator, breaking/removing blocks, operator commands, plugin teleports or arbitrary discontinuous teleport/pearl behavior. Actual snapshots are sequential, not atomic.'}
class ContainmentTests(unittest.TestCase):
def scene(self):
meta = {'interior_columns': [[0, 0], [1, 0], [0, 1], [1, 1]], 'floor_y': 0, 'roof_y': 5}
geo = mask_geometry(meta)
states = {}
for x, z in geo[2]:
for y in ([0, 5] if (x, z) in geo[0] else range(6)):
states[x, y, z] = 'minecraft:barrier'
reader = type('Reader', (), {'state': lambda self, x, y, z: states.get((x, y, z), 'minecraft:air')})()
return meta, geo, states, reader
def test_complete_shell_blocks_flight_diagonals_stairs_and_drops(self):
_, geo, _, reader = self.scene()
self.assertTrue(membrane(reader, geo)['passed'])
self.assertTrue(conservative_exterior_flood(reader, geo, (.5, 1., .5))['passed'])
self.assertTrue(player_probes(reader, geo, (.5, 1., .5))['passed'])
def test_one_missing_wall_or_roof_voxel_leaks(self):
for at in [(-1, 2, 0), (0, 5, 0), (0, 0, 0)]:
_, geo, states, reader = self.scene()
del states[at]
self.assertFalse(membrane(reader, geo)['passed'])
self.assertFalse(conservative_exterior_flood(reader, geo, (.5, 1., .5))['passed'])
def test_floor_need_not_extend_under_exterior_wall(self):
_, geo, states, reader = self.scene()
for x, z in geo[1]:
states.pop((x, 0, z), None)
self.assertTrue(membrane(reader, geo)['passed'])
self.assertTrue(conservative_exterior_flood(reader, geo, (.5, 1., .5))['passed'])
def test_inward_fold_preserves_partial_stair_outside_and_missing_fold_leaks(self):
meta, _, states, reader = self.scene()
meta['interior_excluded_voxels'] = [[0, 2, 0]]
geo = mask_geometry(meta)
states[-1, 2, 0] = 'minecraft:stone_brick_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]'
states[0, 2, 0] = 'minecraft:barrier'
self.assertNotIn((-1, 2, 0), geo[7])
self.assertTrue(membrane(reader, geo)['passed'])
self.assertTrue(conservative_exterior_flood(reader, geo, (1.5, 1., 1.5))['passed'])
self.assertTrue(player_probes(reader, geo, (1.5, 1., 1.5))['passed'])
del states[0, 2, 0]
self.assertFalse(membrane(reader, geo)['passed'])
self.assertFalse(conservative_exterior_flood(reader, geo, (1.5, 1., 1.5))['passed'])
def test_exclusions_must_be_unique_voxels_inside_original_volume(self):
meta, _, _, _ = self.scene()
for exclusions in [[[0, 2, 0], [0, 2, 0]], [[100, 2, 100]], [[0, 0, 0]]]:
meta['interior_excluded_voxels'] = exclusions
with self.assertRaises(ValueError):
mask_geometry(meta)
def test_stair_or_slab_cannot_substitute_for_solid_membrane(self):
_, geo, states, reader = self.scene()
states[-1, 2, 0] = 'minecraft:stone_brick_slab[type=bottom,waterlogged=false]'
self.assertFalse(membrane(reader, geo)['passed'])
self.assertFalse(conservative_exterior_flood(reader, geo, (.5, 1., .5))['passed'])
states[-1, 2, 0] = 'minecraft:stone'
self.assertTrue(membrane(reader, geo)['passed'])
def test_swept_aabb_detects_diagonal_corner_and_vertical_collision(self):
reader = type('Reader', (), {'state': lambda self, x, y, z: 'minecraft:barrier' if (x, y, z) == (1, 1, 1) else 'minecraft:air'})()
self.assertTrue(swept_player_hits_full_cube(reader, (.5, 1., .5), (2.5, 1., 2.5)))
self.assertFalse(swept_player_hits_full_cube(reader, (.2, 1., .2), (.2, 2., .2)))
def test_standing_contact_with_floor_does_not_mask_an_open_horizontal_route(self):
reader = type('Reader', (), {'state': lambda self, x, y, z: 'minecraft:stone' if y == 0 else 'minecraft:air'})()
self.assertFalse(swept_player_hits_full_cube(reader, (.5, 1., .5), (2.5, 1., 2.5)))
self.assertTrue(swept_player_hits_full_cube(reader, (.5, 1., .5), (.5, .5, .5)))
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--before', type=Path)
parser.add_argument('--after', type=Path)
parser.add_argument('--layout', type=Path)
parser.add_argument('--metadata', type=Path)
parser.add_argument('--report', type=Path)
parser.add_argument('--self-test', action='store_true')
args = parser.parse_args()
if args.self_test:
result = unittest.TextTestRunner().run(unittest.defaultTestLoader.loadTestsFromTestCase(ContainmentTests))
raise SystemExit(0 if result.wasSuccessful() else 1)
if any(getattr(args, key) is None for key in ('before', 'layout', 'metadata', 'report')):
parser.error('--before, --layout, --metadata and --report are required')
before = survey.load_snapshot(args.before)
after = survey.load_snapshot(args.after, before.scope) if args.after else None
report = audit(before, json.loads(args.layout.read_text()), json.loads(args.metadata.read_text()), after)
paths = {key: getattr(args, key) for key in ('before', 'after', 'layout', 'metadata') if getattr(args, key)}
report['input_sha256'] = {key: hashlib.sha256(path.read_bytes()).hexdigest() for key, path in paths.items()}
args.report.parent.mkdir(parents=True, exist_ok=True)
survey.terrain.save(args.report, report)
print(json.dumps(report, indent=2))
raise SystemExit(0 if report['passed'] else 1)
if __name__ == '__main__':
main()