code wiki / _hdl_build / nx_rpgstats_gate.nx

nx_rpgstats_gate.nx source

↩ module page · 211 lines · 9876 B

1// nx_rpgstats_gate.nx -- CERTIFICATION of nx_rpgstats (gamebench gap-queue rank 1 after save/load landed). 2// T1 XP curve strictly monotonic across 1..200 (a character can never fail to progress) 3// T2 level_for_xp is the EXACT inverse of xp_for_level at every boundary and boundary-1 4// T3 ★MODIFIER STACKING IS ORDER-INDEPENDENT -- forward, reversed and rotated orders agree. 5// This is the real bug class: +10 then +20% != +20% then +10 in most engines, so buff order 6// silently changes outcomes and desyncs multiplayer. 7// T4 saturation: absurd inputs clamp instead of overflowing into negative HP / wrapped levels 8// T5 skill advancement monotonic in uses and capped at maxrank 9// T6 DATA-DRIVEN: changing only the curve/formula parameters changes the outputs (proves the numbers 10// are configuration, not literals baked into the code -- rule 11) 11// T7 ANTI-VACUITY: a normal progression yields DISTINCT, sensibly ordered values. Without this a part 12// that returned a constant (or zero) would satisfy monotonicity and clamping vacuously. 13// T8 ★COMPOSES WITH nx_gamesave: a built character round-trips through gs_save/gs_load unchanged. 14// This is the recombinator claim under test -- parts must actually compose, not just coexist. 15// license_tier: ORIGINAL expect_exit: 0 16import "nx_syscalls.nx" 17import "nx_rpgstats.nx" 18import "nx_gamesave.nx" 19 20func rw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 21func rn(v: i64) -> i64 { 22 if v==0 { sys_write(1,"0" as *u8,1); return 0 } 23 var m: i64=v; if m<0 { sys_write(1,"-" as *u8,1); m=0-m } 24 let t: *u8=sys_mmap(32); var k: i64=0 25 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 26 let o: *u8=sys_mmap(32); var q: i64=k-1; var i: i64=0 27 while q>=0 { o[i]=t[q]; i=i+1; q=q-1 } 28 sys_write(1,o,i); return 0 29} 30 31func main() -> i64 { 32 rw("=== nx_rpgstats_gate: is character progression safe to compose into every game? ===\n\n") 33 var pass: i64 = 0 34 var checks: i64 = 0 35 36 let BASE: i64 = 100 // xp curve linear term 37 let QUAD: i64 = 25 // xp curve quadratic term 38 39 // ---------- T1 XP curve strictly monotonic ---------- 40 checks = checks + 1 41 var mono: i64 = 1 42 var prev: i64 = 0-1 43 var L: i64 = 1 44 while L <= RS_MAXLEVEL { 45 let x: i64 = rs_xp_for_level(L, BASE, QUAD) 46 if x <= prev { mono = 0 } 47 prev = x 48 L = L + 1 49 } 50 if mono==1 { 51 rw("T1 GREEN xp curve strictly increasing over 1.."); rn(RS_MAXLEVEL) 52 rw(" (L200 threshold="); rn(rs_xp_for_level(200,BASE,QUAD)); rw(" xp)\n") 53 pass = pass + 1 54 } else { rw("T1 RED xp curve not strictly increasing\n") } 55 56 // ---------- T2 exact inverse at boundaries ---------- 57 checks = checks + 1 58 var inv: i64 = 1 59 var lv: i64 = 2 60 while lv <= 120 { 61 let thr: i64 = rs_xp_for_level(lv, BASE, QUAD) 62 if rs_level_for_xp(thr, BASE, QUAD) != lv { inv = 0 } // exactly at threshold -> that level 63 if rs_level_for_xp(thr-1, BASE, QUAD) != lv-1 { inv = 0 } // one xp short -> previous level 64 lv = lv + 1 65 } 66 if inv==1 { 67 rw("T2 GREEN level_for_xp is the exact inverse of xp_for_level at every boundary and boundary-1\n") 68 pass = pass + 1 69 } else { rw("T2 RED level/xp inversion mismatch\n") } 70 71 // ---------- T3 order-independent modifier stacking ---------- 72 checks = checks + 1 73 let NM: i64 = 6 74 let m1: *i64 = sys_mmap(NM*2*8) as *i64 75 // (kind,value): additive and percent interleaved, deliberately mixed 76 m1[0]=0; m1[1]=10 // +10 flat 77 m1[2]=1; m1[3]=20 // +20% 78 m1[4]=0; m1[5]=0-5 // -5 flat 79 m1[6]=1; m1[7]=0-30 // -30% 80 m1[8]=0; m1[9]=7 // +7 flat 81 m1[10]=1; m1[11]=15 // +15% 82 let BASEV: i64 = 100 83 let fwd: i64 = rs_apply_mods(BASEV, m1, NM) 84 // reversed 85 let m2: *i64 = sys_mmap(NM*2*8) as *i64 86 var i: i64 = 0 87 while i < NM { m2[i*2] = m1[(NM-1-i)*2]; m2[i*2+1] = m1[(NM-1-i)*2+1]; i = i + 1 } 88 let rev: i64 = rs_apply_mods(BASEV, m2, NM) 89 // rotated by 3 90 let m3: *i64 = sys_mmap(NM*2*8) as *i64 91 i = 0 92 while i < NM { let s: i64 = (i+3) % NM; m3[i*2] = m1[s*2]; m3[i*2+1] = m1[s*2+1]; i = i + 1 } 93 let rot: i64 = rs_apply_mods(BASEV, m3, NM) 94 if fwd==rev { if fwd==rot { 95 rw("T3 GREEN modifier stacking ORDER-INDEPENDENT: fwd=rev=rot="); rn(fwd) 96 rw(" (base 100, +12 flat, +5pct pooled)\n") 97 pass = pass + 1 98 } } 99 if fwd!=rev { rw("T3 RED order matters: fwd="); rn(fwd); rw(" rev="); rn(rev); rw("\n") } 100 if fwd==rev { if fwd!=rot { rw("T3 RED rotation changed result: fwd="); rn(fwd); rw(" rot="); rn(rot); rw("\n") } } 101 102 // ---------- T4 saturation, no overflow ---------- 103 checks = checks + 1 104 let huge: i64 = 0x7FFFFFFFFFFFFFF 105 let hp_big: i64 = rs_derived_hp(huge, huge, huge, huge, huge) 106 let xp_big: i64 = rs_xp_for_level(RS_MAXLEVEL, huge, huge) 107 let lv_big: i64 = rs_level_for_xp(huge, BASE, QUAD) 108 var sat: i64 = 1 109 // must saturate UPWARD. hp==1 would mean the sum wrapped negative and the floor rescued it -- 110 // that is how this tooth passed for the wrong reason on the first build. 111 if hp_big < RS_SAT / 2 { sat = 0 } 112 if hp_big > RS_SAT { sat = 0 } 113 if xp_big < RS_SAT / 2 { sat = 0 } 114 if lv_big < 1 { sat = 0 } 115 if lv_big > RS_MAXLEVEL { sat = 0 } 116 // a big-but-not-absurd case must still compute exactly, not saturate 117 let hp_norm: i64 = rs_derived_hp(20, 50, 30, 4, 8) 118 if hp_norm != 30 + 80 + 392 { sat = 0 } 119 if sat==1 { 120 rw("T4 GREEN saturation holds UPWARD: hp="); rn(hp_big); rw(" xp="); rn(xp_big) 121 rw(" level="); rn(lv_big); rw("; normal case still exact hp="); rn(hp_norm); rw("\n") 122 pass = pass + 1 123 } else { 124 rw("T4 RED bad saturation: hp="); rn(hp_big); rw(" (1 => wrapped-then-floored) xp="); rn(xp_big) 125 rw(" lv="); rn(lv_big); rw(" hp_norm="); rn(hp_norm); rw("\n") 126 } 127 128 // ---------- T5 skills monotonic + capped ---------- 129 checks = checks + 1 130 var skmono: i64 = 1 131 var lastr: i64 = 0-1 132 var u: i64 = 0 133 while u <= 4000 { 134 let r: i64 = rs_skill_rank(u, 50, 10) 135 if r < lastr { skmono = 0 } 136 if r > 10 { skmono = 0 } 137 lastr = r 138 u = u + 7 139 } 140 let capped: i64 = rs_skill_rank(99999999, 50, 10) 141 if skmono==1 { if capped==10 { 142 rw("T5 GREEN skill rank monotonic in uses and capped at maxrank (huge uses -> rank "); rn(capped); rw(")\n") 143 pass = pass + 1 144 } } 145 if skmono==0 { rw("T5 RED skill rank not monotonic\n") } 146 if skmono==1 { if capped!=10 { rw("T5 RED skill cap breached: "); rn(capped); rw("\n") } } 147 148 // ---------- T6 data-driven (parameters, not literals) ---------- 149 checks = checks + 1 150 let curveA: i64 = rs_xp_for_level(10, 100, 25) 151 let curveB: i64 = rs_xp_for_level(10, 300, 25) // only base changed 152 let curveC: i64 = rs_xp_for_level(10, 100, 90) // only quad changed 153 let hpA: i64 = rs_derived_hp(12, 5, 20, 3, 6) 154 let hpB: i64 = rs_derived_hp(12, 5, 20, 9, 6) // only per-con changed 155 var dd: i64 = 1 156 if curveA==curveB { dd = 0 } 157 if curveA==curveC { dd = 0 } 158 if hpA==hpB { dd = 0 } 159 if dd==1 { 160 rw("T6 GREEN data-driven: curve "); rn(curveA); rw("/"); rn(curveB); rw("/"); rn(curveC) 161 rw(" hp "); rn(hpA); rw("/"); rn(hpB); rw(" -- parameters move the outputs, nothing is hardcoded\n") 162 pass = pass + 1 163 } else { rw("T6 RED changing parameters did not change outputs -- values are baked in\n") } 164 165 // ---------- T7 anti-vacuity: real, distinct, ordered progression ---------- 166 checks = checks + 1 167 let hp1: i64 = rs_derived_hp(14, 1, 20, 3, 6) 168 let hp5: i64 = rs_derived_hp(14, 5, 20, 3, 6) 169 let hp9: i64 = rs_derived_hp(14, 9, 20, 3, 6) 170 var vac: i64 = 1 171 if hp1 >= hp5 { vac = 0 } 172 if hp5 >= hp9 { vac = 0 } 173 if hp1 <= 1 { vac = 0 } 174 if rs_level_for_xp(0,BASE,QUAD) != 1 { vac = 0 } 175 if rs_apply_mods(100, m1, 0) != 100 { vac = 0 } // zero mods must be identity, not zero 176 if vac==1 { 177 rw("T7 GREEN anti-vacuity: hp L1/L5/L9 = "); rn(hp1); rw("/"); rn(hp5); rw("/"); rn(hp9) 178 rw(", empty modifier set is identity (100) -- not a constant-returning stub\n") 179 pass = pass + 1 180 } else { rw("T7 RED progression is degenerate/constant -- T1-T6 would be vacuous\n") } 181 182 // ---------- T8 composes with nx_gamesave ---------- 183 checks = checks + 1 184 let CP: *u8 = "knowledge/nx_rpgstats_char.sav" as *u8 185 sys_unlinkat(CP) 186 let NF: i64 = 8 187 let ch: *i64 = sys_mmap(NF*8) as *i64 188 ch[0]=14; ch[1]=11; ch[2]=16; ch[3]=9 // attributes 189 ch[4]=rs_xp_for_level(7,BASE,QUAD) + 40 // xp 190 ch[5]=rs_level_for_xp(ch[4],BASE,QUAD) // level 191 ch[6]=rs_derived_hp(ch[2], ch[5], 20, 3, 6) // hp 192 ch[7]=rs_skill_rank(600, 50, 10) // a skill rank 193 let wrote: i64 = gs_save(CP, 90210, ch, NF, 1784578000) 194 let back: *i64 = sys_mmap(NF*8) as *i64 195 let got: i64 = gs_load(CP, back, NF, 0 as *i64) 196 var same: i64 = 1 197 i = 0 198 while i < NF { if back[i]!=ch[i] { same=0 } i=i+1 } 199 if wrote>0 { if got==NF { if same==1 { 200 rw("T8 GREEN composes with nx_gamesave: L"); rn(ch[5]); rw(" hp"); rn(ch[6]); rw(" skill"); rn(ch[7]) 201 rw(" round-tripped through save/load byte-clean ("); rn(wrote); rw("B)\n") 202 pass = pass + 1 203 } } } 204 if same==0 { rw("T8 RED character did not survive the save/load round trip\n") } 205 if got!=NF { if same==1 { rw("T8 RED gs_load rc="); rn(got); rw("\n") } } 206 207 rw("\n=== nx_rpgstats_gate "); rn(pass); rw("/"); rn(checks) 208 if pass == checks { rw(" verdict=GREEN ===\n"); return 0 } 209 rw(" RED ===\n") 210 return 1 211}