"""Settle a defensive pair, then observe native skating with no position resets."""
from pathlib import Path
import json,math
p=Path(outdir);s=json.loads((p/'spec.json').read_text());rows=[];team=s['team'];orient=s['orient'];sign=s['sign'];own=team*6;opp=(1-team)*6;carrier=opp+2;receiver=opp;backs=[own+3,own+4]
def rw(a):return(wram[ram(a)]<<8)|wram[ram(a+1)]
def sw(a):v=rw(a);return v-65536 if v&32768 else v
def ww(a,v):v&=65535;wram[ram(a)]=v>>8;wram[ram(a+1)]=v&255
def obj(i):return 0xb04a+128*i
def put(i,x,y):
 b=obj(i)
 for off,v in [(0,x),(2,0),(0x14,y*orient),(0x16,0),(0x18,0),(0x1a,0),(0x28,0),(0x2a,0),(0x2c,0),(0x2e,-1),(0x30,0),(0x32,0),(0x36,0),(0x40,0),(0x42,8),(0x44,60),(0x46,-220*orient),(0x48,16),(0x58,0),(0x5a,0),(0x5c,0),(0x5e,0)]:ww(b+off,v)
 for off in [0x60,0x61]:wram[ram(b+off)]=255
 wram[ram(b+0x62)]=(64 if i//6 else 0)+(128 if (orient if i//6==team else -orient)==1 else 0);wram[ram(b+0x63)]=0;wram[ram(b+0x64)]=0
 role=[5,3,4,2,1,0][i%6];ww(b+0x34,role);wram[ram(b+0x38)]={5:3,3:3,4:5,2:2,1:2,0:14}[role]
def test_tick(f):
 if f<100:return
 if f<=108:
  held.clear();ww(0xd046,1);ww(0xd04a,2);ww(0xf07c,0);ww(0xf0d4,0)
  for i in range(12):put(i,140 if i%2 else -140,30 if i//6==team else 10)
  put(own+5,0,-246);put(opp+5,0,246)
  depth=s.get('depth',-140)
  put(backs[0],sign*60,-205);put(backs[1],-sign*60,-220)
  put(carrier,sign*104,depth);put(receiver,-sign*40,-216)
  for i in range(4):ww(0xc328+2*i,0);ww(0xc320+2*i,65535)
  for pad,i in enumerate((carrier,receiver)):ww(0xc328+2*pad,i//6+1);ww(0xc320+2*pad,i);wram[ram(obj(i)+0x62)]|=8
  # Human carrier can drive toward the slot; the other attacker stays idle.
  wram[ram(0xc2ea)]&=~0x53
  if (orient if team==0 else -orient)==-1:wram[ram(0xc2ea)]|=2
  for a,mask in [(0xc2ec,128),(0xc2ee,128),(0xc2f2,4),(0xc2fa,5)]:wram[ram(a)]&=~mask
  ww(0xf07a,s.get('difficulty',1));ww(0xd050,0);ww(0xc468,290);ww(0xd058,1);ww(0xd052,0)
  ww(0xb7aa,carrier);ww(0xb74a,sign*104);ww(0xb75e,depth*orient)
  for a in [0xb74c,0xb760,0xb762,0xb764,0xb772,0xb774,0xb776]:ww(a,0)
  ww(0xbee0,-1);ww(0xbeda,-1);ww(0xf060,0);ww(0xf002,0)
 elif f<=708:
  if s.get('moving') and (not s.get('shot_at') or f<109+s['shot_at']):
   if (f-109)%8==0:
    held[(0,IDS['Left' if sign>0 else 'Right'])]=8
    held[(0,IDS['Down' if orient==1 else 'Up'])]=8
  if s.get('shot_at') and f==109+s['shot_at']:
   held.clear();held[(0,IDS['C'])]=40
  c=obj(carrier);r=obj(receiver);cx,cy=sw(c),sw(c+0x14)*orient
  positions=[[sw(obj(i)),sw(obj(i)+0x14)*orient] for i in backs]
  # Goal-side blocker: between the carrier and the central goal at (0,-246).
  near=min(math.hypot(x-cx,y-cy) for x,y in positions if y<=cy+8) if any(y<=cy+8 for x,y in positions) else 999
  cover=min(math.hypot(x-sw(r),y-sw(r+0x14)*orient) for x,y in positions)
  rows.append(dict(frame=f-108,owner=rw(0xb7aa),carrier=carrier,receiver=receiver,positions=positions,carrier_xy=[cx,cy],receiver_xy=[sw(r),sw(r+0x14)*orient],gap=round(near,3),cover=round(cover,3),jobs=[wram[ram(obj(i)+0x38+min(rw(obj(i)+0x36),7))] for i in backs],controls=[rw(0xc320),rw(0xc322)],clock=rw(0xc468),sprite_bytes=rw(0xc2e8),shotplayer=rw(0xbed8),passer=rw(0xbeda),shot_clock=rw(0xbeec),score=[rw(0xc6da),rw(0xca3e)]))
 if f==s.get('end_frame',308):(p/'trajectory.json').write_text(json.dumps(rows,indent=2)+'\n')
