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.
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user