master
John Lauer Add bounded obstacle planning and live calculations using the Adom activity palette e936bc3 1mo ago
"""Compose synchronized native Fusion and Adom-palette captures; preserve actual live text."""
from pathlib import Path
import json,subprocess,sys
ROOT=Path(__file__).resolve().parent
sys.path.insert(0,str(ROOT.parent/'crossbar'))
from render_crossbar import HEADER,stamp
TAKE=ROOT/'take-04'
board=next(TAKE.glob('window-8916576-*.mp4'));panel=next(TAKE.glob('window-11280636-*.mp4'))
final=json.loads((TAKE/'final.json').read_text());assert final['drc']['clean'] and not final['remainingNets']
calls=json.loads((TAKE/'calls.json').read_text());starts=[c for c in calls if c['verb']=='desktop_record_window_start']
offset=starts[1]['start']-starts[0]['start']
def probe(f):return json.loads(subprocess.check_output(['ffprobe','-v','error','-show_streams','-of','json',str(f)]))['streams'][0]
b,p=probe(board),probe(panel)
assert b['r_frame_rate']==p['r_frame_rate']=='30/1'
# Trim the long completed-state tail; final hold in output is five seconds.
common=min(float(b['duration'])-offset,float(p['duration']))
end=common-3
for speed in [3,1]:
 name=f'fusion-obstacle-live-{speed}x';duration=end/speed+5
 events=[]
 def text(a,z,t):events.append(f'Dialogue: 0,{stamp(a)},{stamp(z)},Main,,0,0,0,,{t}')
 text(0,duration,r'{\an7\pos(36,18)\fs36\b1\c&H00E5CF65&}CODEX ASTRA IN ADOM HYDROGEN  /  LIVE FUSION ROUTING')
 text(0,duration,fr'{{\an7\pos(1455,75)\fs25}}{speed}x playback / live SSE panel')
 text(0,duration,r'{\an7\pos(36,75)\fs25}Native AB capture / board + live Adom palette')
 text(0,end/speed,r'{\an2\pos(960,1034)\fs28}fusion_plan_route calculates paths; fusion_route_net creates and verifies copper.\NThe Adom palette displays actual results during routing. No external router.')
 text(end/speed,duration,r'{\an2\pos(960,1034)\fs30}3 connections / 8 new segments / 2 vias / both barriers preserved.\Nfusion_routing_drc: 0 errors / 0 warnings / 0 airwires.')
 ass=ROOT/(name+'.ass');ass.write_text(HEADER+'\n'.join(events)+'\n')
 # Board crop retains editor toolbar + all board copper. Panel crop removes unused lower area.
 vf=(f'[0:v]trim=start={offset}:end={offset+end},setpts=(PTS-STARTPTS)/{speed},fps=30,crop=1700:1440:0:0,scale=1062:900,pad=1370:900:154:0:color=0x0a0f13[b];'
     f'[1:v]trim=start=0:end={end},setpts=(PTS-STARTPTS)/{speed},fps=30,crop=570:850:0:0,scale=570:850,pad=570:900:0:0:color=0x0a0f13[p];'
     f'[b][p]hstack=inputs=2,crop=1920:900:0:0,pad=1920:1080:0:110:color=0x0a0f13,tpad=stop_mode=clone:stop_duration=5,ass={ass}[v]')
 out=ROOT/(name+'.mp4')
 subprocess.run(['ffmpeg','-v','warning','-i',str(board),'-i',str(panel),'-filter_complex',vf,'-map','[v]','-an','-c:v','libx264','-preset','fast','-crf','19','-pix_fmt','yuv420p','-movflags','+faststart','-y',str(out)],check=True)
 (ROOT/(name+'.edit.json')).write_text(json.dumps({'sources':[str(board),str(panel)],'boardOffsetSeconds':offset,'synchronization':'Call-start timestamps; first-frame timing can differ by capture initialization latency','commonSourceSeconds':[0,end],'trimmedCompletedTailSeconds':common-end,'speed':speed,'finalHoldSeconds':5,'durationSeconds':duration,'livePanel':'Original native Adom palette video; no reconstructed status text'},indent=2)+'\n')
 print(out,flush=True)