P08-03: 配置の通常操作受入
This commit is contained in:
@@ -149,7 +149,80 @@ def keyed(rows, key):
|
||||
require(len(result) == len(rows), 'duplicate '+key)
|
||||
return result
|
||||
|
||||
def state(node):
|
||||
def infrastructure(r, facilities, root):
|
||||
"""Runtime separates schema proxies from FacilityService; audit live topology.
|
||||
|
||||
FacilityInfrastructureProjector._projection_from_project_input,
|
||||
_projection_live_matches and _expected_stair_connector are the authority.
|
||||
"""
|
||||
kinds = {'guest_corridor', 'service_corridor', 'staff_door', 'guest_elevator',
|
||||
'service_elevator', 'guest_stairs', 'service_stairs'}
|
||||
proxies = {k:v for k,v in facilities.items() if v['definition_id'] in kinds}
|
||||
projections = keyed(r['facility_infrastructure'].get('projections', []), 'instance_id')
|
||||
require(set(projections) == set(proxies), 'missing/extra infrastructure projection')
|
||||
entities = keyed(r['spatial_subslots'].get('entities', []), 'entity_id')
|
||||
connectors = keyed(r['path'].get('connectors', []), 'id')
|
||||
routes, links, shafts = set(), set(), set()
|
||||
for fid, f in proxies.items():
|
||||
p = projections[fid]; kind = f['definition_id']
|
||||
for field in ('definition_id', 'origin', 'rotation', 'layer', 'source_job_id', 'completed_at'):
|
||||
require(p[field] == f[field], 'infrastructure construction drift '+field)
|
||||
route = 'infra.route:'+fid
|
||||
link = 'infra.connector:'+fid if kind in {'staff_door','guest_stairs','service_stairs'} else ''
|
||||
require(p['kind'] == kind and p['cells'] == f['occupied_cells'] and
|
||||
p['route_entity_id'] == route and p['connector_id'] == link,
|
||||
'wrong infrastructure projection')
|
||||
# Definition inputs are independently read from the unchanged registry resources.
|
||||
definition = (root/'data/facilities/infrastructure'/(kind+'.tres')).read_text()
|
||||
tags = re.findall(r'&"([^"]+)"', re.search(r'^tags = (.+)$', definition, re.M)[1])
|
||||
footprint = [int(v) for v in re.search(r'footprint = Vector2i\((\d+), (\d+)\)', definition).groups()]
|
||||
require(p['definition_tags'] == sorted(tags) and p['footprint'] == footprint and
|
||||
p['pass_through'] == ('pass_through = true' in definition) and
|
||||
p['staff_only'] == (kind in {'service_corridor','staff_door','service_elevator','service_stairs'}),
|
||||
'infrastructure definition drift')
|
||||
canonical = 'projection|'
|
||||
for field in ('instance_id','definition_id','kind','route_entity_id','connector_id','source_job_id'):
|
||||
text = p[field]; canonical += '%d:%s|' % (len(text),text)
|
||||
canonical += 'layer=%d|staff=%d|pass=%d|footprint=%d,%d|rotation=%d|completed=%d|' % (
|
||||
p['layer'],p['staff_only'],p['pass_through'],*p['footprint'],p['rotation'],p['completed_at'])
|
||||
canonical += 'origin=%d,%d,%d|tags=' % tuple(p['origin'])
|
||||
canonical += ''.join('%d:%s;' % (len(t),t) for t in p['definition_tags'])+'|cells='
|
||||
canonical += ''.join('%d,%d,%d;' % tuple(c) for c in p['cells'])
|
||||
require(p['record_fingerprint'] == hashlib.sha256(canonical.encode()).hexdigest(), 'infrastructure fingerprint drift')
|
||||
require(entities.get(route) == {'entity_id':route,'category':'ROUTE','cells':p['cells']}, 'wrong infrastructure ROUTE topology')
|
||||
routes.add(route)
|
||||
if link:
|
||||
upper = p['origin']; lower = [upper[0],upper[1]-1,upper[2]]
|
||||
stair = kind.endswith('stairs')
|
||||
expected = {'id':link,'from':upper if stair else [upper[0],upper[1],0],
|
||||
'to':lower if stair else upper,'cost':150 if stair else 120,
|
||||
'bidirectional':True,'access_id':'staff' if p['staff_only'] else '',
|
||||
'enabled':True,'movement_type':'CONNECTOR','dynamic_cost':0,
|
||||
'operational_state':'OPERATIONAL','cabin_capacity':8,'queue_capacity':256,
|
||||
'cabin_state':'IDLE','current_floor':upper[1],'travel_minutes_per_floor':1,'request_queue':[]}
|
||||
require(connectors.get(link) == expected, 'wrong infrastructure connector topology')
|
||||
if stair:
|
||||
require(lower in r['route_projection']['projected_walkable_cells'], 'missing stair landing')
|
||||
links.add(link)
|
||||
if kind.endswith('elevator'):
|
||||
shafts.add('infra.shaft:'+fid)
|
||||
require({k for k in entities if k.startswith('infra.route:')} == routes, 'extra infrastructure ROUTE topology')
|
||||
require({k for k in connectors if k.startswith('infra.connector:')} == links, 'extra infrastructure connector topology')
|
||||
blockers = keyed(r['path'].get('vertical_walk_blockers', []), 'owner_id')
|
||||
require({k for k in blockers if k.startswith('infra.shaft:')} == shafts, 'wrong infrastructure shaft topology')
|
||||
for fid, p in projections.items():
|
||||
if 'infra.shaft:'+fid in shafts:
|
||||
require(blockers['infra.shaft:'+fid]['cells'] == p['cells'], 'wrong infrastructure shaft cells')
|
||||
if entities or proxies:
|
||||
route_cells = {tuple(c) for e in entities.values() if e['category'] == 'ROUTE' for c in e['cells']}
|
||||
projection = r['route_projection']
|
||||
require(route_cells == {tuple(c) for c in projection['projected_route_cells']}, 'wrong projected ROUTE cells')
|
||||
walkable = route_cells | {tuple(c) for c in projection['base_walkable_cells']}
|
||||
require(walkable == {tuple(c) for c in projection['projected_walkable_cells']} ==
|
||||
{tuple(c['cell']) for c in r['path']['cell_traffic']}, 'wrong projected walkable topology')
|
||||
return proxies
|
||||
|
||||
def state(node, root=HERE.parent):
|
||||
snap = decode(node)
|
||||
require(set(snap) == {'schema_version', 'clock', 'game_state', 'systems'}, 'missing whole snapshot envelope')
|
||||
r = snap['systems']['runtime']
|
||||
@@ -170,7 +243,8 @@ def state(node):
|
||||
require(not seen.intersection(actual), 'overlapping occupancy')
|
||||
seen.update(actual)
|
||||
operational = keyed(r['facilities']['instances'], 'instance_id')
|
||||
require(set(operational) == set(facilities), 'partial demolition/completion')
|
||||
proxies = infrastructure(r, facilities, root)
|
||||
require(set(operational) == set(facilities)-set(proxies), 'partial demolition/completion')
|
||||
for key, row in operational.items():
|
||||
require(row['occupied_cells'] == facilities[key]['occupied_cells'], 'operational cells drift')
|
||||
return snap
|
||||
@@ -209,10 +283,10 @@ def preserve(a, b, results):
|
||||
for key in ('pending', 'in_flight'):
|
||||
require(nq[key] == oq[key], 'refusal queue loss')
|
||||
|
||||
def action_gate(row, typed):
|
||||
def action_gate(row, typed, root=HERE.parent):
|
||||
coordinates(row['position'])
|
||||
before, after = typed(row['before']), typed(row['after'])
|
||||
a, b = state(before), state(after)
|
||||
a, b = state(before, root), state(after, root)
|
||||
require(row['position']['bounds'] == a['systems']['runtime']['world']['bounds'], 'invented world bounds')
|
||||
kind, label = row['kind'], row['id']
|
||||
expected_kind = 'camera' if label == 'pan_zoom' else 'cancel' if label == 'cancel_demolition' else 'demolish' if label in ('demolition','refused_demolition') else 'build'
|
||||
@@ -256,7 +330,7 @@ def action_gate(row, typed):
|
||||
resources_a, resources_b = a['game_state']['resources'], b['game_state']['resources']
|
||||
require(set(resources_a) == set(resources_b) and all(resources_b[k] == v + refund.get(k, 0) for k, v in resources_a.items()), 'demolition refund mismatch')
|
||||
if kind == 'build' and not row['expected']:
|
||||
completed = state(typed(row['completed']))
|
||||
completed = state(typed(row['completed']), root)
|
||||
facilities = completed['systems']['runtime']['construction']['facilities']
|
||||
require(any(f['definition_id'] == row['definition'] and f['origin'] == row['position']['world_cell'] for f in facilities), 'construction completion missing')
|
||||
require(row['report']['can_build'], 'positive support invalid')
|
||||
@@ -426,7 +500,7 @@ def check(project_root, evidence_dir):
|
||||
require(total == budget['needed'] and delta == budget['deltas'], 'diagnostic budget mismatch')
|
||||
for row in doc['actions']:
|
||||
require(row['input_method'] == ('native_os' if native_os else 'handler'), 'fake OS provenance')
|
||||
action_gate(row, typed)
|
||||
action_gate(row, typed, root)
|
||||
if row['kind'] == 'build':
|
||||
wanted_definition = 'standard_room' if row['id'] == 'unsupported' else 'guest_stairs' if row['id'] in ('missing_connection','lower_stair','upper_stair') else 'reception'
|
||||
require(row['definition'] == wanted_definition, 'diagnostic definition substituted')
|
||||
@@ -439,12 +513,12 @@ def check(project_root, evidence_dir):
|
||||
width,height = map(int,re.search(r'^footprint = Vector2i\((\d+), (\d+)\)$',text,re.M).groups())
|
||||
x,y,z = row['position']['world_cell']
|
||||
cells = {(x+dx,y+dy,z) for dx in range(width) for dy in range(height)}
|
||||
completed = state(typed(row['completed']))['systems']['runtime']['construction']['facilities']
|
||||
completed = state(typed(row['completed']), root)['systems']['runtime']['construction']['facilities']
|
||||
matches = [f for f in completed if f['origin'] == [x,y,z] and f['definition_id'] == wanted_definition]
|
||||
require(len(matches) == 1 and {tuple(c) for c in matches[0]['occupied_cells']} == cells, 'production footprint mismatch')
|
||||
rows += doc['actions']
|
||||
checkpoint = typed('writer-checkpoint')
|
||||
state(checkpoint)
|
||||
state(checkpoint, root)
|
||||
formal = typed('writer-formal-model')
|
||||
require(not typed_differences(typed('writer-presave'),formal), 'wrong presave formal model')
|
||||
require(sha(artifact(directory+'/writer-presave.var')) == sha(artifact(directory+'/writer-formal-model.var')), 'wrong raw presave formal model')
|
||||
@@ -573,6 +647,13 @@ def check(project_root, evidence_dir):
|
||||
require(request['world_cell'] == row['position']['world_cell']
|
||||
and request['viewport'] == row['position']['viewport']
|
||||
and request['screen'] == row['position']['screen'], 'OS transformed cell drift')
|
||||
matrix = request['viewport_to_screen']; x,y = request['viewport']
|
||||
require(len(matrix) == 6 and all(math.isfinite(v) for v in matrix), 'invalid OS canvas mapping')
|
||||
require(all(abs(a-b) < 0.001 for a,b in zip(request['screen'],
|
||||
[matrix[0]*x+matrix[2]*y+matrix[4],matrix[1]*x+matrix[3]*y+matrix[5]])), 'OS canvas mapping drift')
|
||||
if row['id'] == 'unsupported' and request['guide_visible']:
|
||||
gx,gy,gw,gh = request['guide_rect']
|
||||
require(not (gx <= x < gx+gw and gy <= y < gy+gh), 'OS guide interception')
|
||||
if request['action'] == 'wheel':
|
||||
wheel_requests.append(request)
|
||||
if row['id'] == 'pan_zoom':
|
||||
|
||||
@@ -13,6 +13,8 @@ import shutil
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import gzip
|
||||
import hashlib
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -331,6 +333,25 @@ class EvidenceTests(unittest.TestCase):
|
||||
self.edit('os-pair/unsupported-1/ack.json', lambda d: d.update(request_sha256='0'*64))
|
||||
self.reject('OS ack request mismatch')
|
||||
|
||||
def test_os_canvas_mapping_drift(self):
|
||||
self.edit('os-pair/unsupported-1/request.json', lambda d: d.update(viewport_to_screen=[1,0,0,1,32,0]))
|
||||
# Repin acknowledgement too: test semantics, rather than checksum rejection.
|
||||
self.edit('os-pair/unsupported-1/ack.json', lambda d: d.update(request_sha256=C.sha(self.out/'os-pair/unsupported-1/request.json')))
|
||||
self.reject('OS canvas mapping drift')
|
||||
|
||||
def test_os_guide_interception(self):
|
||||
self.edit('os-pair/unsupported-1/request.json', lambda d: d.update(guide_visible=True,guide_rect=[0,0,10000,10000]))
|
||||
self.edit('os-pair/unsupported-1/ack.json', lambda d: d.update(request_sha256=C.sha(self.out/'os-pair/unsupported-1/request.json')))
|
||||
self.reject('OS guide interception')
|
||||
|
||||
def test_whole_checker_missing_stair_projection(self):
|
||||
self.edit('pair/writer-lower_stair-completed.json', lambda d: C.typed_member(d,'systems','runtime','facility_infrastructure','projections')['items'].pop())
|
||||
self.reject('missing/extra infrastructure projection')
|
||||
|
||||
def test_whole_checker_wrong_stair_connector(self):
|
||||
self.edit('pair/writer-lower_stair-completed.json', lambda d: C.typed_member(C.typed_member(d,'systems','runtime','path','connectors')['items'][0],'cost').update(text='999'))
|
||||
self.reject('wrong infrastructure connector topology')
|
||||
|
||||
def test_refusal_cannot_be_relabelled(self):
|
||||
self.edit('pair/writer-actions.json', lambda d: d['actions'][4].update(expected='OTHER_REASON'))
|
||||
self.reject('required refusal reason changed')
|
||||
@@ -411,5 +432,70 @@ class EvidenceTests(unittest.TestCase):
|
||||
def test_mock_native_timeout_restores_project_and_cleans_owned_child(self):
|
||||
self.collector_mock(fail_wait=True)
|
||||
|
||||
class RealR05InfrastructureTests(unittest.TestCase):
|
||||
"""Actual native snapshot, never a fabricated acceptance specimen."""
|
||||
def setUp(self):
|
||||
folder = ROOT/'tests/fixtures/p08-03'
|
||||
provenance = json.loads((folder/'r05-reader-checkpoint-provenance.json').read_bytes())
|
||||
raw = gzip.decompress((folder/'r05-reader-checkpoint.json.gz').read_bytes())
|
||||
self.assertEqual(provenance['raw_sha256'],hashlib.sha256(raw).hexdigest())
|
||||
self.assertEqual(provenance['raw_bytes'],len(raw))
|
||||
self.node = json.loads(raw)
|
||||
self.model = C.decode(self.node)
|
||||
|
||||
def test_real_reader_checkpoint(self):
|
||||
self.assertEqual(self.model,C.state(self.node))
|
||||
|
||||
def reject(self, mutate, message):
|
||||
runtime = self.model['systems']['runtime']; mutate(runtime)
|
||||
with self.assertRaisesRegex(ValueError,message): C.state(F.typed(self.model))
|
||||
|
||||
def test_missing_projection(self):
|
||||
self.reject(lambda r:r['facility_infrastructure']['projections'].pop(),'missing/extra infrastructure projection')
|
||||
|
||||
def test_extra_projection(self):
|
||||
def mutate(r):
|
||||
p=copy.deepcopy(r['facility_infrastructure']['projections'][0]); p['instance_id']='ghost'
|
||||
r['facility_infrastructure']['projections'].append(p)
|
||||
self.reject(mutate,'missing/extra infrastructure projection')
|
||||
|
||||
def test_wrong_projected_cells(self):
|
||||
self.reject(lambda r:r['facility_infrastructure']['projections'][0].update(cells=[[9,9,0]]),'wrong infrastructure projection')
|
||||
|
||||
def test_missing_route(self):
|
||||
self.reject(lambda r:r['spatial_subslots']['entities'].pop(),'wrong infrastructure ROUTE topology')
|
||||
|
||||
def test_extra_route(self):
|
||||
self.reject(lambda r:r['spatial_subslots']['entities'].append({'entity_id':'infra.route:ghost','category':'ROUTE','cells':[[8,4,0]]}),'extra infrastructure ROUTE topology')
|
||||
|
||||
def test_wrong_route(self):
|
||||
self.reject(lambda r:r['spatial_subslots']['entities'][0].update(category='UTILITY'),'wrong infrastructure ROUTE topology')
|
||||
|
||||
def test_missing_connector(self):
|
||||
self.reject(lambda r:r['path']['connectors'].pop(0),'wrong infrastructure connector topology')
|
||||
|
||||
def test_extra_connector(self):
|
||||
def mutate(r):
|
||||
c=copy.deepcopy(r['path']['connectors'][0]); c['id']='infra.connector:ghost'; r['path']['connectors'].append(c)
|
||||
self.reject(mutate,'extra infrastructure connector topology')
|
||||
|
||||
def test_wrong_connector(self):
|
||||
self.reject(lambda r:r['path']['connectors'][0].update(to=[8,0,0]),'wrong infrastructure connector topology')
|
||||
|
||||
def test_wrong_projected_walkable(self):
|
||||
self.reject(lambda r:r['route_projection']['projected_walkable_cells'].pop(),'wrong projected walkable topology')
|
||||
|
||||
def test_wrong_path_cells(self):
|
||||
self.reject(lambda r:r['path']['cell_traffic'].pop(),'wrong projected walkable topology')
|
||||
|
||||
def test_ordinary_facility_loss(self):
|
||||
self.reject(lambda r:r['facilities']['instances'].clear(),'partial demolition/completion')
|
||||
|
||||
def test_proxy_occupancy_loss(self):
|
||||
self.reject(lambda r:r['world']['occupancies'].pop(),'orphan/lost occupancy')
|
||||
|
||||
def test_proxy_operational_duplication(self):
|
||||
self.reject(lambda r:r['facilities']['instances'].append(copy.deepcopy(r['construction']['facilities'][0])),'partial demolition/completion')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user