P23-03: 生活の観察
This commit is contained in:
@@ -155,9 +155,9 @@ def image_kind(data):
|
||||
raise ValueError('unknown screenshot byte magic')
|
||||
|
||||
|
||||
def inspect_conversion(rec, cua_receipt, converter_sha256=None):
|
||||
def inspect_conversion(rec, cua_receipt, converter_sha256=None, resolve_path=Path):
|
||||
"""CUA receipt and converter pin are independent of conversion assertions."""
|
||||
raw, png = Path(rec['raw_path']), Path(rec['png_path'])
|
||||
raw, png = resolve_path(rec['raw_path']), resolve_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']
|
||||
@@ -169,7 +169,7 @@ def inspect_conversion(rec, cua_receipt, converter_sha256=None):
|
||||
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)],
|
||||
require(rec['argv'] == ['/usr/bin/sips', '-s', 'format', 'png', rec['raw_path'], '--out', rec['png_path']],
|
||||
'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'):
|
||||
@@ -206,27 +206,92 @@ def affine_rect(matrix, rect):
|
||||
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.
|
||||
def screenshot_transform(calibration, request, image_size, clicks):
|
||||
"""Content -> window screenshot pixels, cross-checked against actual clicks.
|
||||
|
||||
This is independent of the renderer's content-space screen_rect. In
|
||||
particular, window_position is a desktop coordinate, not an image origin.
|
||||
No titlebar height, scale, or image extent is assumed by the checker.
|
||||
"""
|
||||
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']
|
||||
scale = calibration['backing_scale']
|
||||
stretch = calibration['canvas_stretch']
|
||||
titlebar = calibration['titlebar_pixels']
|
||||
require(all(type(v) in (int, float) and math.isfinite(v) for v in
|
||||
(scale, stretch, titlebar)) and scale > 0 and stretch > 0 and titlebar >= 0,
|
||||
'invalid screenshot calibration')
|
||||
close(calibration['window_size'], request['window_size'], 'calibrated window size')
|
||||
close(calibration['window_position'], request['window_position'], 'calibrated window position')
|
||||
close(calibration['screenshot_pixels'], list(image_size), 'calibrated image size')
|
||||
close([request['screen_scale'], request['content_scale_factor']], [scale, stretch],
|
||||
'calibrated engine scale')
|
||||
close([request['window_size'][0]*scale, request['window_size'][1]*scale+titlebar],
|
||||
list(image_size), 'calibrated content extent')
|
||||
matrix = [scale, 0, 0, scale, 0, titlebar]
|
||||
matched = 0
|
||||
for receipt in clicks:
|
||||
if receipt.get('target_pid') != request['target_pid']:
|
||||
continue
|
||||
for event in receipt['events']:
|
||||
if event.get('action') != 'click':
|
||||
continue
|
||||
close(event['calibration'], calibration, 'click calibration')
|
||||
point = event['point']
|
||||
close(affine_point(point['viewport_to_screen'], point['viewport']),
|
||||
point['screen'], 'click content affine')
|
||||
close(affine_point(matrix, point['screen']), event['coordinates'],
|
||||
'click screenshot offset')
|
||||
matched += 1
|
||||
require(matched > 0, 'missing independent click calibration')
|
||||
return matrix
|
||||
|
||||
|
||||
def rect_intersection(a, b):
|
||||
require(len(a) == len(b) == 4 and all(type(v) in (int, float) and math.isfinite(v)
|
||||
for v in a+b) and min(a[2:]+b[2:]) >= 0, 'invalid clipping rectangle')
|
||||
x, y = max(a[0], b[0]), max(a[1], b[1])
|
||||
return [x, y, max(0, min(a[0]+a[2], b[0]+b[2])-x),
|
||||
max(0, min(a[1]+a[3], b[1]+b[3])-y)]
|
||||
|
||||
|
||||
def rectangle_eligibility(rect, viewport):
|
||||
clipped = rect_intersection(list(rect), [0, 0, *viewport])
|
||||
if not clipped[2] or not clipped[3]:
|
||||
return 'outside', clipped
|
||||
return ('visible' if clipped == list(rect) else 'clipped'), clipped
|
||||
|
||||
|
||||
def paired_crop_noise(before, after, rect):
|
||||
"""Measured temporal differences, never an invented JPEG RGB tolerance.
|
||||
|
||||
A stable pair alone does not establish class or state identity. The result
|
||||
is diagnostic data only and must not be promoted to pixels_<category>.
|
||||
"""
|
||||
require(before[:2] == after[:2], 'paired image dimensions')
|
||||
x, y, w, h = rect_intersection(list(rect), [0, 0, *before[:2]])
|
||||
errors = [max(abs(a-b) for a, b in zip(before[2][py][px][:3], after[2][py][px][:3]))
|
||||
for py in range(math.ceil(y), math.floor(y+h))
|
||||
for px in range(math.ceil(x), math.floor(x+w))]
|
||||
require(errors, 'empty noise crop')
|
||||
return dict(samples=len(errors), max_channel_delta=max(errors),
|
||||
mean_channel_delta=sum(errors)/len(errors), unchanged=sum(v == 0 for v in errors))
|
||||
|
||||
|
||||
def drawing_rectangle_rows(report):
|
||||
"""Derive identities and local positions; do not read audit verdicts."""
|
||||
expected = []
|
||||
tower, people, world = 'scripts/ui/pixel_tower_art.gd', 'scripts/ui/pixel_people.gd', 'scenes/main/world_preview.gd'
|
||||
tower = 'scripts/ui/pixel_tower_art.gd'
|
||||
people = 'scripts/ui/pixel_people.gd'
|
||||
world = '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))
|
||||
def badge(category, identifier, b):
|
||||
expected.append((category, identifier, b['at'] + [None, 14], world, b['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'])
|
||||
badge('queue', str(i), b)
|
||||
for cabin in report['cabins']:
|
||||
expected.append(('EV', cabin['shaft_id'], cabin['rect'], tower, None))
|
||||
for a in cabin['passengers']:
|
||||
@@ -236,36 +301,254 @@ def compare_rectangles(report, audit, local_to_viewport, viewport_to_screen, loc
|
||||
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')
|
||||
badge('goods' if b['kind'] == 'supply' else 'business', str(i), b)
|
||||
for row in report.get('traffic', []):
|
||||
if row['traffic']['current_density'] > 0 or row['traffic']['temporary_block']:
|
||||
expected.append(('traffic', ','.join(map(str, row['cell'])), row['rect'], world, None))
|
||||
return expected
|
||||
|
||||
|
||||
def validate_drawing_rows(report, audit, root=ROOT):
|
||||
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):
|
||||
require((row['category'], row['id'], row['style_source']) == (category, identifier, source),
|
||||
'offline rectangle identity')
|
||||
rect = list(rect)
|
||||
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')
|
||||
require(row['text'] == text and type(row['font_width']) in (int, float)
|
||||
and math.isfinite(row['font_width']) and row['font_width'] > 0, 'offline 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')
|
||||
close(row['rect'], rect, 'offline report drawing 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
|
||||
require(row['style_colors'] == colors and colors, 'offline source palette provenance')
|
||||
|
||||
|
||||
def encode_png(rows):
|
||||
"""Lossless review crop from decoded pixels, without an imaging dependency."""
|
||||
require(rows and rows[0], 'empty crop')
|
||||
width, channels = len(rows[0]), len(rows[0][0])
|
||||
require(channels in (3, 4), 'crop channels')
|
||||
def chunk(kind, body):
|
||||
return (struct.pack('>I', len(body)) + kind + body
|
||||
+ struct.pack('>I', zlib.crc32(kind + body)))
|
||||
raw = b''.join(b'\0' + bytes(v for pixel in row for v in pixel) for row in rows)
|
||||
return (b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack(
|
||||
'>IIBBBBB', width, len(rows), 8, 2 if channels == 3 else 6, 0, 0, 0))
|
||||
+ chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b''))
|
||||
|
||||
|
||||
def frame_rows(report, audit, request, receipt, images, clicks, root=ROOT):
|
||||
"""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 = [], []
|
||||
try:
|
||||
validate_drawing_rows(report, audit, root)
|
||||
except (ValueError, KeyError, TypeError) as exc:
|
||||
errors.append(str(exc))
|
||||
calibration = screenshot_transform(receipt['calibration'], request, images[0][:2], clicks)
|
||||
for image in images:
|
||||
require(image[:2] == images[0][:2], 'frame image dimensions')
|
||||
for point in ([0, 0], [1, 0], [0, 1]):
|
||||
close(affine_point(request['viewport_to_screen'], affine_point(request['local_to_viewport'], point)),
|
||||
affine_point(request['local_to_screen'], point), 'frame affine composition')
|
||||
for index, row in enumerate(audit['rows']):
|
||||
failures = list(errors)
|
||||
close(row['viewport_rect'], affine_rect(request['local_to_viewport'], row['rect']),
|
||||
'frame viewport rectangle')
|
||||
close(row['screen_rect'], affine_rect(request['local_to_screen'], row['rect']),
|
||||
'frame content rectangle')
|
||||
status, clipped = rectangle_eligibility(row['rect'], report['viewport'])
|
||||
screenshot_rect = affine_rect(calibration, affine_rect(request['local_to_screen'], clipped))
|
||||
room = report['rooms'].get(row['id']) if row['category'] == 'room' else None
|
||||
if row['category'] in ('business', 'goods'):
|
||||
room = report['rooms'].get(report['badges'][int(row['id'])]['facility'])
|
||||
operation, state = None, None
|
||||
if room is not None:
|
||||
states = enum_names(root, 'scripts/facility/facility_instance.gd', 'State')
|
||||
operations = enum_names(root, 'scripts/facility/facility_instance.gd', 'OperationState')
|
||||
require(type(room['state']) is int and 0 <= room['state'] < len(states), 'frame room enum')
|
||||
state = states[room['state']]
|
||||
require(state == room['state_name'], 'frame room state name')
|
||||
require(type(room['operation']) is int and 0 <= room['operation'] < len(operations), 'frame operation enum')
|
||||
operation = operations[room['operation']]
|
||||
actors = list(report['actor_drawings']) + [a for c in report['cabins'] for a in c['passengers']]
|
||||
actor = next((a for a in actors if a['key'] == row['id']), None)
|
||||
if actor is not None:
|
||||
actor_states = enum_names(root, 'scripts/staff/staff_runtime.gd' if actor['staff']
|
||||
else 'scripts/guest/guest_runtime.gd', 'State')
|
||||
require(type(actor['state']) is int and 0 <= actor['state'] < len(actor_states),
|
||||
'frame actor state enum')
|
||||
state = actor_states[actor['state']]
|
||||
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'] == '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')}
|
||||
if row['category'] == 'traffic':
|
||||
traffic = next(r['traffic'] for r in report.get('traffic', [])
|
||||
if ','.join(map(str, r['cell'])) == row['id'])
|
||||
state = {k: traffic[k] for k in ('current_density', 'temporary_block')}
|
||||
result = dict(row_index=index, category=row['category'], id=row['id'], layer=report['layer'],
|
||||
state=state, state_name=state if isinstance(state, str) else None, operation=operation,
|
||||
dimmed=row['category'] == 'room' and operation != 'OPERATING',
|
||||
text=row.get('text'), local_rect=row['rect'], screenshot_rect=screenshot_rect,
|
||||
style_source=row['style_source'], style_source_sha256=sha(root/row['style_source']),
|
||||
style_colors=row['style_colors'], font_width=row.get('font_width'),
|
||||
eligibility=status, reason={'visible': 'whole rectangle in viewport',
|
||||
'clipped': 'rectangle intersects viewport', 'outside': 'no viewport intersection'}[status],
|
||||
crops=[])
|
||||
if status != 'outside':
|
||||
for number, (width, height, pixels) in enumerate(images):
|
||||
x, y, w, h = rect_intersection(screenshot_rect, [0, 0, width, height])
|
||||
crop = [line[math.ceil(x):math.floor(x+w)]
|
||||
for line in pixels[math.ceil(y):math.floor(y+h)]]
|
||||
if not crop or not crop[0]:
|
||||
failures.append('eligible crop outside image')
|
||||
continue
|
||||
if len({pixel for line in crop for pixel in line}) == 1:
|
||||
failures.append('blank crop')
|
||||
data = encode_png(crop)
|
||||
result['crops'].append(dict(image_index=number, sha256=hashlib.sha256(data).hexdigest(),
|
||||
png_bytes=data))
|
||||
result['mechanical_pass'] = not failures
|
||||
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)))
|
||||
|
||||
|
||||
def write_frame_review(directory, frame, provenance):
|
||||
directory = Path(directory)
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
manifest = copy.deepcopy(frame)
|
||||
manifest['images'] = provenance
|
||||
for row in manifest['rows']:
|
||||
for crop in row['crops']:
|
||||
name = 'row-%04d-image-%d.png' % (row['row_index'], crop['image_index'])
|
||||
(directory/name).write_bytes(crop.pop('png_bytes'))
|
||||
crop['path'] = name
|
||||
data = (json.dumps(manifest, sort_keys=True, ensure_ascii=False, indent=2) + '\n').encode()
|
||||
(directory/'manifest.json').write_bytes(data)
|
||||
digest = hashlib.sha256(data).hexdigest()
|
||||
(directory/'manifest.sha256').write_text(digest + '\n')
|
||||
return manifest, digest
|
||||
|
||||
|
||||
def reviewed_pixel_categories(manifest, digest, verdict_path):
|
||||
"""The worker never writes verdicts; absent independent review earns nothing."""
|
||||
if not Path(verdict_path).is_file():
|
||||
return set()
|
||||
verdict = json.loads(Path(verdict_path).read_bytes())
|
||||
require(verdict['manifest_sha256'] == digest, 'review manifest SHA mismatch')
|
||||
require(verdict['reviewer'] == 'independent-opus-direct-image', 'independent image reviewer required')
|
||||
eligible = [r for r in manifest['rows'] if r['eligibility'] != 'outside']
|
||||
require([v['row_index'] for v in verdict['rows']] == [r['row_index'] for r in eligible],
|
||||
'review row coverage')
|
||||
for row, decision in zip(eligible, verdict['rows']):
|
||||
require(all(decision[k] is True for k in ('category', 'state', 'position')), 'review row rejected')
|
||||
if not manifest['mechanical_pass']:
|
||||
return set()
|
||||
return {r['category'] for r in eligible if r['mechanical_pass']}
|
||||
|
||||
|
||||
def mirrored_inventory(directory):
|
||||
"""Read every indexed byte; preserve original receipt paths as authority."""
|
||||
directory = Path(directory).resolve()
|
||||
index = json.loads((directory/'MIRROR_INDEX.json').read_bytes())
|
||||
require(Path(index['mirror']).resolve() == directory, 'mirror root mismatch')
|
||||
paths, mapping = set(), []
|
||||
source = Path(index['source'])
|
||||
for row in index['files']:
|
||||
name = Path(row['path'])
|
||||
require(not name.is_absolute() and '..' not in name.parts and str(name) not in paths,
|
||||
'invalid/duplicate mirror path')
|
||||
paths.add(str(name))
|
||||
local = directory/name
|
||||
require(local.resolve().is_relative_to(directory) and not local.is_symlink(), 'mirror escape')
|
||||
data = local.read_bytes()
|
||||
digest = hashlib.sha256(data).hexdigest()
|
||||
require(digest == row['sha256'] and len(data) == row['bytes'], 'mirror hash/size mismatch')
|
||||
mapping.append(dict(original=str(source/name), mirror=str(local), sha256=digest))
|
||||
prior = index['previous_inventory']
|
||||
prior_name = Path(prior['path'])
|
||||
require(not prior_name.is_absolute() and '..' not in prior_name.parts
|
||||
and (directory/prior_name).resolve().is_relative_to(directory)
|
||||
and not (directory/prior_name).is_symlink(), 'previous inventory escape')
|
||||
require(sha(directory/prior['path']) == prior['sha256'], 'previous mirror inventory hash')
|
||||
old = json.loads((directory/prior['path']).read_bytes())
|
||||
require(all(row in index['files'] for row in old['files']), 'previous mirror entries changed')
|
||||
return index, mapping
|
||||
|
||||
|
||||
def inspect_offline_mirror(directory):
|
||||
"""Preparation diagnostics, deliberately separate from the acceptance gate.
|
||||
|
||||
Frozen incomplete draw audits cannot acquire missing rows by relabelling
|
||||
source-derived rectangles as observed engine output.
|
||||
"""
|
||||
directory = Path(directory).resolve()
|
||||
index, mapping = mirrored_inventory(directory)
|
||||
by_original = {row['original']: row for row in mapping}
|
||||
def resolve(original):
|
||||
require(original in by_original, 'unindexed original path')
|
||||
return Path(by_original[original]['mirror'])
|
||||
pair = directory/'os-pair/A'
|
||||
clicks = [json.loads((pair/name/'cua-receipt.json').read_bytes())
|
||||
for name in ('writer-request-0001', 'writer-request-0003')]
|
||||
results, inspected = [], []
|
||||
for name in ('writer-request-0002', 'writer-request-0004'):
|
||||
folder = pair/name
|
||||
request = json.loads((folder/'request.json').read_bytes())
|
||||
receipt = json.loads((folder/'cua-receipt.json').read_bytes())
|
||||
require(sha(folder/'request.json') == receipt['request_sha256'], 'offline request binding')
|
||||
require(receipt['target_pid'] == request['target_pid'], 'offline screenshot PID')
|
||||
report, audit = decode(request['report']), decode(request['rectangles'])
|
||||
images = []
|
||||
for number, suffix in enumerate(('', '-after')):
|
||||
conversion_path = folder/('root-early-conversion'+suffix+'.json')
|
||||
conversion = json.loads(conversion_path.read_bytes())
|
||||
shot = receipt['screenshots'][number]
|
||||
require(resolve(conversion['raw_path']) == directory/shot['path'], 'offline raw path binding')
|
||||
image = inspect_conversion(conversion, shot, resolve_path=resolve)
|
||||
for stream in ('stdout', 'stderr'):
|
||||
require(sha(Path(str(conversion_path)+'.'+stream)) == conversion[stream+'_sha256'],
|
||||
'offline converter log hash')
|
||||
images.append(image)
|
||||
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)
|
||||
provenance = []
|
||||
for number, suffix in enumerate(('', '-after')):
|
||||
conversion_path = folder/('root-early-conversion'+suffix+'.json')
|
||||
conversion = json.loads(conversion_path.read_bytes())
|
||||
provenance.append(dict(whole_image_sha256=conversion['png_sha256'],
|
||||
raw_sha256=conversion['raw_sha256'],
|
||||
conversion_sha256=sha(conversion_path),
|
||||
request_sha256=sha(folder/'request.json'),
|
||||
receipt_sha256=sha(folder/'cua-receipt.json')))
|
||||
review_dir = ROOT/'docs/plans/original-completion-2026-10-02/execution/p23-03/offline-r07-review'/name
|
||||
manifest, digest = write_frame_review(review_dir, frame, provenance)
|
||||
credited = reviewed_pixel_categories(manifest, digest, review_dir/'independent-verdict.json')
|
||||
present = {r['category'] for r in frame['rows'] if r['eligibility'] != 'outside'}
|
||||
overlays = [dict(cell=r['cell'], rect=r['rect']) for r in report['traffic']
|
||||
if (r['traffic']['current_density'] > 0 or r['traffic']['temporary_block'])
|
||||
and not any(a['category'] == 'traffic' and a['id'] == ','.join(map(str, r['cell']))
|
||||
for a in audit['rows'])]
|
||||
results.append(dict(request=name, layer=report['layer'], manifest_sha256=digest,
|
||||
manifest_path=str(review_dir/'manifest.json'), rows=manifest['rows'],
|
||||
mechanical_pass=frame['mechanical_pass'], errors=frame['errors'],
|
||||
unaudited_traffic_overlays=overlays,
|
||||
pending_categories=[c for c in CATEGORIES if c not in present],
|
||||
pixel_categories=sorted(credited), independent_review='pending'))
|
||||
return dict(task_id='P23-03', acceptance='pending', status='blocked',
|
||||
inventory_count=len(mapping), path_map=mapping, inspected_eight=inspected,
|
||||
frames=results, root_native_os_full_regression_independent_review='pending')
|
||||
|
||||
|
||||
def enum_names(root, relative, name):
|
||||
@@ -1171,9 +1454,18 @@ def inspect_os_requests(directory, layout, role, pid, root=ROOT):
|
||||
# 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)
|
||||
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)
|
||||
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'],
|
||||
conversion_sha256=sha(path(binding['conversion'])) if conversion else None,
|
||||
request_sha256=digest, receipt_sha256=sha(path(ack['input_receipt'])))])
|
||||
require(frame['mechanical_pass'], 'frame mechanical checks: ' + str(frame['errors'])
|
||||
+ str([r['failures'] for r in frame['rows'] if not r['mechanical_pass']]))
|
||||
pixel_categories.update(reviewed_pixel_categories(
|
||||
manifest, manifest_sha, review_dir/'independent-verdict.json'))
|
||||
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
|
||||
@@ -1301,9 +1593,15 @@ def main(argv=None):
|
||||
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('--offline-mirror', type=Path)
|
||||
parser.add_argument('--layout-a', type=Path)
|
||||
parser.add_argument('--layout-b', type=Path)
|
||||
args = parser.parse_args(argv)
|
||||
if args.offline_mirror:
|
||||
require(not any((args.verify_only, args.diagnostics, args.layout_a, args.layout_b)),
|
||||
'offline preparation must not be combined with acceptance checks')
|
||||
print(json.dumps(inspect_offline_mirror(args.offline_mirror), ensure_ascii=False))
|
||||
return 2
|
||||
if args.verify_only:
|
||||
build = os.environ.get('BUILD_OUTPUT_DIR')
|
||||
if not build:
|
||||
|
||||
@@ -288,27 +288,6 @@ class InfrastructureTests(unittest.TestCase):
|
||||
if change == 'id': q[0]['ack']['sequence'] = 2
|
||||
with self.assertRaises(ValueError): check.inspect_resume(writer, r, saved, q, members)
|
||||
|
||||
def test_rectangle_pixel_and_transform_negatives(self):
|
||||
source = 'scripts/ui/pixel_people.gd'
|
||||
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"', (check.ROOT/source).read_text())))
|
||||
report = dict(rooms={}, landing_badges=[], cabins=[], resting_staff={}, badges=[],
|
||||
actor_drawings=[dict(key='staff/s', staff=True, at=[1, 1], scale=1)])
|
||||
row = dict(category='staff', id='staff/s', rect=[1, 1, 16, 18], viewport_rect=[1, 1, 16, 18],
|
||||
screen_rect=[1, 1, 16, 18], style_source=source, style_colors=colors)
|
||||
audit = dict(rows=[row])
|
||||
matrix = [1, 0, 0, 1, 0, 0]
|
||||
image = (24, 24, [[(113, 157, 148)]*24 for _ in range(24)])
|
||||
self.assertEqual(check.compare_rectangles(report, audit, matrix, matrix, matrix, image), ['staff/staff/s'])
|
||||
for change in ('rectangle', 'pixels', 'transform', 'coverage', 'palette'):
|
||||
with self.subTest(change=change):
|
||||
a, m, img = copy.deepcopy(audit), matrix[:], image
|
||||
if change == 'rectangle': a['rows'][0]['rect'][0] += 1
|
||||
if change == 'pixels': img = (24, 24, [[(0, 0, 0)]*24 for _ in range(24)])
|
||||
if change == 'transform': m[4] = 10
|
||||
if change == 'coverage': a['rows'].clear()
|
||||
if change == 'palette': a['rows'][0]['style_colors'] = ['000000']
|
||||
with self.assertRaises(ValueError): check.compare_rectangles(report, a, matrix, m, matrix, img)
|
||||
|
||||
def test_interpolation_history_tampering(self):
|
||||
state = dict(systems=dict(runtime=dict(guests=dict(guests=[]), staff=dict(staff=[]),
|
||||
elevators=dict(shafts=[], trips=[], current_minute=0))))
|
||||
@@ -568,5 +547,215 @@ class ComparisonTests(unittest.TestCase):
|
||||
check.compare_layouts(a, b)
|
||||
|
||||
|
||||
class OfflineGeometryTests(unittest.TestCase):
|
||||
"""Synthetic calibration examples; no OS or acceptance credit."""
|
||||
|
||||
def setUp(self):
|
||||
self.calibration = dict(window_size=[100, 80], window_position=[240, 150],
|
||||
screenshot_pixels=[200, 190], backing_scale=2,
|
||||
canvas_stretch=1, titlebar_pixels=30)
|
||||
self.request = dict(window_size=[100, 80], window_position=[240, 150],
|
||||
screen_scale=2, content_scale_factor=1, target_pid=123)
|
||||
self.clicks = [dict(target_pid=123, events=[dict(action='click',
|
||||
coordinates=[20, 70], calibration=copy.deepcopy(self.calibration),
|
||||
point=dict(screen=[10, 20], viewport=[10, 20],
|
||||
viewport_to_screen=[1, 0, 0, 1, 0, 0]))])]
|
||||
|
||||
def transform(self):
|
||||
return check.screenshot_transform(self.calibration, self.request, [200, 190], self.clicks)
|
||||
|
||||
def test_uses_measured_scale_origin_not_desktop_position(self):
|
||||
self.assertEqual(check.affine_rect(self.transform(), [10, 20, 5, 7]), [20, 70, 10, 14])
|
||||
|
||||
def test_shifted_titlebar_fails_independent_click(self):
|
||||
self.calibration['titlebar_pixels'] += 1
|
||||
self.calibration['screenshot_pixels'][1] += 1
|
||||
self.clicks[0]['events'][0]['calibration'] = copy.deepcopy(self.calibration)
|
||||
with self.assertRaises(ValueError):
|
||||
check.screenshot_transform(self.calibration, self.request, [200, 191], self.clicks)
|
||||
|
||||
def test_wrong_click_offset_fails(self):
|
||||
self.clicks[0]['events'][0]['coordinates'][1] -= 30
|
||||
with self.assertRaisesRegex(ValueError, 'click screenshot offset'):
|
||||
self.transform()
|
||||
|
||||
def test_wrong_pid_cannot_calibrate(self):
|
||||
self.clicks[0]['target_pid'] += 1
|
||||
with self.assertRaisesRegex(ValueError, 'missing independent click'):
|
||||
self.transform()
|
||||
|
||||
def test_nonfinite_and_false_scale_rejected(self):
|
||||
for scale in (False, float('nan'), 0, -1):
|
||||
self.calibration['backing_scale'] = scale
|
||||
with self.assertRaisesRegex(ValueError, 'invalid screenshot calibration'):
|
||||
self.transform()
|
||||
|
||||
def test_all_visibility_cases_are_accounted_for(self):
|
||||
self.assertEqual(check.rectangle_eligibility([2, 3, 4, 5], [10, 10]),
|
||||
('visible', [2, 3, 4, 5]))
|
||||
self.assertEqual(check.rectangle_eligibility([-2, 3, 4, 5], [10, 10]),
|
||||
('clipped', [0, 3, 2, 5]))
|
||||
self.assertEqual(check.rectangle_eligibility([10, 3, 4, 5], [10, 10])[0], 'outside')
|
||||
self.assertEqual(check.rectangle_eligibility([2, 3, 0, 5], [10, 10])[0], 'outside')
|
||||
|
||||
def test_invalid_rectangles_rejected(self):
|
||||
for rect in ([0, 0, -1, 4], [float('nan'), 0, 1, 4]):
|
||||
with self.assertRaisesRegex(ValueError, 'invalid clipping rectangle'):
|
||||
check.rectangle_eligibility(rect, [10, 10])
|
||||
|
||||
def test_noise_is_measured_and_does_not_assert_class(self):
|
||||
before = (2, 2, [[(10, 20, 30)]*2 for _ in range(2)])
|
||||
after = copy.deepcopy(before)
|
||||
after[2][1][1] = (10, 27, 30)
|
||||
noise = check.paired_crop_noise(before, after, [0, 0, 2, 2])
|
||||
self.assertEqual(noise, dict(samples=4, max_channel_delta=7,
|
||||
mean_channel_delta=1.75, unchanged=3))
|
||||
self.assertNotIn('pass', noise)
|
||||
self.assertNotIn('state', noise)
|
||||
|
||||
def test_wrong_dimensions_and_empty_crop_rejected(self):
|
||||
image = (1, 1, [[(0, 0, 0)]])
|
||||
with self.assertRaisesRegex(ValueError, 'paired image dimensions'):
|
||||
check.paired_crop_noise(image, (2, 1, []), [0, 0, 1, 1])
|
||||
with self.assertRaisesRegex(ValueError, 'empty noise crop'):
|
||||
check.paired_crop_noise(image, image, [1, 0, 1, 1])
|
||||
|
||||
def test_report_positions_and_source_provenance_are_recomputed(self):
|
||||
source = 'scripts/ui/pixel_people.gd'
|
||||
report = dict(rooms={}, landing_badges=[], cabins=[], resting_staff={}, badges=[],
|
||||
actor_drawings=[dict(key='staff/s', staff=True, at=[1, 1], scale=1)])
|
||||
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
|
||||
(check.ROOT/source).read_text())))
|
||||
audit = dict(rows=[dict(category='staff', id='staff/s', rect=[1, 1, 16, 18],
|
||||
style_source=source, style_colors=colors)])
|
||||
check.validate_drawing_rows(report, audit)
|
||||
for change in ('shift', 'identity', 'missing', 'palette'):
|
||||
tampered = copy.deepcopy(audit)
|
||||
if change == 'shift': tampered['rows'][0]['rect'][0] += 1
|
||||
if change == 'identity': tampered['rows'][0]['category'] = 'guest'
|
||||
if change == 'missing': tampered['rows'] = []
|
||||
if change == 'palette': tampered['rows'][0]['style_colors'] = ['000000']
|
||||
with self.subTest(change=change), self.assertRaises(ValueError):
|
||||
check.validate_drawing_rows(report, tampered)
|
||||
|
||||
|
||||
class FrameReviewTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
OfflineGeometryTests.setUp(self)
|
||||
self.request.update(local_to_viewport=[1, 0, 0, 1, 0, 0],
|
||||
viewport_to_screen=[1, 0, 0, 1, 0, 0],
|
||||
local_to_screen=[1, 0, 0, 1, 0, 0])
|
||||
self.report = dict(layer=0, viewport=[100, 80], rooms={}, cabins=[],
|
||||
landing_badges=[], badges=[], resting_staff={}, traffic=[],
|
||||
actor_drawings=[dict(key='staff/s', staff=True, at=[1, 1], scale=1, state=0)])
|
||||
source = 'scripts/ui/pixel_people.gd'
|
||||
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
|
||||
(check.ROOT/source).read_text())))
|
||||
self.audit = dict(rows=[dict(category='staff', id='staff/s', rect=[1, 1, 16, 18],
|
||||
viewport_rect=[1, 1, 16, 18], screen_rect=[1, 1, 16, 18],
|
||||
style_source=source, style_colors=colors)])
|
||||
self.image = (200, 190, [[(x % 256, y % 256, 0) for x in range(200)] for y in range(190)])
|
||||
|
||||
def frame(self):
|
||||
return check.frame_rows(self.report, self.audit, self.request,
|
||||
dict(calibration=self.calibration), [self.image], self.clicks)
|
||||
|
||||
def test_blank_crop_fails_without_threshold(self):
|
||||
self.image = (200, 190, [[(0, 0, 0)] * 200 for _ in range(190)])
|
||||
result = self.frame()
|
||||
self.assertFalse(result['mechanical_pass'])
|
||||
self.assertIn('blank crop', result['rows'][0]['failures'])
|
||||
|
||||
def test_missing_extra_and_unaudited_traffic_fail(self):
|
||||
for change in ('missing', 'extra', 'traffic'):
|
||||
with self.subTest(change=change):
|
||||
original = copy.deepcopy(self.audit)
|
||||
if change == 'missing': self.audit['rows'].clear()
|
||||
if change == 'extra': self.audit['rows'] *= 2
|
||||
if change == 'traffic':
|
||||
self.report['traffic'] = [dict(cell=[-6, 2, 0], rect=[1, 1, 5, 5],
|
||||
traffic=dict(current_density=1, temporary_block=False))]
|
||||
result = self.frame()
|
||||
self.assertFalse(result['mechanical_pass'])
|
||||
self.assertEqual(len(result['rows']), len(self.audit['rows']))
|
||||
self.assertIn('offline rectangle coverage', result['errors'])
|
||||
self.audit = original
|
||||
|
||||
def test_dense_or_blocked_traffic_row_closes_audit_gap(self):
|
||||
source = 'scenes/main/world_preview.gd'
|
||||
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
|
||||
(check.ROOT/source).read_text())))
|
||||
for density, blocked in ((1, False), (0, True), (0, False)):
|
||||
with self.subTest(density=density, blocked=blocked):
|
||||
self.report['traffic'] = [dict(cell=[-6, 2, 0], rect=[1, 1, 5, 5],
|
||||
traffic=dict(current_density=density, temporary_block=blocked))]
|
||||
active = density > 0 or blocked
|
||||
missing = self.frame()
|
||||
self.assertEqual(missing['mechanical_pass'], not active)
|
||||
if active:
|
||||
self.assertIn('offline rectangle coverage', missing['errors'])
|
||||
self.audit['rows'].append(dict(category='traffic', id='-6,2,0',
|
||||
rect=[1, 1, 5, 5], viewport_rect=[1, 1, 5, 5],
|
||||
screen_rect=[1, 1, 5, 5], style_source=source, style_colors=colors))
|
||||
complete = self.frame()
|
||||
self.assertTrue(complete['mechanical_pass'], complete['errors'])
|
||||
self.audit['rows'].pop()
|
||||
|
||||
def test_wrong_state_enum_and_name_fail(self):
|
||||
source = 'scripts/ui/pixel_tower_art.gd'
|
||||
self.report['actor_drawings'] = []
|
||||
room = dict(rect=[1, 1, 16, 18], state=3, state_name='NO_STAFF', operation=0)
|
||||
self.report['rooms'] = {'r': room}
|
||||
self.audit['rows'][0].update(category='room', id='r', style_source=source,
|
||||
style_colors=list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
|
||||
(check.ROOT/source).read_text()))))
|
||||
self.assertEqual(self.frame()['rows'][0]['operation'], 'INACTIVE')
|
||||
for state, name in ((99, 'NO_STAFF'), (3, 'OPEN'), (True, 'NO_ACCESS')):
|
||||
room.update(state=state, state_name=name)
|
||||
with self.assertRaises(ValueError): self.frame()
|
||||
|
||||
def test_frame_rejects_affine_and_calibration_shift(self):
|
||||
self.request['local_to_screen'][4] += 1
|
||||
with self.assertRaisesRegex(ValueError, 'affine composition'): self.frame()
|
||||
self.request['local_to_screen'][4] -= 1
|
||||
self.clicks[0]['events'][0]['coordinates'][1] += 1
|
||||
with self.assertRaisesRegex(ValueError, 'click screenshot offset'): self.frame()
|
||||
|
||||
def test_eligible_crop_fully_outside_image_fails(self):
|
||||
for key in ('viewport_to_screen', 'local_to_screen'):
|
||||
self.request[key][4] = 300
|
||||
self.audit['rows'][0]['screen_rect'][0] += 300
|
||||
result = self.frame()
|
||||
self.assertFalse(result['mechanical_pass'])
|
||||
self.assertIn('eligible crop outside image', result['rows'][0]['failures'])
|
||||
|
||||
def test_manifest_crop_and_independent_review_binding(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
directory = Path(temporary)
|
||||
manifest, digest = check.write_frame_review(directory, self.frame(),
|
||||
[dict(whole_image_sha256='a'*64, raw_sha256='b'*64, conversion_sha256='c'*64)])
|
||||
self.assertEqual(check.sha(directory/'manifest.json'), digest)
|
||||
crop = manifest['rows'][0]['crops'][0]
|
||||
self.assertEqual(check.sha(directory/crop['path']), crop['sha256'])
|
||||
self.assertEqual(check.png_pixels((directory/crop['path']).read_bytes())[:2], (32, 36))
|
||||
verdict_path = directory/'independent-verdict.json'
|
||||
self.assertEqual(check.reviewed_pixel_categories(manifest, digest, verdict_path), set())
|
||||
verdict = dict(manifest_sha256=digest, reviewer='independent-opus-direct-image',
|
||||
rows=[dict(row_index=0, category=True, state=True, position=True)])
|
||||
for change in ('wrong_sha', 'missing_row', 'extra_row', 'rejected_state'):
|
||||
bad = copy.deepcopy(verdict)
|
||||
if change == 'wrong_sha': bad['manifest_sha256'] = '0'*64
|
||||
if change == 'missing_row': bad['rows'] = []
|
||||
if change == 'extra_row': bad['rows'] *= 2
|
||||
if change == 'rejected_state': bad['rows'][0]['state'] = False
|
||||
verdict_path.write_text(json.dumps(bad))
|
||||
with self.subTest(change=change), self.assertRaises(ValueError):
|
||||
check.reviewed_pixel_categories(manifest, digest, verdict_path)
|
||||
verdict_path.write_text(json.dumps(verdict))
|
||||
self.assertEqual(check.reviewed_pixel_categories(manifest, digest, verdict_path), {'staff'})
|
||||
manifest['mechanical_pass'] = False
|
||||
self.assertEqual(check.reviewed_pixel_categories(manifest, digest, verdict_path), set())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user