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}