import json, pathlib, copy, os
ROOT=pathlib.Path('/mnt/data/karaoke_v423h/root')
p=ROOT/'assets/js/data/nex-nexdatah-full-bank-physio-ik-keypose-specs.json'
D=json.loads(p.read_text())
prof=next(x for x in D['specs'] if x.get('profileId')=='warmup_karaoke_ik_v1')
keys=prof['physioTruthSpec']['keyPositions']

upper_names=['Pelvis','Hip','Waist','Spine01','Spine02','Head','L_Clavicle','R_Clavicle','L_Upperarm','R_Upperarm','L_Forearm','R_Forearm','L_Hand','R_Hand']
def shift_upper_y(t,dy):
    for n in upper_names:
        if n in t: t[n][1]+=dy

def shoulder_counter(t, helper_side, amount):
    # Positive Z is toward the camera/front in this authored coordinate system.
    if helper_side=='R':
        if 'R_Clavicle' in t: t['R_Clavicle'][2]+=amount
        if 'L_Clavicle' in t: t['L_Clavicle'][2]-=amount
        if 'Spine02' in t: t['Spine02'][2]+=amount*.10
    else:
        if 'L_Clavicle' in t: t['L_Clavicle'][2]+=amount
        if 'R_Clavicle' in t: t['R_Clavicle'][2]-=amount
        if 'Spine02' in t: t['Spine02'][2]+=amount*.10

changed=[]
for k in keys:
    kid=k['id']; t=k['ikControllerTargetsMeters']
    # Elastic load before each explosive front cross, followed by a slightly taller release.
    if kid.endswith('_front_toeoff'):
        shift_upper_y(t,-0.012)
        helper='R' if kid.startswith('out') else 'L'
        shoulder_counter(t,helper,0.012)
        changed.append((kid,'preload -12mm + early shoulder windup'))
    elif kid.endswith('_front_knee'):
        shift_upper_y(t,+0.010)
        helper='R' if kid.startswith('out') else 'L'
        shoulder_counter(t,helper,0.028)
        changed.append((kid,'release +10mm + shoulder counter-rotation'))
    elif kid.endswith('_front_over'):
        shift_upper_y(t,+0.008)
        helper='R' if kid.startswith('out') else 'L'
        shoulder_counter(t,helper,0.032)
        changed.append((kid,'cross-through lift +8mm + peak shoulder counter-rotation'))
    elif kid.endswith('_front_land'):
        helper='R' if kid.startswith('out') else 'L'
        shoulder_counter(t,helper,0.015)
        changed.append((kid,'retain shoulder momentum into landing'))
    elif kid.endswith('_behind_knee') or kid.endswith('_behind_over'):
        # Smaller opposite counter-rotation in the soft behind phase.
        helper='L' if kid.startswith('out') else 'R'
        shoulder_counter(t,helper,0.010)
        changed.append((kid,'soft behind-step counter-rotation'))

# Update evidence only; this remains the same 99 source-authored keypose geometry family.
prof['physioTruthSpec']['version']='V423H_DYNAMICS_THROUGH_POSE'
prof['physioTruthSpec']['dynamicsNote']='V423H adds elastic preload/release and subtle shoulder-pelvis counter-rotation at source-defined front-cross accents. Geometry/timing remains V423G source cadence; interpolation is handled in the resolved bake with continuous through-pose velocity.'

tmp=p.with_suffix('.tmp')
tmp.write_text(json.dumps(D,indent=2)+'\n')
os.replace(tmp,p)
report=ROOT/'diagnostics/v423_karaoke/V423H_AUTHORING_DYNAMICS_REPORT.json'
report.parent.mkdir(parents=True,exist_ok=True)
report.write_text(json.dumps({'pass':True,'keyposeCount':len(keys),'changedCount':len(changed),'changed':changed,'newSolver':False,'postSolveOverride':False},indent=2)+'\n')
print(json.dumps({'pass':True,'keyposeCount':len(keys),'changedCount':len(changed)},indent=2))
