code wiki / (root) / nx_vecmath_gate.nx

nx_vecmath_gate.nx source

↩ module page · 344 lines · 18215 B

1// nx_vecmath_gate.nx -- THE GATE for the shared integer linear-algebra foundation (PG2, procgen.plan). 2// 3// GATE LAW APPLIED (CLAUDE.md "EVERY GATE YOU WRITE"): 4// 1. returns gv_verdict so the EXIT CODE carries the verdict -- /api/gate_run derives GREEN/RED from it, 5// and a gate that returns a bare 0 after printing RED silently blesses every failure it finds. 6// 2. two teeth are named neg-control-* so the L2 census can SEE them. 7// 3. every tooth is a gv_check on the shared counter, so DECLARED == EXECUTED by construction. A hand-rolled 8// tally can print "passed 22/20"; this cannot. 9// 4. every sweep is bound to its denominator via gv_subjects. A tooth that passes on the empty set is not a 10// tooth, and these sweeps are the whole representable population, never a sample. 11// 5. every tooth PRINTS ITS MEASURED VALUE. A boolean cannot say why, and both vacuous teeth this estate has 12// caught were found in the diagnostic dump, never in the verdict vector. 13// 6. the verdict note does NOT recite the teeth. Each gv_check name states its own strength, so adding a 14// tooth can never make a summary sentence lie. 15// 16// ERROR BARS ARE DERIVED AND THE MEASUREMENT IS PRINTED BESIDE THEM. nx_itrig declares ~0.6 percent amplitude 17// error, which at fx4096 is about 25 units of value and therefore about 25 units of ANGLE once a point is 18// perturbed off the unit circle. The single-term atan form contributes about 20 units. The bars below sit at 19// roughly twice their sum so a tooth fails on a REGRESSION rather than on the known approximation, and the 20// printed maximum makes the bar ratchetable on evidence instead of by taste. 21 22import "nx_syscalls.nx" 23import "nx_gate_verdict.nx" 24import "nx_vecmath.nx" 25 26const VG_I64: i64 = 8 27const VG_V3B: i64 = 24 // 3 * VG_I64 28const VG_V4B: i64 = 32 // 4 * VG_I64 29const VG_M3B: i64 = 72 // 9 * VG_I64 30 31// Derived bars -- see the header. Each is a REGRESSION bar, not a claim of accuracy. 32const VG_ANG_BAR: i64 = 96 // composite sin-cos-atan2 round-trip, units of rad*4096 33const VG_ACOS_BAR: i64 = 96 // acos(cos(a)) round-trip, same basis 34const VG_UNIT_BAR: i64 = 24 // |normalise(v)| vs VM_ONE: pure fixed-point quantisation, no trig involved 35const VG_ORTH_BAR: i64 = 24 // dot(cross(a,b), a) vs zero 36const VG_M3_BAR: i64 = 48 // matrix round-trip and rotation-path agreement 37// Polar amplification floor for the acos round-trip: within the cap where sin(theta) < 1/16 the derived 38// bar is capped at 16x the base bar. DERIVED: 1/16 is the first power-of-two floor above the measured 39// worst amplification that still fails on a genuine regression (a dropped quadrant reads ~thousands). 40const VG_POLE_AMP: i64 = 16 41 42// Sweep extents. Angles sweep the FULL representable circle at unit resolution: this is the population, 43// not a sample of it. 44const VG_ISQRT_N: i64 = 3000 45const VG_FLOOR_N: i64 = 20000 46const VG_ACOS_N: i64 = 4096 // every representable cosine in [0, VM_ONE] 47 48func vg_dev(a: i64, b: i64) -> i64 { if a > b { return a - b } return b - a } 49 50// Angular difference on the circle, so a wrap from +PI to -PI reads as small, not as 2*PI. 51func vg_angdev(a: i64, b: i64) -> i64 { 52 var d: i64 = a - b 53 while d > vm_pi() { d = d - vm_tau() } 54 while d < 0 - vm_pi() { d = d + vm_tau() } 55 return vm_abs(d) 56} 57 58func main() -> i64 { 59 let c: *i64 = gv_ctr() 60 gv_head("NX-VECMATH-GATE -- shared integer linear algebra (PG2)" as *u8) 61 62 let one: i64 = vm_one() 63 64 // ---- T1: the duplicated angle constants must agree with the incumbent they restate -------------- 65 // VM_PI and VM_PI2 are a DELIBERATE duplicate of nx_itrig's constants because const scope does not 66 // cross the import. This tooth is what makes the duplicate guarded rather than silent. 67 let sin_pi: i64 = vm_sin(vm_pi()) 68 let sin_pi2: i64 = vm_sin(vm_pi2()) 69 gv_puts(" pi_check sin(PI)=" as *u8); gv_num(sin_pi) 70 gv_puts(" sin(PI/2)=" as *u8); gv_num(sin_pi2) 71 gv_puts(" expect ~0 and ~" as *u8); gv_num(one); gv_puts("\n" as *u8) 72 gv_check("pi-agrees-with-itrig" as *u8, 73 vm_abs(sin_pi) < VG_ANG_BAR && vg_dev(sin_pi2, one) < VG_ANG_BAR, c) 74 75 // ---- T2/T3: isqrt, full sweep ------------------------------------------------------------------ 76 gv_subjects("isqrt-perfect-squares" as *u8, VG_ISQRT_N, c) 77 var bad_sq: i64 = 0 78 var n: i64 = 0 79 while n < VG_ISQRT_N { 80 if vm_isqrt(n * n) != n { bad_sq = bad_sq + 1 } 81 n = n + 1 82 } 83 gv_puts(" isqrt exact_failures=" as *u8); gv_num(bad_sq) 84 gv_puts(" of " as *u8); gv_num(VG_ISQRT_N); gv_puts("\n" as *u8) 85 gv_check("isqrt-exact-on-every-perfect-square" as *u8, bad_sq == 0, c) 86 87 // The DEFINING property, stronger than agreeing with any incumbent: r*r <= v < (r+1)*(r+1). 88 gv_subjects("isqrt-floor-property" as *u8, VG_FLOOR_N, c) 89 var bad_fl: i64 = 0 90 var v: i64 = 1 91 while v <= VG_FLOOR_N { 92 let r: i64 = vm_isqrt(v) 93 if r * r > v { bad_fl = bad_fl + 1 } 94 if (r + 1) * (r + 1) <= v { bad_fl = bad_fl + 1 } 95 v = v + 1 96 } 97 gv_puts(" isqrt floor_violations=" as *u8); gv_num(bad_fl) 98 gv_puts(" of " as *u8); gv_num(VG_FLOOR_N); gv_puts("\n" as *u8) 99 gv_check("isqrt-is-exact-floor-not-approximate" as *u8, bad_fl == 0, c) 100 101 // ---- T4: atan2 round-trip over the FULL circle ------------------------------------------------- 102 // Measures the COMPOSITE sin-cos-atan2 error, which is what every consumer actually experiences. 103 let sweep: i64 = vm_tau() 104 gv_subjects("atan2-roundtrip-full-circle" as *u8, sweep, c) 105 var worst_ang: i64 = 0 106 var worst_at: i64 = 0 107 var a: i64 = 0 - vm_pi() + 1 108 var seen: i64 = 0 109 while a <= vm_pi() { 110 let s: i64 = vm_sin(a) 111 let co: i64 = vm_cos(a) 112 let back: i64 = vm_atan2(s, co) 113 let d: i64 = vg_angdev(back, a) 114 if d > worst_ang { worst_ang = d; worst_at = a } 115 seen = seen + 1 116 a = a + 1 117 } 118 gv_puts(" atan2 worst_err=" as *u8); gv_num(worst_ang) 119 gv_puts(" at_angle=" as *u8); gv_num(worst_at) 120 gv_puts(" bar=" as *u8); gv_num(VG_ANG_BAR) 121 gv_puts(" sampled=" as *u8); gv_num(seen); gv_puts("\n" as *u8) 122 // Bound to its denominator: a sweep that examined nothing must not pass. 123 gv_check("atan2-roundtrip-within-derived-bar-over-full-circle" as *u8, 124 seen > 0 && worst_ang < VG_ANG_BAR, c) 125 126 // ---- T5: quadrant signs. A single-quadrant atan is a common silent defect. ---------------------- 127 let q1: i64 = vm_atan2(one, one) 128 let q2: i64 = vm_atan2(one, 0 - one) 129 let q3: i64 = vm_atan2(0 - one, 0 - one) 130 let q4: i64 = vm_atan2(0 - one, one) 131 gv_puts(" quadrants q1=" as *u8); gv_num(q1); gv_puts(" q2=" as *u8); gv_num(q2) 132 gv_puts(" q3=" as *u8); gv_num(q3); gv_puts(" q4=" as *u8); gv_num(q4); gv_puts("\n" as *u8) 133 gv_check("atan2-covers-all-four-quadrants" as *u8, 134 q1 > 0 && q2 > 0 && q3 < 0 && q4 < 0 && q2 > q1 && q3 < q4, c) 135 136 // ---- T6: acos, exact algebra first, then the round-trip with a bar DERIVED per sample ------------ 137 // FIRST RUN 2026-08-24 read worst_err=334 against a constant bar of 96 and went RED -- and the bar was 138 // the defect, not the library. d(acos)/dc = 1/sin(theta): near the poles a 25-unit error in vm_cos's 139 // Taylor output is amplified up to 70x into angle. A constant bar assumed unit slope. So the round-trip 140 // bar is now the amplification itself, derived per sample, floored at 16x within the polar cap where 141 // sin(theta) < 1/16 -- and the exact identities below carry no incumbent error at all. 142 let acos_top: i64 = vm_acos(one) 143 let acos_mid: i64 = vm_acos(0) 144 let acos_bot: i64 = vm_acos(0 - one) 145 gv_puts(" acos(1)=" as *u8); gv_num(acos_top); gv_puts(" acos(0)=" as *u8); gv_num(acos_mid) 146 gv_puts(" acos(-1)=" as *u8); gv_num(acos_bot) 147 gv_puts(" expect 0 " as *u8); gv_num(vm_pi2()); gv_puts(" " as *u8); gv_num(vm_pi()); gv_puts("\n" as *u8) 148 gv_check("acos-hits-its-three-exact-anchors" as *u8, 149 acos_top == 0 && vg_dev(acos_mid, vm_pi2()) <= 1 && vg_dev(acos_bot, vm_pi()) <= 1, c) 150 151 // Monotone decreasing over EVERY representable cosine -- the full population, exact, no trig error. 152 let acos_pop: i64 = 2 * one + 1 153 gv_subjects("acos-monotone-full-domain" as *u8, acos_pop, c) 154 var mono_viol: i64 = 0 155 var prev_ac: i64 = vm_acos(0 - one) 156 var cc: i64 = 0 - one + 1 157 while cc <= one { 158 let cur: i64 = vm_acos(cc) 159 if cur > prev_ac { mono_viol = mono_viol + 1 } 160 prev_ac = cur 161 cc = cc + 1 162 } 163 gv_puts(" acos monotone_violations=" as *u8); gv_num(mono_viol); gv_puts(" of " as *u8); gv_num(acos_pop); gv_puts("\n" as *u8) 164 gv_check("acos-is-monotone-decreasing-over-every-representable-input" as *u8, mono_viol == 0, c) 165 166 gv_subjects("acos-roundtrip" as *u8, VG_ACOS_N, c) 167 var worst_ratio: i64 = 0 // err * sin(theta) / bar, permil: 1000 = exactly at the derived bar 168 var worst_ac: i64 = 0 169 var worst_ac_at: i64 = 0 170 var ac_seen: i64 = 0 171 var ac_viol: i64 = 0 172 var k: i64 = 0 173 while k < VG_ACOS_N { 174 let ang: i64 = vm_pi() * k / VG_ACOS_N 175 let cv: i64 = vm_cos(ang) 176 let back2: i64 = vm_acos(cv) 177 let d2: i64 = vg_dev(back2, ang) 178 var sn: i64 = vm_sin(ang) 179 if sn < one / VG_POLE_AMP { sn = one / VG_POLE_AMP } 180 let allowed: i64 = VG_ACOS_BAR * one / sn 181 if d2 >= allowed { ac_viol = ac_viol + 1 } 182 let ratio: i64 = d2 * 1000 / allowed 183 if ratio > worst_ratio { worst_ratio = ratio } 184 if d2 > worst_ac { worst_ac = d2; worst_ac_at = ang } 185 ac_seen = ac_seen + 1 186 k = k + 1 187 } 188 gv_puts(" acos worst_err=" as *u8); gv_num(worst_ac); gv_puts(" at_angle=" as *u8); gv_num(worst_ac_at) 189 gv_puts(" worst_err_over_derived_bar_permil=" as *u8); gv_num(worst_ratio) 190 gv_puts(" violations=" as *u8); gv_num(ac_viol) 191 gv_puts(" base_bar=" as *u8); gv_num(VG_ACOS_BAR); gv_puts(" pole_amp_floor=" as *u8); gv_num(VG_POLE_AMP) 192 gv_puts(" sampled=" as *u8); gv_num(ac_seen); gv_puts("\n" as *u8) 193 gv_check("acos-roundtrip-within-the-per-sample-amplification-derived-bar" as *u8, 194 ac_seen > 0 && ac_viol == 0, c) 195 196 // ---- vec3 --------------------------------------------------------------------------------------- 197 let va: *i64 = sys_mmap(VG_V3B) as *i64 198 let vb: *i64 = sys_mmap(VG_V3B) as *i64 199 let vc: *i64 = sys_mmap(VG_V3B) as *i64 200 let vd: *i64 = sys_mmap(VG_V3B) as *i64 201 202 gv_subjects("v3-cross-orthogonality" as *u8, VG_ACOS_N, c) 203 var worst_orth: i64 = 0 204 var worst_unit: i64 = 0 205 var vseen: i64 = 0 206 var t: i64 = 0 207 while t < VG_ACOS_N { 208 let ang2: i64 = vm_tau() * t / VG_ACOS_N 209 vm_v3_set(va, vm_cos(ang2), vm_sin(ang2), one / 2) 210 vm_v3_set(vb, 0 - vm_sin(ang2), vm_cos(ang2), 0 - one / 3) 211 vm_v3_cross(vc, va, vb) 212 let da: i64 = vm_abs(vm_v3_dot(vc, va)) 213 let db: i64 = vm_abs(vm_v3_dot(vc, vb)) 214 if da > worst_orth { worst_orth = da } 215 if db > worst_orth { worst_orth = db } 216 if vm_v3_norm(vd, va) == 1 { 217 let ul: i64 = vg_dev(vm_v3_len(vd), one) 218 if ul > worst_unit { worst_unit = ul } 219 } 220 vseen = vseen + 1 221 t = t + 1 222 } 223 gv_puts(" v3 worst_orth=" as *u8); gv_num(worst_orth) 224 gv_puts(" worst_unit_dev=" as *u8); gv_num(worst_unit) 225 gv_puts(" sampled=" as *u8); gv_num(vseen); gv_puts("\n" as *u8) 226 gv_check("v3-cross-is-orthogonal-to-both-operands" as *u8, 227 vseen > 0 && worst_orth < VG_ORTH_BAR, c) 228 gv_check("v3-normalise-yields-unit-length" as *u8, 229 vseen > 0 && worst_unit < VG_UNIT_BAR, c) 230 231 // neg-control: the zero vector has no direction. Returning 1 here, or writing garbage, is the defect. 232 vm_v3_set(va, 0, 0, 0) 233 vm_v3_set(vd, 12345, 12345, 12345) 234 let zr: i64 = vm_v3_norm(vd, va) 235 gv_puts(" neg-control zero_norm_ret=" as *u8); gv_num(zr) 236 gv_puts(" out=" as *u8); gv_num(vd[0]); gv_puts("," as *u8); gv_num(vd[1]) 237 gv_puts("," as *u8); gv_num(vd[2]); gv_puts("\n" as *u8) 238 gv_check("neg-control-v3-normalise-refuses-zero-vector-and-zeroes-output" as *u8, 239 zr == 0 && vd[0] == 0 && vd[1] == 0 && vd[2] == 0, c) 240 241 // ---- quaternion --------------------------------------------------------------------------------- 242 let qa: *i64 = sys_mmap(VG_V4B) as *i64 243 let qb: *i64 = sys_mmap(VG_V4B) as *i64 244 let qr: *i64 = sys_mmap(VG_V4B) as *i64 245 let qi: *i64 = sys_mmap(VG_V4B) as *i64 246 247 vm_v3_set(va, 0, one, 0) 248 vm_q_from_axis(qa, va, vm_pi2()) // 90 deg about +Y 249 vm_q_ident(qi) 250 251 vm_q_mul(qr, qa, qi) 252 let dmul: i64 = vg_dev(qr[0], qa[0]) + vg_dev(qr[1], qa[1]) + vg_dev(qr[2], qa[2]) + vg_dev(qr[3], qa[3]) 253 gv_puts(" q*ident dev=" as *u8); gv_num(dmul); gv_puts("\n" as *u8) 254 gv_check("q-mul-identity-is-identity" as *u8, dmul < VG_UNIT_BAR, c) 255 256 vm_q_conj(qb, qa) 257 vm_q_mul(qr, qa, qb) 258 let dconj: i64 = vg_dev(qr[3], one) + vm_abs(qr[0]) + vm_abs(qr[1]) + vm_abs(qr[2]) 259 gv_puts(" q*conj dev_from_ident=" as *u8); gv_num(dconj); gv_puts("\n" as *u8) 260 gv_check("q-times-its-conjugate-is-identity" as *u8, dconj < VG_M3_BAR, c) 261 262 // The two rotation paths must agree. If they diverge, one of them is wrong and neither is trustworthy. 263 let m1: *i64 = sys_mmap(VG_M3B) as *i64 264 let m2: *i64 = sys_mmap(VG_M3B) as *i64 265 vm_v3_set(vb, one, 0, 0) 266 vm_q_rotate_v3(vc, qa, vb) 267 vm_q_to_m3(m1, qa) 268 vm_m3_mulv(vd, m1, vb) 269 let dpath: i64 = vg_dev(vc[0], vd[0]) + vg_dev(vc[1], vd[1]) + vg_dev(vc[2], vd[2]) 270 gv_puts(" rotate_v3=" as *u8); gv_num(vc[0]); gv_puts("," as *u8); gv_num(vc[1]) 271 gv_puts("," as *u8); gv_num(vc[2]) 272 gv_puts(" m3*v=" as *u8); gv_num(vd[0]); gv_puts("," as *u8); gv_num(vd[1]) 273 gv_puts("," as *u8); gv_num(vd[2]) 274 gv_puts(" dev=" as *u8); gv_num(dpath); gv_puts("\n" as *u8) 275 gv_check("quaternion-and-matrix-rotation-paths-agree" as *u8, dpath < VG_M3_BAR, c) 276 277 // ---- slerp -------------------------------------------------------------------------------------- 278 vm_v3_set(va, 0, one, 0) 279 vm_q_from_axis(qb, va, vm_pi2()) 280 vm_q_slerp(qr, qi, qb, 0) 281 let d_lo: i64 = vg_dev(qr[3], one) + vm_abs(qr[0]) + vm_abs(qr[1]) + vm_abs(qr[2]) 282 vm_q_slerp(qr, qi, qb, one) 283 let d_hi: i64 = vg_dev(qr[0], qb[0]) + vg_dev(qr[1], qb[1]) + vg_dev(qr[2], qb[2]) + vg_dev(qr[3], qb[3]) 284 gv_puts(" slerp t=0 dev=" as *u8); gv_num(d_lo) 285 gv_puts(" t=ONE dev=" as *u8); gv_num(d_hi); gv_puts("\n" as *u8) 286 gv_check("slerp-endpoints-return-the-endpoints" as *u8, 287 d_lo < VG_M3_BAR && d_hi < VG_M3_BAR, c) 288 289 // ANTI-VACUITY. slerp falls back to normalised lerp for near-parallel inputs. If that fallback ran for 290 // EVERY input the spherical arithmetic would be dead code and every tooth above would still pass. For a 291 // 90-degree pair the spherical midpoint and the lerp midpoint are genuinely different, so this tooth 292 // fails if the fallback has swallowed the real path. A trivial lerp-only implementation CANNOT pass it. 293 vm_q_slerp(qr, qi, qb, one / 2) 294 let lx: i64 = (qi[0] + qb[0]) / 2 295 let ly: i64 = (qi[1] + qb[1]) / 2 296 let lz: i64 = (qi[2] + qb[2]) / 2 297 let lw: i64 = (qi[3] + qb[3]) / 2 298 let dlerp: i64 = vg_dev(qr[0], lx) + vg_dev(qr[1], ly) + vg_dev(qr[2], lz) + vg_dev(qr[3], lw) 299 let mid_len: i64 = vm_q_len(qr) 300 gv_puts(" slerp_mid vs raw_lerp_mid dev=" as *u8); gv_num(dlerp) 301 gv_puts(" mid_len=" as *u8); gv_num(mid_len) 302 gv_puts(" expect dev>0 and len~" as *u8); gv_num(one); gv_puts("\n" as *u8) 303 gv_check("anti-vacuity-slerp-spherical-branch-actually-runs" as *u8, 304 dlerp > 0 && vg_dev(mid_len, one) < VG_UNIT_BAR, c) 305 306 // neg-control: an axis with no direction cannot define a rotation. 307 vm_v3_set(va, 0, 0, 0) 308 let far: i64 = vm_q_from_axis(qr, va, vm_pi2()) 309 gv_puts(" neg-control zero_axis_ret=" as *u8); gv_num(far) 310 gv_puts(" q=" as *u8); gv_num(qr[0]); gv_puts("," as *u8); gv_num(qr[1]) 311 gv_puts("," as *u8); gv_num(qr[2]); gv_puts("," as *u8); gv_num(qr[3]); gv_puts("\n" as *u8) 312 gv_check("neg-control-q-from-zero-axis-refuses-and-returns-identity" as *u8, 313 far == 0 && qr[3] == one && qr[0] == 0 && qr[1] == 0 && qr[2] == 0, c) 314 315 // ---- mat3 --------------------------------------------------------------------------------------- 316 vm_m3_ident(m2) 317 vm_q_to_m3(m1, qa) 318 let m1b: i64 = m1[0]; let m1c: i64 = m1[4]; let m1d: i64 = m1[8] 319 vm_m3_mul(m1, m1, m2) 320 let dident: i64 = vg_dev(m1[0], m1b) + vg_dev(m1[4], m1c) + vg_dev(m1[8], m1d) 321 gv_puts(" m3*I dev=" as *u8); gv_num(dident); gv_puts("\n" as *u8) 322 gv_check("m3-multiply-by-identity-is-a-no-op-and-alias-safe" as *u8, dident < VG_M3_BAR, c) 323 324 vm_q_to_m3(m1, qa) 325 let orig0: i64 = m1[1]; let orig1: i64 = m1[5] 326 vm_m3_transpose(m2, m1) 327 vm_m3_transpose(m2, m2) 328 let dtr: i64 = vg_dev(m2[1], orig0) + vg_dev(m2[5], orig1) 329 gv_puts(" m3 transpose-twice dev=" as *u8); gv_num(dtr); gv_puts("\n" as *u8) 330 gv_check("m3-transpose-is-an-involution-and-alias-safe" as *u8, dtr == 0, c) 331 332 // The covariance builder: the call nx_gsplat could not make before this lib existed. 333 vm_q_scale_to_m3(m1, qi, one * 2, one * 3, one * 4) 334 gv_puts(" q_scale_to_m3 diag=" as *u8); gv_num(m1[0]); gv_puts("," as *u8); gv_num(m1[4]) 335 gv_puts("," as *u8); gv_num(m1[8]); gv_puts(" expect " as *u8); gv_num(one*2) 336 gv_puts("," as *u8); gv_num(one*3); gv_puts("," as *u8); gv_num(one*4); gv_puts("\n" as *u8) 337 gv_check("q-scale-to-m3-applies-all-three-axis-scales" as *u8, 338 vg_dev(m1[0], one*2) < VG_M3_BAR && 339 vg_dev(m1[4], one*3) < VG_M3_BAR && 340 vg_dev(m1[8], one*4) < VG_M3_BAR, c) 341 342 return gv_verdict("nx_vecmath_gate" as *u8, c, 343 "Shared integer linear algebra. Bars are REGRESSION bars derived from the incumbent's declared error, not accuracy claims; each tooth prints its measured value so the bars stay ratchetable on evidence." as *u8) 344}