P23-03: 生活の観察
This commit is contained in:
@@ -0,0 +1,491 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Partial read-only semantic diagnostics. This version cannot accept P23-03.
|
||||
|
||||
Whole typed inputs are preserved. The existing typed codec is reused verbatim.
|
||||
Unimplemented visual/OS checks are explicit blockers, never successful checks.
|
||||
"""
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SPEC = 'docs/spec/acceptance/P23-03.json'
|
||||
SPEC_SHA = '8568457df849ef587c731f072d77244ef5420fc4e1190c5069cfd6cfeea9c89f'
|
||||
CONDITIONS = (
|
||||
'room_occupancy_and_operation', 'queues_and_overflow',
|
||||
'elevator_state_and_passengers', 'staff_state_and_jobs',
|
||||
'supplies_and_carried_goods', 'same_demand_route_improvement',
|
||||
'normal_os_and_distinct_pid_resume',
|
||||
)
|
||||
BLOCKERS = {
|
||||
CONDITIONS[0]: 'Service/rescue occupancy, reason fields and room rectangles checked; rest route selection and badge pixels unfinished.',
|
||||
CONDITIONS[1]: 'Queue lane allocation, overflow and positions checked; screenshot pixel provenance unfinished.',
|
||||
CONDITIONS[2]: 'Shaft/trip and cabin geometry checked; prior-frame interpolation history and passenger geometry unfinished.',
|
||||
CONDITIONS[3]: 'Typed staff/task projection implemented; motion/rest/rescue/cleaning drawing recomputation unfinished.',
|
||||
CONDITIONS[4]: 'Carried inventory ID and logistics report checked; supply badge pixels unfinished.',
|
||||
CONDITIONS[5]: 'Public-command A/B fixture and typed arrival metric added; native execution, definition pins and full command/path provenance checks pending.',
|
||||
CONDITIONS[6]: 'P23 passive request/ACK collector and independent CUA/formal-resume gates unfinished; Root evidence missing.',
|
||||
}
|
||||
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
result = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(result)
|
||||
return result
|
||||
|
||||
|
||||
codec = module('p23_existing_typed_codec', ROOT / 'tools/check_p08_migration_evidence.py')
|
||||
def decode(node):
|
||||
# Keep P08 scalar semantics, adding the renderer's Rect2 and vector keys.
|
||||
if node['type'] == 'Dictionary':
|
||||
result = {}
|
||||
for key, value in node['entries']:
|
||||
key = decode(key)
|
||||
if isinstance(key, list):
|
||||
key = tuple(key)
|
||||
require(key not in result, 'duplicate typed dictionary key')
|
||||
result[key] = decode(value)
|
||||
return result
|
||||
if node['type'] == 'Array':
|
||||
return [decode(value) for value in node['items']]
|
||||
if node['type'] == 'Rect2':
|
||||
match = re.fullmatch(r'Rect2\(([^()]*)\)', node['text'])
|
||||
require(match is not None, 'invalid typed Rect2')
|
||||
values = [float(v.strip()) for v in match[1].split(',')]
|
||||
require(len(values) == 4 and all(math.isfinite(v) for v in values), 'invalid rectangle')
|
||||
return values
|
||||
return codec.decode(node)
|
||||
|
||||
|
||||
def require(value, message):
|
||||
if not value:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def enum_names(root, relative, name):
|
||||
source = (root / relative).read_text()
|
||||
match = re.search(r'\benum\s+' + re.escape(name) + r'\s*\{([^}]+)\}', source)
|
||||
require(match is not None, 'missing source enum ' + name)
|
||||
names = [x.strip() for x in match[1].split(',') if x.strip()]
|
||||
require(all(re.fullmatch(r'[A-Z][A-Z_0-9]*', x) for x in names), 'unsupported enum schema')
|
||||
require(len(set(names)) == len(names), 'duplicate source enum')
|
||||
return names
|
||||
|
||||
|
||||
def projection(rows, mapping, states):
|
||||
result = []
|
||||
ids = set()
|
||||
for row in rows:
|
||||
value = {target: copy.deepcopy(row[source]) for target, source in mapping.items()}
|
||||
require(value['id'] not in ids, 'duplicate actor/task ID')
|
||||
ids.add(value['id'])
|
||||
require(value['state'] in states, 'unknown typed state')
|
||||
result.append(value)
|
||||
return result
|
||||
|
||||
|
||||
def compare_actor_rows(snapshot, report, root=ROOT):
|
||||
runtime = snapshot['systems']['runtime']
|
||||
guest_states = enum_names(root, 'scripts/guest/guest_runtime.gd', 'State')
|
||||
staff_states = enum_names(root, 'scripts/staff/staff_runtime.gd', 'State')
|
||||
task_states = enum_names(root, 'scripts/staff/staff_task.gd', 'State')
|
||||
mappings = (
|
||||
('guests', runtime['guests']['guests'], guest_states,
|
||||
dict(id='guest_id', state='state', cell='current_cell', target='target_facility_id',
|
||||
service='current_service_id', queue='queue_request_id')),
|
||||
('staff', runtime['staff']['staff'], staff_states,
|
||||
dict(id='staff_id', state='state', cell='current_cell', task='current_task_id',
|
||||
facility='assigned_facility_id', role='role_id', carried_inventory_id='carried_inventory_id')),
|
||||
('tasks', runtime['staff']['tasks'], task_states,
|
||||
dict(id='task_id', state='state', type='task_type', staff='assigned_staff_id',
|
||||
target='target_entity_id', rescue_facility='rescue_facility_id')),
|
||||
)
|
||||
for key, rows, states, mapping in mappings:
|
||||
expected = projection(rows, mapping, states)
|
||||
require(report[key] == expected, 'typed projection mismatch: ' + key)
|
||||
return [key for key, *_ in mappings]
|
||||
|
||||
|
||||
def elevator_report(snapshot):
|
||||
saved = snapshot['systems']['runtime']['elevators']
|
||||
rows = []
|
||||
for shaft in saved['shafts']:
|
||||
row = copy.deepcopy(shaft)
|
||||
row.update(shaft['config'])
|
||||
floor = float(shaft['current_floor'])
|
||||
if shaft['phase'] in ('PICKUP', 'TRAVEL'):
|
||||
destination = shaft['boarding_floor'] if shaft['phase'] == 'PICKUP' else shaft['target_floor']
|
||||
progress = 1.0 - shaft['remaining_minutes'] / max(float(shaft['phase_total_minutes']), 1.0)
|
||||
floor = shaft['phase_origin_floor'] + (destination - shaft['phase_origin_floor']) * progress
|
||||
row.update(visual_floor=floor, passenger_count=len(shaft['boarded']),
|
||||
passenger_ids=copy.deepcopy(shaft['boarded']),
|
||||
waiting_count=sum(t['shaft_id'] == shaft['shaft_id'] and t['state'] == 'QUEUED'
|
||||
for t in saved['trips']))
|
||||
rows.append(row)
|
||||
# WorldPreview sorts its report's trip copy for queue placement.
|
||||
trips = sorted(copy.deepcopy(saved['trips']), key=lambda t: (t['enqueued_at'], t['actor_id']))
|
||||
return dict(current_minute=saved['current_minute'], shafts=rows, trips=trips)
|
||||
|
||||
|
||||
def close(actual, expected, label):
|
||||
if isinstance(expected, (float, int)) and not isinstance(expected, bool):
|
||||
require(type(actual) in (int, float) and math.isfinite(actual)
|
||||
and math.isclose(actual, expected, abs_tol=0.0001, rel_tol=0.000001), label)
|
||||
elif isinstance(expected, dict):
|
||||
require(isinstance(actual, dict) and actual.keys() == expected.keys(), label + ' keys')
|
||||
for key in expected:
|
||||
close(actual[key], expected[key], label + '/' + str(key))
|
||||
elif isinstance(expected, list):
|
||||
require(isinstance(actual, list) and len(actual) == len(expected), label + ' length')
|
||||
for a, e in zip(actual, expected):
|
||||
close(a, e, label)
|
||||
else:
|
||||
require(type(actual) is type(expected) and actual == expected, label)
|
||||
|
||||
|
||||
class Transform:
|
||||
"""Independent port of the shared cell/map transform; no report rect input."""
|
||||
def __init__(self, runtime, report):
|
||||
self.bounds = runtime['world']['bounds']
|
||||
b = self.bounds
|
||||
columns, rows = b['max_x'] - b['min_x'] + 1, b['max_y'] - b['min_y'] + 1
|
||||
width, height = report['viewport']
|
||||
require(width > 0 and height > 0 and report['zoom'] > 0, 'camera dimensions')
|
||||
fit = min(max(width - 152, 1) / (columns * 16), max(height - 140, 1) / (rows * 24))
|
||||
pixel = max(math.floor(fit), 1) if fit >= 1 else max(fit, .25)
|
||||
self.dim = [max(math.floor(v * pixel * report['zoom'] + .5), minimum)
|
||||
for v, minimum in ((16, 4), (24, 6))]
|
||||
self.origin = [math.floor((extent - count * dim) / 2 + pan)
|
||||
for extent, count, dim, pan in zip((width, height), (columns, rows), self.dim, report['pan'])]
|
||||
|
||||
def rect(self, cell):
|
||||
b = self.bounds
|
||||
return [self.origin[0] + (cell[0] - b['min_x']) * self.dim[0],
|
||||
self.origin[1] + (b['max_y'] - cell[1]) * self.dim[1], *self.dim]
|
||||
|
||||
def center(self, cell):
|
||||
x, y, w, h = self.rect(cell)
|
||||
return [x + w / 2, y + h / 2]
|
||||
|
||||
def room(self, cells):
|
||||
rects = [self.rect(c) for c in cells]
|
||||
x, y = min(r[0] for r in rects), min(r[1] for r in rects)
|
||||
return [x, y, max(r[0] + r[2] for r in rects) - x, max(r[1] + r[3] for r in rects) - y]
|
||||
|
||||
|
||||
def compare_traffic(snapshot, report):
|
||||
runtime = snapshot['systems']['runtime']
|
||||
transform = Transform(runtime, report)
|
||||
rows = []
|
||||
for saved in sorted(runtime['path']['cell_traffic'], key=lambda r: (r['cell'][1], r['cell'][0])):
|
||||
cell = saved['cell']
|
||||
if cell[2] != report['layer']:
|
||||
continue
|
||||
x, y, w, h = transform.rect(cell)
|
||||
rows.append(dict(cell=cell, traffic={k: v for k, v in saved.items() if k != 'cell'},
|
||||
rect=[x + 2, y + 2, w - 4, h - 4]))
|
||||
close(report['traffic'], rows, 'traffic and cell rect')
|
||||
|
||||
|
||||
def compare_queues(snapshot, report):
|
||||
runtime = snapshot['systems']['runtime']
|
||||
transform = Transform(runtime, report)
|
||||
facilities = codec.indexed(runtime['facilities']['instances'], 'instance_id')
|
||||
requests = codec.indexed(runtime['facilities']['requests'], 'request_id')
|
||||
guests = codec.indexed(runtime['guests']['guests'], 'guest_id')
|
||||
groups = {}
|
||||
def add(key, cell):
|
||||
if cell[2] != report['layer']:
|
||||
return
|
||||
members = groups.setdefault(tuple(cell), [])
|
||||
if key not in members:
|
||||
members.append(key)
|
||||
for facility in facilities.values():
|
||||
for rid in facility['queue_entries']:
|
||||
request = requests.get(rid)
|
||||
if request is None:
|
||||
continue
|
||||
for gid in request['guest_ids']:
|
||||
guest = guests.get(gid)
|
||||
if guest and guest['state'] == 'QUEUING':
|
||||
add('guest/' + gid, guest['current_cell'])
|
||||
elevators = elevator_report(snapshot)
|
||||
for trip in elevators['trips']:
|
||||
if trip['state'] == 'QUEUED':
|
||||
add(trip['actor_id'].replace('guest:', 'guest/', 1).replace('staff:', 'staff/', 1), trip['from'])
|
||||
lift_cells = {tuple(cell) for shaft in elevators['shafts'] for cell in shaft['stops']}
|
||||
walkable = {tuple(cell) for cell in runtime['world']['walkable_cells']}
|
||||
owners = {tuple(cell): row['entity_id'] for row in runtime['world']['occupancies'] for cell in row['cells']}
|
||||
reserved, positions, hidden, badges = set(), {}, {}, []
|
||||
for cell in sorted(groups, key=lambda c: (c[1], c[0])):
|
||||
members = groups[cell]
|
||||
lane = [] if cell in lift_cells else [cell]
|
||||
for direction in (-1, 1):
|
||||
for distance in range(1, 9):
|
||||
target = (cell[0] + direction * distance, cell[1], cell[2])
|
||||
facility = facilities.get(owners.get(target))
|
||||
if target not in walkable or (facility and facility['definition_id'] not in ('guest_corridor', 'service_corridor')):
|
||||
break
|
||||
lane.append(target)
|
||||
index = 0
|
||||
for target in lane:
|
||||
if index >= len(members):
|
||||
break
|
||||
if target in reserved:
|
||||
continue
|
||||
reserved.add(target)
|
||||
positions[members[index]] = transform.center(target)
|
||||
index += 1
|
||||
hidden.update({member: True for member in members[index:]})
|
||||
overflow = len(members) - index
|
||||
rect = transform.rect(cell)
|
||||
badges.append(dict(at=[rect[0] + (-80 if cell in lift_cells else 0), rect[1] - 16],
|
||||
text=f'待ち{len(members)}人' + (f'(他{overflow}人)' if overflow else ''),
|
||||
cell=list(cell), members=members, visible_count=index, overflow=overflow))
|
||||
for key, expected in [('queue_positions', positions), ('queue_hidden', hidden), ('landing_badges', badges)]:
|
||||
close(report[key], expected, key)
|
||||
|
||||
|
||||
def compare_room_services(snapshot, report, root=ROOT):
|
||||
"""Check authoritative service occupants and reasons; rest selection remains pending.
|
||||
|
||||
Extra rest/rescue occupants are checked separately below, never silently
|
||||
counted as service users. This does not claim the missing path-cost proof.
|
||||
"""
|
||||
runtime = snapshot['systems']['runtime']
|
||||
states = enum_names(root, 'scripts/facility/facility_instance.gd', 'State')
|
||||
operations = enum_names(root, 'scripts/facility/facility_instance.gd', 'OperationState')
|
||||
rooms = [r for r in runtime['facilities']['instances'] if r['origin'][2] == report['layer']]
|
||||
treated, carried = {}, {}
|
||||
for task in runtime['staff']['tasks']:
|
||||
if task['task_type'] != 'rescue_transport' or task['state'] in ('COMPLETED', 'CANCELLED', 'FAILED') or not task['assigned_staff_id']:
|
||||
continue
|
||||
if task['state'] == 'WORKING':
|
||||
treated[task['target_entity_id']] = task['rescue_facility_id']
|
||||
else:
|
||||
carried[task['target_entity_id']] = task['assigned_staff_id']
|
||||
close(report['treated_patients'], treated, 'rescue treatment')
|
||||
close(report['carried_patients'], carried, 'rescue carrier')
|
||||
staff = codec.indexed(runtime['staff']['staff'], 'staff_id')
|
||||
all_rooms = codec.indexed(runtime['facilities']['instances'], 'instance_id')
|
||||
for sid, fid in report['resting_staff'].items():
|
||||
require(sid in staff and fid in all_rooms, 'rest reference')
|
||||
member, room = staff[sid], all_rooms[fid]
|
||||
require(member['state'] == 'OFF_DUTY' and member['health'] > 0 and not member['current_task_id'], 'resting staff eligibility')
|
||||
require(room['definition_id'] in ('staff_waiting_room', 'staff_dormitory')
|
||||
and room['state'] in ('OPEN', 'DEGRADED') and not room['suspension_reason'], 'operating rest room')
|
||||
require(set(report['rooms']) == {r['instance_id'] for r in rooms}, 'room identity')
|
||||
for room in rooms:
|
||||
observed = report['rooms'][room['instance_id']]
|
||||
users = sorted({gid for service in room['active_services'] for gid in service['guest_ids']})
|
||||
users += [sid for sid in staff if report['resting_staff'].get(sid) == room['instance_id']]
|
||||
users += [task['target_entity_id'] for task in runtime['staff']['tasks']
|
||||
if task['task_type'] == 'rescue_transport' and task['state'] == 'WORKING'
|
||||
and task['assigned_staff_id'] and task['rescue_facility_id'] == room['instance_id']]
|
||||
require(observed['people'] == users, 'service/rest/rescue occupant list')
|
||||
require(observed['count'] == len(observed['people']), 'room count')
|
||||
operation = 'SUSPENDED' if room['suspension_reason'] else ('OPERATING' if room['state'] in ('OPEN', 'DEGRADED') else 'INACTIVE')
|
||||
expected = dict(state=states.index(room['state']), state_name=room['state'],
|
||||
reasons=room['state_reasons'], operation=operations.index(operation),
|
||||
limit=room['customer_capacity'], staff=room['active_staff_ids'], suspension=room['suspension_reason'])
|
||||
for key, value in expected.items():
|
||||
close(observed[key], value, 'room/' + key)
|
||||
cells = next((r['cells'] for r in runtime['world']['occupancies'] if r['entity_id'] == room['instance_id']), [])
|
||||
if cells:
|
||||
close(observed['rect'], Transform(runtime, report).room(cells), 'room rect')
|
||||
require(observed['definition_id'] == room['definition_id'], 'room definition')
|
||||
|
||||
|
||||
def comparison_records(directory):
|
||||
records = {}
|
||||
for line in (directory / 'manifest.jsonl').read_text().splitlines():
|
||||
row = json.loads(line)
|
||||
if 'object' not in row:
|
||||
continue
|
||||
value = read_object(directory, row['object'])
|
||||
if row['label'] == 'event':
|
||||
continue
|
||||
require(row['label'] not in records, 'duplicate experiment record')
|
||||
records[row['label']] = value
|
||||
return records
|
||||
|
||||
|
||||
def compare_goods(snapshot, report, root=ROOT):
|
||||
runtime = snapshot['systems']['runtime']
|
||||
states = enum_names(root, 'scripts/inventory/logistics_request.gd', 'State')
|
||||
candidates = codec.indexed(json.loads((root / 'data/characters/mvp_staff_candidates.json').read_text())['entries'], 'id')
|
||||
staff = codec.indexed(runtime['staff']['staff'], 'staff_id')
|
||||
facilities = runtime['facilities']['instances']
|
||||
def owner(inventory):
|
||||
return next((f['instance_id'] for f in facilities if f['local_inventory_id'] == inventory), '')
|
||||
rows, active, recent = [], [], []
|
||||
for request in sorted(runtime['logistics_requests'], key=lambda r: r['request_id']):
|
||||
require(request['state'] in states, 'logistics enum')
|
||||
task = next((t for t in runtime['staff']['tasks']
|
||||
if t['target_entity_id'] == request['request_id']
|
||||
and t['task_type'] in ('supply_pickup', 'supply_delivery', 'supply_return')
|
||||
and t['state'] not in ('COMPLETED', 'CANCELLED', 'FAILED')), None)
|
||||
carrier = request['assigned_carrier_id']
|
||||
row = dict(source_facility_id=owner(request['source_inventory_id']),
|
||||
destination_facility_id=owner(request['destination_inventory_id']),
|
||||
carrier_name=candidates.get(carrier, {}).get('display_name', carrier) if carrier in staff else '',
|
||||
reason_codes=[request['state_reason']] if request['state_reason'] else [],
|
||||
request_id=request['request_id'], state=request['state'], reason=request['state_reason'],
|
||||
carrier_id=carrier, source_inventory_id=request['source_inventory_id'],
|
||||
destination_inventory_id=request['destination_inventory_id'],
|
||||
item_requirements=request['item_requirements'], weight=request['total_weight'],
|
||||
retry_count=request['retry_count'], task_id=task['task_id'] if task else '')
|
||||
rows.append(row)
|
||||
if request['state'] in ('COMPLETED', 'CANCELLED'):
|
||||
recent.append((request['state_changed_at'], request['request_id'], row))
|
||||
else:
|
||||
active.append(row)
|
||||
recent.sort(key=lambda r: (r[0], r[1]), reverse=True)
|
||||
close(report['goods'], dict(requests=rows, deliveries=active + [r[2] for r in recent[:10]]), 'goods')
|
||||
|
||||
|
||||
def compare_cabin_geometry(snapshot, report):
|
||||
runtime = snapshot['systems']['runtime']
|
||||
transform = Transform(runtime, report)
|
||||
expected = []
|
||||
p = transform.dim[0] / 16
|
||||
for shaft in elevator_report(snapshot)['shafts']:
|
||||
if not shaft['stops'] or shaft['stops'][0][2] != report['layer']:
|
||||
continue
|
||||
floor = shaft['visual_floor']
|
||||
track = report['elevator_tracks'][shaft['shaft_id']]
|
||||
close(track['floor'], floor, 'authoritative elevator track endpoint')
|
||||
require(track['at'] <= report['animation_time'], 'elevator track time')
|
||||
if not report['paused']:
|
||||
# The prior-frame origin still needs a chained visual-history proof.
|
||||
t = max(0, min(1, (report['animation_time'] - track['at']) / .42))
|
||||
floor = track['from'] + (floor - track['from']) * t
|
||||
x, y, w, h = transform.rect(shaft['stops'][0])
|
||||
baseline = y + h - (floor - shaft['stops'][0][1]) * transform.dim[1] - 3 * p
|
||||
def rounded(v):
|
||||
return math.copysign(math.floor(abs(v) + .5), v)
|
||||
rect = [rounded(x + 4*p), rounded(baseline - 19*p), max(shaft.get('width', 2), 1)*w - 8*p, 19*p]
|
||||
if rect[0] >= report['viewport'][0] or rect[1] >= report['viewport'][1] or rect[0]+rect[2] <= 0 or rect[1]+rect[3] <= 0:
|
||||
continue
|
||||
count, capacity = len(shaft['boarded']), shaft['capacity']
|
||||
badge = f'{count}/{capacity}人'
|
||||
if capacity > 0 and count >= capacity:
|
||||
badge = '満員 ' + badge
|
||||
if not shaft['operational']:
|
||||
badge = '停止 ' + badge
|
||||
expected.append(dict(shaft_id=shaft['shaft_id'], rect=rect, pixel=p, operational=shaft['operational'],
|
||||
doors_open=shaft['operational'] and shaft['phase'] in ('IDLE', 'BOARDING', 'ALIGHTING'),
|
||||
badge_at=[rect[0], rect[1]-14], badge=badge))
|
||||
require(len(report['cabins']) == len(expected), 'cabin count')
|
||||
for row, wanted in zip(report['cabins'], expected):
|
||||
for key, value in wanted.items():
|
||||
close(row[key], value, 'cabin/' + key)
|
||||
|
||||
|
||||
def compare_layouts(a, b):
|
||||
require(a['declaration'] == b['declaration'], 'identical predeclared inputs')
|
||||
declaration = a['declaration']
|
||||
require(declaration['metric'] == 'sum_destination_arrival_minutes_entire_cohort', 'predeclared metric')
|
||||
require(a['initial'] == b['initial'], 'identical seed budget clock definitions and initial state')
|
||||
arrivals = {r['id']: r['offset'] for r in declaration['arrivals']}
|
||||
require(len(arrivals) == len(declaration['arrivals']) and arrivals, 'whole nonempty cohort')
|
||||
builds = []
|
||||
metrics = []
|
||||
for records in (a, b):
|
||||
commands = [records[k] for k in sorted(records) if k.startswith('build-')]
|
||||
require(all(r['result']['code'] == 'OK' for r in commands), 'paid construction succeeded')
|
||||
builds.append([r['payload'] for r in commands])
|
||||
require({k.removeprefix('arrival-') for k in records if k.startswith('arrival-')} == set(arrivals), 'arrival cohort identity')
|
||||
for gid, offset in arrivals.items():
|
||||
row = records['arrival-' + gid]
|
||||
require(row['offset'] == offset and row['result']['code'] == 'OK', 'actual accepted arrival')
|
||||
require(row['payload'] == a['arrival-' + gid]['payload'], 'same actual arrival command')
|
||||
completed = {}
|
||||
first_minute = records['tick-000']['minute']
|
||||
for offset in range(declaration['window'] + 1):
|
||||
tick = records['tick-%03d' % offset]
|
||||
require(tick['minute'] == first_minute + offset, 'contiguous actual clock')
|
||||
for guest in tick['guests']['guests']:
|
||||
gid = guest['guest_id']
|
||||
require(gid in arrivals and offset >= arrivals[gid], 'actual cohort membership')
|
||||
if guest['current_cell'] == declaration['to']:
|
||||
completed.setdefault(gid, offset - arrivals[gid])
|
||||
require(set(completed) == set(arrivals), 'no unfinished or omitted journeys')
|
||||
metrics.append(sum(completed.values()))
|
||||
require(builds[1] == builds[0] + [declaration['improvement']], 'only predeclared layout improvement')
|
||||
require(b['tick-000']['minute'] == a['tick-000']['minute'], 'same observation start')
|
||||
require(metrics[1] < metrics[0], 'strict whole-cohort improvement')
|
||||
return dict(metric=declaration['metric'], A=metrics[0], B=metrics[1])
|
||||
|
||||
|
||||
def read_object(directory, digest):
|
||||
require(isinstance(digest, str) and re.fullmatch('[0-9a-f]{64}', digest), 'invalid object digest')
|
||||
path = directory / 'objects' / (digest + '.json')
|
||||
require(not path.is_symlink() and sha(path) == digest, 'whole typed object hash')
|
||||
typed = json.loads(path.read_bytes())
|
||||
require(typed.get('type') == 'Dictionary', 'whole dictionary required')
|
||||
return decode(typed)
|
||||
|
||||
|
||||
def inspect_stream(directory, root=ROOT):
|
||||
labels = set()
|
||||
checked = []
|
||||
with (directory / 'manifest.jsonl').open() as stream:
|
||||
for line in stream:
|
||||
row = json.loads(line)
|
||||
if 'object' in row:
|
||||
read_object(directory, row['object'])
|
||||
continue
|
||||
require(row['label'] not in labels, 'duplicate streamed label')
|
||||
labels.add(row['label'])
|
||||
require(type(row['pid']) is int and row['pid'] > 0, 'actual diagnostic PID required')
|
||||
state = read_object(directory, row['state'])
|
||||
report = read_object(directory, row['report'])
|
||||
fields = compare_actor_rows(state, report, root)
|
||||
require(report['elevators'] == elevator_report(state), 'typed elevator report mismatch')
|
||||
compare_room_services(state, report, root)
|
||||
compare_queues(state, report)
|
||||
compare_traffic(state, report)
|
||||
compare_goods(state, report, root)
|
||||
compare_cabin_geometry(state, report)
|
||||
checked.append(dict(label=row['label'], fields=fields + ['elevators', 'room_service_fields',
|
||||
'queues', 'traffic', 'goods', 'cabin_geometry']))
|
||||
require(checked, 'empty diagnostic stream')
|
||||
return checked
|
||||
|
||||
|
||||
def condition_matrix():
|
||||
return [dict(condition=name, status='blocked', reason=BLOCKERS[name]) for name in CONDITIONS]
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--diagnostics', type=Path)
|
||||
parser.add_argument('--layout-a', type=Path)
|
||||
parser.add_argument('--layout-b', type=Path)
|
||||
args = parser.parse_args(argv)
|
||||
# Registration hashes the immutable JSON after trimming outer whitespace.
|
||||
require(hashlib.sha256((ROOT / SPEC).read_text().strip().encode()).hexdigest() == SPEC_SHA,
|
||||
'immutable spec mismatch')
|
||||
checked = inspect_stream(args.diagnostics.resolve()) if args.diagnostics else []
|
||||
require(bool(args.layout_a) == bool(args.layout_b), 'both layout streams required')
|
||||
comparison = compare_layouts(comparison_records(args.layout_a), comparison_records(args.layout_b)) if args.layout_a else None
|
||||
print(json.dumps(dict(task_id='P23-03', status='blocked', semantic_diagnostics=checked,
|
||||
layout_comparison=comparison, condition_matrix=condition_matrix(), root_evidence='pending'), ensure_ascii=False))
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user