code wiki / (root) / nx_frameab_gate.nx

nx_frameab_gate.nx source

↩ module page · 161 lines · 10372 B

1// nx_frameab_gate.nx -- THE REFEREE FOR THE FRAMETRACE A/B WIRING (nx_frameab). 2// 3// nx_abstat_gate already proves the STATISTICS. This gate proves the three things nx_frameab adds on top of 4// them, each of which can be wrong while the arithmetic stays perfect: 5// 1. the CONFOUND GUARD, in BOTH directions -- a guard that refuses everything passes every deny test, so 6// the positive control (an identical-budget pair it must ALLOW) is the load-bearing tooth here; 7// 2. the two per-frame INDICATOR DERIVATIONS at their boundaries, where an off-by-one hides; 8// 3. the end-to-end push, so a real window really does produce the axis verdicts claimed. 9// license_tier: ORIGINAL No hw writes (Rule 26). 10import "nx_syscalls.nx" 11import "nx_gate_verdict.nx" 12import "nx_frameab.nx" 13 14const FBG_N: i64 = 40 15 16func fbg_perf(centre: i64, spread: i64, budget: i64) -> *i64 { 17 let r: *i64 = sys_mmap(pf_words() * 8) 18 pf_init(r) 19 var i: i64 = 0 20 while i < FBG_N { 21 let ph: i64 = i % 5 22 var v: i64 = centre 23 if ph == 0 { v = centre - spread } 24 if ph == 1 { v = centre + spread } 25 if ph == 3 { v = centre - spread } 26 if ph == 4 { v = centre + spread } 27 pf_push(r, v, budget) 28 i = i + 1 29 } 30 return r 31} 32 33func main() -> i64 { 34 let ctr: *i64 = gv_ctr() 35 gv_head("nx_frameab_gate -- the frametrace A/B wiring" as *u8) 36 37 // ---- 1. THE CONFOUND GUARD, BOTH DIRECTIONS ------------------------------------------------------ 38 gv_check_eq("confound-guard-refuses-differing-budgets" as *u8, fab_confounded(16, 94), 1, ctr) 39 gv_check_eq("confound-guard-refuses-either-order" as *u8, fab_confounded(94, 16), 1, ctr) 40 // POSITIVE CONTROL, and it is the load-bearing one: a guard that refused every pair would satisfy both 41 // teeth above and be useless. This is the input it MUST let through. 42 gv_check_eq("pos-control-confound-guard-ALLOWS-equal-budgets" as *u8, fab_confounded(16, 16), 0, ctr) 43 gv_check_eq("pos-control-confound-guard-allows-equal-at-6ms" as *u8, fab_confounded(6, 6), 0, ctr) 44 45 // ---- 2. INDICATOR DERIVATIONS AT THEIR BOUNDARIES ------------------------------------------------- 46 gv_check_eq("over-budget-strictly-greater-fires" as *u8, fab_over_of(17, 16), 1, ctr) 47 gv_check_eq("over-budget-EQUAL-is-not-over" as *u8, fab_over_of(16, 16), 0, ctr) 48 gv_check_eq("over-budget-under-is-not-over" as *u8, fab_over_of(15, 16), 0, ctr) 49 gv_check_eq("jank-strictly-above-2x-median-fires" as *u8, fab_jank_of(41, 20), 1, ctr) 50 gv_check_eq("jank-EXACTLY-2x-median-is-not-jank" as *u8, fab_jank_of(40, 20), 0, ctr) 51 gv_check_eq("jank-below-2x-median-is-not-jank" as *u8, fab_jank_of(39, 20), 0, ctr) 52 // an unobservable median must ABSTAIN to 0 rather than divide or fire 53 gv_check_eq("jank-with-no-median-abstains-to-zero" as *u8, fab_jank_of(999, 0), 0, ctr) 54 gv_check_eq("jank-with-negative-median-abstains-to-zero" as *u8, fab_jank_of(999, 0 - 1), 0, ctr) 55 56 // ---- 3. END TO END: two real windows through the real push -------------------------------------- 57 let pb: *i64 = fbg_perf(20, 2, 16) 58 let pa: *i64 = fbg_perf(14, 2, 16) 59 gv_check_eq("baseline-window-filled" as *u8, pf_n(pb), FBG_N, ctr) 60 gv_check_eq("candidate-window-filled" as *u8, pf_n(pa), FBG_N, ctr) 61 62 let reg: *i64 = sys_mmap(ab_words() * 8) 63 ab_init(reg) 64 gv_check_eq("push-arm-returns-the-window-count" as *u8, fab_push_arm(reg, AB_ARM_B, pb, 16), FBG_N, ctr) 65 fab_push_arm(reg, AB_ARM_A, pa, 16) 66 67 // the frame-time axis is LOWER-IS-BETTER, so a faster candidate must read UP 68 let vframe: i64 = ab_axis_verdict(reg, FAB_AX_FRAME, FAB_AXES, 0, 1024) 69 gv_kv("frame_delta_q10" as *u8, ab_delta_q10(reg, FAB_AX_FRAME)) 70 gv_check_eq("faster-candidate-reads-UP-on-frame-ms" as *u8, vframe, AB_AX_UP, ctr) 71 72 // THE INSTRUMENT CHOICE IS LOAD-BEARING, AND THIS PAIR OF TEETH IS THE PROOF. Every baseline frame is 73 // over a 16 ms budget and no candidate frame is: a true 100-percent-to-0 shift with BOTH arms constant. 74 // Under the CONTINUOUS test the denominator is undefined and the honest answer is INSUFFICIENT. Under 75 // the PROPORTION test -- where spread is a function of the mean rather than estimated apart from it -- 76 // the same data is decidable. Running both on ONE fixture is what shows the choice is not cosmetic. 77 gv_kv("over_delta_q10" as *u8, ab_delta_q10(reg, FAB_AX_OVER)) 78 gv_check_eq("degenerate-rate-under-CONTINUOUS-test-abstains" as *u8, 79 ab_axis_verdict(reg, FAB_AX_OVER, FAB_AXES, 0, 0), AB_AX_INSUFF, ctr) 80 gv_check_eq("degenerate-rate-continuous-reason-is-ZEROVAR" as *u8, 81 ab_axis_reason(reg, FAB_AX_OVER), AB_R_ZEROVAR, ctr) 82 let vover: i64 = ab_prop_verdict(reg, FAB_AX_OVER, FAB_AXES, 0, 0) 83 gv_check_eq("SAME-DATA-under-PROPORTION-test-is-decided-UP" as *u8, vover, AB_AX_UP, ctr) 84 gv_check_eq("proportion-axis-is-recognised-as-binary" as *u8, ab_is_binary(reg, FAB_AX_OVER, AB_ARM_B), 1, ctr) 85 // THE TOOTH THAT CATCHES A SELF-CONTRADICTING LINE: this axis is DECIDED, so its reason must say OK. 86 // The live organ printed "verdict=UP reason=ZERO-VARIANCE-NOT-A-TEST" until the reason was taken from 87 // the same test as the verdict; a reader trusts the reason field, so a stale one is worse than none. 88 gv_check_eq("decided-proportion-axis-reason-is-OK-not-an-abstention" as *u8, 89 ab_prop_reason(reg, FAB_AX_OVER), AB_R_OK, ctr) 90 gv_check_eq("proportion-reason-refuses-a-continuous-axis-BY-NAME" as *u8, 91 ab_prop_reason(reg, FAB_AX_FRAME), AB_R_NOTBINARY, ctr) 92 // NEG-CONTROL FOR THE NEW TEST: a CONTINUOUS axis is not a proportion, and asking the proportion test 93 // about it must REFUSE rather than return a confident number from invalid arithmetic. 94 gv_check_eq("neg-control-continuous-axis-is-not-binary" as *u8, ab_is_binary(reg, FAB_AX_FRAME, AB_ARM_B), 0, ctr) 95 gv_check_eq("neg-control-proportion-test-REFUSES-a-continuous-axis" as *u8, 96 ab_prop_verdict(reg, FAB_AX_FRAME, FAB_AXES, 0, 0), AB_AX_INSUFF, ctr) 97 98 let axv: *i64 = sys_mmap(FAB_AXES * 8) 99 axv[FAB_AX_FRAME] = vframe 100 axv[FAB_AX_OVER] = vover 101 axv[FAB_AX_JANK] = ab_prop_verdict(reg, FAB_AX_JANK, FAB_AXES, 0, 0) 102 gv_check_eq("family-is-IMPROVED" as *u8, ab_family_verdict(axv, FAB_AXES), AB_V_IMPROVED, ctr) 103 // THE DENOMINATOR IS THE POINT: an IMPROVED computed over 2 of 3 axes is not the claim an IMPROVED over 104 // 3 of 3 would be, and a consumer that prints only the word publishes the stronger one. Picking the 105 // right instrument per axis moved this from 1 to 2 -- the third is jank, genuinely zero in both arms. 106 gv_check_eq("tested-axis-denominator-is-two-of-three" as *u8, ab_tested_axes(axv, FAB_AXES), 2, ctr) 107 gv_check_eq("jank-identical-and-zero-in-both-arms-truly-abstains" as *u8, 108 axv[FAB_AX_JANK], AB_AX_INSUFF, ctr) 109 110 // ---- THE PRODUCTION ESTIMATOR IS fab_push_window, NOT fab_push_arm, AND IT NEEDS ITS OWN TEETH ---- 111 // fab_push_arm builds a synthetic arm with a known distribution and is how the STATISTICS above are 112 // tested. Production pushes ONE observation per WINDOW, because frames inside a run are not independent 113 // draws. A gate that only exercised the fixture builder would prove the arithmetic and say nothing 114 // about the estimator that actually ships -- the vacuous-test defect one layer up. 115 let wreg: *i64 = sys_mmap(ab_words() * 8) 116 ab_init(wreg) 117 gv_check_eq("window-push-contributes-exactly-ONE-observation" as *u8, 118 fab_push_window(wreg, AB_ARM_B, pb, 16), 1, ctr) 119 gv_check_eq("one-window-gives-n-of-one-not-the-frame-count" as *u8, ab_n(wreg, FAB_AX_FRAME, AB_ARM_B), 1, ctr) 120 // the window MEAN is carried in MICROSECONDS so it survives a 1 ms frame timer: the baseline window is 121 // a sawtooth about 20 ms, so its mean must land at 20000 us, not 20 122 gv_kv("window_mean_us_q10" as *u8, ab_mean_q10(wreg, FAB_AX_FRAME, AB_ARM_B)) 123 gv_check_eq("window-mean-is-microseconds-not-milliseconds" as *u8, 124 ab_mean_q10(wreg, FAB_AX_FRAME, AB_ARM_B), 20000 * 1024, ctr) 125 // pushing more windows raises n by one each: the sample size is the RUN COUNT 126 fab_push_window(wreg, AB_ARM_B, pa, 16) 127 gv_check_eq("a-second-window-makes-n-two" as *u8, ab_n(wreg, FAB_AX_FRAME, AB_ARM_B), 2, ctr) 128 // ...and a single window per arm must ABSTAIN, because one run cannot estimate its own variance 129 let sreg: *i64 = sys_mmap(ab_words() * 8) 130 ab_init(sreg) 131 fab_push_window(sreg, AB_ARM_B, pb, 16) 132 fab_push_window(sreg, AB_ARM_A, pa, 16) 133 gv_check_eq("one-window-per-arm-abstains-TOO-FEW-not-ZEROVAR" as *u8, 134 ab_axis_reason(sreg, FAB_AX_FRAME), AB_R_FEW_B, ctr) 135 gv_check_eq("one-window-per-arm-verdict-is-INSUFF" as *u8, 136 ab_axis_verdict(sreg, FAB_AX_FRAME, FAB_AXES, 0, 1024), AB_AX_INSUFF, ctr) 137 138 // a REGRESSION must survive the same path: swap the arms and the frame axis must invert 139 let reg2: *i64 = sys_mmap(ab_words() * 8) 140 ab_init(reg2) 141 fab_push_arm(reg2, AB_ARM_B, pa, 16) 142 fab_push_arm(reg2, AB_ARM_A, pb, 16) 143 gv_check_eq("swapped-arms-read-DOWN-on-frame-ms" as *u8, 144 ab_axis_verdict(reg2, FAB_AX_FRAME, FAB_AXES, 0, 1024), AB_AX_DOWN, ctr) 145 146 gv_values_head() 147 gv_kv("axes_declared" as *u8, FAB_AXES) 148 gv_kv("bonferroni_crit_t_q10_at_3_axes" as *u8, ab_crit_t_q10(FAB_AXES)) 149 gv_kv("crit_t_q10_at_1_axis_for_contrast" as *u8, ab_crit_t_q10(1)) 150 gv_kv("mde_ms_default_is_the_sample_quantum" as *u8, FAB_MDE_MS_DEFAULT) 151 gv_kv("jank_multiplier" as *u8, FAB_JANK_MULT) 152 gv_kv("frame_mean_b_q10" as *u8, ab_mean_q10(reg, FAB_AX_FRAME, AB_ARM_B)) 153 gv_kv("frame_mean_a_q10" as *u8, ab_mean_q10(reg, FAB_AX_FRAME, AB_ARM_A)) 154 gv_kv("frame_var_b_q10" as *u8, ab_var_q10(reg, FAB_AX_FRAME, AB_ARM_B)) 155 gv_kv("frame_se2_q10" as *u8, ab_se2_q10(reg, FAB_AX_FRAME)) 156 gv_kv("over_mean_b_q10_all_frames_over" as *u8, ab_mean_q10(reg, FAB_AX_OVER, AB_ARM_B)) 157 gv_kv("over_mean_a_q10_no_frames_over" as *u8, ab_mean_q10(reg, FAB_AX_OVER, AB_ARM_A)) 158 159 return gv_verdict("nx_frameab_gate" as *u8, ctr, 160 "confound guard proven in both directions; indicator boundaries pinned; degenerate rate axis abstains" as *u8) 161}