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.
290 lines
16 KiB
Python
290 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""Read-only candidate and observed-volume QA for the arrival-square balustrade.
|
|
|
|
Checks stable capped-wall states, cardinal continuity, grounded footings, original
|
|
road masks, preserved garden fixtures and remaining walkable floor. Actual mode
|
|
also compares every captured voxel against baseline plus the desired overlay.
|
|
"""
|
|
import argparse
|
|
from collections import Counter, deque
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SPEC = importlib.util.spec_from_file_location('balustrade_plaza_qa', ROOT / 'scripts/verify-plaza.py')
|
|
plaza = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(plaza)
|
|
survey = plaza.survey
|
|
N = {'east': (1, 0), 'north': (0, -1), 'south': (0, 1), 'west': (-1, 0)}
|
|
CAP = 'minecraft:smooth_sandstone_slab[type=bottom,waterlogged=false]'
|
|
|
|
|
|
def coordinates(document):
|
|
result = {}
|
|
for row in document['blocks']:
|
|
at = tuple(survey.point(row)[axis] for axis in ('x', 'y', 'z'))
|
|
if at in result:
|
|
raise ValueError(f'Duplicate desired position {at}')
|
|
result[at] = row['block']
|
|
return result
|
|
|
|
|
|
def stable_wall_properties(connected, capped=True):
|
|
"""Vanilla 26.2 WallBlock under a full bottom face (the bottom slab cap).
|
|
|
|
Cap coverage makes arms tall. shouldRaisePost suppresses the post for either
|
|
opposite tall pair only after its endpoint/corner/T asymmetry condition.
|
|
"""
|
|
ns = connected['north'] != connected['south']
|
|
ew = connected['east'] != connected['west']
|
|
opposite = ((connected['north'] and connected['south']) or
|
|
(connected['east'] and connected['west']))
|
|
up = ns or ew or not opposite
|
|
return {**{name: ('tall' if capped else 'low') if connected[name] else 'none' for name in N},
|
|
'up': str(up).lower(), 'waterlogged': 'false'}
|
|
|
|
|
|
def wall_neighbor(state):
|
|
name, _ = plaza.parse(state)
|
|
return name.endswith('_wall') or name in plaza.STATIC_CUBES
|
|
|
|
|
|
def check_guard(reader, metadata):
|
|
columns = set(map(tuple, metadata['fence_columns']))
|
|
piers = set(map(tuple, metadata['pier_columns']))
|
|
failures, listed = [], []
|
|
if not columns or not piers <= columns:
|
|
raise ValueError('Fence columns and piers must form a nonempty valid set')
|
|
for index, raw in enumerate(metadata['path_runs']):
|
|
run = list(map(tuple, raw))
|
|
if len(run) < 2 or run[0] not in piers or run[-1] not in piers:
|
|
failures.append({'reason': 'run_needs_pier_endpoints', 'run': index})
|
|
for a, b in zip(run, run[1:]):
|
|
if abs(a[0] - b[0]) + abs(a[1] - b[1]) != 1:
|
|
failures.append({'reason': 'noncardinal_guard_gap', 'from': a, 'to': b})
|
|
listed.extend(run)
|
|
if len(listed) != len(set(listed)) or set(listed) != columns:
|
|
failures.append({'reason': 'path_runs_do_not_cover_fence_once'})
|
|
for x, z in sorted(columns):
|
|
state = reader.state(x, 96, z)
|
|
material, props = plaza.parse(state)
|
|
if (x, z) in piers:
|
|
if material != 'cut_sandstone':
|
|
failures.append({'at': [x, 96, z], 'reason': 'pier_material', 'actual': state})
|
|
else:
|
|
connections = {name: wall_neighbor(reader.state(x + dx, 96, z + dz))
|
|
for name, (dx, dz) in N.items()}
|
|
expected = stable_wall_properties(connections)
|
|
if material != 'stone_brick_wall' or props != expected:
|
|
failures.append({'at': [x, 96, z], 'reason': 'unstable_wall_state',
|
|
'actual': state, 'expected_properties': expected})
|
|
if reader.state(x, 97, z) != CAP:
|
|
failures.append({'at': [x, 97, z], 'reason': 'missing_continuous_bottom_cap'})
|
|
if plaza.parse(reader.state(x, 95, z))[0] not in plaza.STATIC_CUBES:
|
|
failures.append({'at': [x, 95, z], 'reason': 'guard_without_full_base'})
|
|
for footing in metadata['footing_columns']:
|
|
x, z, bottom = footing['x'], footing['z'], footing['support_y']
|
|
if (x, z) not in columns or not 70 <= bottom < 95:
|
|
raise ValueError('Unexpected footing coordinate or support height')
|
|
if plaza.parse(reader.state(x, bottom, z))[0] == 'grass_block':
|
|
failures.append({'at': [x, bottom, z], 'reason': 'grass_under_opaque_footing_will_decay_to_dirt'})
|
|
for y in range(bottom, 96):
|
|
if plaza.parse(reader.state(x, y, z))[0] not in plaza.STATIC_CUBES:
|
|
failures.append({'at': [x, y, z], 'reason': 'footing_gap_or_unknown_support'})
|
|
# A newly connected pier also changes the reciprocal side of an old road
|
|
# rail. Inspect real neighbors, including walls absent from compiler masks.
|
|
adjacent = {(x + dx, z + dz) for x, z in columns for dx, dz in N.values()
|
|
if (x + dx, z + dz) not in columns and
|
|
plaza.parse(reader.state(x + dx, 96, z + dz))[0] == 'stone_brick_wall'}
|
|
for x, z in sorted(adjacent):
|
|
above = reader.state(x, 97, z)
|
|
if above not in survey.AIR and above != CAP:
|
|
failures.append({'at': [x, 97, z], 'reason': 'unknown_neighbor_rail_top_shape', 'actual': above})
|
|
continue
|
|
connected = {name: wall_neighbor(reader.state(x + dx, 96, z + dz))
|
|
for name, (dx, dz) in N.items()}
|
|
actual = reader.state(x, 96, z)
|
|
expected = stable_wall_properties(connected, above == CAP)
|
|
if plaza.parse(actual)[1] != expected:
|
|
failures.append({'at': [x, 96, z], 'reason': 'unstable_reciprocal_road_rail',
|
|
'actual': actual, 'expected_properties': expected})
|
|
pending, components = set(columns), []
|
|
while pending:
|
|
seen, queue = set(), deque([next(iter(pending))])
|
|
while queue:
|
|
p = queue.popleft()
|
|
if p not in pending:
|
|
continue
|
|
pending.remove(p)
|
|
seen.add(p)
|
|
queue.extend((p[0] + dx, p[1] + dz) for dx, dz in N.values())
|
|
components.append(len(seen))
|
|
if sorted(components) != sorted(len(run) for run in metadata['path_runs']):
|
|
failures.append({'reason': 'actual_cardinal_components_differ_from_runs', 'components': components})
|
|
return {'passed': not failures, 'columns': len(columns), 'piers': len(piers),
|
|
'cardinal_components': sorted(components), 'grounded_footings': len(metadata['footing_columns']),
|
|
'adjacent_road_rails': len(adjacent),
|
|
'failures': failures}
|
|
|
|
|
|
def captured_voxels(snapshot):
|
|
for cell in snapshot.cells.values():
|
|
lo, hi, index = cell['min'], cell['max'], 0
|
|
for y in range(lo['y'], hi['y'] + 1):
|
|
for z in range(lo['z'], hi['z'] + 1):
|
|
for x in range(lo['x'], hi['x'] + 1):
|
|
yield (x, y, z), cell['palette'][cell['indices'][index]]
|
|
index += 1
|
|
|
|
|
|
def compare_volume(before, after, desired):
|
|
mismatches, counts = [], Counter()
|
|
for at, baseline in captured_voxels(before):
|
|
changed = at in desired
|
|
counts['desired_voxels' if changed else 'outside_desired_voxels'] += 1
|
|
actual, expected = after.state(*at), desired.get(at, baseline)
|
|
if actual != expected:
|
|
counts['desired_mismatches' if changed else 'outside_desired_mismatches'] += 1
|
|
if len(mismatches) < 30:
|
|
mismatches.append({'at': list(at), 'expected': expected, 'actual': actual,
|
|
'inside_desired': changed})
|
|
if counts['desired_voxels'] != len(desired):
|
|
raise ValueError('Desired blocks extend outside the captured baseline volume')
|
|
return {'passed': not mismatches, **dict(counts), 'mismatch_examples': mismatches,
|
|
'scope_note': 'Every voxel inside the supplied before snapshot; no claim for unobserved exterior voxels.'}
|
|
|
|
|
|
def audit(before, layout, metadata, garden_plan, garden_metadata, garden_walk, nav, after=None):
|
|
scope = survey.scope_of(layout['scope'])
|
|
if any(value != scope for value in (before.scope, metadata['scope'], garden_plan['scope'],
|
|
garden_metadata['scope'], garden_walk['scope'])) or (after and after.scope != scope):
|
|
raise ValueError('Project/world/epoch differs between inputs')
|
|
desired, failures = coordinates(layout), []
|
|
for row in layout['blocks']:
|
|
at = tuple(row[a] for a in ('x', 'y', 'z'))
|
|
if before.state(*at) != row['expected']:
|
|
raise ValueError(f'Stale expected block at {at}')
|
|
fence = set(map(tuple, metadata['fence_columns']))
|
|
road = {tuple(p) for route in nav['routes'] for p in route['clear_cells']}
|
|
bench = set(map(tuple, garden_metadata['bench_access_columns']))
|
|
for x, y, z in desired:
|
|
if (x, z) in road or (x, z) in bench:
|
|
failures.append({'at': [x, y, z], 'reason': 'protected_road_or_bench_column_edited'})
|
|
if fence & (road | bench):
|
|
failures.append({'reason': 'fence_declared_on_protected_road_or_bench'})
|
|
reader = plaza.Reader(after or before, None if after else desired)
|
|
guard = check_guard(reader, metadata)
|
|
old_desired = coordinates(garden_plan)
|
|
protected = {at: state for at, state in old_desired.items()
|
|
if plaza.parse(state)[0] in plaza.FLOWERS | {'spruce_stairs', 'spruce_fence', 'lantern',
|
|
'iron_chain', 'iron_bars', 'gold_block'} or any(part in state for part in ('_leaves', '_log', 'waxed_oxidized_cut_copper'))}
|
|
for fixture in garden_metadata['fixtures']:
|
|
if fixture['type'] in ('conifer', 'topiary'):
|
|
at = fixture['x'], 95, fixture['z']
|
|
protected[at] = before.state(*at)
|
|
lost = [{'at': list(at), 'expected': state, 'actual': reader.state(*at)}
|
|
for at, state in protected.items() if reader.state(*at) != state]
|
|
fixture_report = plaza.check_fixtures(reader, old_desired, garden_metadata)
|
|
blocked = set(map(tuple, garden_metadata['unwalkable_columns'])) | fence
|
|
points = [p for p in garden_walk['points'] if (p['x'], p['z']) not in fence]
|
|
walking = survey.verify_walkable(reader, points)
|
|
navigation = json.loads(json.dumps(nav))
|
|
for cell in navigation['cells']:
|
|
if (cell['x'], cell['z']) in blocked:
|
|
cell['clear'] = False
|
|
nav_report = plaza.navigation.audit(navigation)
|
|
graph = plaza.navigation.surface_graph(plaza.navigation.normalize_cells(navigation))
|
|
reached = plaza.navigation.reachable(graph, (0, 9))
|
|
bench_report = [plaza.navigation.endpoint_report(f'bench-access-{x}-{z}', (x, z), graph, reached)
|
|
for x, z in sorted(bench)]
|
|
nav_report['bench_access'] = bench_report
|
|
nav_report['passed'] &= all(row['passed'] for row in bench_report)
|
|
volume = compare_volume(before, after, desired) if after else None
|
|
passed = (not failures and not lost and guard['passed'] and fixture_report['passed'] and
|
|
walking['passed'] and nav_report['passed'] and (volume is None or volume['passed']))
|
|
return {'version': 1, 'scope': scope, 'mode': 'actual_after_snapshot' if after else 'candidate_overlay',
|
|
'passed': passed, 'world_edits': 0, 'desired_blocks': len(desired), 'guard': guard,
|
|
'protected_road_columns': len(road), 'protection_failures': failures,
|
|
'preserved_fixture_states': len(protected), 'lost_fixtures': lost[:30],
|
|
'fixtures': fixture_report, 'walking': walking, 'navigation': nav_report,
|
|
'volume': volume, 'note': 'Point-sampled body headroom and navigation; not a full moving-player collision simulation.'}
|
|
|
|
|
|
class BalustradeTests(unittest.TestCase):
|
|
def test_stable_capped_wall_corner_and_straight_and_cross(self):
|
|
for sides, up in [({'north', 'south'}, 'false'), ({'east', 'west'}, 'false'),
|
|
(set(N), 'false'), ({'north', 'east'}, 'true'),
|
|
({'north', 'east', 'south'}, 'true'), ({'north'}, 'true')]:
|
|
self.assertEqual(stable_wall_properties({n: n in sides for n in N})['up'], up)
|
|
uncapped = stable_wall_properties({n: n in {'east', 'west'} for n in N}, False)
|
|
self.assertEqual((uncapped['east'], uncapped['west'], uncapped['up']), ('low', 'low', 'false'))
|
|
|
|
def test_volume_detects_unrelated_change_and_exact_properties(self):
|
|
before = type('Snapshot', (), {'cells': {0: {'min': dict(x=0, y=0, z=0),
|
|
'max': dict(x=1, y=0, z=0), 'palette': ['minecraft:air'], 'indices': [0, 0]}}})()
|
|
states = {(0, 0, 0): CAP, (1, 0, 0): 'minecraft:stone'}
|
|
after = type('After', (), {'state': lambda self, x, y, z: states[x, y, z]})()
|
|
result = compare_volume(before, after, {(0, 0, 0): CAP})
|
|
self.assertFalse(result['passed'])
|
|
self.assertEqual(result['outside_desired_mismatches'], 1)
|
|
states[(1, 0, 0)] = 'minecraft:air'
|
|
self.assertTrue(compare_volume(before, after, {(0, 0, 0): CAP})['passed'])
|
|
states[(0, 0, 0)] = CAP.replace('bottom', 'top')
|
|
self.assertEqual(compare_volume(before, after, {(0, 0, 0): CAP})['desired_mismatches'], 1)
|
|
|
|
def test_diagonal_run_is_rejected(self):
|
|
states = {(x, y, z): 'minecraft:air' for x in range(-1, 3) for z in range(-1, 3) for y in range(95, 98)}
|
|
for x, z in [(0, 0), (1, 1)]:
|
|
states[x, 95, z] = 'minecraft:stone'
|
|
states[x, 96, z] = 'minecraft:cut_sandstone'
|
|
states[x, 97, z] = CAP
|
|
reader = type('Reader', (), {'state': lambda self, x, y, z: states[x, y, z]})()
|
|
meta = {'fence_columns': [[0, 0], [1, 1]], 'pier_columns': [[0, 0], [1, 1]],
|
|
'path_runs': [[[0, 0], [1, 1]]], 'footing_columns': []}
|
|
result = check_guard(reader, meta)
|
|
self.assertIn('noncardinal_guard_gap', {f['reason'] for f in result['failures']})
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
stage, garden = ROOT / '.runtime/balustrade-stage04', ROOT / '.runtime/plaza-stage03'
|
|
parser.add_argument('--before', type=Path, default=stage / 'before-full.json.gz')
|
|
parser.add_argument('--after', type=Path)
|
|
parser.add_argument('--layout', type=Path, default=stage / 'balustrade.json')
|
|
parser.add_argument('--metadata', type=Path, default=stage / 'balustrade.metadata.json')
|
|
parser.add_argument('--garden-plan', type=Path, default=garden / 'plaza-polished.json')
|
|
parser.add_argument('--garden-metadata', type=Path, default=garden / 'plaza-polished.metadata.json')
|
|
parser.add_argument('--garden-walk', type=Path, default=garden / 'plaza-polished.walk.json')
|
|
parser.add_argument('--navigation', type=Path, default=ROOT / '.runtime/foundations-stage02/navigation-verified-walkable-input.json')
|
|
parser.add_argument('--report', type=Path, default=stage / 'candidate-qa.json')
|
|
parser.add_argument('--self-test', action='store_true')
|
|
args = parser.parse_args()
|
|
if args.self_test:
|
|
result = unittest.TextTestRunner().run(unittest.defaultTestLoader.loadTestsFromTestCase(BalustradeTests))
|
|
raise SystemExit(0 if result.wasSuccessful() else 1)
|
|
paths = {key: getattr(args, key) for key in ('layout', 'metadata', 'garden_plan', 'garden_metadata', 'garden_walk', 'navigation')}
|
|
documents = [json.loads(path.read_text()) for path in paths.values()]
|
|
before = survey.load_snapshot(args.before)
|
|
after = survey.load_snapshot(args.after, before.scope) if args.after else None
|
|
report = audit(before, *documents, after=after)
|
|
paths['before'] = args.before
|
|
if args.after:
|
|
paths['after'] = args.after
|
|
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({key: report[key] for key in ('passed', 'mode', 'desired_blocks', 'guard', 'protection_failures',
|
|
'lost_fixtures', 'fixtures', 'volume')}
|
|
| {'walk_points': report['walking']['checked_points'], 'walk_failures': report['walking']['failures'][:10],
|
|
'navigation_passed': report['navigation']['passed'],
|
|
'unreachable_columns': report['navigation']['unreachable_clear_columns'],
|
|
'report': str(args.report.resolve())}, indent=2))
|
|
raise SystemExit(0 if report['passed'] else 1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|