code wiki / _hdl_build / nx_pose_coach_gate.nx
nx_pose_coach_gate.nx source
↩ module page · 81 lines · 4882 B
1// nx_pose_coach_gate.nx -- prove the COACH (R8): compare a user's motion to a reference and (T1) score a clean
2// attempt near-perfect, (T2) score a FORM ERROR low AND name the off joint, (T3) be TEMPO-INVARIANT (a slower
3// attempt with the same form scores near-perfect under DTW alignment though frame-aligned looks off). This is
4// "help people achieve their goals": name the mistake, forgive the tempo. expect_exit:0 ORIGINAL
5import "nx_syscalls.nx"
6import "nx_figure_render.nx"
7import "nx_pose_coach.nx"
8
9func hw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
10func pn(v: i64) -> i64 { let b: *u8=sys_mmap(32) as *u8; var x: i64=v; var ng: i64=0; if x<0{ng=1;x=0-x} var i: i64=31; if x==0{b[i]=48 as u8;i=i-1} while x>0{b[i]=(48+x%10) as u8;x=x/10;i=i-1} if ng==1{b[i]=45 as u8;i=i-1} sys_write(1,(b as i64+i+1) as *u8,31-i); return 0 }
11func pdg(d: i64) -> i64 { return d * 12868 / 180 }
12
13const T: i64 = 16
14const NJ: i64 = 3 // shoulder, elbow, wrist (the arm chain)
15
16// drive the arm through an arm-raise and record SHR/ELR/WRR positions per frame.
17// mode 0=clean, 1=elbow-bent error, 2=tempo warp (ease-in: same poses, different timing).
18func gen_track(sk: i64, track: *i64, mode: i64) -> i64 {
19 rig_build(sk)
20 var f: i64 = 0
21 while f < T {
22 var phase: i64 = f * 1000 / (T - 1)
23 if mode == 2 { phase = phase * phase / 1000 } // ease-in tempo warp
24 let pitch: i64 = phase * 90 / 1000
25 var elbow: i64 = 0
26 if mode == 1 { elbow = 65 } // form error: elbow bent
27 sk_pose(sk, RG_SHR, 0, pdg(pitch))
28 sk_pose(sk, RG_ELR, pdg(elbow), 0)
29 sk_update(sk)
30 let b0: *i64 = sk_bone(sk, RG_SHR); track[(f*NJ+0)*3]=b0[15]; track[(f*NJ+0)*3+1]=b0[16]; track[(f*NJ+0)*3+2]=b0[17]
31 let b1: *i64 = sk_bone(sk, RG_ELR); track[(f*NJ+1)*3]=b1[15]; track[(f*NJ+1)*3+1]=b1[16]; track[(f*NJ+1)*3+2]=b1[17]
32 let b2: *i64 = sk_bone(sk, RG_WRR); track[(f*NJ+2)*3]=b2[15]; track[(f*NJ+2)*3+1]=b2[16]; track[(f*NJ+2)*3+2]=b2[17]
33 f = f + 1
34 }
35 return 0
36}
37func jname(j: i64) -> *u8 { if j == 0 { return "shoulder" as *u8 } if j == 1 { return "elbow" as *u8 } return "wrist" as *u8 }
38
39func main() -> i64 {
40 hw("=== nx_pose_coach_gate -- the coach reads your form (R8) ===\n" as *u8)
41 let sk: i64 = sys_mmap(sk_bytes()) as i64
42 let reft: *i64 = sys_mmap(T * NJ * 3 * 8) as *i64
43 let clean: *i64 = sys_mmap(T * NJ * 3 * 8) as *i64
44 let errt: *i64 = sys_mmap(T * NJ * 3 * 8) as *i64
45 let tempo: *i64 = sys_mmap(T * NJ * 3 * 8) as *i64
46 gen_track(sk, reft, 0)
47 gen_track(sk, clean, 0)
48 gen_track(sk, errt, 1)
49 gen_track(sk, tempo, 2)
50 let jdev: *i64 = sys_mmap(NJ * 8) as *i64
51 let tol: i64 = 220 // per-joint-frame deviation that scores 0 (bone units)
52 var fails: i64 = 0
53
54 // T1 clean attempt -> near perfect. Coaching score is driven by the WORST joint (one bad joint = bad form),
55 // not the average (which would dilute a single-joint error into a passing grade).
56 pc_score(reft, clean, T, NJ, jdev)
57 let wj1: i64 = pc_worst_joint(jdev, NJ)
58 let sc: i64 = pc_form_score(jdev[wj1], 1, tol)
59 hw(" clean attempt: worst-joint-dev="); pn(jdev[wj1]); hw(" form-score="); pn(sc); hw("/1000\n" as *u8)
60 if sc >= 950 { hw("T1 PASS clean attempt scores near-perfect\n" as *u8) }
61 else { fails = fails + 1; hw("T1 FAIL clean not perfect\n" as *u8) }
62
63 // T2 form error -> low score AND names the off joint
64 pc_score(reft, errt, T, NJ, jdev)
65 let wj: i64 = pc_worst_joint(jdev, NJ)
66 let se: i64 = pc_form_score(jdev[wj], 1, tol)
67 hw(" elbow-bent attempt: form-score="); pn(se); hw("/1000 worst-joint="); hw(jname(wj)); hw(" (dev="); pn(jdev[wj]); hw(")\n" as *u8)
68 if se < 700 { if wj == 2 { hw("T2 PASS form error caught + coach names the wrist (the hand landed wrong)\n" as *u8) } else { fails = fails + 1; hw("T2 FAIL named wrong joint\n" as *u8) } }
69 else { fails = fails + 1; hw("T2 FAIL error not penalised (score "); pn(se); hw(")\n" as *u8) }
70
71 // T3 tempo-invariance: frame-aligned looks off, DTW says form is fine
72 let tt_fa: i64 = pc_score(reft, tempo, T, NJ, jdev)
73 let tt_dtw: i64 = pc_dtw(reft, tempo, T, NJ)
74 hw(" slower attempt: frame-aligned dev="); pn(tt_fa); hw(" DTW-aligned dev="); pn(tt_dtw); hw("\n" as *u8)
75 if tt_dtw * 2 < tt_fa { hw("T3 PASS TEMPO-INVARIANT (DTW forgives the timing; same form -> DTW dev << frame-aligned)\n" as *u8) }
76 else { fails = fails + 1; hw("T3 FAIL DTW not << frame-aligned\n" as *u8) }
77
78 if fails == 0 { hw("POSE-COACH-GATE 3/3 GREEN -- coach scores form, names the error, forgives tempo (R8 engine up)\n" as *u8); sys_exit(0); return 0 }
79 hw("POSE-COACH-GATE RED fails="); pn(fails); hw("\n" as *u8)
80 sys_exit(1); return 1
81}