203 lines
12 KiB
Python
203 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""Offline comparisons from retained pixels/events; no native causality invented.
|
|
|
|
CONTRACT: full RGB and pre-registered regions; traces absent remain absent.
|
|
INVARIANT: raw runs and original source manifests are not rewritten here.
|
|
"""
|
|
import hashlib
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
lab = Path(sys.argv[1]).resolve()
|
|
out = lab / 'results'
|
|
fixture = Path(__file__).resolve().parent
|
|
ROIS = {'center': (520,100,720,500), 'source': (20,100,160,500),
|
|
'boundary': (720,50,800,550), 'physical_right': (750,0,800,600),
|
|
'physical_bottom': (0,550,800,600)}
|
|
PAIRS = [('P0-FX0','P0-FX2'), ('P0-FX2','P1-B-natural'),
|
|
('P1-B-natural','P2-B-explicit'), ('P0-FX2','P3-A-natural'),
|
|
('P3-A-natural','P3-confirm')]
|
|
NAMES = ['OPT_NODE_CREATE','OPT_NODE_RENDER','OPT_BACKGROUND_COMMIT','OPT_DIRTY_SET',
|
|
'OPT_DIRTY_CLEAR','OPT_REGEN_ATTEMPT','OPT_REGEN_RESULT','OPT_GPU_REBUILD',
|
|
'OPT_GPU_RESULT','OPT_GPU_COPY','OPT_GPU_COPY_DRAWN','OPT_BLUR_USE',
|
|
'OPT_CACHE_STATE','OPT_REGULAR_NODE','OPT_OUTPUT_BUFFER','PI_SCENE',
|
|
'PI_OWNER','PI_OUTPUT_COMMIT','PI_OUTPUT_PRESENT','TEMPORAL_DAMAGE']
|
|
|
|
def write(name, value):
|
|
(out / (name + '.json')).write_text(json.dumps(value, indent=2, sort_keys=True) + '\n')
|
|
|
|
def sha(path):
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
def crop(image, box):
|
|
left, top, right, bottom = box
|
|
return image[top:bottom,left:right]
|
|
|
|
def metric(first, second):
|
|
delta = np.abs(first.astype(np.int16) - second.astype(np.int16))
|
|
mask = np.any(delta, axis=2)
|
|
ys, xs = np.where(mask)
|
|
return {'pixels': int(mask.sum()), 'total_pixels': int(mask.size),
|
|
'max_channel': int(delta.max()), 'mean_abs_channel': float(delta.mean()),
|
|
'bbox': [int(xs.min()),int(ys.min()),int(xs.max()+1),int(ys.max()+1)] if mask.any() else None,
|
|
'channel_amplitude_histogram': {str(i):int(n) for i,n in enumerate(np.bincount(delta.ravel(),minlength=256)) if n},
|
|
'changed_pixel_max_amplitude_histogram': {str(i):int(n) for i,n in enumerate(np.bincount(delta.max(axis=2).ravel(),minlength=256)) if n}}
|
|
|
|
def compare(first, second):
|
|
return {'whole': metric(first,second),
|
|
'ROIs': {name: metric(crop(first,box),crop(second,box)) for name,box in ROIS.items()}}
|
|
|
|
def parsed(lines, name):
|
|
return [{'raw': line, **dict(re.findall(r'([A-Za-z_]+)=([^ ]+)',line))}
|
|
for line in lines if re.search(r'\b' + name + r'\b',line)]
|
|
|
|
runs = {}
|
|
pixels = {}
|
|
columns = {}
|
|
rows = {}
|
|
renderers = {}
|
|
commits = {}
|
|
states = {}
|
|
geometries = {}
|
|
settings = {}
|
|
windows = {}
|
|
phase_traces = {}
|
|
damage = {}
|
|
contrast = {}
|
|
ledger = json.loads((out/'campaign-budget.json').read_text())
|
|
for mode in ('R0','R1','R2','R3'):
|
|
run = lab/'runs'/mode
|
|
status = json.loads((run/'exit-status.json').read_text())
|
|
events = [json.loads(line) for line in (run/'events.jsonl').read_text().splitlines()]
|
|
lines = (run/'compositor.log').read_text().splitlines()
|
|
traces = {name: parsed(lines,name) for name in NAMES}
|
|
images = {event['tag']+'-'+event['output']: np.array(Image.open(run/event['path']).convert('RGB'))
|
|
for event in events if event['kind']=='capture'}
|
|
runs[mode] = {'status':status,'events':events,'traces':traces,'images':images}
|
|
renderer_event = next(e for e in events if e['kind']=='renderer_start')
|
|
loaded = sorted(set(line.split()[-1] for line in (run/'library-maps.txt').read_text().splitlines()
|
|
if 'libscenefx' in line or 'libwlroots' in line))
|
|
renderers[mode] = {'lines':[line for line in lines if any(text in line for text in
|
|
('Using OpenGL ES','GL renderer:','GL vendor:','EGL vendor:','FX RENDERER:'))],
|
|
'loaded_libraries':{path:sha(Path(path)) for path in loaded},
|
|
'binary':renderer_event['binary'],'binary_sha256':sha(Path(renderer_event['binary'])),
|
|
'OPT_TRACE':renderer_event['OPT_TRACE'],'native_OPT_evidence':mode=='R3'}
|
|
client = parsed((run/'background.log').read_text().splitlines(),'CLIENT_COMMIT')
|
|
reserves = [e for e in events if e['kind']=='mutation_reserved']
|
|
configures = parsed((run/'background.log').read_text().splitlines(),'BACKGROUND_CONFIGURED')
|
|
assert len(client)==ledger['modes'][mode]['mutation']==len(reserves)
|
|
assert [c['pattern'] for c in client]==[r['pattern'] for r in reserves]
|
|
assert all(int(c['ns'])>r['ns'] and c['size']=='800x600' for c,r in zip(client,reserves))
|
|
assert len(status['captures'])==ledger['modes'][mode]['capture']
|
|
assert status['duration']<150 and status['runtime_removed'] and not (run/'runtime').exists()
|
|
commits[mode] = {'client':client,'configures':configures,'reservations':reserves,
|
|
'server':traces['OPT_BACKGROUND_COMMIT'],
|
|
'reservation_protocol':'PASS before every observed A/B image',
|
|
'no_configure_draw':'PASS duplicate configure ACK without autonomous image',
|
|
'identity_limit':'surface/buffer IDs may be reused; sequence/lifetime required'}
|
|
states[mode] = [e for e in events if e['kind']=='graphic_state']
|
|
geometries[mode] = {p.stem:json.loads(p.read_text()) for p in run.glob('*-outputs.json')}
|
|
settings[mode] = {'config':(run/'config').read_text(),
|
|
'commands':[e for e in events if e['kind']=='ipc' and e['args'] and e['args'][0].startswith('[app_id=')],
|
|
'pixel_effect_gate':[e for e in events if e['kind']=='P0_gate'],
|
|
'setting_limit':'accepted config + source audit; filtering proves effect; no OPT parameters fabricated for untraced controls'}
|
|
windows[mode] = status['captures']
|
|
if status['failure']:
|
|
pixels[mode] = {'failure':status['failure']}
|
|
continue
|
|
assert [c['pattern'] for c in client]==['A','B','A']
|
|
baseline = images['P0-FX0-HEADLESS-2']
|
|
p = {'comparisons':{},'H2_full':{},'capture_sha256':{q.name:sha(q) for q in run.glob('*.png')}}
|
|
columns[mode] = {}; rows[mode] = {}; contrast[mode] = {}
|
|
for first,second in PAIRS:
|
|
key=first+' -> '+second
|
|
a=images[first+'-HEADLESS-1']; b=images[second+'-HEADLESS-1']
|
|
p['comparisons'][key]=compare(a,b)
|
|
mask=np.any(a!=b,axis=2)
|
|
columns[mode][key]=mask.sum(axis=0).astype(int).tolist()
|
|
rows[mode][key]=mask.sum(axis=1).astype(int).tolist()
|
|
for name,image in images.items():
|
|
if name.endswith('HEADLESS-2'):
|
|
p['H2_full'][name]=metric(baseline,image)
|
|
else:
|
|
center=crop(image,ROIS['center']).astype(float)
|
|
luminance=center@[.2126,.7152,.0722]
|
|
contrast[mode][name]={'luminance_std':float(luminance.std()),
|
|
'adjacent_luminance':float(np.abs(np.diff(luminance,axis=1)).mean()),
|
|
'mean_RGB':center.mean(axis=(0,1)).tolist()}
|
|
# Full exposed-source calibration in every measured state, not just membership.
|
|
x=np.arange(20,160)
|
|
for tag,pattern in [('P0-FX0','A'),('P0-FX2','A'),('P1-B-natural','B'),('P2-B-explicit','B'),('P3-A-natural','A')]:
|
|
colors=np.array([[245,245,245],[15,25,45]] if pattern=='A' else [[32,192,224],[176,32,64]],dtype=np.uint8)
|
|
expected=np.broadcast_to(colors[(x//8)%2],(400,140,3))
|
|
assert np.array_equal(expected,crop(images[tag+'-HEADLESS-1'],ROIS['source']))
|
|
p['source_calibration']='PASS exact stripes A/B/B/A in exposed ROI'
|
|
pixels[mode]=p
|
|
phase_traces[mode]={}
|
|
for tag in ('P0-FX2','P1-B-natural','P2-B-explicit','P3-A-natural'):
|
|
capture=next(e for e in status['captures'] if e['tag']==tag and e['output']=='HEADLESS-1')
|
|
if tag=='P0-FX2':
|
|
start=next(e['after_ns'] for e in status['captures'] if e['tag']=='P0-reference')
|
|
elif tag=='P2-B-explicit':
|
|
start=next(e['ns'] for e in events if e['kind']=='explicit_cycle_start')
|
|
else:
|
|
start=reserves[1 if tag=='P1-B-natural' else 2]['ns']
|
|
phase_traces[mode][tag]={'start_ns':start,'capture_before_ns':capture['before_ns'],
|
|
'end_ns':capture['after_ns'],
|
|
'trace':{name:[t for t in values if 'ns' in t and start<=int(t['ns'])<=capture['after_ns']]
|
|
for name,values in traces.items()}}
|
|
if mode!='R3':
|
|
damage[mode]={'output_state_OPT_damage':'NOT AVAILABLE',
|
|
'old_scene_damage':traces['TEMPORAL_DAMAGE'],
|
|
'limit':'older private scene damage (if present) is not OPT output-state buffer damage; no equivalent coverage claim'}
|
|
else:
|
|
damage[mode]={}
|
|
for first,second in [('P0-FX0','P0-FX2'),('P0-FX2','P1-B-natural'),('P1-B-natural','P2-B-explicit'),('P2-B-explicit','P3-A-natural')]:
|
|
ca=next(e for e in status['captures'] if e['tag']==first and e['output']=='HEADLESS-1')
|
|
cb=next(e for e in status['captures'] if e['tag']==second and e['output']=='HEADLESS-1')
|
|
mask=np.any(images[first+'-HEADLESS-1']!=images[second+'-HEADLESS-1'],axis=2)
|
|
cover=np.zeros((600,800),bool); records=[]
|
|
for t in traces['OPT_OUTPUT_BUFFER']:
|
|
if t['output']!='HEADLESS-1' or not ca['before_ns']<=int(t['ns'])<=cb['after_ns']:continue
|
|
left,top,right,bottom=map(int,t['damage'].split(','));cover[max(0,top):min(600,bottom),max(0,left):min(800,right)]=True;records.append(t)
|
|
damage[mode][first+' -> '+second]={'changed_pixels':int(mask.sum()),
|
|
'uncovered_by_output_damage_extents':int((mask&~cover).sum()),'records':records,
|
|
'limit':'extent union, stabilized captures only; no all-frame coverage/minimal damage/native PNG identity'}
|
|
|
|
cross={}
|
|
for first,second in [('R0','R1'),('R2','R3')]:
|
|
key=first+' -> '+second
|
|
a=runs[first];b=runs[second]
|
|
comparable=not a['status']['failure'] and not b['status']['failure']
|
|
cross[key]={'geometry_equal':states[first]==states[second], 'comparable':comparable}
|
|
# State records contain timestamps; compare applied geometry and tags only.
|
|
cross[key]['geometry_equal']=[(e['tag'],e['geometry']) for e in states[first]]==[(e['tag'],e['geometry']) for e in states[second]]
|
|
if comparable and cross[key]['geometry_equal']:
|
|
cross[key]['states']={tag:compare(a['images'][tag+'-HEADLESS-1'],b['images'][tag+'-HEADLESS-1'])
|
|
for tag in ('P0-FX0','P0-FX2','P1-B-natural','P2-B-explicit','P3-A-natural','P3-confirm')}
|
|
cross[key]['H2']=metric(a['images']['P0-FX0-HEADLESS-2'],b['images']['P0-FX0-HEADLESS-2'])
|
|
else:cross[key]['limit']='control geometry/fixture not comparable'
|
|
write('renderer-identities',renderers);write('source-commits',commits)
|
|
write('graphic-states',states);write('output-geometries',geometries);write('blur-settings',settings)
|
|
write('edge-pixel-analysis',pixels);write('column-distribution',columns);write('row-distribution',rows)
|
|
write('capture-windows',windows);write('damage-analysis',damage);write('phase-events',phase_traces)
|
|
write('filtering-analysis',contrast);write('private-trace-comparison',cross['R2 -> R3']);write('pristine-control',cross['R0 -> R1'])
|
|
for category,names in [('cache-use',['OPT_BLUR_USE','OPT_CACHE_STATE','OPT_REGULAR_NODE']),
|
|
('dirty-events',['OPT_DIRTY_SET','OPT_DIRTY_CLEAR']),
|
|
('regeneration-events',['OPT_REGEN_ATTEMPT','OPT_REGEN_RESULT','OPT_GPU_REBUILD','OPT_GPU_RESULT','OPT_GPU_COPY','OPT_GPU_COPY_DRAWN'])]:
|
|
write(category,{mode:{name:data['traces'][name] for name in names} for mode,data in runs.items()})
|
|
write('matrix',{mode:{'failure':data['status']['failure'],'duration':data['status']['duration'],
|
|
'compositor_exit':data['status']['compositor'],'captures':len(data['status']['captures']),
|
|
'images':len(commits[mode]['client']),'runtime_removed':data['status']['runtime_removed']}
|
|
for mode,data in runs.items()})
|
|
# Never infer the final scientific verdict merely from process success.
|
|
print(json.dumps({'edge_counts':{mode:{pair:value['whole'] for pair,value in data.get('comparisons',{}).items()
|
|
if pair in ('P1-B-natural -> P2-B-explicit','P0-FX2 -> P3-A-natural')}
|
|
for mode,data in pixels.items()},
|
|
'cross':{key:{'geometry_equal':data['geometry_equal'],
|
|
'state_changed_pixels':{tag:value['whole']['pixels'] for tag,value in data.get('states',{}).items()}}
|
|
for key,data in cross.items()}},indent=2))
|