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}