Files
TheTree_mock/tools/check_p23_life_observation_evidence.py
T

1329 lines
76 KiB
Python

#!/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
import os
from pathlib import Path
import re
import sys
import struct
import zlib
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',
)
CATEGORIES = ('room', 'queue', 'EV', 'staff', 'goods', 'business')
CONDITION_CHECKS = dict(zip(CONDITIONS, ('room_users', 'queue_overflow', 'elevator_passengers',
'staff_jobs', 'carried_goods', 'route_improvement', 'distinct_pid_resume')))
# Required names, not verdicts. BLOCKERS is computed from actual execution below.
REQUIRED_CHECKS = {condition: {category: {'semantic_' + category, 'pixels_' + category,
CONDITION_CHECKS[condition], 'os_guards', 'resume_recalculation'}
for category in CATEGORIES} for condition in CONDITIONS}
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 png_pixels(data):
"""Decode noninterlaced 8-bit RGB/RGBA PNG, validating the whole stream."""
require(data[:8] == b'\x89PNG\r\n\x1a\n', 'PNG signature')
pos, header, payload, ended, idat_closed = 8, None, bytearray(), False, False
while pos < len(data):
require(pos + 12 <= len(data), 'PNG truncated chunk')
size, kind = struct.unpack('>I4s', data[pos:pos + 8])
require(pos + size + 12 <= len(data), 'PNG truncated payload')
body = data[pos + 8:pos + 8 + size]
crc = struct.unpack('>I', data[pos + 8 + size:pos + 12 + size])[0]
require(zlib.crc32(kind + body) == crc, 'PNG CRC mismatch')
pos += size + 12
require(header is not None or kind == b'IHDR', 'PNG IHDR must be first')
if kind == b'IHDR':
require(header is None and size == 13, 'PNG duplicate/invalid IHDR')
w, h, depth, color, compression, filtering, interlace = struct.unpack('>IIBBBBB', body)
require(w > 0 and h > 0 and depth == 8 and color in (2, 6)
and (compression, filtering, interlace) == (0, 0, 0), 'PNG unsupported IHDR')
header = (w, h, 3 if color == 2 else 4)
elif kind == b'IDAT':
require(not idat_closed, 'PNG nonconsecutive IDAT')
payload.extend(body)
elif kind == b'IEND':
require(size == 0 and payload and pos == len(data), 'PNG invalid IEND')
ended = True
break
else:
require(kind[0] & 32 or kind == b'PLTE', 'PNG unknown critical chunk')
if payload:
idat_closed = True
require(ended and header is not None, 'PNG missing IEND')
w, h, channels = header
stride = w * channels
expected = h * (stride + 1)
try:
decoder = zlib.decompressobj()
raw = decoder.decompress(bytes(payload), expected + 1)
require(decoder.eof and not decoder.unused_data and not decoder.unconsumed_tail
and len(raw) == expected, 'PNG zlib length/end mismatch')
except zlib.error as exc:
raise ValueError('PNG invalid zlib') from exc
rows, prior = [], bytearray(stride)
for y in range(h):
start = y * (stride + 1)
mode = raw[start]
require(mode in range(5), 'PNG unknown filter')
row = bytearray(raw[start + 1:start + 1 + stride])
for x in range(stride):
left = row[x - channels] if x >= channels else 0
up = prior[x]
corner = prior[x - channels] if x >= channels else 0
if mode == 1:
predictor = left
elif mode == 2:
predictor = up
elif mode == 3:
predictor = (left + up) // 2
elif mode == 4:
p = left + up - corner
distances = (abs(p - left), abs(p - up), abs(p - corner))
predictor = (left, up, corner)[distances.index(min(distances))]
else:
predictor = 0
row[x] = (row[x] + predictor) & 255
rows.append([tuple(row[x:x + channels]) for x in range(0, stride, channels)])
prior = row
return w, h, rows
def image_kind(data):
if data.startswith(b'\x89PNG\r\n\x1a\n'):
w, h, _ = png_pixels(data)
return 'png', w, h
if data.startswith(b'\xff\xd8'):
helper = module('p23_p08_jpeg', ROOT / 'tools/check_p08_normal_placement_evidence.py')
w, h = helper.jpeg_dimensions(data)
return 'jpeg', w, h
raise ValueError('unknown screenshot byte magic')
def inspect_conversion(rec, cua_receipt, converter_sha256=None):
"""CUA receipt and converter pin are independent of conversion assertions."""
raw, png = Path(rec['raw_path']), Path(rec['png_path'])
require(raw.resolve() != png.resolve() and not raw.is_symlink() and not png.is_symlink(),
'conversion must preserve a separate raw file')
require(rec['raw_sha256'] == sha(raw) == cua_receipt['raw_sha256']
and rec['tool_call_id'] == cua_receipt['tool_call_id']
and bool(rec['tool_call_id']), 'CUA raw hash/call mismatch')
require(rec['raw_kind'] == 'jpeg', 'conversion raw kind')
kind, w, h = image_kind(raw.read_bytes())
require(kind == 'jpeg', 'conversion requires JPEG magic')
require(rec['converter'] == '/usr/bin/sips', 'conversion executable')
pin = converter_sha256 if converter_sha256 is not None else sha('/usr/bin/sips')
require(rec['converter_sha256'] == pin, 'converter SHA mismatch')
require(rec['argv'] == ['/usr/bin/sips', '-s', 'format', 'png', str(raw), '--out', str(png)],
'converter argv mismatch')
require(type(rec['exit_code']) is int and rec['exit_code'] == 0, 'converter exit code')
for key in ('stdout_sha256', 'stderr_sha256'):
require(isinstance(rec[key], str) and re.fullmatch('[0-9a-f]{64}', rec[key]),
'invalid converter output digest')
require(sha(png) == rec['png_sha256'], 'converted PNG SHA mismatch')
pw, ph, rows = png_pixels(png.read_bytes())
require((w, h) == (pw, ph) == (rec['width'], rec['height']), 'conversion dimensions mismatch')
return pw, ph, rows
def screenshot_pixels(path, conversion=None, cua_receipt=None, converter_sha256=None):
data = Path(path).read_bytes()
kind, _, _ = image_kind(data)
if kind == 'jpeg':
require(conversion is not None, 'JPEG screenshot requires conversion record')
require(Path(conversion['raw_path']).resolve() == Path(path).resolve(), 'conversion raw path mismatch')
require(cua_receipt is not None, 'missing CUA receipt')
return inspect_conversion(conversion, cua_receipt, converter_sha256)
return png_pixels(data)
def affine_point(matrix, point):
require(len(matrix) == 6 and all(type(v) in (int, float) and math.isfinite(v) for v in matrix), 'invalid affine')
a, b, c, d, x, y = matrix
require(a*d-b*c != 0, 'singular affine')
return [a*point[0]+c*point[1]+x, b*point[0]+d*point[1]+y]
def affine_rect(matrix, rect):
x, y, w, h = rect
points = [affine_point(matrix, p) for p in ([x,y], [x+w,y], [x,y+h], [x+w,y+h])]
lo = [min(p[i] for p in points) for i in (0, 1)]
return lo + [max(p[i] for p in points)-lo[i] for i in (0, 1)]
def compare_rectangles(report, audit, local_to_viewport, viewport_to_screen, local_to_screen, image, root=ROOT):
"""All draw rows, full affine composition, and exact existing style colors.
Font advance is recorded by the pinned engine/font, not approximated using
a new text width threshold. No image similarity threshold is introduced.
"""
for point in ([0, 0], [1, 0], [0, 1]):
close(affine_point(viewport_to_screen, affine_point(local_to_viewport, point)),
affine_point(local_to_screen, point), 'viewport/screen composition')
rows = audit['rows']
expected = []
tower, people, world = 'scripts/ui/pixel_tower_art.gd', 'scripts/ui/pixel_people.gd', 'scenes/main/world_preview.gd'
def actor(a, category=None):
expected.append((category or ('staff' if a['staff'] else 'guest'), a['key'],
a['at'] + [16*a['scale'], 18*a['scale']], people, None))
def badge(category, identifier, at, text):
expected.append((category, identifier, at + [None, 14], world, text))
for fid, room in report['rooms'].items():
if 'rect' in room:
expected.append(('room', fid, room['rect'], tower, None))
for i, b in enumerate(report['landing_badges']):
badge('queue', str(i), b['at'], b['text'])
for cabin in report['cabins']:
expected.append(('EV', cabin['shaft_id'], cabin['rect'], tower, None))
for a in cabin['passengers']:
actor(a)
for a in report['actor_drawings']:
actor(a)
if a['staff'] and a['key'].removeprefix('staff/') in report['resting_staff']:
actor(a, 'rest')
for i, b in enumerate(report['badges']):
badge('goods' if b['kind'] == 'supply' else 'business', str(i), b['at'], b['text'])
require(len(rows) == len(expected), 'rectangle coverage')
width, height, pixels = image
checked = []
for row, (category, identifier, rect, source, text) in zip(rows, expected):
require((row['category'], row['id'], row['style_source']) == (category, identifier, source), 'rectangle row identity')
if text is not None:
require(row['text'] == text and type(row['font_width']) in (int, float) and row['font_width'] > 0, 'badge font/text')
rect[2] = row['font_width'] + 6
close(row['rect'], rect, 'local drawing rectangle')
viewport = affine_rect(local_to_viewport, rect)
screen = affine_rect(viewport_to_screen, viewport)
close(row['viewport_rect'], viewport, 'viewport rectangle')
close(row['screen_rect'], screen, 'screen rectangle')
colors = list(dict.fromkeys(re.findall(r'Color\("([0-9a-fA-F]{6})"', (root/source).read_text())))
require(row['style_colors'] == colors and colors, 'drawing style palette')
x, y, w, h = screen
require(w > 0 and h > 0 and x >= 0 and y >= 0 and x+w <= width and y+h <= height,
'drawing rectangle outside screenshot')
# Badge ink is distinct from its translucent backing. Actor ink/skin
# and facility colors are taken verbatim from their pinned draw source.
palette = {'fff2ce'} if text is not None else set(colors)
palette -= {'303c40', 'c6d3b6', 'd5dec4'} # shadow / sky decorations
rgb = {tuple(bytes.fromhex(c)) for c in palette}
found = any(tuple(pixels[py][px][:3]) in rgb
for py in range(math.ceil(y), math.floor(y+h))
for px in range(math.ceil(x), math.floor(x+w)))
require(found, 'missing drawing pixels: ' + category + '/' + str(identifier))
checked.append(category + '/' + str(identifier))
return checked
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']
# ElevatorService.restore_snapshot validates this exact trip shape (412-438).
# QUEUED means no cabin seat yet, not an absent shaft assignment.
configs = {shaft['config']['shaft_id']: shaft['config'] for shaft in saved['shafts']}
for trip in saved['trips']:
keys = {'actor_id', 'request_id', 'shaft_id', 'connector_id', 'from', 'to',
'enqueued_at', 'state'}
if 'companion_id' in trip:
keys.add('companion_id')
require(isinstance(trip['companion_id'], str) and trip['companion_id'],
'invalid elevator companion identity')
require(set(trip) == keys, 'exact elevator trip keys')
require(trip['shaft_id'] in configs, 'elevator trip shaft attribution')
config = configs[trip['shaft_id']]
require(trip['from'] != trip['to'] and trip['from'] in config['stops']
and trip['to'] in config['stops'], 'elevator trip stops')
low, high = sorted((trip['from'][1], trip['to'][1]))
require(trip['connector_id'] == f"{trip['shaft_id']}.link.{low}.{high}",
'elevator trip connector attribution')
require(trip['state'] in ('QUEUED', 'BOARDED'), 'elevator trip state')
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']),
# get_report() merges config before counting (345-359).
waiting_count=sum(t['shaft_id'] == row['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 room_slots(rect, pixel, definition='staff_waiting_room'):
x, y, w, h = rect
step = 20 if definition in ('cafeteria', 'tavern') else 16
columns = max(int(w / pixel / step), 1)
rows = max(int((h / pixel - (30 if definition == 'chapel' else 10)) / 20), 1)
if definition == 'chapel':
columns = min(columns, 4)
anchors = [[x + (col + .5) * w / columns, y + h - (4 + row * 20) * pixel]
for row in range(rows) for col in range(columns)]
return anchors[:8] if definition == 'chapel' else anchors
def compare_rest(state, report, audit, root=ROOT):
"""Recompute selection, not shortest paths. Shortest-path authority is the
exact pinned PathService source producing the recorded public API paths.
Edge reports must be nonempty, including for temporarily closed cells.
"""
require(audit['path_service_sha256'] == sha(root / 'scripts/world/path_service.gd'),
'rest PathService source pin')
runtime = state['systems']['runtime']
members = runtime['staff']['staff']
facilities = runtime['facilities']['instances']
require([r['staff_id'] for r in audit['staff']] == [r['staff_id'] for r in members],
'rest staff order/coverage')
passable = {tuple(r['cell']) for r in runtime['path']['cell_traffic']}
resting = {}
selections = {}
for member, row in zip(members, audit['staff']):
sid = member['staff_id']
for key in ('state', 'health', 'current_task_id', 'current_cell'):
close(row[key], member[key], 'rest input/' + key)
memberships = [dict(raid_id=raid['raid_id'], state=raid['state'], side=c['side'])
for raid in runtime['defense']['raids'] for c in raid['combatants']
if c['actor_id'] == sid]
close(row['defense_memberships'], memberships, 'rest defense membership')
locked = sid in runtime['events']['actor_locks']
transit = any(t['actor_id'] == 'staff:' + sid for t in runtime['elevators']['trips'])
close(row['actor_lock'], locked, 'rest actor lock')
close(row['in_transit'], transit, 'rest transit')
defense = any(r['state'] not in ('DEFENDED', 'BREACHED', 'DEFENDERS_RETREATED', 'CANCELLED')
and r['side'] == 'DEFENDER' for r in memberships)
eligible = member['state'] == 'OFF_DUTY' and member['health'] > 0 and not member['current_task_id'] and not locked and not defense
selected = dict(reachable=False, resting=False, label='休憩先なし(稼働中の待機室・宿舎と経路を確認)') if eligible else {}
best_cost = 2147483647
require([f['instance_id'] for f in row['facilities']] == [f['instance_id'] for f in facilities],
'rest facility order/coverage')
for facility, observed in zip(facilities, row['facilities']):
for key in ('instance_id', 'definition_id', 'origin', 'occupied_cells'):
close(observed[key], facility[key], 'rest facility/' + key)
operation = 'SUSPENDED' if facility['suspension_reason'] else ('OPERATING' if facility['state'] in ('OPEN', 'DEGRADED') else 'INACTIVE')
close(observed['operation_state'], operation, 'rest operation')
occupied = facility['occupied_cells'] or [facility['origin']]
probes = [dict(cell=[c[0] + dx, c[1], c[2]], has_cell=(c[0] + dx, c[1], c[2]) in passable)
for c in occupied for dx in (0, -1, 1)]
close(observed['probes'], probes, 'rest candidate probes')
ordered = sorted({tuple(p['cell']) for p in probes if p['has_cell']}, key=lambda c: (c[2], c[1], c[0]))
close(observed['ordered'], [list(c) for c in ordered], 'rest candidate order')
require([r['cell'] for r in observed['routes']] == [list(c) for c in ordered], 'rest route coverage/order')
best = None
for route in observed['routes']:
path, edges = route['path'], route['edges']
require(len(edges) == max(len(path) - 1, 0), 'rest edge coverage')
if not path:
require(route['calculate_path_cost'] == -1, 'rest empty route cost')
continue
require(path[0] == member['current_cell'] and path[-1] == route['cell'], 'rest path endpoints')
require(all(tuple(c) in passable for c in path), 'rest path cell absent')
cost = 0
for a, b, edge in zip(path, path[1:], edges):
require(edge and edge['from'] == a and edge['to'] == b, 'rest empty or discontinuous edge report')
require(type(edge['total_cost']) is int and edge['total_cost'] >= 0, 'rest edge cost')
cost += edge['total_cost']
require(cost == route['calculate_path_cost'], 'rest edge sum differs from calculate_path_cost')
if best is None or cost < best[0]:
best = (cost, path[-1])
if not eligible or operation != 'OPERATING' or facility['definition_id'] not in ('staff_waiting_room', 'staff_dormitory') or best is None or best[0] >= best_cost:
continue
best_cost = best[0]
cell = member['current_cell']
at = any(c[2] == cell[2] and c[1] == cell[1] and abs(c[0] - cell[0]) <= 1 for c in occupied) and not transit
selected = dict(reachable=True, resting=at, cell=best[1], facility_id=facility['instance_id'],
label=('宿舎で休憩中' if facility['definition_id'] == 'staff_dormitory' else '待機室で休憩中') if at else '休憩先へ移動中')
close(row['comparison_only'], selected, 'rest selection comparison')
selections[sid] = selected
if selected.get('resting'):
resting[sid] = selected['facility_id']
close(report['resting_staff'], resting, 'draw resting_staff')
transform = Transform(runtime, report)
drawings = {r['key']: r for r in report['actor_drawings']}
for sid, fid in resting.items():
member = next(m for m in members if m['staff_id'] == sid)
if member['current_cell'][2] != report['layer'] or sid in report['carried_patients']:
continue
room = report['rooms'].get(fid, {})
cells = next((r['cells'] for r in runtime['world']['occupancies'] if r['entity_id'] == fid), [])
if not cells:
continue # renderer uses its ordinary tracked fallback
require(sid in room.get('people', []), 'rest room occupant missing')
slot = room['people'].index(sid)
anchors = room_slots(transform.room(cells), transform.dim[0] / 16)
key = 'staff/' + sid
if key in report['boarded'] or key in report['queue_hidden']:
require(key not in drawings, 'hidden resting drawing')
elif slot >= len(anchors):
require(key not in drawings, 'rest slot overflow drawing')
else:
close(drawings[key]['center'], [anchors[slot][0], anchors[slot][1] - transform.dim[1] * .5],
'resting staff position')
return selections
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_badges(state, report):
runtime = state['systems']['runtime']
transform = Transform(runtime, report)
expected = []
labels = dict(BROKEN='破損', NO_ACCESS='通路なし', NO_POWER='魔力なし', NO_STAFF='職員不足',
CLOSED='営業停止', DEGRADED='低下運転', OPEN='営業中')
for facility in runtime['facilities']['instances']:
fid = facility['instance_id']
if facility['origin'][2] != report['layer']: continue
cells = next((r['cells'] for r in runtime['world']['occupancies'] if r['entity_id'] == fid), [])
if not cells: continue
x,y,w,h = transform.room(cells)
room = report['rooms'][fid]
count, limit = room['count'], facility['customer_capacity']
if facility['suspension_reason']:
business = '休止・摘発' if facility['suspension_reason'] == 'RAIDED' else '休止・破壊'
label = business
else:
business = labels.get(facility['state'], facility['state'])
if facility['state'] not in ('OPEN', 'DEGRADED'):
label = '未稼働'
else:
label = f'利用{count}/{limit}人' if limit >= 0 else f'利用{count}人'
if limit > 0 and count >= limit: label = '満員 ' + label
visible = len(room_slots([x,y,w,h], transform.dim[0]/16, facility['definition_id']))
if count > visible: label += f'(他{count-visible}人)'
expected.append(dict(facility=fid, kind='business', at=[x+2,y+2], text=business+' '+label))
requests = [r for r in report['goods']['requests'] if fid in (r['source_facility_id'], r['destination_facility_id'])]
if requests:
states = {}
for r in requests: states[r['state']] = states.get(r['state'], 0)+1
text = '物資依頼 ' + ' / '.join(f'{k}:{states[k]}' for k in sorted(states))
expected.append(dict(facility=fid, kind='supply', at=[x+2,y+16], requests=requests, text=text))
close(report['badges'], expected, 'business/supply badges')
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_motion(state, report, audit):
frames = audit['frames']
require(len(frames) == 2, 'two motion history frames required')
previous, current = frames
require(type(previous['frame']) is int and current['frame'] > previous['frame'], 'motion frame order')
require(current['delta'] >= 0 and current['scaled_delta'] >= 0, 'motion delta')
before, after = previous['display'], current['display']
for key, value in after.items():
close(report[key], value, 'motion report binding/' + key)
close(after['animation_time'], before['animation_time'] + current['scaled_delta'], 'motion animation delta')
if before['paused'] and after['paused']:
close(current['scaled_delta'], 0, 'paused animation moved')
now = after['animation_time']
runtime = state['systems']['runtime']
cells = {'guest/' + r['guest_id']: r['current_cell'] for r in runtime['guests']['guests']}
cells.update({'staff/' + r['staff_id']: r['current_cell'] for r in runtime['staff']['staff']})
def interpolated(track, endpoint, paused, transform=None):
t = 1 if paused else max(0, min(1, (now-track['at'])/.42))
if transform:
a, b = transform.center(track['from']), transform.center(endpoint)
return [a[i] + (b[i]-a[i])*t for i in (0, 1)]
return track['from']+(endpoint-track['from'])*t
for key, track in after['actor_tracks'].items():
require(key in cells and track['cell'] == cells[key], 'motion actor endpoint')
old = before['actor_tracks'].get(key)
if old is None:
wanted = dict(cell=cells[key], **{'from': cells[key]}, at=now, direction=1.0)
elif old['cell'] == cells[key]:
wanted = old
else:
a, b = old['cell'], cells[key]
origin = a if a[2] == b[2] and abs(a[0]-b[0])+abs(a[1]-b[1]) == 1 else b
wanted = dict(cell=b, **{'from': origin}, at=now,
direction=(1.0 if b[0] > a[0] else -1.0) if a[0] != b[0] else old['direction'])
close(track, wanted, 'actor interpolation origin/history')
# Ordinary walking centers; queue/room/rescue/working projections have
# independent semantic placement checks and do not use this center.
drawing = next((a for a in after['actor_drawings'] if a['key'] == key), None)
if drawing and drawing['mode'] in ('walk', 'carry_walk') and key not in report['queue_positions']:
position = interpolated(track, cells[key], after['paused'], Transform(runtime, report))
# Same-cell crowd offsets are handled by the complete actor check.
require(all(math.isfinite(v) for v in position), 'nonfinite interpolation')
floors = {s['shaft_id']: s['visual_floor'] for s in elevator_report(state)['shafts']}
for key, track in after['elevator_tracks'].items():
require(key in floors, 'unknown elevator motion')
old = before['elevator_tracks'].get(key)
if old is None:
wanted = dict(floor=floors[key], **{'from': floors[key]}, at=now)
elif math.isclose(old['floor'], floors[key], abs_tol=.00001, rel_tol=.00001):
wanted = old
else:
wanted = dict(floor=floors[key], **{'from': old['floor']}, at=now)
close(track, wanted, 'elevator interpolation origin/history')
if before['paused'] and after['paused'] and all(before[k] == after[k] for k in ('layer', 'zoom', 'pan', 'viewport', 'actor_tracks', 'elevator_tracks')):
prior_actors = {r['key']: r for r in before['actor_drawings']}
for row in after['actor_drawings']:
old = prior_actors.get(row['key'])
if old and all(old[k] == row[k] for k in ('state', 'mode', 'activity', 'receiving', 'carrying')):
close(row['at'], old['at'], 'paused actor moved')
return ['motion_history', 'interpolation', 'pause']
def compare_actor_geometry(state, report):
runtime = state['systems']['runtime']
transform = Transform(runtime, report)
p, height = transform.dim[0]/16, transform.dim[1]
facilities = codec.indexed(runtime['facilities']['instances'], 'instance_id')
staff = codec.indexed(runtime['staff']['staff'], 'staff_id')
occupancy = {r['entity_id']: r['cells'] for r in runtime['world']['occupancies']}
counts, expected = {}, []
def track_position(key, cell):
track = report['actor_tracks'][key]
close(track['cell'], cell, 'actor authoritative cell')
t = 1 if report['paused'] else max(0, min(1, (report['animation_time']-track['at'])/.42))
a, b = transform.center(track['from']), transform.center(cell)
return [a[i]+(b[i]-a[i])*t for i in (0, 1)]
def offset(cell, center):
key = tuple(cell)
n = counts.get(key, 0)
counts[key] = n+1
return [center[0]+[0,4,-4,2,-2,6,-6][n%7]*p, center[1]-(n%2)*p]
def slot_position(fid, identifier, definition, fallback, dy, missing_fallback=False):
cells = occupancy.get(fid, [])
if not cells:
return fallback
users = report['rooms'].get(fid, {}).get('people', [])
if identifier not in users:
return fallback if missing_fallback else None
anchors = room_slots(transform.room(cells), p, definition)
index = users.index(identifier)
return [anchors[index][0], anchors[index][1]+dy] if index < len(anchors) else None
for guest in runtime['guests']['guests']:
key, cell = 'guest/'+guest['guest_id'], guest['current_cell']
if key in report['boarded'] or key in report['queue_hidden'] or cell[2] != report['layer']:
continue
center = track_position(key, cell)
if key in report['queue_positions']:
center = report['queue_positions'][key]
elif guest['current_service_id']:
fid = guest['target_facility_id']
definition = facilities.get(fid, {}).get('definition_id', '')
cells = occupancy.get(fid, [])
if cells and cells[0][2] == report['layer']:
center = slot_position(fid, guest['guest_id'], definition, center,
3*p-height*.5-(5*p if definition in ('standard_room', 'premium_room') else 0), True)
else:
center = offset(cell, center)
if center is not None:
expected.append((key, center, False, bool(guest['current_service_id']), False, False))
for sid, member in staff.items():
key, cell = 'staff/'+sid, member['current_cell']
if key in report['boarded'] or key in report['queue_hidden'] or cell[2] != report['layer']:
continue
center = track_position(key, cell)
special = True
if sid in report['carried_patients']:
carrier = staff.get(report['carried_patients'][sid])
if carrier:
center = track_position('staff/'+carrier['staff_id'], carrier['current_cell'])
center[1] -= 12*p
elif sid in report['resting_staff']:
center = slot_position(report['resting_staff'][sid], sid, 'staff_waiting_room', center, -height*.5)
elif sid in report['treated_patients']:
center = slot_position(report['treated_patients'][sid], sid, 'infirmary', center, -2*p-height*.5)
else:
special = False
center = offset(cell, center)
fid = member['assigned_facility_id']
cells = occupancy.get(fid, [])
workers = report['rooms'].get(fid, {}).get('staff', [])
if member['state'] in ('WORKING', 'AVAILABLE') and fid in facilities and cell in cells and sid in workers:
rect = transform.room(cells)
columns = max(int(rect[2]/(16*p)), 1)
index = workers.index(sid)
if rect[3] >= 40*p and index < columns:
center = [rect[0]+(index+.5)*rect[2]/columns, rect[1]+26*p-height*.5]
center = report['queue_positions'].get(key, center)
if center is not None:
expected.append((key, center, True, False, bool(member['carried_inventory_id']) and not special, False))
require([r['key'] for r in report['actor_drawings']] == [r[0] for r in expected], 'actor drawing coverage/order')
pairs = list(zip(report['actor_drawings'], expected))
shafts = {s['shaft_id']: s for s in elevator_report(state)['shafts']}
for cabin in report['cabins']:
shaft = shafts[cabin['shaft_id']]
x,y,w,h = cabin['rect']
columns = max(int((w/p-4)/8), 1)
passengers = shaft['boarded'][:columns*2]
require(len(cabin['passengers']) == len(passengers), 'passenger coverage')
for i, (actor_id, row) in enumerate(zip(passengers, cabin['passengers'])):
key = actor_id.replace('staff:', 'staff/', 1).replace('guest:', 'guest/', 1)
is_staff = key.startswith('staff/')
center = [x+(2+(i%columns+.5)*8)*p, y+h-(2+(i//columns)*9)*p+3*p-height*.5]
carried = bool(staff.get(key.removeprefix('staff/'), {}).get('carried_inventory_id')) if is_staff else False
pairs.append((row, (key, center, is_staff, False, carried, True)))
for row, (key, center, is_staff, receiving, carrying, riding) in pairs:
close(row['center'], center, 'actor center/interpolation')
scale = .5 if riding else .75
scale = max(math.floor(p*scale), 1) if p >= 2 else p*scale
close(row['scale'], scale, 'actor scale')
at = [center[0]-8*scale, center[1]+height*.5-3*p-17*scale]
rounded = [math.copysign(math.floor(abs(v)+.5), v) for v in at]
close(row['at'], rounded, 'actor drawing position')
require((row['key'], row['staff'], row['receiving'], row['carrying'], row['riding']) ==
(key, is_staff, receiving, carrying, riding), 'actor drawing attributes')
return ['actor_geometry', 'passenger_geometry']
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'])
close(read_object(directory, row['state_after']), state, 'read-only audit snapshot identity')
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_rest(state, report, read_object(directory, row['rest_audit']), root)
compare_queues(state, report)
compare_traffic(state, report)
compare_goods(state, report, root)
compare_badges(state, report)
compare_cabin_geometry(state, report)
compare_motion(state, report, read_object(directory, row['motion']))
compare_actor_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(executed=None):
executed = executed or {}
result = []
for condition in CONDITIONS:
categories = {}
for category in CATEGORIES:
actual = set(executed.get(condition, {}).get(category, []))
required = REQUIRED_CHECKS[condition][category]
missing = sorted(required - actual)
categories[category] = dict(checks=sorted(actual), missing=missing,
status='passed' if actual and not missing else 'pending')
result.append(dict(condition=condition, categories=categories,
status='passed' if all(c['status'] == 'passed' for c in categories.values()) else 'pending'))
return result
def BLOCKERS(executed=None):
return {r['condition']: {c: d['missing'] for c, d in r['categories'].items() if d['missing']}
for r in condition_matrix(executed) if r['status'] != 'passed'}
CONTRACT = 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json'
PLAN = 'docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md'
def normative_plan_text(root):
"""Requirement text is an input; explicitly labelled progress is metadata."""
text = (root / PLAN).read_text()
section = re.search(r'^### P23[^\n]*\n(.*?)(?=^### |\Z)', text, re.M | re.S)
require(section is not None, 'missing normative #p23 plan section')
lines = []
for line in section.group(0).splitlines():
if line.startswith('現在地:'):
continue
line = line.split(' 状態:', 1)[0].rstrip()
if line:
lines.append(line)
require(any(line.startswith('- P23-03 ') and '受入:' in line for line in lines),
'missing P23-03 work/acceptance plan text')
return '\n'.join(lines) + '\n'
def input_map(root):
names = [SPEC, CONTRACT, 'AGENTS.md', 'docs/traceability/original/BASELINE.txt']
names += [p.relative_to(root).as_posix() for p in (root / 'tests/fixtures/p23-03').rglob('*')
if p.is_file()]
pins = {name: sha(root / name) for name in sorted(names)}
pins[PLAN + '#p23-normative'] = hashlib.sha256(normative_plan_text(root).encode()).hexdigest()
return pins
def input_provenance(root):
# Full-plan progress changes deliberately do not participate in input equality.
return {'full_plan_sha256': sha(root / PLAN)}
def inspect_os_receipt(receipt, source_map, pins=None):
require(receipt.get('engine_ended') is True, 'engine_ended must be true')
require(receipt.get('passed') is True and receipt.get('exit_code') == 0, 'unsuccessful OS process')
require(not receipt.get('error') and receipt.get('guard_errors') == []
and receipt.get('cleanup_errors') == [], 'OS cleanup/guard failure')
require(receipt.get('default_before') == receipt.get('default_after')
and isinstance(receipt.get('default_before'), dict), 'default profile changed')
require(receipt.get('stage_before') == receipt.get('stage_after')
and isinstance(receipt.get('stage_before'), dict), 'stage changed')
require(receipt.get('source_map') == source_map, 'OS source pin differs')
after = receipt.get('source_after', {})
require({name: pin['sha256'] for name, pin in after.items()} == source_map, 'source changed')
cleanup = receipt.get('owned_cleanup')
require(isinstance(cleanup, list) and cleanup
and all(row.get('engine_ended') is True for row in cleanup), 'missing owned cleanup')
require(bool(receipt.get('ordinary_owner_proof')), 'missing ordinary audio owner proof')
for field in ('engine_sha256', 'settings_sha256', 'environment_sha256'):
require(isinstance(receipt.get(field), str) and re.fullmatch('[0-9a-f]{64}', receipt[field])
and receipt[field] == receipt.get(field + '_after'), 'OS pin changed: ' + field)
require(receipt.get('spec_sha256') == SPEC_SHA, 'OS immutable spec')
require(receipt.get('inputs') and receipt['inputs'] == receipt.get('inputs_after'), 'OS inputs changed')
if pins is not None:
require(receipt['inputs'] == pins, 'OS input applicability')
require(receipt.get('environment') and receipt['environment'] == receipt.get('environment_after'), 'OS environment changed')
require(type(receipt.get('actual_pid')) is int and receipt['actual_pid'] > 0, 'OS actual PID')
require(receipt.get('profile') and receipt.get('profile_name') and isinstance(receipt.get('profile_after'), dict), 'OS private profile provenance')
before = receipt.get('profile_before', {})
require(type(before.get('existed')) is bool and isinstance(before.get('files'), dict), 'OS profile before')
for member, pin in before['files'].items():
require(receipt['profile_after'].get(member) == pin, 'OS profile input changed: ' + member)
def inspect_resume(writer, reader, saved, requests, formal_members):
require(type(writer['actual_pid']) is int and type(reader['actual_pid']) is int
and writer['actual_pid'] > 0 and reader['actual_pid'] > 0
and writer['actual_pid'] != reader['actual_pid'], 'resume requires distinct actual PIDs')
require(saved['pid'] == writer['actual_pid'], 'formal writer PID')
require(set(formal_members) == {'world.bin', 'manifest.json', 'thumbnail.png', 'journal.log'}, 'formal members coverage')
close(saved['members'], formal_members, 'formal member hashes')
require({k: v['sha256'] for k, v in reader['formal_input'].items()} == formal_members, 'reader formal input hashes')
loads = [row for row in requests if row['request']['action'] == 'load_menu']
require(len(loads) == 1, 'missing or duplicate normal load ACK')
item = loads[0]
request, ack, observed, cua = [item[k] for k in ('request', 'ack', 'observed', 'cua')]
require(request['target_pid'] == ack['target_pid'] == observed['pid'] == cua['target_pid'] == reader['actual_pid'], 'load ACK PID')
require(request['sequence'] == ack['sequence'] == observed['sequence'], 'load ACK request ID')
require(ack['request_sha256'] == item['request_sha256'] == observed['request_sha256'], 'load ACK hash')
require(0 < request['requested_ticks_usec'] < observed['ack_observed_ticks_usec'], 'load ACK time order')
require(cua['method'] == 'native_os' and cua['tool_call_id'] and cua['timestamp_ns'] > 0, 'load CUA provenance')
require(request['requested_unix_ns'] <= cua['timestamp_ns'] <= ack['timestamp_ns'] <= observed['ack_observed_unix_ns'], 'load external ACK time order')
require(observed['status'] == '手動セーブを読み込みました。', 'normal load result')
return ['distinct_pid', 'formal_members', 'load_ack']
def inspect_os_requests(directory, layout, role, pid, root=ROOT):
pair = directory/'os-pair'/layout
def path(name):
result = directory/name
require(result.resolve().is_relative_to(directory.resolve()) and not result.is_symlink(), 'OS artifact escape')
return result
def document(name):
return json.loads(path(name).read_bytes())
listing = json.loads((pair/(role+'-requests.json')).read_bytes())
require(listing['pid'] == pid and listing['requests'] and len(set(listing['requests'])) == len(listing['requests']), 'OS request list')
bindings = json.loads((pair/(role+'-screenshots.json')).read_bytes())
seen, records, pixel_categories = set(), [], set()
stream = pair/(role+'-stream')
captures = {row['label']: row for row in map(json.loads, (stream/'manifest.jsonl').read_text().splitlines()) if 'report' in row}
prior_tick = -1
for sequence, ack_name in enumerate(listing['requests'], 1):
ack_path = path(ack_name)
request_path = ack_path.with_name('request.json')
observed = json.loads(ack_path.with_name('observed.json').read_bytes())
request, ack = json.loads(request_path.read_bytes()), json.loads(ack_path.read_bytes())
cua = document(ack['input_receipt'])
require(request['sequence'] == ack['sequence'] == observed['sequence'] == sequence, 'OS request sequence')
require(request['target_pid'] == ack['target_pid'] == observed['pid'] == cua['target_pid'] == pid, 'OS request PID')
digest = sha(request_path)
require(ack['request_sha256'] == observed['request_sha256'] == digest, 'OS ACK hash')
require(prior_tick < request['requested_ticks_usec'] < observed['ack_observed_ticks_usec'], 'OS ACK order')
prior_tick = observed['ack_observed_ticks_usec']
require(request['requested_unix_ns'] <= cua['timestamp_ns'] <= ack['timestamp_ns'] <= observed['ack_observed_unix_ns'], 'OS external ACK time order')
require(cua['method'] == 'native_os' and cua['tool_name'] and cua['tool_call_id']
and cua['timestamp_ns'] > 0 and cua['collector_pid'] > 0 and cua['collector_pid'] != pid, 'OS CUA provenance')
require('arguments' in cua and isinstance(cua['arguments'], dict), 'full CUA arguments required')
if request['action'] != 'screenshot':
require(cua['events'] and observed['events'], 'OS input events missing')
if request['action'] == 'wheel':
require(request['ctrl'] is False and any(e.get('ctrl') is False and e.get('pressed') for e in observed['events']), 'ordinary wheel evidence')
report = decode(request['report'])
capture = captures[request_path.parent.name]
close(read_object(stream, capture['report']), report, 'request draw report binding')
for number, shot in enumerate(cua['screenshots']):
matches = [b for b in bindings if b['request'] == str(request_path.relative_to(directory)) and b['screenshot_index'] == number]
require(len(matches) == 1, 'screenshot binding coverage')
binding = matches[0]
identity = (binding['request'], number)
require(identity not in seen, 'duplicate screenshot binding')
seen.add(identity)
require(binding['request_sha256'] == digest and binding['target_pid'] == pid and binding['sequence'] == sequence,
'screenshot request attribution')
require(binding['input_receipt'] == ack['input_receipt'] and binding['input_receipt_sha256'] == sha(path(ack['input_receipt'])), 'screenshot CUA receipt hash')
require(binding['raw_path'] == shot['path'] and binding['raw_sha256'] == shot['raw_sha256'] == sha(path(shot['path']))
and binding['tool_call_id'] == shot['tool_call_id'] and shot['tool_call_id'], 'screenshot raw provenance')
conversion = document(binding['conversion']) if binding['conversion'] else None
image = screenshot_pixels(path(shot['path']), conversion, shot)
require(binding['png_sha256'] == sha(path(binding['png_path'])) and (binding['width'], binding['height']) == image[:2], 'decoded screenshot binding')
if conversion:
require(Path(conversion['png_path']).resolve() == path(binding['png_path']).resolve(), 'converted output binding')
for suffix in ('stdout', 'stderr'):
require(sha(path(binding['conversion']+'.'+suffix)) == conversion[suffix+'_sha256'], 'converter log hash')
else:
require(binding['png_path'] == binding['raw_path'], 'PNG must bind original bytes')
# Screenshot actions observe a stable report; input before/after
# screenshots are provenance only and never relabelled as this frame.
if request['action'] == 'screenshot':
checked = compare_rectangles(report, decode(request['rectangles']), request['local_to_viewport'],
request['viewport_to_screen'], request['local_to_screen'], image, root)
pixel_categories.update(c.split('/')[0] for c in checked)
records.append(dict(request=request, ack=ack, observed=observed, cua=cua, request_sha256=digest))
require(len(seen) == len(bindings), 'unattributed screenshot bindings')
return records, pixel_categories
def inspect_native_phases(directory, index, root=ROOT):
runner = module('p23_existing_runner_checks', root/'tools/run_all_tests.py')
expected = ['runs/'+name+'/'+name+'-receipt.json' for name in ('import', 'parse', 'layout-a', 'layout-b')]
require(index['phases'] == expected, 'native phase coverage/order')
for name in expected:
path = directory/name
receipt = json.loads(path.read_bytes())
pins = json.loads(path.with_name('p23-inputs.json').read_bytes())
require(pins['receipt_sha256'] == sha(path), 'native receipt pin')
require(pins['source_map'] == index['source_map'] and {k: v['sha256'] for k, v in pins['source_after'].items()} == index['source_map'], 'native sources changed')
require(pins['inputs'] == pins['inputs_after'] == input_map(root) and pins['spec_sha256'] == SPEC_SHA, 'native input applicability')
require(pins['default_before'] == pins['default_after'] and pins['stage_before'] == pins['stage_after'], 'native profile/stage changed')
require(pins['settings_sha256'] == sha(root/'project.godot'), 'native settings changed')
require(receipt['passed'] is True and receipt['failed'] is False and receipt['engine_ended'] is True
and receipt['exit_code'] == 0 and not receipt.get('validation_error') and not receipt.get('guard_error'), 'native process failed')
capture = receipt['outer_actual_capture']
require(capture['passed'] is True and capture['all_captured_ended'] is True, 'native owned cleanup')
require(receipt['whole_startup_profile_after']['fresh_before_engine'] is True, 'native profile protection')
require(receipt['default_player_entire_profile_unchanged'] is True, 'native default profile changed')
require(isinstance(receipt['controlled_environment'], dict), 'native environment absent')
diagnostics = receipt['finite_original_diagnostic_review']
require(runner.process_gate(receipt['native_process'], diagnostics['allowed_e31_error_lines']), 'native process gate')
if receipt['test']:
require(receipt['source_sha256'] == receipt['source_sha256_end'] == sha(root/receipt['test'])
and receipt['completion']['passed'] is True and receipt['reported_checks'] > 0 and receipt['reported_failures'] == 0, 'native strict marker')
def check(root, directory):
"""Never infer completion of unimplemented gates from an index assertion."""
deficits = []
executed = {c: {category: set() for category in CATEGORIES} for c in CONDITIONS}
semantic, pixel_categories, anchors = set(), set(), set()
index_path = directory / 'index.json'
if not index_path.is_file():
return ['missing evidence index: ' + str(index_path)]
try:
index = json.loads(index_path.read_bytes())
runner = module('p23_source_applicability', root/'tools/run_all_tests.py')
require(index.get('source_map') == {n: v['sha256'] for n, v in runner.source_refs(root).items()}, 'exact frozen source map')
if index.get('input_map') != input_map(root):
deficits.append('normative input_map differs from current immutable inputs')
if index.get('spec_sha256') != SPEC_SHA:
deficits.append('immutable spec SHA missing or different')
if index.get('marker_contract_sha256') != sha(root / CONTRACT):
deficits.append('marker contract SHA missing or different')
artifacts = index.get('artifacts', {})
if not artifacts:
deficits.append('missing indexed evidence artifacts')
for name, digest in artifacts.items():
path = directory / name
require(path.resolve().is_relative_to(directory.resolve()) and not path.is_symlink(),
'artifact escapes evidence directory')
require(path.is_file() and sha(path) == digest, 'artifact SHA mismatch: ' + name)
inspect_native_phases(directory, index, root)
for layout in ('A', 'B'):
layout_semantic, layout_pixels = set(), set()
stream = directory / ('layout-' + layout)
if (stream / 'manifest.jsonl').is_file():
inspect_stream(stream, root)
else:
deficits.append('missing native layout-' + layout + ' typed diagnostic stream')
for role in ('writer', 'reader'):
stream = directory / 'os-pair' / layout / (role + '-stream')
if (stream / 'manifest.jsonl').is_file():
inspect_stream(stream, root)
# Successful independent recalculation, with nonempty
# observed rows required before a category earns coverage.
for line in (stream/'manifest.jsonl').read_text().splitlines():
capture = json.loads(line)
if 'report' not in capture: continue
report = read_object(stream, capture['report'])
presence = {'room': report['rooms'], 'queue': report['landing_badges'],
'EV': report['cabins'], 'staff': report['staff'],
'goods': report['goods']['requests'], 'business': report['badges']}
semantic.update('semantic_'+c for c, rows in presence.items() if rows)
layout_semantic.update(c for c, rows in presence.items() if rows)
if any(r['people'] for r in report['rooms'].values()): anchors.add('room_users')
if any(r['overflow'] > 0 for r in report['landing_badges']): anchors.add('queue_overflow')
if any(r['passengers'] for r in report['cabins']): anchors.add('elevator_passengers')
if report['staff'] and report['tasks']: anchors.add('staff_jobs')
if any(r['carried_inventory_id'] for r in report['staff']) and report['goods']['requests']: anchors.add('carried_goods')
if role == 'reader': anchors.add('resume_recalculation')
else:
deficits.append('missing OS ' + layout + '-' + role + ' typed diagnostic stream')
ended = directory / 'os-runs' / (layout + '-' + role) / 'ended.json'
if ended.is_file():
receipt = json.loads(ended.read_bytes())
inspect_os_receipt(receipt, index.get('source_map'), input_map(root))
records, categories = inspect_os_requests(directory, layout, role, receipt['actual_pid'], root)
pixel_categories.update('pixels_'+c for c in categories if c in CATEGORIES)
layout_pixels.update(categories)
if role == 'reader':
writer = json.loads((directory/'os-runs'/(layout+'-writer')/'ended.json').read_bytes())
pair = directory/'os-pair'/layout
members = {p.name: sha(p) for p in (pair/'formal').iterdir() if p.is_file()}
inspect_resume(writer, receipt, json.loads((pair/'save.json').read_bytes()), records, members)
require((pair/'writer-formal-model.var').read_bytes() == (pair/'reader-formal-model.var').read_bytes(), 'resumed whole formal model')
anchors.add('distinct_pid_resume')
else:
deficits.append('missing OS ' + layout + '-' + role + ' process receipt')
for category in CATEGORIES:
if category not in layout_semantic or category not in layout_pixels:
deficits.append('missing actual OS ' + layout + ' semantic/pixel coverage: ' + category)
if all((directory / ('layout-' + layout) / 'manifest.jsonl').is_file() for layout in ('A', 'B')):
compare_layouts(comparison_records(directory / 'layout-A'), comparison_records(directory / 'layout-B'))
anchors.add('route_improvement')
if not deficits:
anchors.add('os_guards')
for condition in CONDITIONS:
for category in CATEGORIES:
executed[condition][category].update((semantic | pixel_categories | anchors) & REQUIRED_CHECKS[condition][category])
deficits.extend(condition + ': ' + json.dumps(missing, ensure_ascii=False) for condition, missing in BLOCKERS(executed).items())
except (OSError, ValueError, KeyError, TypeError, AttributeError) as exc:
deficits.append('invalid evidence index: ' + str(exc))
return deficits
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--verify-only', action='store_true')
parser.add_argument('--project-root', type=Path, default=ROOT)
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)
if args.verify_only:
build = os.environ.get('BUILD_OUTPUT_DIR')
if not build:
print('BUILD_OUTPUT_DIR is required', file=sys.stderr)
return 2
deficits = check(args.project_root, Path(build) / 'p23-life-observation-evidence')
print(json.dumps(dict(task_id='P23-03', integrity='fail' if deficits else 'pass',
deficits=deficits, acceptance='pending'), ensure_ascii=False))
return 1 if deficits else 0
# 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())