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}