"""Fixed puck-carrier/receiver scenario, natural defending AI and skating after setup.
Setup spans nine frontend frames to settle the captured CPU registers and pads;
no positions/velocities are reset during the 140 measured frames.
The two attackers are idle human-controlled actors: no repeated position resets.
"""
from pathlib import Path
import json,math
p=Path(outdir);spec=json.loads((p/'spec.json').read_text());rows=[];team=spec['team'];orient=spec['orient'];px=spec['px'];weak=-1 if px>0 else 1
role=5 if weak==orient else 3;actor=team*6+(0 if role==5 else 1);opp=(1-team)*6;carrier=opp+2;receiver=opp+(0 if weak==-orient else 1)
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 rb(a):return wram[ram(a)]
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):
 o=obj(i)
 for a,v in [(o,x),(o+2,0),(o+0x14,y),(o+0x16,0),(o+0x18,0),(o+0x1a,0),(o+0x28,0),(o+0x2a,0),(o+0x2c,0),(o+0x2e,-1),(o+0x30,0),(o+0x32,0),(o+0x36,0),(o+0x40,0),(o+0x42,8),(o+0x48,0),(o+0x5e,0)]:ww(a,v)
 wram[ram(o+0x60)]=255;wram[ram(o+0x61)]=255

def test_tick(f):
 if f<100:return
 if 100<=f<=108:
  held.clear()
  ww(0xd046,1);ww(0xd04a,3);ww(0xf07c,0);ww(0xf0d4,0)
  for i in range(4):ww(0xc328+2*i,0);ww(0xc320+2*i,65535)
  for i in range(12):
   side=i//6;direction=orient if side==team else -orient;o=obj(i)
   wram[ram(o+0x62)]=(128 if direction==1 else 0)+(64 if side else 0)
   wram[ram(o+0x63)]=0;wram[ram(o+0x64)]=0;ww(o+0x58,0);ww(o+0x5a,0);ww(o+0x5c,0)
  # Defending shape; the puck-side winger is the nearest presser.
  for local,(x,y) in enumerate([(orient*100,-155),(-orient*100,-155),(0,-175),(orient*65,-220),(-orient*65,-220),(0,-246)]):put(team*6+local,x,y*orient)
  for local,(x,y) in enumerate([(80,-110),(-80,-110),(px,-230),(70,-65),(-70,-65),(0,246)]):put(opp+local,x,y*orient)
  put(actor,weak*100,-155*orient);put(receiver,weak*48,-210*orient)
  # Keep the attackers stationary through real idle pads; defending AI is free.
  for pad,i in [(0,carrier),(1,receiver)]:ww(0xc328+pad*2,opp//6+1);ww(0xc320+pad*2,i);wram[ram(obj(i)+0x62)]|=8
  # Native defensive winger job (assignment 3); carrier job is ignored for the human.
  for i in [team*6,team*6+1]:wram[ram(obj(i)+0x38)]=3
  homeorient=orient if team==0 else -orient
  wram[ram(0xc2ea)]&=~0x53
  if homeorient==-1:wram[ram(0xc2ea)]|=2
  wram[ram(0xc2ec)]=0;wram[ram(0xc2fa)]&=~4;wram[ram(0xc2f2)]&=~4
  ww(0xf07a,spec['difficulty']);ww(0xd050,spec['period']);ww(0xc468,[300,420,600,1200][spec['period']]-10)
  ww(0xb7aa,carrier);ww(0xb74a,px);ww(0xb75e,-230*orient)
  for a in [0xb74c,0xb760,0xb762,0xb764,0xb772,0xb774,0xb776]:ww(a,0)
  ww(0xbee0,65535);ww(0xbeda,65535);ww(0xf060,0);ww(0xf002,0)
 if f>108:
  o=obj(actor);r=obj(receiver);row=dict(velocity=[sw(o+0x28),sw(o+0x2a)],animation=rw(o+0x58),framenum=rw(o+6),flags_scene=rw(0xc2ea),receiver_flags=rw(r+0x62),controls=[rw(0xc320),rw(0xc322)],frame=f-108,owner=rw(0xb7aa),actor=actor,receiver=receiver,carrier=carrier,x=sw(o),y=sw(o+0x14),rx=sw(r),ry=sw(r+0x14),assignment=rb(o+0x38+min(rw(o+0x36),7)),flags=rw(o+0x62),distance=round(math.hypot(sw(o)-sw(r),sw(o+0x14)-sw(r+0x14)),3),clock=rw(0xc468),score=[rw(0xc6da),rw(0xca3e)])
  rows.append(row)
 if f==248:(p/'trajectory.json').write_text(json.dumps(rows,indent=2)+'\n')
