code wiki / (root) / nx_strand_groom_gate.nx

nx_strand_groom_gate.nx source

↩ module page · 154 lines · 7950 B

1// nx_strand_groom_gate.nx -- PG14: DO GUIDE CURVES PLUS INTERPOLATION MAKE A GROOM, HIERARCHICAL BY CONSTRUCTION? 2// 3// SUBJECT: nx_hairdyn_lib.gr_strand (in-process). Every geometric claim is recomputed from the groom's own positions. 4// The two load-bearing teeth are KATs on the interpolation: a child rooted ON a guide reproduces that guide exactly, 5// and a child between guides lies inside the box its guides span (a convex combination cannot leave it). Fixtures are 6// arithmetic; the gate leaks no filesystem state. 7import "nx_syscalls.nx" 8import "nx_gate_verdict.nx" 9import "nx_hairdyn_lib.nx" 10const SG_SEED: i64 = 20260906 11const SG_CX: i64 = 0 12const SG_CY: i64 = 40000 // Q8: a head 156 units up 13const SG_CZ: i64 = 0 14const SG_R: i64 = 2560 // scalp radius 10 units 15const SG_NG: i64 = 12 16const SG_NC: i64 = 180 17const SG_NSEG: i64 = 16 18const SG_SEGLEN: i64 = 512 // 2 units per segment 19const SG_JIT: i64 = 150 20const SG_ROOT_TOL: i64 = 8 // Q8: the integer normalisation's own error on the scalp radius 21const SG_LEN_DEV_PERMIL: i64 = 150 22 23func sg_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 24 25func main() -> i64 { 26 let ctr: *i64 = gv_ctr() 27 gv_head("=== NX-STRAND-GROOM gate (PG14): guides plus interpolation, hierarchical by construction ===" as *u8) 28 let n: i64 = gr_strand(SG_SEED, SG_CX, SG_CY, SG_CZ, SG_R, SG_NG, SG_NC, SG_NSEG, SG_SEGLEN, SG_JIT) 29 gv_check_eq("groom-grows: strand count is guides plus children" as *u8, n, SG_NG + SG_NC, ctr) 30 var lvl0: i64 = 0 31 var lvl1: i64 = 0 32 var k: i64 = 0 33 while k < n { if gr_level(k) == 0 { lvl0 = lvl0 + 1 } if gr_level(k) == 1 { lvl1 = lvl1 + 1 } k = k + 1 } 34 gv_check("hierarchy: level 0 is exactly the guides and level 1 exactly the children (partition sums)" as *u8, ((lvl0 == SG_NG) as i64) * ((lvl1 == SG_NC) as i64) * ((lvl0 + lvl1 == n) as i64), ctr) 35 // roots on the upper scalp cap 36 var on_cap: i64 = 1 37 var worst_r: i64 = 0 38 k = 0 39 while k < n { 40 let dx: i64 = gr_rootx(k) - SG_CX 41 let dy: i64 = gr_rooty(k) - SG_CY 42 let dz: i64 = gr_rootz(k) - SG_CZ 43 let d: i64 = sd_isqrt(dx*dx + dy*dy + dz*dz) 44 let e: i64 = sg_abs(d - SG_R) 45 if e > worst_r { worst_r = e } 46 if e > SG_ROOT_TOL { on_cap = 0 } 47 if dy < 0 { on_cap = 0 } 48 k = k + 1 49 } 50 gv_check("every-root-on-the-upper-scalp-cap (radius within tolerance, never below the centre plane)" as *u8, on_cap, ctr) 51 // KAT 1: a child rooted exactly on guide 3, jitter 0, reproduces guide 3 segment for segment 52 let out: *i64 = sys_mmap(GR_MAXSEG*3*8) as *i64 53 gr_child_at(gr_rootx(3), gr_rooty(3), gr_rootz(3), 0, 1, out) 54 var same: i64 = 1 55 var j: i64 = 0 56 while j < SG_NSEG { 57 if out[j*3] != gr_x(3, j) { same = 0 } 58 if out[j*3+1] != gr_y(3, j) { same = 0 } 59 if out[j*3+2] != gr_z(3, j) { same = 0 } 60 j = j + 1 61 } 62 gv_check("KAT-child-on-a-guide-IS-that-guide (jitter 0, every segment byte-equal)" as *u8, same, ctr) 63 // KAT 2: a child at the midpoint of guides 0 and 1, jitter 0, lies inside the offset box its nearest guides span 64 let mx: i64 = (gr_rootx(0) + gr_rootx(1))/2 65 let my: i64 = (gr_rooty(0) + gr_rooty(1))/2 66 let mz: i64 = (gr_rootz(0) + gr_rootz(1))/2 67 let kn: i64 = gr_child_at(mx, my, mz, 0, 1, out) 68 var inside: i64 = 1 69 j = 0 70 while j < SG_NSEG { 71 // bounds over ALL guides' offsets at this segment (a superset of the nearest three): a convex combination 72 // of any subset cannot leave the hull of the whole set 73 var lox: i64 = 1000000000 74 var hix: i64 = 0 - 1000000000 75 var loz: i64 = 1000000000 76 var hiz: i64 = 0 - 1000000000 77 var g: i64 = 0 78 while g < SG_NG { 79 let ox: i64 = gr_x(g, j) - gr_rootx(g) 80 let oz: i64 = gr_z(g, j) - gr_rootz(g) 81 if ox < lox { lox = ox } 82 if ox > hix { hix = ox } 83 if oz < loz { loz = oz } 84 if oz > hiz { hiz = oz } 85 g = g + 1 86 } 87 let cox: i64 = out[j*3] - mx 88 let coz: i64 = out[j*3+2] - mz 89 if cox < lox - 1 { inside = 0 } 90 if cox > hix + 1 { inside = 0 } 91 if coz < loz - 1 { inside = 0 } 92 if coz > hiz + 1 { inside = 0 } 93 j = j + 1 94 } 95 gv_check("KAT-child-between-guides-stays-inside-the-guides-hull (jitter 0, every segment)" as *u8, ((inside == 1) as i64) * ((kn == GR_KNN) as i64), ctr) 96 // segment lengths stay near seglen for every strand (the groom is a rest shape a chain can hold) 97 var worst_dev: i64 = 0 98 k = 0 99 while k < n { 100 j = 0 101 while j < SG_NSEG { 102 let l: i64 = gr_seglen_at(k, j) 103 let dev: i64 = sg_abs(l - SG_SEGLEN)*1000/SG_SEGLEN 104 if dev > worst_dev { worst_dev = dev } 105 j = j + 1 106 } 107 k = k + 1 108 } 109 gv_check("segment-lengths-hold: worst deviation from seglen within the declared band, all strands all segments" as *u8, (worst_dev <= SG_LEN_DEV_PERMIL) as i64, ctr) 110 // jitter does something and stays bounded: the same child root with jitter vs without 111 let out0: *i64 = sys_mmap(GR_MAXSEG*3*8) as *i64 112 let outj: *i64 = sys_mmap(GR_MAXSEG*3*8) as *i64 113 gr_child_at(mx, my, mz, 0, 7, out0) 114 gr_child_at(mx, my, mz, SG_JIT, 7, outj) 115 var moved: i64 = 0 116 var jmax: i64 = 0 117 j = 0 118 while j < SG_NSEG { 119 let dxj: i64 = sg_abs(outj[j*3] - out0[j*3]) 120 let dzj: i64 = sg_abs(outj[j*3+2] - out0[j*3+2]) 121 if dxj > 0 { moved = 1 } 122 if dzj > 0 { moved = 1 } 123 if dxj > jmax { jmax = dxj } 124 if dzj > jmax { jmax = dzj } 125 if outj[j*3+1] != out0[j*3+1] { moved = 0 - 100 } 126 j = j + 1 127 } 128 gv_check("jitter-frays-the-tip-and-never-the-hang: some x or z moves, y never, within the declared amplitude" as *u8, ((moved == 1) as i64) * ((jmax <= SG_SEGLEN*SG_JIT/1000) as i64), ctr) 129 // determinism and seed diversity 130 let hx: i64 = gr_x(SG_NG + 5, SG_NSEG - 1) 131 let hz: i64 = gr_z(SG_NG + 5, SG_NSEG - 1) 132 let g3x: i64 = gr_x(3, SG_NSEG - 1) 133 gr_strand(SG_SEED, SG_CX, SG_CY, SG_CZ, SG_R, SG_NG, SG_NC, SG_NSEG, SG_SEGLEN, SG_JIT) 134 gv_check("determinism: the same seed grooms the same strands (a child tip and a guide tip byte-equal)" as *u8, ((gr_x(SG_NG + 5, SG_NSEG - 1) == hx) as i64) * ((gr_z(SG_NG + 5, SG_NSEG - 1) == hz) as i64) * ((gr_x(3, SG_NSEG - 1) == g3x) as i64), ctr) 135 gr_strand(SG_SEED + 1, SG_CX, SG_CY, SG_CZ, SG_R, SG_NG, SG_NC, SG_NSEG, SG_SEGLEN, SG_JIT) 136 gv_check("two-seeds-two-grooms: a guide tip differs" as *u8, (gr_x(3, SG_NSEG - 1) != g3x) as i64, ctr) 137 // the groom seats into the chain state the dynamics run on 138 let st: *i64 = hd_alloc(SG_NSEG) 139 let seated: i64 = gr_to_chain(st, 2) 140 gv_check("groom-seats-into-the-softdyn-chain (nseg joints, tip matches, velocities zero)" as *u8, ((seated == SG_NSEG) as i64) * ((hd_tip_x_q8(st, SG_NSEG) == gr_x(2, SG_NSEG - 1)) as i64) * ((st[3] == 0) as i64), ctr) 141 // neg-controls: refusals by name 142 gv_check_eq("neg-control-zero-guides-REFUSES (-GR_E_ARG)" as *u8, gr_strand(SG_SEED, SG_CX, SG_CY, SG_CZ, SG_R, 0, 10, SG_NSEG, SG_SEGLEN, 0), 0 - GR_E_ARG, ctr) 143 gv_check_eq("neg-control-over-capacity-REFUSES (-GR_E_ARG)" as *u8, gr_strand(SG_SEED, SG_CX, SG_CY, SG_CZ, SG_R, 4, GR_MAXS, SG_NSEG, SG_SEGLEN, 0), 0 - GR_E_ARG, ctr) 144 gv_values_head() 145 gv_kv("strands" as *u8, n) 146 gv_kv("guides" as *u8, lvl0) 147 gv_kv("children" as *u8, lvl1) 148 gv_kv("worst_root_radius_error_q8" as *u8, worst_r) 149 gv_kv("worst_seglen_deviation_permil" as *u8, worst_dev) 150 gv_kv("jitter_max_q8" as *u8, jmax) 151 gv_kv("jitter_amp_q8" as *u8, SG_SEGLEN*SG_JIT/1000) 152 gv_kv("knn_used" as *u8, kn) 153 return gv_verdict("NX-STRAND-GROOM" as *u8, ctr, "a groom is guides plus a convex interpolation the gate can pin, and it seats into the one chain solver" as *u8) 154}