from pathlib import Path
import re,json,math,shutil,sys,hashlib
root=Path('/home/jakub/devel/escapistsrereintroduced');out=root/'docs/evidence/uaz-coordination-2026-10-04';sys.path.insert(0,str(root/'tools'))
from source_manifest import addon_manifest
runs={'baseline':Path('/tmp/erri-uaz-scene-native/baseline'),'manoeuvre':Path('/tmp/erri-uaz-scene-native/final'),'final':Path('/tmp/erri-uaz-scene-native/qualified')}
allmetrics={};traces={}
for name,run in runs.items():
 session=next((run/'profile/logs').glob('logs_*'));s=(session/'console.log').read_text(errors='replace');j=(session/'rereintroduced-hq.log').read_text(errors='replace')
 traces[name]={}
 for match in re.finditer(r'ERR SCENE MAN: t=([\d.]+) id=(\S+) man=(\d+) pos=<([^>]+)> mounted=(\d) alive=(\d)',s):
  t,unit,member,xyz,mounted,alive=match.groups();x,y,z=map(float,xyz.split(','));traces[name].setdefault((unit,int(member)),[]).append((float(t),x,z,int(mounted)))
 metrics={}
 for unit in sorted({u for u,m in traces[name]}):
  chars={m:v for (u,m),v in traces[name].items() if u==unit};leader=chars.get(0,[]);first_mount=min((v[0] for v in leader if v[3]),default=float('inf'));foot=[v for v in leader if not v[3] and v[0]>first_mount]
  ammo=[int(m[1]) for m in re.findall(r'ERR SCENE AMMO: t=([\d.]+) id='+re.escape(unit)+r' rounds=(\d+)',s)]
  metrics[unit]={'members_observed':len(chars),'members_seen_on_foot':sum(any(not p[3] and p[0]>min((m[0] for m in v if m[3]),default=float('inf')) for p in v) for v in chars.values()),'leader_max_displacement_on_foot_m':round(max((math.hypot(p[1]-foot[0][1],p[2]-foot[0][2]) for p in foot),default=0),1),'mounted_rounds_start':ammo[0] if ammo else None,'mounted_rounds_min':min(ammo) if ammo else None}
 allmetrics[name]={'complete':'ERR SCENE COMPLETE' in s,'units':metrics,'script_errors':[l for l in s.splitlines() if 'SCRIPT    (E)' in l],'tactical_events':[l for l in j.splitlines() if 'HUNTER TACTICS' in l or 'HUNTER COORDINATION' in l]}
 for src,dst in [(session/'console.log',out/(name+'-console.log')),(run/'result.json',out/(name+'-result.json'))]:
  if src.exists():shutil.copy2(src,dst)
 (out/(name+'-events.log')).write_text('\n'.join(l for l in j.splitlines() if any(w in l for w in ['HUNTER','CONTACT REPORT','CONTACT LOCAL']))+'\n')
 manifest={str(p.relative_to(run/'source')):hashlib.sha256(p.read_bytes()).hexdigest() for p in (run/'source').rglob('*.c')}
 (out/(name+'-script-sha256.json')).write_text(json.dumps(manifest,indent=2)+'\n')
(out/'metrics.json').write_text(json.dumps(allmetrics,indent=2)+'\n')
# A spatial reconstruction of logged positions, not a game screenshot.
svg=['<svg xmlns="http://www.w3.org/2000/svg" width="1260" height="700" viewBox="0 0 1260 700">','<rect width="1260" height="700" fill="#f5f7fb"/>','<style>text{font-family:sans-serif;fill:#182235}.small{font-size:12px}.title{font-size:20px;font-weight:bold}</style>','<text x="30" y="34" class="title">Native UAZ encounter — recorded movement on Nogova</text>','<text x="30" y="57" class="small">Protected US AI stand-ins; real production hunter crews, perception, radio and navigation. No terrain background.</text>']
colors=['#d97706','#2563eb','#9d28b0'];origin=(9606.07,4826.07)
for panel,name in enumerate(['baseline','final']):
 left=40+panel*615;top=110;size=540;scale=.61
 def xy(x,z):return left+size/2+(x-origin[0])*scale,top+size/2-(z-origin[1])*scale
 svg.append(f'<text x="{left}" y="94" class="title">'+('Original orders (2 UAZs)' if name=='baseline' else 'Coordinated orders (3 UAZs)')+'</text>')
 svg.append(f'<rect x="{left}" y="{top}" width="{size}" height="{size}" fill="white" stroke="#c6cbd4"/>')
 for dist in range(-400,401,100):
  x,y=xy(origin[0]+dist,origin[1]+dist);svg.extend([f'<path d="M{x},{top}V{top+size} M{left},{y}H{left+size}" stroke="#edf0f5"/>'])
 svg.append(f'<defs><clipPath id="panel{panel}"><rect x="{left}" y="{top}" width="{size}" height="{size}"/></clipPath></defs><g clip-path="url(#panel{panel})">')
 cx,cy=xy(*origin);svg.append(f'<circle cx="{cx}" cy="{cy}" r="{130*scale}" fill="none" stroke="#b7bcc7" stroke-dasharray="5 5"/><circle cx="{cx}" cy="{cy}" r="6" fill="#111827"/><text x="{cx+10}" y="{cy-8}" class="small">Initial escapee area</text>')
 units=sorted({u for u,m in traces[name]})
 for i,unit in enumerate(units):
  color=colors[i]
  for (u,member),points in traces[name].items():
   if u!=unit:continue
   # Limit comparison to the first 210 seconds shared by both recordings.
   points=[p for p in points if p[0]<=210]
   for mounted in [True,False]:
    segments=[];current=[]
    for p in points:
     if bool(p[3])==mounted:current.append(p)
     elif current:segments.append(current);current=[]
    if current:segments.append(current)
    for segment in segments:
     coords=' '.join(f'{x:.1f},{y:.1f}' for x,y in [xy(p[1],p[2]) for p in segment]);dash=' stroke-dasharray="4 3"' if mounted else ''
     svg.append(f'<polyline points="{coords}" fill="none" stroke="{color}" stroke-width="{2 if member==0 else 1}" opacity="{1 if member==0 else .4}"{dash}/>')
   if points:
    x,y=xy(points[-1][1],points[-1][2]);svg.append(f'<circle cx="{x}" cy="{y}" r="3" fill="{color}"/>')
  svg.append(f'<text x="{left+10}" y="{top+19+i*17}" class="small" style="fill:{color}">UAZ {i+1} / original crew</text>')
 svg.append('</g>')
 svg.append(f'<text x="{left}" y="674" class="small">First 210 seconds · 100 m grid · dashed: mounted · solid: on foot</text>')
svg.append('</svg>');(out/'movement.svg').write_text('\n'.join(svg))
(out/'source-sha256.json').write_text(json.dumps(addon_manifest(root),indent=2)+'\n')
print(json.dumps({n: {'complete':v['complete'],'units':v['units']} for n,v in allmetrics.items()},indent=2))
