P23-03: 生活の観察

This commit is contained in:
Agent Control Runner
2026-10-15 18:01:00 +09:00
parent 49445d4a4d
commit 19fc3f50c7
8 changed files with 1888 additions and 71 deletions
+372 -44
View File
@@ -329,6 +329,8 @@ def drawing_rectangle_rows(report):
actor(a)
if a['staff'] and a['key'].removeprefix('staff/') in report['resting_staff']:
actor(a, 'rest')
for i, b in enumerate(report.get('actor_aggregations', [])):
badge('aggregation', str(i), b)
for i, b in enumerate(report['badges']):
badge('goods' if b['kind'] == 'supply' else 'business', str(i), b)
for row in report.get('traffic', []):
@@ -337,7 +339,77 @@ def drawing_rectangle_rows(report):
return expected
def validate_drawing_rows(report, audit, root=ROOT):
PIXEL_PEOPLE_CORE_SHA256 = 'a171703083aa2d00f8be2b63e1daef9c41717c8fe59d8056878bc4b7df1e0b0c'
def actor_identity_core(actor):
"""Grid identity bounds from the pinned PixelPeople pose, including bob.
Standard-room breathing extends the upper edge, not the lower edge.
Arms, legs, props and the sleep symbol are outside the identity core.
"""
key, at, scale = actor['key'], actor['at'], actor['scale']
require(len(at) == 2 and all(type(v) in (int, float) and math.isfinite(v) for v in at)
and type(scale) in (int, float) and math.isfinite(scale) and scale > 0,
'invalid actor footprint: ' + key)
require(isinstance(actor.get('mode'), str) and type(actor.get('frame')) is int
and type(actor.get('flip')) is bool, 'missing actor pose: ' + key)
mode, phase = actor['mode'], actor['frame'] % 8
if mode in ('sleep', 'collapsed', 'standard_room_sleep'):
breathing = -1 if mode == 'standard_room_sleep' and phase in (2, 3, 4) else 0
x, y, w, h = 2, 11 + breathing, 13, 5 - breathing
else:
bob = -1 if mode in ('walk', 'carry_walk') and phase in (2, 6) else 0
x, y, w, h = 6, 1 + bob, 6, 12
if actor['flip']:
x = 16 - x - w
return [at[0] + x * scale, at[1] + y * scale, w * scale, h * scale]
def validate_actor_nonoverlap(report, root=ROOT, transit_keys=()):
"""Recompute identity cores from drawing inputs, including cabin passengers.
Do not consume audit eligibility or boolean judgments: neither an off-screen
label nor a claimed pass exempts a submitted actor from this invariant.
Transparent margins may overlap. No asserted transit flag grants exemption.
"""
require(hashlib.sha256((root / 'scripts/ui/pixel_people.gd').read_bytes()).hexdigest()
== PIXEL_PEOPLE_CORE_SHA256, 'PixelPeople core source SHA256')
actors = list(report['actor_drawings']) + [
actor for cabin in report['cabins'] for actor in cabin['passengers']]
rects = []
keys = set()
for actor in actors:
key = actor['key']
require(key not in keys, 'duplicate actor drawing: ' + key)
keys.add(key)
rect = actor_identity_core(actor)
if key in transit_keys:
continue
for other_key, other in rects:
intersects = (rect[0] < other[0] + other[2] and other[0] < rect[0] + rect[2]
and rect[1] < other[1] + other[3] and other[1] < rect[1] + rect[3])
require(not intersects, 'actor-actor core overlap: ' + other_key + ' / ' + key)
rects.append((key, rect))
def validate_drawing_rows(report, audit, root=ROOT, state=None, rest_audit=None):
# Without raw inputs this is deliberately conservative: NO transit exemption.
# With inputs, use the same state-derived classification and fit proof as the
# stream checker, never a drawing's region/in_transit assertion.
if state is None:
validate_actor_nonoverlap(report, root)
else:
require(isinstance(state, dict) and isinstance(state.get('systems', {}).get('runtime'), dict),
'malformed raw state context')
if report['resting_staff']:
require(rest_audit is not None, 'missing rest authority for drawing geometry')
if rest_audit is not None:
require(isinstance(rest_audit, dict) and isinstance(rest_audit.get('staff'), list),
'malformed rest authority')
compare_rest(state, report, rest_audit, root)
compare_cabin_geometry(state, report)
compare_actor_geometry(state, report)
expected = drawing_rectangle_rows(report)
require(len(audit['rows']) == len(expected), 'offline rectangle coverage')
for row, (category, identifier, rect, source, text) in zip(audit['rows'], expected):
@@ -367,15 +439,17 @@ def encode_png(rows):
+ chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b''))
def frame_rows(report, audit, request, receipt, images, clicks, root=ROOT):
def frame_rows(report, audit, request, receipt, images, clicks, root=ROOT, state=None, rest_audit=None):
"""One mechanical path for frozen diagnostics and actual OS frames.
Errors are retained as failures, including incomplete frozen audits. Every
supplied row is accounted for; no palette value is an image oracle.
"""
errors, rows = [], []
transit_authority = ('raw state, track, time and pause' if state is not None
else 'absent: fail closed, no transit exemptions')
try:
validate_drawing_rows(report, audit, root)
validate_drawing_rows(report, audit, root, state, rest_audit)
except (ValueError, KeyError, TypeError) as exc:
errors.append(str(exc))
calibration = screenshot_transform(receipt['calibration'], request, images[0][:2], clicks)
@@ -415,6 +489,9 @@ def frame_rows(report, audit, request, receipt, images, clicks, root=ROOT):
if row['category'] == 'queue':
badge = report['landing_badges'][int(row['id'])]
state = {k: badge[k] for k in ('members', 'visible_count', 'overflow')}
if row['category'] == 'aggregation':
badge = report['actor_aggregations'][int(row['id'])]
state = {k: badge[k] for k in ('members', 'drawn_keys')}
if row['category'] == 'EV':
cabin = next(c for c in report['cabins'] if c['shaft_id'] == row['id'])
state = {k: cabin[k] for k in ('operational', 'doors_open')}
@@ -448,7 +525,8 @@ def frame_rows(report, audit, request, receipt, images, clicks, root=ROOT):
result['failures'] = failures
rows.append(result)
return dict(rows=rows, errors=errors, mechanical_pass=not errors and all(r['mechanical_pass'] for r in rows),
expected_row_count=len(drawing_rectangle_rows(report)))
expected_row_count=len(drawing_rectangle_rows(report)),
transit_authority=transit_authority)
def write_frame_review(directory, frame, provenance):
@@ -551,7 +629,14 @@ def inspect_offline_mirror(directory):
for original in (str(Path(index['source'])/conversion_path.relative_to(directory)),
conversion['png_path']):
inspected.append(by_original[original])
frame = frame_rows(report, audit, request, receipt, images, clicks, ROOT)
stream = pair/'writer-stream'
captures = [row for row in map(json.loads, (stream/'manifest.jsonl').read_text().splitlines())
if row.get('label') == name and 'report' in row]
require(len(captures) == 1, 'offline raw-state capture coverage')
capture = captures[0]
state, rest_audit = drawing_context(stream, capture, report)
frame = frame_rows(report, audit, request, receipt, images, clicks, ROOT,
state, rest_audit)
provenance = []
for number, suffix in enumerate(('', '-after')):
conversion_path = folder/('root-early-conversion'+suffix+'.json')
@@ -720,9 +805,56 @@ def compare_traffic(snapshot, report):
if cell[2] != report['layer']:
continue
x, y, w, h = transform.rect(cell)
rect = [x + 2, y + 2, w - 4, h - 4]
thickness = max(2, min(5, (h - 4) * .16))
band = [rect[0] + rect[2] * .12, y + h - 2 * (w / 16) - thickness / 2,
rect[2] * .76, thickness]
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]))
rect=rect, band_rect=band))
close(report['traffic'], rows, 'traffic and cell rect')
validate_life_overlay(report)
def validate_life_overlay(report, root=ROOT):
"""Check consumed frame and source wiring, not merely reported order flags."""
serial = report.get('observation_serial')
require(type(serial) is int and serial > 0
and type(report.get('overlay_consumed_serial')) is int
and report['overlay_consumed_serial'] == serial, 'stale LifeOverlay serial')
require(report.get('overlay_passes') == ['traffic', 'actors'], 'LifeOverlay pass order')
indices = report.get('overlay_child_indices', [])
require(len(indices) == 3 and all(type(i) is int and i >= 0 for i in indices)
and indices[0] < indices[1] < indices[2], 'LifeOverlay child order')
source = (root / 'scenes/main/world_preview.gd').read_text()
def body(name):
match = re.search(r'^func ' + name + r'\([^\n]*\n(.*?)(?=^func |\Z)', source, re.M | re.S)
require(match is not None, 'missing overlay source function: ' + name)
return match[1]
ready, draw, overlay = body('_ready'), body('_draw_contents'), body('_draw_life_overlay')
ordered = [ready.find('add_child(' + node + ')')
for node in ('_inactive_layer_art', '_life_overlay', '_layer_lens')]
require(all(i >= 0 for i in ordered) and ordered == sorted(set(ordered)), 'LifeOverlay source child order')
require('_life_overlay.draw.connect(_draw_life_overlay)' in ready
and '_life_overlay.queue_redraw()' in draw, 'LifeOverlay source connection')
require('_draw_congestion_overlay(' not in draw and '_draw_actors(' not in draw
and '_draw_observed_actor(' not in body('_draw_elevators'), 'actor or band drawn in parent')
require(0 <= overlay.find('_draw_congestion_overlay(_life_overlay)')
< overlay.find('_draw_observed_actor(actor, _life_overlay)')
< overlay.find('_draw_actors(_life_overlay)'), 'LifeOverlay source pass order')
actors = list(report['actor_drawings']) + [a for c in report['cabins'] for a in c['passengers']]
for row in report.get('traffic', []):
x, y, w, h = row['rect']
thickness = max(2, min(5, h * .16))
expected = [x + w * .12, y + h + 2 - 2 * ((w + 4) / 16) - thickness / 2,
w * .76, thickness]
close(row['band_rect'], expected, 'traffic band rectangle')
if row['traffic']['current_density'] <= 0 and not row['traffic']['temporary_block']:
continue
for actor in actors:
core = actor_identity_core(actor)
intersects = (core[0] < expected[0] + expected[2] and expected[0] < core[0] + core[2]
and core[1] < expected[1] + expected[3] and expected[1] < core[1] + core[3])
require(not intersects, 'traffic band covers actor core: ' + actor['key'])
def compare_queues(snapshot, report):
@@ -935,27 +1067,12 @@ def compare_rest(state, report, audit, root=ROOT):
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')
# Fitting is cross-kind, and queue precedence applies to resting staff too.
# Reconstruct the complete group and independently prove p=1 no-fit before
# allowing an identity to appear only in an aggregation's member list.
if any(r['cells'] for r in runtime['world']['occupancies']
if r['entity_id'] in resting.values()):
compare_actor_geometry(state, report)
return selections
@@ -1129,8 +1246,105 @@ def compare_motion(state, report, audit):
return ['motion_history', 'interpolation', 'pause']
def fitted_actor_layout(members, world_pixel, height):
"""Independent interval solver. Inputs are reconstructed preferred rows/bounds."""
def scaled(member, scale):
row = copy.deepcopy(member['row'])
row['scale'] = min(row['scale'], scale)
s = row['scale']
row['at'] = [math.copysign(math.floor(abs(v)+.5), v) for v in
(row['center'][0]-8*s, row['center'][1]+height/2-3*world_pixel-17*s)]
return row
def pack(group, scale):
trial = [scaled(m, scale) for m in group]
right = -math.inf
for member, row in zip(group, trial):
x, y, w, h = actor_identity_core(row)
left = max(x, member['bounds'][0], right)
row['at'][0] += left-x
right = left+w
left = math.inf
for member, row in reversed(list(zip(group, trial))):
x, y, w, h = actor_identity_core(row)
bx, by, bw, bh = member['bounds']
target = min(x, bx+bw-w, left-w)
row['at'][0] += target-x
left = target
if target < bx or y < by or y+h > by+bh:
return None
return trial
active = copy.deepcopy([m for m in members if not m['transit']])
# A smaller sprite keeps its feet, so its vertical interval moves. Rebuild
# components after EVERY trial, including unsuccessful trials. Scale ceilings
# only decrease, making termination independent of an arbitrary iteration cap.
while True:
groups, bottom = [], -math.inf
for member in active:
member['row'] = scaled(member, member['row']['scale'])
for member in sorted(active, key=lambda m: (actor_identity_core(m['row'])[1], m['row']['key'])):
x, y, w, h = actor_identity_core(member['row'])
if y >= bottom:
groups.append([])
bottom = y+h
else:
bottom = max(bottom, y+h)
groups[-1].append(member)
reduced = False
for group in groups:
group.sort(key=lambda m: (m['row']['center'][0], m['row']['key']))
ceiling = max(1, *(m['row']['scale'] for m in group))
if pack(group, ceiling) is None and ceiling > 1:
for member in group:
member['row'] = scaled(member, max(1, int(ceiling)-1))
reduced = True
if not reduced:
break
result = {m['row']['key']: copy.deepcopy(m['row']) for m in members if m['transit']}
aggregations = []
for group in groups:
group.sort(key=lambda m: (m['row']['center'][0], m['row']['key']))
base = max(1, *(m['row']['scale'] for m in group))
fitted = pack(group, base)
if fitted is None:
retained, fitted, identities = [], [], []
for member in group:
row = member['row']
identities.append(dict(key=row['key'], id=row['key'].split('/', 1)[1],
state=row['state'], cell=row['cell'], mode=row['mode']))
attempt = pack(retained+[member], 1)
if attempt is not None:
retained.append(member)
fitted = attempt
aggregations.append(dict(members=identities, drawn_keys=[r['key'] for r in fitted],
at=group[0]['bounds'][:2], text='+'+str(len(group)-len(fitted))))
result.update({row['key']: row for row in fitted})
return result, aggregations
def actor_track_transit(report, key, cell):
"""Track endpoint is bound to raw state by the caller, not to a draw flag."""
require(type(report['paused']) is bool, 'actor pause must be boolean')
track = report['actor_tracks'][key]
close(track['cell'], cell, 'actor authoritative cell')
origin = track['from']
require(isinstance(origin, list) and len(origin) == 3
and all(type(v) is int for v in origin)
and origin[2] == cell[2]
and abs(origin[0]-cell[0])+abs(origin[1]-cell[1]) <= 1,
'actor track is not a source-valid adjacent step')
now, started = report['animation_time'], track['at']
require(all(type(v) in (int, float) and math.isfinite(v) for v in (now, started))
and now >= started, 'invalid or future actor track time')
return not report['paused'] and origin != cell and now-started < .42
def compare_actor_geometry(state, report):
runtime = state['systems']['runtime']
compare_queues(state, report)
compare_room_services(state, report)
boarded = {actor.replace('staff:', 'staff/', 1).replace('guest:', 'guest/', 1): True
for shaft in elevator_report(state)['shafts'] for actor in shaft['boarded']}
close(report['boarded'], boarded, 'boarded actor coverage')
transform = Transform(runtime, report)
p, height = transform.dim[0]/16, transform.dim[1]
facilities = codec.indexed(runtime['facilities']['instances'], 'instance_id')
@@ -1139,7 +1353,7 @@ def compare_actor_geometry(state, report):
counts, expected = {}, []
def track_position(key, cell):
track = report['actor_tracks'][key]
close(track['cell'], cell, 'actor authoritative cell')
actor_track_transit(report, key, 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)]
@@ -1182,7 +1396,10 @@ def compare_actor_geometry(state, report):
continue
center = track_position(key, cell)
special = True
if sid in report['carried_patients']:
if key in report['queue_positions']:
special = False
center = report['queue_positions'][key]
elif sid in report['carried_patients']:
carrier = staff.get(report['carried_patients'][sid])
if carrier:
center = track_position('staff/'+carrier['staff_id'], carrier['current_cell'])
@@ -1206,31 +1423,127 @@ def compare_actor_geometry(state, report):
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))
floor_keys = [r[0] for r in expected]
cabin_bounds = {}
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'])):
passengers = shaft['boarded']
for i, actor_id in enumerate(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]
center = [x+(2+(i%columns+.5)*8)*p, y+h-(2+((i//columns)%2)*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')
expected.append((key, center, is_staff, False, carried, True))
cabin_bounds[key] = cabin['rect']
people = {'guest/'+r['guest_id']: r for r in runtime['guests']['guests']}
people.update({'staff/'+sid: r for sid, r in staff.items()})
def tracked_bounds(key, cell):
track = report['actor_tracks'][key]
transit = actor_track_transit(report, key, cell)
rect = transform.rect(cell)
if transit:
other = transform.rect(track['from'])
x, y = min(rect[0], other[0]), min(rect[1], other[1])
rect = [x, y, max(rect[0]+rect[2], other[0]+other[2])-x,
max(rect[1]+rect[3], other[1]+other[3])-y]
return rect, transit
members = []
for key, center, is_staff, receiving, carrying, riding in expected:
person = people[key]
cell = person['current_cell']
track = report['actor_tracks'].get(key, {})
region, transit = (cabin_bounds[key], False) if riding else tracked_bounds(key, cell)
fid = ''
if not riding:
if key in report['queue_positions']:
cx, cy = report['queue_positions'][key]
region, transit = [cx-transform.dim[0]/2, cy-height/2, transform.dim[0], height], False
elif not is_staff:
target = person['target_facility_id']
if receiving and target in facilities and key.split('/', 1)[1] in report['rooms'].get(target, {}).get('people', []):
fid = target
else:
sid = person['staff_id']
if sid in report['carried_patients']:
carrier = staff[report['carried_patients'][sid]]
region, transit = tracked_bounds('staff/'+carrier['staff_id'], carrier['current_cell'])
region[1] -= 12*p
elif sid in report['resting_staff']:
fid = report['resting_staff'][sid]
elif sid in report['treated_patients']:
fid = report['treated_patients'][sid]
else:
target = person['assigned_facility_id']
cells = occupancy.get(target, [])
workers = report['rooms'].get(target, {}).get('staff', [])
if person['state'] in ('WORKING', 'AVAILABLE') and target in facilities and cell in cells and sid in workers:
rect = transform.room(cells)
columns = max(int(rect[2]/(16*p)), 1)
slot = workers.index(sid)
if rect[3] >= 40*p and slot < columns:
region, transit = [rect[0]+slot*rect[2]/columns, rect[1], rect[2]/columns, rect[3]], False
if fid and occupancy.get(fid):
region, transit = transform.room(occupancy[fid]), False
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')
hashed = 5381
for char in key:
hashed = (hashed*33+ord(char)) & 0xffffffff
variant = hashed % 15
frame = int(report['animation_time']*12)+variant
activity = facilities.get(person.get('target_facility_id'), {}).get('definition_id', '') if not is_staff else 'work'
if is_staff and person['role_id'] == 'cleaner':
activity = 'idle'
task = next((t for t in runtime['staff']['tasks'] if t['task_id'] == person['current_task_id']), None)
room = facilities.get(person['assigned_facility_id'], {})
if (person['state'] == 'WORKING' and person['health'] > 0 and not person['is_retiring']
and room.get('definition_id') == 'cleaning_room' and room.get('state') in ('OPEN', 'DEGRADED')
and not room.get('suspension_reason') and task and task['task_type'] == 'facility_clean'
and task['state'] == 'WORKING' and task['assigned_staff_id'] == person['staff_id']
and cell == task['target_cell'] and any(r['cell'] == cell for r in runtime['path']['cell_traffic'])
and not any(t['actor_id'] == 'staff:'+person['staff_id'] for t in runtime['elevators']['trips'])
and task['target_entity_id'] in facilities
and any(r['id'] == task['target_entity_id'] for r in runtime['facility_cleanliness']['facilities'])):
activity = 'clean'
moving = (not receiving and key not in report['queue_positions'] and not riding
and not report['paused'] and track.get('cell') != track.get('from')
and report['animation_time']-track.get('at', 0) < .42)
mode = 'idle'
if is_staff and person['state'] == 'INCAPACITATED':
mode = 'collapsed'
elif receiving and activity in ('standard_room', 'premium_room'):
mode = 'standard_room_sleep' if activity == 'standard_room' else 'sleep'
elif moving:
mode = 'carry_walk' if carrying else 'walk'
elif carrying:
mode = 'carry'
elif is_staff and person['state'] == 'WORKING' and not riding:
mode = activity if activity in ('clean', 'idle', 'work') else 'work'
elif receiving and activity in ('cafeteria', 'tavern'):
mode = 'eat'
elif key in report['queue_positions'] or (not is_staff and person['state'] == 'QUEUING'):
mode = 'wait'
states = enum_names(ROOT, 'scripts/staff/staff_runtime.gd' if is_staff else 'scripts/guest/guest_runtime.gd', 'State')
row = dict(key=key, center=center, at=rounded, scale=scale, cell=cell, mode=mode,
frame=frame, variant=variant, flip=track.get('direction', 1) < 0,
staff=is_staff, state=states.index(person['state']), receiving=receiving,
carrying=carrying, riding=riding)
members.append(dict(row=row, bounds=[region[0]+p, region[1]+p, region[2]-2*p, region[3]-2*p], transit=transit))
fitted, aggregations = fitted_actor_layout(members, p, height)
close(report.get('actor_aggregations'), aggregations, 'actor aggregation/no-fit')
require([r['key'] for r in report['actor_drawings']] == [k for k in floor_keys if k in fitted], 'actor drawing coverage/order')
actual = list(report['actor_drawings']) + [r for c in report['cabins'] for r in c['passengers']]
require(len(actual) == len(fitted) and {r['key'] for r in actual} == set(fitted), 'missing or duplicate actor')
for row in actual:
for field, value in fitted[row['key']].items():
close(row[field], value, 'actor fitted '+field)
exemptions = {m['row']['key'] for m in members if m['transit']}
validate_actor_nonoverlap(report, transit_keys=exemptions)
return ['actor_geometry', 'passenger_geometry']
@@ -1270,6 +1583,20 @@ def compare_layouts(a, b):
return dict(metric=declaration['metric'], A=metrics[0], B=metrics[1])
def drawing_context(stream, capture, report):
"""Shared OS/offline binding: a missing stream never grants exemptions."""
require(isinstance(capture, dict) and all(k in capture for k in ('report', 'state', 'rest_audit')),
'missing raw drawing context')
close(read_object(stream, capture['report']), report, 'raw-state report binding')
state = read_object(stream, capture['state'])
rest = read_object(stream, capture['rest_audit'])
require(isinstance(state, dict) and isinstance(state.get('systems', {}).get('runtime'), dict),
'malformed raw state context')
require(isinstance(rest, dict) and isinstance(rest.get('staff'), list)
and 'path_service_sha256' in rest, 'malformed rest authority')
return state, rest
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')
@@ -1458,7 +1785,7 @@ def inspect_os_requests(directory, layout, role, pid, root=ROOT):
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')
state, rest_audit = drawing_context(stream, capture, report)
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')
@@ -1485,7 +1812,8 @@ def inspect_os_requests(directory, layout, role, pid, root=ROOT):
if request['action'] == 'screenshot':
clicks = [document(json.loads(path(name).read_bytes())['input_receipt'])
for name in listing['requests']]
frame = frame_rows(report, decode(request['rectangles']), request, cua, [image], clicks, root)
frame = frame_rows(report, decode(request['rectangles']), request, cua, [image], clicks, root,
state, rest_audit)
review_dir = request_path.parent/('review-image-' + str(number))
manifest, manifest_sha = write_frame_review(review_dir, frame, [dict(
whole_image_sha256=binding['png_sha256'], raw_sha256=shot['raw_sha256'],