code wiki / _hdl_build / nx_rpgstats_gate.nx

nx_rpgstats_gate.nx source

↩ module page · 218 lines · 10361 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" 19import "nx_gate_verdict.nx" 20 21func rw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 22func rn(v: i64) -> i64 { 23 if v==0 { sys_write(1,"0" as *u8,1); return 0 } 24 var m: i64=v; if m<0 { sys_write(1,"-" as *u8,1); m=0-m } 25 let t: *u8=sys_mmap(32); var k: i64=0 26 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 27 let o: *u8=sys_mmap(32); var q: i64=k-1; var i: i64=0 28 while q>=0 { o[i]=t[q]; i=i+1; q=q-1 } 29 sys_write(1,o,i); return 0 30} 31 32func main() -> i64 { 33 rw("=== nx_rpgstats_gate: is character progression safe to compose into every game? ===\n\n") 34 var pass: i64 = 0 35 var checks: i64 = 0 36 37 let BASE: i64 = 100 // xp curve linear term 38 let QUAD: i64 = 25 // xp curve quadratic term 39 40 // ---------- T1 XP curve strictly monotonic ---------- 41 checks = checks + 1 42 var mono: i64 = 1 43 var prev: i64 = 0-1 44 var L: i64 = 1 45 while L <= RS_MAXLEVEL { 46 let x: i64 = rs_xp_for_level(L, BASE, QUAD) 47 if x <= prev { mono = 0 } 48 prev = x 49 L = L + 1 50 } 51 if mono==1 { 52 rw("T1 GREEN xp curve strictly increasing over 1.."); rn(RS_MAXLEVEL) 53 rw(" (L200 threshold="); rn(rs_xp_for_level(200,BASE,QUAD)); rw(" xp)\n") 54 pass = pass + 1 55 } else { rw("T1 RED xp curve not strictly increasing\n") } 56 57 // ---------- T2 exact inverse at boundaries ---------- 58 checks = checks + 1 59 var inv: i64 = 1 60 var lv: i64 = 2 61 while lv <= 120 { 62 let thr: i64 = rs_xp_for_level(lv, BASE, QUAD) 63 if rs_level_for_xp(thr, BASE, QUAD) != lv { inv = 0 } // exactly at threshold -> that level 64 if rs_level_for_xp(thr-1, BASE, QUAD) != lv-1 { inv = 0 } // one xp short -> previous level 65 lv = lv + 1 66 } 67 if inv==1 { 68 rw("T2 GREEN level_for_xp is the exact inverse of xp_for_level at every boundary and boundary-1\n") 69 pass = pass + 1 70 } else { rw("T2 RED level/xp inversion mismatch\n") } 71 72 // ---------- T3 order-independent modifier stacking ---------- 73 checks = checks + 1 74 let NM: i64 = 6 75 let m1: *i64 = sys_mmap(NM*2*8) as *i64 76 // (kind,value): additive and percent interleaved, deliberately mixed 77 m1[0]=0; m1[1]=10 // +10 flat 78 m1[2]=1; m1[3]=20 // +20% 79 m1[4]=0; m1[5]=0-5 // -5 flat 80 m1[6]=1; m1[7]=0-30 // -30% 81 m1[8]=0; m1[9]=7 // +7 flat 82 m1[10]=1; m1[11]=15 // +15% 83 let BASEV: i64 = 100 84 let fwd: i64 = rs_apply_mods(BASEV, m1, NM) 85 // reversed 86 let m2: *i64 = sys_mmap(NM*2*8) as *i64 87 var i: i64 = 0 88 while i < NM { m2[i*2] = m1[(NM-1-i)*2]; m2[i*2+1] = m1[(NM-1-i)*2+1]; i = i + 1 } 89 let rev: i64 = rs_apply_mods(BASEV, m2, NM) 90 // rotated by 3 91 let m3: *i64 = sys_mmap(NM*2*8) as *i64 92 i = 0 93 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 } 94 let rot: i64 = rs_apply_mods(BASEV, m3, NM) 95 if fwd==rev { if fwd==rot { 96 rw("T3 GREEN modifier stacking ORDER-INDEPENDENT: fwd=rev=rot="); rn(fwd) 97 rw(" (base 100, +12 flat, +5pct pooled)\n") 98 pass = pass + 1 99 } } 100 if fwd!=rev { rw("T3 RED order matters: fwd="); rn(fwd); rw(" rev="); rn(rev); rw("\n") } 101 if fwd==rev { if fwd!=rot { rw("T3 RED rotation changed result: fwd="); rn(fwd); rw(" rot="); rn(rot); rw("\n") } } 102 103 // ---------- T4 saturation, no overflow ---------- 104 checks = checks + 1 105 let huge: i64 = 0x7FFFFFFFFFFFFFF 106 let hp_big: i64 = rs_derived_hp(huge, huge, huge, huge, huge) 107 let xp_big: i64 = rs_xp_for_level(RS_MAXLEVEL, huge, huge) 108 let lv_big: i64 = rs_level_for_xp(huge, BASE, QUAD) 109 var sat: i64 = 1 110 // must saturate UPWARD. hp==1 would mean the sum wrapped negative and the floor rescued it -- 111 // that is how this tooth passed for the wrong reason on the first build. 112 if hp_big < RS_SAT / 2 { sat = 0 } 113 if hp_big > RS_SAT { sat = 0 } 114 if xp_big < RS_SAT / 2 { sat = 0 } 115 if lv_big < 1 { sat = 0 } 116 if lv_big > RS_MAXLEVEL { sat = 0 } 117 // a big-but-not-absurd case must still compute exactly, not saturate 118 let hp_norm: i64 = rs_derived_hp(20, 50, 30, 4, 8) 119 if hp_norm != 30 + 80 + 392 { sat = 0 } 120 if sat==1 { 121 rw("T4 GREEN saturation holds UPWARD: hp="); rn(hp_big); rw(" xp="); rn(xp_big) 122 rw(" level="); rn(lv_big); rw("; normal case still exact hp="); rn(hp_norm); rw("\n") 123 pass = pass + 1 124 } else { 125 rw("T4 RED bad saturation: hp="); rn(hp_big); rw(" (1 => wrapped-then-floored) xp="); rn(xp_big) 126 rw(" lv="); rn(lv_big); rw(" hp_norm="); rn(hp_norm); rw("\n") 127 } 128 129 // ---------- T5 skills monotonic + capped ---------- 130 checks = checks + 1 131 var skmono: i64 = 1 132 var lastr: i64 = 0-1 133 var u: i64 = 0 134 while u <= 4000 { 135 let r: i64 = rs_skill_rank(u, 50, 10) 136 if r < lastr { skmono = 0 } 137 if r > 10 { skmono = 0 } 138 lastr = r 139 u = u + 7 140 } 141 let capped: i64 = rs_skill_rank(99999999, 50, 10) 142 if skmono==1 { if capped==10 { 143 rw("T5 GREEN skill rank monotonic in uses and capped at maxrank (huge uses -> rank "); rn(capped); rw(")\n") 144 pass = pass + 1 145 } } 146 if skmono==0 { rw("T5 RED skill rank not monotonic\n") } 147 if skmono==1 { if capped!=10 { rw("T5 RED skill cap breached: "); rn(capped); rw("\n") } } 148 149 // ---------- T6 data-driven (parameters, not literals) ---------- 150 checks = checks + 1 151 let curveA: i64 = rs_xp_for_level(10, 100, 25) 152 let curveB: i64 = rs_xp_for_level(10, 300, 25) // only base changed 153 let curveC: i64 = rs_xp_for_level(10, 100, 90) // only quad changed 154 let hpA: i64 = rs_derived_hp(12, 5, 20, 3, 6) 155 let hpB: i64 = rs_derived_hp(12, 5, 20, 9, 6) // only per-con changed 156 var dd: i64 = 1 157 if curveA==curveB { dd = 0 } 158 if curveA==curveC { dd = 0 } 159 if hpA==hpB { dd = 0 } 160 if dd==1 { 161 rw("T6 GREEN data-driven: curve "); rn(curveA); rw("/"); rn(curveB); rw("/"); rn(curveC) 162 rw(" hp "); rn(hpA); rw("/"); rn(hpB); rw(" -- parameters move the outputs, nothing is hardcoded\n") 163 pass = pass + 1 164 } else { rw("T6 RED changing parameters did not change outputs -- values are baked in\n") } 165 166 // ---------- T7 anti-vacuity: real, distinct, ordered progression ---------- 167 checks = checks + 1 168 let hp1: i64 = rs_derived_hp(14, 1, 20, 3, 6) 169 let hp5: i64 = rs_derived_hp(14, 5, 20, 3, 6) 170 let hp9: i64 = rs_derived_hp(14, 9, 20, 3, 6) 171 var vac: i64 = 1 172 if hp1 >= hp5 { vac = 0 } 173 if hp5 >= hp9 { vac = 0 } 174 if hp1 <= 1 { vac = 0 } 175 if rs_level_for_xp(0,BASE,QUAD) != 1 { vac = 0 } 176 if rs_apply_mods(100, m1, 0) != 100 { vac = 0 } // zero mods must be identity, not zero 177 if vac==1 { 178 rw("T7 GREEN anti-vacuity: hp L1/L5/L9 = "); rn(hp1); rw("/"); rn(hp5); rw("/"); rn(hp9) 179 rw(", empty modifier set is identity (100) -- not a constant-returning stub\n") 180 pass = pass + 1 181 } else { rw("T7 RED progression is degenerate/constant -- T1-T6 would be vacuous\n") } 182 183 // ---------- T8 composes with nx_gamesave ---------- 184 checks = checks + 1 185 let CP: *u8 = "knowledge/nx_rpgstats_char.sav" as *u8 186 sys_unlinkat(CP) 187 let NF: i64 = 8 188 let ch: *i64 = sys_mmap(NF*8) as *i64 189 ch[0]=14; ch[1]=11; ch[2]=16; ch[3]=9 // attributes 190 ch[4]=rs_xp_for_level(7,BASE,QUAD) + 40 // xp 191 ch[5]=rs_level_for_xp(ch[4],BASE,QUAD) // level 192 ch[6]=rs_derived_hp(ch[2], ch[5], 20, 3, 6) // hp 193 ch[7]=rs_skill_rank(600, 50, 10) // a skill rank 194 let wrote: i64 = gs_save(CP, 90210, ch, NF, 1784578000) 195 let back: *i64 = sys_mmap(NF*8) as *i64 196 let got: i64 = gs_load(CP, back, NF, 0 as *i64) 197 var same: i64 = 1 198 i = 0 199 while i < NF { if back[i]!=ch[i] { same=0 } i=i+1 } 200 if wrote>0 { if got==NF { if same==1 { 201 rw("T8 GREEN composes with nx_gamesave: L"); rn(ch[5]); rw(" hp"); rn(ch[6]); rw(" skill"); rn(ch[7]) 202 rw(" round-tripped through save/load byte-clean ("); rn(wrote); rw("B)\n") 203 pass = pass + 1 204 } } } 205 if same==0 { rw("T8 RED character did not survive the save/load round trip\n") } 206 if got!=NF { if same==1 { rw("T8 RED gs_load rc="); rn(got); rw("\n") } } 207 208 rw("\n=== nx_rpgstats_gate "); rn(pass); rw("/"); rn(checks) 209 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 210 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 211 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 212 let ctr__dry: *i64 = gv_ctr() 213 ctr__dry[0] = pass 214 ctr__dry[1] = checks 215 let rc__dry: i64 = gv_verdict("RPGSTATS-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 216 sys_exit(rc__dry) 217 return rc__dry 218}