Files
EscapistsReintroduced/docs/evidence/coarse-selection-review-2026-09-19-205021/analyze.py
T

43 lines
6.6 KiB
Python

from pathlib import Path
import sys,json,hashlib,statistics,time,re,math
root=Path('/home/jakub/devel/escapistsrereintroduced'); archive=root/'docs/evidence/game-2026-09-19-205021-coarse-selection-refusal';out=Path('/tmp/erri-coarse-selection-audit')
sys.path.insert(0,str(root/'tools'))
from ordinary_gate_choices import join_choice_snapshots
from ordinary_look_evidence import validate_look_evidence
from prison_approach_evidence import validate_approach_evidence
from prison_alignment_evidence import validate_facing_evidence
from prison_action_selection_evidence import validate_selection_evidence
from ordinary_gate_evidence import validate_gate_geometry
h=json.loads((archive/'input-proof.json.txt').read_text()); text=(archive/'console.log.txt').read_text();r=json.loads((archive/'runner.json.txt').read_text());nonce=h['nonce'];a=h['approach_proof'];f=h['facing_proof'];s=h['action_selection_proof'];l=h['look_proof'];data=join_choice_snapshots(text,nonce);frames=data['snapshots']
def digest(p):return hashlib.sha256(p.read_bytes()).hexdigest()
def stats(v):return {'count':len(v),'minimum':min(v),'median':statistics.median(v),'maximum':max(v),'sum':sum(v)}
sourcehash={k:v for group in ('approach_module_sha256','alignment_module_sha256','selection_module_sha256') for k,v in h[group].items()};sourcehash['headless_input_proof.py']=h['helper_sha256'];sourcehash['headless_display_probe.py']=h['ownership_helper_sha256']
bindings={k:{'recorded_sha256':v,'actual_sha256':digest(root/'tools'/k),'matches':digest(root/'tools'/k)==v} for k,v in sourcehash.items()}
assert all(v['matches'] for v in bindings.values()),bindings
validations={
'look':validate_look_evidence(text,nonce,l,a['events'][:2]),
'approach':validate_approach_evidence(text,nonce,l,a),
'facing':validate_facing_evidence(text,nonce,l,a,f),
'selection':validate_selection_evidence(text,nonce,l,a,f,s)}
queued=int(re.findall(r'ERR INPUT OBSERVE: run='+nonce+r' sample=0 real=(\d+) world=[^ ]+ QUEUED',text)[0]); ready=data['ready']['real_ms'];ready_host=a['deadline_monotonic']-35
original_end=ready_host+(45000-(ready-queued))/1000-.5
motions=[]
for name,proof in [('approach',a),('facing',f)]:
receipts={x['sample']:x['received_monotonic'] for x in proof['samples']}
for m in proof['motions']:
before=frames[m['after_sample']];after=frames[m['response_sample']];yaw=lambda x:math.degrees(math.atan2(x[0],x[2]));turn=(yaw(after['look']['direction'])-yaw(before['look']['direction'])+180)%360-180
motions.append({'stage':name,**m,'native_turn_degrees':turn,'before_receipt_to_dispatch_s':m['monotonic']-receipts[m['after_sample']],'dispatch_to_response_receipt_s':receipts[m['response_sample']]-m['monotonic'],'native_span_ms':after['real_ms']-before['real_ms']})
events=h['events'];holds=[events[i+1]['monotonic']-events[i]['monotonic'] for i in range(0,len(events),2)];releaseguards=[g['returned_monotonic']-g['entered_monotonic'] for t in a['release_timings'] for g in t['guards']]
metrics={'queued_real_ms':queued,'ready_real_ms':ready,'ready_host':ready_host,'raw_original_end_ms':queued+45000,'conservative_original_end_host':original_end,'look_duration_s':l['finished_monotonic']-l['started_monotonic'],'approach_duration_s':a['finished_monotonic']-a['started_monotonic'],'approach_first_turn_through_first_W_s':events[0]['monotonic']-a['motions'][0]['monotonic'],'approach_final_distance_m':validations['approach']['final_distance_metres'],'approach_path_m':validations['approach']['path_metres'],'approach_pulses':len(holds),'approach_yaw_requests':len(a['motions']),'holds_s':stats(holds),'release_guards_s':stats(releaseguards),'total_release_guard_s':sum(releaseguards),'actual_release_over_guard_s':sum(holds)-sum(releaseguards),'largest_stop_m':max(t['distance'] for t in a['stops']),'facing_wrapper_started_host':f['deadline_monotonic']-8,'facing_prerequisite_before_module_s':f['started_monotonic']-(f['deadline_monotonic']-8),'facing_duration_s':f['finished_monotonic']-f['started_monotonic'],'facing_yaw_requests':len(f['motions']),'facing_finish_to_selection_start_s':s['started_monotonic']-f['finished_monotonic'],'selection_initial_read_s':s['finished_monotonic']-s['started_monotonic'],'shared_deadline_host':f['deadline_monotonic'],'remaining_at_facing_end_s':f['deadline_monotonic']-f['finished_monotonic'],'remaining_at_selection_entry_s':f['deadline_monotonic']-s['started_monotonic'],'remaining_at_refusal_s':f['deadline_monotonic']-s['finished_monotonic'],'shortfall_to_1_5_admission_s':1.5-(f['deadline_monotonic']-s['finished_monotonic']),'unused_original_time_beyond_stage_s':original_end-f['deadline_monotonic'],'raw_original_ms_remaining_at_facing_last_sample':queued+45000-frames[f['final_sample']]['real_ms'],'facing_final_pair':f['facing_samples'],'facing_last_response_sample':f['motions'][-1]['response_sample'],'facing_last_response_receipt_to_finish_s':f['finished_monotonic']-next(x['received_monotonic'] for x in f['samples'] if x['sample']==f['motions'][-1]['response_sample'])}
# Timing here is a separate offline replay, not the archived runtime latency.
functions={'join_choice_snapshots':lambda:join_choice_snapshots(text,nonce),'validate_gate_geometry':lambda:validate_gate_geometry(text,nonce,data['ready']['sample'],max(frames)),'validate_approach_evidence':lambda:validate_approach_evidence(text,nonce,l,a),'validate_facing_evidence':lambda:validate_facing_evidence(text,nonce,l,a,f)}
bench={}
for name,fn in functions.items():
times=[]
for _ in range(7):
start=time.perf_counter();fn();times.append(time.perf_counter()-start)
bench[name]=stats(times)
result={'scope':'Read-only diagnosis of archived local coarse approach/facing refusal; no input/runtime/source changes, no selection or held-action qualification','archive':str(archive),'nonce':nonce,'artifact_hashes':{p.name:digest(p) for p in archive.iterdir() if p.is_file() and p.suffix!='.bmp'},'tool_source_bindings':bindings,'independent_raw_validation':validations,'metrics':metrics,'motions':motions,'offline_replay_seconds_not_native_timings':bench,'input_end':{'keys':[e['key'] for e in events],'wheel_events':h['wheel_events'],'paired_release_errors':h['paired_release_errors'],'wheel_release_errors':h['wheel_release_errors'],'held_key':h['unconfirmed_held_key'],'held_button':h['unconfirmed_held_button'],'all_attempted_presses_released':h['all_attempted_presses_released'],'runner_result':r['result'],'runner_cleanup_confirmed':r['cleanup_confirmed'],'helper_supervision_error':h.get('supervision_error')}}
(out/'diagnostic.json').write_text(json.dumps(result,indent=2)+'\n')
print(json.dumps({'metrics':metrics,'validation':{k:v.get('verified') for k,v in validations.items()},'offline_benchmark':bench,'source_bindings_match':all(x['matches'] for x in bindings.values())},indent=2))