"""Compare new Runtime to frozen saved decisions; no baseline inference/evaluator."""
import sys,json,time,hashlib
from pathlib import Path
sys.path[:0]=['/out/source','/opt/contextra','/evidence/2026-11-01_contextra-fall-spatial-28clips_r01/helpers']
from common import OUT,read,save,load,evidence,sha
sys.path.insert(0,'/out/source')
from app.fall_runtime import FallSettings,FallScenario
from app.fall_r2.core import R2Core
from app.contracts.frames import FrameEnvelope,PoseObservation
from app.pose_config import POSE_MODEL
from app.event_cooldown import EventCooldown

items=[];total=0;full=0;errors=[]
for clip,spec in read(OUT/'specs.json').items():
 rows,selected,_,_,_=load(clip);refs=[json.loads(l) for l in evidence(spec['reference']).open()]
 assert len(rows)==len(refs)
 cfg=FallSettings.parse({'enabled':True,'engine':'r2'});engine=FallScenario(cfg)
 clock=[0.];gate=EventCooldown(10,clock=lambda:clock[0]);engine.engine.event_admission=gate.allow
 raw_core=R2Core();count=0;red=0;events=0;clipped=0;differences=[];raw_errors=[]
 output=OUT/'download'/clip;output.mkdir(parents=True,exist_ok=True)
 with (output/'runtime.jsonl').open('w') as out:
  for row,ref in zip(rows,refs):
   assert (row['sample'],row['source_frame'],row['seconds'])==(ref['sample'],ref['source_frame'],ref['seconds'])
   clock[0]=row['seconds']-rows[0]['seconds'];w,h=row['width'],row['height']
   raw,_,_=raw_core.step(row)
   for key in ('hit','observable','display','display_hold','association_uncertain'):
    if raw[key]!=ref['r2'][key]:raw_errors.append([row['sample'],key])
   frame=FrameEnvelope('replay','cache',clip,clip+'-R2',1,row['sample'],round(row['seconds']*1e9),0,w,h,'frozen')
   poses=[];ids=[]
   for p,b in zip(row['persons'],ref['people']):
    box=tuple(v/(w if i%2==0 else h) for i,v in enumerate(p['bbox']));bounded=tuple(min(1.,max(0.,v)) for v in box);clipped+=bounded!=box
    poses.append(PoseObservation(frame,POSE_MODEL,p['score'],bounded,tuple((x/w,y/h,s) for x,y,s in p['keypoints'])))
    ids.append(b['track_id'])
   displays,issued=engine.evaluate(frame,tuple(poses),tuple(ids));result=engine.engine.last_result
   for key in ('hit','observable','display','display_hold','association_uncertain'):
    if result[key]!=ref['r2'][key]:differences.append([row['sample'],key,result[key],ref['r2'][key]])
   if bool(issued)!=bool(ref['r2']['event']):differences.append([row['sample'],'event',bool(issued),bool(ref['r2']['event'])])
   expected_colors=[(next(i for i,p in enumerate(ref['people']) if p['query']==c['query']),c['state']) for c in ref['r2']['colors']]
   actual_colors=[(c['query'],c['state']) for c in result['colors']]
   if expected_colors!=actual_colors:differences.append([row['sample'],'colors',actual_colors,expected_colors])
   out.write(json.dumps(dict(sample=row['sample'],source_frame=row['source_frame'],seconds=row['seconds'],displayed=ref['displayed'],hit=result['hit'],display=result['display'],event=bool(issued),colors=actual_colors))+'\n')
   full+=1
   if ref['displayed']:count+=1;red+=result['hit'];events+=bool(issued)
 assert count==len(selected);total+=count
 item=dict(clip=clip,displayed=count,red=red,events=events,canonical_bbox_clips=clipped,differences=differences,raw_errors=raw_errors,cache_sha256=spec['cache_sha256'],reference_sha256=sha(evidence(spec['reference'])))
 save(output/'validation.json',item);items.append(item)
 if differences or raw_errors:errors.append(clip)
 print('REPLAY',clip,count,len(differences),len(raw_errors),flush=True)
assert total==20145 and len(items)==36
save(OUT/'reports/replay_result.json',dict(status='PASS' if not errors else 'FAIL',inputs=36,displayed=total,full=full,failed=errors,items=items))
raise SystemExit(bool(errors))
