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

239 lines
13 KiB
Python

#!/usr/bin/env python3
"""Independently audit planned foundation navigation on a half-block surface grid.
This is geometry QA, not a Minecraft collision simulator or a live block survey.
Only clear cells are traversable. Four quarter-center samples describe each full
block or straight bottom stair; adjacent samples require <= 0.5-block height change.
"""
import argparse
from collections import Counter, deque
import hashlib
import json
from pathlib import Path
import unittest
def normalize_cells(document):
result = {}
for cell in document['cells']:
if any(type(cell.get(key)) is not int for key in ('x', 'z', 'block_y')):
raise ValueError('Geometry coordinates and block_y must be integers')
if type(cell.get('clear')) is not bool or cell.get('kind') not in ('full', 'stairs'):
raise ValueError('Geometry needs explicit clear and full/stairs kind')
if cell['kind'] == 'stairs' and cell.get('facing') not in ('north', 'east', 'south', 'west'):
raise ValueError('Stairs need an explicit cardinal facing')
at = (cell['x'], cell['z'])
if at in result:
raise ValueError(f'Duplicate geometry cell {at}')
result[at] = cell
return result
def cell_nodes(x, z):
return [(2 * x + i, 2 * z + j) for i in (0, 1) for j in (0, 1)]
def height2(cell, sample):
if cell['kind'] == 'full':
return 2 * cell['block_y'] + 2
ix, iz = sample[0] % 2, sample[1] % 2
facing = cell['facing']
high = ((facing == 'north' and iz == 0) or (facing == 'south' and iz == 1)
or (facing == 'west' and ix == 0) or (facing == 'east' and ix == 1))
return 2 * cell['block_y'] + (2 if high else 1)
def surface_graph(cells):
return {node: height2(cell, node) for (x, z), cell in cells.items() if cell['clear']
for node in cell_nodes(x, z)}
def neighbors(node):
x, z = node
return ((x - 1, z), (x + 1, z), (x, z - 1), (x, z + 1))
def reachable(graph, source):
starts = [p for p in cell_nodes(*source) if p in graph]
if len(starts) != 4:
raise ValueError(f'Navigation source {source} is missing or non-clear')
# One source prevents accidentally joining two disconnected halves of a cell.
queue, reached = deque(starts[:1]), {starts[0]: 0}
while queue:
node = queue.popleft()
for other in neighbors(node):
if other in graph and other not in reached and abs(graph[node] - graph[other]) <= 1:
reached[other] = reached[node] + 1
queue.append(other)
return reached
def at_node(node, height=None):
value = {'x': node[0] / 2 + .25, 'z': node[1] / 2 + .25}
if height is not None:
value['standing_y'] = height / 2
return value
def endpoint_report(name, at, graph, reached):
nodes = cell_nodes(*at)
existing = [p for p in nodes if p in graph]
connected = [p for p in nodes if p in reached]
return {'name': name, 'x': at[0], 'z': at[1], 'surface_samples': len(existing),
'reachable_samples': len(connected), 'passed': len(connected) == 4,
'shortest_surface_route_blocks': min((reached[p] / 2 for p in connected), default=None)}
def route_report(route, cells, graph, reached):
path = [tuple(p) for p in route.get('centerline_4', route['centerline'])]
corridor = {tuple(p) for p in route['corridor']}
clear = {tuple(p) for p in route['clear_cells']}
width = route['clear_width']
if type(width) is not int or width < 1 or width % 2 != 1:
raise ValueError('This cross-section audit requires positive odd clear_width')
missing_corridor = sorted(corridor - cells.keys())
nonclear = sorted(p for p in clear if p not in cells or not cells[p]['clear'])
unreachable = sorted(p for p in clear if any(n not in reached for n in cell_nodes(*p)))
jumps, invalid_steps = [], []
max_boundary_step = 0
for p, q in zip(path, path[1:]):
dx, dz = q[0] - p[0], q[1] - p[1]
if abs(dx) + abs(dz) != 1:
invalid_steps.append({'from': list(p), 'to': list(q)})
continue
# Check both parallel quarter-center lanes across the shared block face.
for a in cell_nodes(*p):
b = (a[0] + dx, a[1] + dz)
if (b[0] // 2, b[1] // 2) != q:
continue
if a not in graph or b not in graph:
jumps.append({'from': at_node(a), 'to': at_node(b), 'reason': 'missing_clear_surface'})
continue
step = abs(graph[a] - graph[b]) / 2
max_boundary_step = max(max_boundary_step, step)
if step > .5:
jumps.append({'from': at_node(a, graph[a]), 'to': at_node(b, graph[b]),
'height_change': step, 'reason': 'height_step_exceeds_half_block'})
cross_sections, narrow = [], []
for i, p in enumerate(path):
# Use at least one corridor-width of tangent support. Inserted cardinal
# elbows near an axis-clipped endpoint must not rotate the cross-section
# to measure longitudinally past that deliberate terminal boundary.
window = max(6, width)
before, after = path[max(0, i - window)], path[min(len(path) - 1, i + window)]
tx, tz = after[0] - before[0], after[1] - before[1]
normal = (0, 1) if abs(tx) > abs(tz) else (1, 0)
radius = width // 2
samples = [(p[0] + normal[0] * offset, p[1] + normal[1] * offset)
for offset in range(-radius, radius + 1)]
present = sum(s in cells for s in samples)
usable = sum(s in cells and cells[s]['clear'] for s in samples)
connected = sum(all(n in reached for n in cell_nodes(*s)) for s in samples)
cross_sections.append((present, usable, connected))
if min(present, usable, connected) < width:
narrow.append({'x': p[0], 'z': p[1], 'normal': list(normal), 'required_width': width,
'structural_columns': present, 'clear_columns': usable,
'reachable_columns': connected,
'problem_columns': [list(s) for s in samples if s not in cells or not cells[s]['clear']
or any(n not in reached for n in cell_nodes(*s))]})
ends = [endpoint_report(route['id'] + ':' + name, at, graph, reached)
for name, at in [('start', path[0]), ('end', path[-1])]]
passed = not (missing_corridor or nonclear or unreachable or jumps or invalid_steps or narrow)
return {'id': route['id'], 'passed': passed, 'declared_clear_width': width,
'corridor_columns': len(corridor), 'missing_corridor_columns': missing_corridor,
'declared_clear_columns': len(clear), 'nonclear_declared_columns': nonclear,
'unreachable_declared_columns': unreachable,
'cross_sections': len(cross_sections),
'minimum_structural_cross_section': min(row[0] for row in cross_sections),
'minimum_clear_cross_section': min(row[1] for row in cross_sections),
'minimum_reachable_cross_section': min(row[2] for row in cross_sections),
'narrow_cross_sections': narrow, 'centerline_invalid_steps': invalid_steps,
'centerline_maximum_boundary_step': max_boundary_step,
'centerline_jumps': jumps, 'endpoints': ends}
def audit(document, source=(0, 9), station=(-6, -105)):
cells = normalize_cells(document)
graph = surface_graph(cells)
reached = reachable(graph, source)
unreachable = sorted(p for p, cell in cells.items() if cell['clear']
and any(n not in reached for n in cell_nodes(*p)))
routes = [route_report(route, cells, graph, reached) for route in document['routes']]
goal = endpoint_report('clock-station', station, graph, reached)
return {'version': 1, 'source': {'x': source[0], 'z': source[1]}, 'world_edits': 0,
'method': 'Four quarter-center surface samples per clear cell; cardinal half-block BFS, rise/drop <= 0.5 block.',
'limitations': 'Planned surface topology only. No headroom, body-width collision, material state or live-world verification. Width checks are cardinal cross-sections using the dominant local tangent; full declared masks are also checked.',
'geometry_sha256': hashlib.sha256(json.dumps(document, sort_keys=True, separators=(',', ':')).encode()).hexdigest(),
'planned_columns': len(cells), 'clear_columns': sum(c['clear'] for c in cells.values()),
'surface_samples': len(graph), 'reachable_samples': len(reached),
'unreachable_clear_columns': [list(p) for p in unreachable],
'unreachable_by_group': dict(Counter(cells[p].get('group', 'unknown') for p in unreachable)),
'station': goal, 'routes': routes,
'passed': not unreachable and goal['passed'] and all(r['passed'] for r in routes)}
class GeometryAuditTests(unittest.TestCase):
def test_full_block_jump_separates_components(self):
cells = {(x, 0): {'kind': 'full', 'block_y': 10 if x == 0 else 11, 'clear': True}
for x in range(2)}
graph = surface_graph(cells)
self.assertEqual(len(reachable(graph, (0, 0))), 4)
def test_stairs_connect_two_levels_for_all_directions(self):
for facing, direction in [('north', (0, -1)), ('south', (0, 1)), ('west', (-1, 0)), ('east', (1, 0))]:
with self.subTest(facing=facing):
dx, dz = direction
cells = {(-dx, -dz): {'kind': 'full', 'block_y': 9, 'clear': True},
(0, 0): {'kind': 'stairs', 'block_y': 10, 'facing': facing, 'clear': True},
(dx, dz): {'kind': 'full', 'block_y': 10, 'clear': True}}
graph = surface_graph(cells)
self.assertEqual(len(reachable(graph, (-dx, -dz))), 12)
def test_nonclear_surface_is_never_traversable(self):
graph = surface_graph({(0, 0): {'kind': 'full', 'block_y': 10, 'clear': False}})
self.assertFalse(graph)
def test_missing_width_column_is_reported(self):
cells = {(x, z): {'x': x, 'z': z, 'kind': 'full', 'block_y': 10, 'clear': True}
for x in range(-1, 2) for z in range(3)}
del cells[(-1, 1)]
graph = surface_graph(cells)
route = {'id': 'test', 'centerline': [(0, 0), (0, 1), (0, 2)],
'corridor': list(cells), 'clear_cells': list(cells), 'clear_width': 3}
report = route_report(route, cells, graph, reachable(graph, (0, 0)))
self.assertFalse(report['passed'])
self.assertEqual(report['minimum_structural_cross_section'], 2)
self.assertEqual(report['narrow_cross_sections'][0]['problem_columns'], [[-1, 1]])
def main():
root = Path(__file__).resolve().parents[2]
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--geometry', type=Path, default=root / '.runtime/foundation-study/design/geometry.json')
parser.add_argument('--report', type=Path, default=root / '.runtime/foundation-study/design/navigation-qa.json')
parser.add_argument('--source', type=int, nargs=2, default=(0, 9), metavar=('X', 'Z'))
parser.add_argument('--station', type=int, nargs=2, default=(-6, -105), metavar=('X', 'Z'))
parser.add_argument('--self-test', action='store_true')
args = parser.parse_args()
if args.self_test:
result = unittest.TextTestRunner().run(unittest.defaultTestLoader.loadTestsFromTestCase(GeometryAuditTests))
raise SystemExit(0 if result.wasSuccessful() else 1)
report = audit(json.loads(args.geometry.read_text()), tuple(args.source), tuple(args.station))
args.report.parent.mkdir(parents=True, exist_ok=True)
args.report.write_text(json.dumps(report, indent=2) + '\n')
print(json.dumps({'passed': report['passed'], 'clear_columns': report['clear_columns'],
'reachable_samples': report['reachable_samples'], 'surface_samples': report['surface_samples'],
'unreachable_clear_columns': len(report['unreachable_clear_columns']),
'unreachable_by_group': report['unreachable_by_group'], 'station': report['station'],
'routes': [{'id': r['id'], 'passed': r['passed'],
'minimum_clear_width': r['minimum_clear_cross_section'],
'narrow_sections': len(r['narrow_cross_sections']),
'centerline_jumps': len(r['centerline_jumps']),
'unreachable_columns': len(r['unreachable_declared_columns'])} for r in report['routes']],
'report': str(args.report.resolve())}, indent=2))
raise SystemExit(0 if report['passed'] else 1)
if __name__ == '__main__':
main()