code wiki / _hdl_build / nx_gx9_saveload_gate.nx
nx_gx9_saveload_gate.nx source
↩ module page · 247 lines · 12912 B
1// nx_gx9_saveload_gate.nx -- Gx-9: SAVE / LOAD, the capability the gamebench ruler ranks #1 across the
2// ingested corpus (it blocks 11 of 12 real titles). Demand-ranked by measurement, not chosen by taste.
3//
4// ★THE TEST THAT ONLY WE CAN RUN. Most engines can only check that a save round-trips its fields. Because
5// our pipeline is bit-deterministic, we can assert the far stronger property: SAVING AND RELOADING MUST BE
6// INDISTINGUISHABLE FROM NEVER HAVING SAVED. Play 14 ticks straight and keep the final frame. Then play 7,
7// save, load into a FRESH state, play the remaining 7 -- and require the final frame to be BYTE-IDENTICAL
8// and the final state to match field for field. That catches the entire class of "the save forgot something"
9// bugs, which field round-trip tests structurally cannot see.
10//
11// The other half is refusal: a damaged save must FAIL WITH A REASON, never half-load. A corrupted save that
12// partially applies strands the player in an impossible world and reads as a game bug forever.
13// license_tier: ORIGINAL expect_exit: 0
14import "nx_game_world.nx"
15import "nx_game_save.nx"
16import "nx_png_write.nx"
17
18const SL_STEP: i64 = 8192
19const SL_TURN: i64 = 3217
20const SL_TICKS: i64 = 14
21const SL_N: i64 = 5
22const SL_PICKR: i64 = 4915
23const SL_EYE: i64 = 6600
24const SL_WORDS: i64 = 6 + SL_N // ppx ppz yaw tick collected won + alive[SL_N]
25
26func sl_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
27func sl_pn(v: i64) -> i64 { let b: *u8=sys_mmap(32) as *u8; var x: i64=v; var ng: i64=0; if x<0{ng=1;x=0-x} var i: i64=31; if x==0{b[i]=48 as u8;i=i-1} while x>0{b[i]=(48+x%10) as u8;x=x/10;i=i-1} if ng==1{b[i]=45 as u8;i=i-1} sys_write(1,(b as i64+i+1) as *u8,31-i); return 0 }
28
29func sl_script(t: i64) -> i64 {
30 if t==2 { return 2 }
31 if t==3 { return 3 }
32 if t==8 { return 2 }
33 if t==9 { return 3 }
34 return 1
35}
36
37func sl_init(st: *i64) -> i64 {
38 st[0] = 14*GW_CELL; st[1] = 4*GW_CELL; st[2] = 0
39 st[3] = 0; st[4] = 0; st[5] = 0
40 var k: i64 = 0
41 while k < SL_N { st[6+k] = 1; k = k + 1 }
42 return 0
43}
44
45// Advance the world from `st` for ticks [from,to), RENDERING EVERY TICK and folding each frame into a
46// running checksum -- so the comparison covers the whole resumed span, not just its last frame. `cap` names
47// the tick whose frame is copied to dst (pass a negative to capture nothing). Rendering every tick matters:
48// the final tick leaves the player on the peak where the remaining grid falls below the viewport, so a
49// last-frame-only comparison would compare two empty skies and pass vacuously.
50func sl_sim(base: i64, st: *i64, from: i64, to: i64, cap: i64, dst: *u8) -> i64 {
51 let ex: *i64 = sys_mmap(8*8) as *i64
52 let ez: *i64 = sys_mmap(8*8) as *i64
53 let scratch: *u8 = sys_mmap(ww()*hh()*3+16)
54 var ck: i64 = 0
55 var k: i64 = 0
56 while k < SL_N { ex[k] = 14*GW_CELL; ez[k] = (8 + k*4)*GW_CELL; k = k + 1 }
57 var t: i64 = from
58 while t < to {
59 let a: i64 = sl_script(t)
60 if a==1 { st[0] = st[0] + it_sin4096(st[2])*SL_STEP/4096; st[1] = st[1] + it_cos4096(st[2])*SL_STEP/4096 }
61 if a==2 { st[2] = st[2] + SL_TURN }
62 if a==3 { st[2] = st[2] - SL_TURN }
63 var c: i64 = 0
64 while c < SL_N {
65 if st[6+c] == 1 {
66 if gw_dist2(st[0], st[1], ex[c], ez[c]) <= SL_PICKR * SL_PICKR { st[6+c] = 0; st[4] = st[4] + 1 }
67 }
68 c = c + 1
69 }
70 if st[4] >= SL_N { st[5] = 1 }
71 st[3] = t + 1
72 sl_render(base, st, ex, ez, scratch)
73 var q: i64 = 0
74 while q < ww()*hh()*3 { ck = ck + (scratch[q] as i64)*(1 + q % 7); q = q + 97 }
75 if t == cap { var w: i64 = 0; while w < ww()*hh()*3 { dst[w] = scratch[w]; w = w + 1 } }
76 t = t + 1
77 }
78 return ck
79}
80
81// render the world implied by `st` -- the same composition the shipped game uses
82func sl_render(base: i64, st: *i64, ex: *i64, ez: *i64, dst: *u8) -> i64 {
83 var gi: i64 = st[0] / GW_CELL; if gi < 0 { gi = 0 } if gi > GW_N { gi = GW_N }
84 var gj: i64 = st[1] / GW_CELL; if gj < 0 { gj = 0 } if gj > GW_N { gj = GW_N }
85 let eyey: i64 = gw_h(gi,gj) + SL_EYE
86 let lax: i64 = st[0] + it_sin4096(st[2]) * (6*GW_CELL) / 4096
87 let laz: i64 = st[1] + it_cos4096(st[2]) * (6*GW_CELL) / 4096
88 var pitch: i64 = (0 - ((gw_ground(lax, laz) + SL_EYE) - eyey)) * 4096 / (6*GW_CELL)
89 if pitch < (0-2800) { pitch = 0-2800 }
90 if pitch > 2800 { pitch = 2800 }
91 gw_build_terrain(base, 0, st[0], st[1], eyey)
92 sg_project_clip_pitch(base, st[2], pitch, 0)
93 sg_render(base, 74, 156, 62)
94 gw_build_terrain(base, 1, st[0], st[1], eyey)
95 sg_project_clip_pitch(base, st[2], pitch, 0)
96 sg_render_pass(base, 128, 112, 100)
97 gw_build_terrain(base, 2, st[0], st[1], eyey)
98 sg_project_clip_pitch(base, st[2], pitch, 0)
99 sg_render_pass(base, 232, 234, 240)
100 let live: i64 = gw_build_entities(base, ex, ez, ((st as i64) + 6*8) as *i64, SL_N, st[0], st[1], eyey, 1400, 5200)
101 if live > 0 { sg_project_clip_pitch(base, st[2], pitch, 0); sg_render_pass(base, 240, 200, 60) }
102 sg_ssao(base, 190)
103 let fb: *i64 = sg_fb(base)
104 var p: i64 = 0
105 while p < ww()*hh() {
106 let v: i64 = fb[p]
107 dst[p*3] = (v % 256) as u8
108 dst[p*3+1] = ((v/256) % 256) as u8
109 dst[p*3+2] = ((v/65536) % 256) as u8
110 p = p + 1
111 }
112 return 0
113}
114
115func main() -> i64 {
116 sl_puts("=== nx_gx9_saveload_gate -- save/load proven INDISTINGUISHABLE from never having saved ===\n" as *u8)
117 var fails: i64 = 0
118 let base: i64 = sys_mmap(swgpu_bytes()) as i64
119 let frameRef: *u8 = sys_mmap(ww()*hh()*3+16)
120 let frameSplit: *u8 = sys_mmap(ww()*hh()*3+16)
121 let stRef: *i64 = sys_mmap(8*32) as *i64
122 let stSplit: *i64 = sys_mmap(8*32) as *i64
123 let stLoad: *i64 = sys_mmap(8*32) as *i64
124 let path: *u8 = "knowledge/nx_gx9_save.bin\x00" as *u8
125
126 // ---- T1 a save round-trips every field exactly ----
127 let scratch: *u8 = sys_mmap(ww()*hh()*3+16)
128 sl_init(stRef)
129 sl_sim(base, stRef, 0, 7, 0-1, scratch)
130 let wrote: i64 = gs_save(path, stRef, SL_WORDS)
131 let got: i64 = gs_load(path, stLoad, 32)
132 var t1: i64 = 1
133 if got != SL_WORDS { t1 = 0 }
134 var i: i64 = 0
135 while i < SL_WORDS { if stLoad[i] != stRef[i] { t1 = 0 } i = i + 1 }
136 sl_puts(" wrote "); sl_pn(wrote); sl_puts(" bytes, loaded "); sl_pn(got); sl_puts(" words\n" as *u8)
137 if t1==1 { sl_puts("T1 PASS every state field round-trips exactly\n" as *u8) }
138 else { fails=fails+1; sl_puts("T1 FAIL roundtrip\n" as *u8) }
139
140 // ---- T2 THE REAL TEST: straight run vs save/reload/resume must be BYTE-IDENTICAL ----
141 sl_init(stRef)
142 sl_sim(base, stRef, 0, 7, 0-1, scratch) // play half
143 let ckRef: i64 = sl_sim(base, stRef, 7, SL_TICKS, 10, frameRef) // ...and just keep playing
144 sl_init(stSplit)
145 sl_sim(base, stSplit, 0, 7, 0-1, scratch) // play the same half
146 gs_save(path, stSplit, SL_WORDS) // SAVE
147 var z: i64 = 0
148 while z < 32 { stLoad[z] = 0; z = z + 1 } // FRESH state: nothing carried in memory
149 gs_load(path, stLoad, 32) // LOAD
150 let ckSplit: i64 = sl_sim(base, stLoad, 7, SL_TICKS, 10, frameSplit) // resume from the loaded state
151 var diff: i64 = 0
152 var p: i64 = 0
153 while p < ww()*hh()*3 { if frameRef[p] != frameSplit[p] { diff = diff + 1 } p = p + 1 }
154 sl_puts(" resumed-span frame checksum: continuous="); sl_pn(ckRef); sl_puts(" save-reload="); sl_pn(ckSplit); sl_puts("\n" as *u8)
155 if ckRef != ckSplit { diff = diff + 1 } // every tick in the span must match, not just one
156 var stdiff: i64 = 0
157 i = 0
158 while i < SL_WORDS { if stLoad[i] != stRef[i] { stdiff = stdiff + 1 } i = i + 1 }
159 sl_puts(" straight-run vs save/reload/resume: differing bytes="); sl_pn(diff)
160 sl_puts(" differing state fields="); sl_pn(stdiff); sl_puts("\n" as *u8)
161 // ★NON-VACUITY: "0 differing bytes" is worthless if BOTH frames are empty sky. Require the reference
162 // frame to actually contain rendered world before its equality means anything.
163 var refGeom: i64 = 0
164 var yy: i64 = 0
165 while yy < hh() {
166 let bgr: i64 = 26 + yy*36/hh()
167 let bgg: i64 = 28 + yy*34/hh()
168 let bgb: i64 = 42 + yy*30/hh()
169 var xx: i64 = 0
170 while xx < ww() {
171 let o: i64 = (yy*ww()+xx)*3
172 var d1: i64 = (frameRef[o] as i64)-bgr; if d1<0 {d1=0-d1}
173 var d2: i64 = (frameRef[o+1] as i64)-bgg; if d2<0 {d2=0-d2}
174 var d3: i64 = (frameRef[o+2] as i64)-bgb; if d3<0 {d3=0-d3}
175 if d1+d2+d3 > 18 { refGeom = refGeom + 1 }
176 xx = xx + 1
177 }
178 yy = yy + 1
179 }
180 sl_puts(" reference frame world pixels="); sl_pn(refGeom); sl_puts(" of "); sl_pn(ww()*hh())
181 sl_puts(" (non-vacuity guard)\n" as *u8)
182 var t2: i64 = 0
183 if diff == 0 { if stdiff == 0 { if refGeom > 20000 { t2 = 1 } } }
184 if t2==1 { sl_puts("T2 PASS save/load is TRANSPARENT: resumed run byte-identical to the uninterrupted one, on a frame with real world in it\n" as *u8) }
185 else { fails=fails+1; sl_puts("T2 FAIL diff="); sl_pn(diff); sl_puts(" stdiff="); sl_pn(stdiff); sl_puts(" refGeom="); sl_pn(refGeom); sl_puts(" (if refGeom is low the comparison was VACUOUS)\n" as *u8) }
186
187 // ---- T3 a CORRUPTED save is refused, not half-applied ----
188 gs_save(path, stRef, SL_WORDS)
189 let szp: *i64 = sys_mmap(16) as *i64
190 let raw: *u8 = sys_read_file(path, szp)
191 var t3: i64 = 0
192 if (raw as i64) != 0 {
193 raw[40] = ((raw[40] as i64) + 7) % 256 as u8 // flip a payload byte
194 let fd: i64 = sys_openat_wr(path, 420)
195 if fd >= 0 { sys_write(fd, raw, szp[0]); sys_close(fd) }
196 var probe: *i64 = sys_mmap(8*32) as *i64
197 var q: i64 = 0
198 while q < 32 { probe[q] = 0-999; q = q + 1 } // sentinel: must stay untouched
199 let rc: i64 = gs_load(path, probe, 32)
200 var untouched: i64 = 1
201 q = 0
202 while q < SL_WORDS { if probe[q] != (0-999) { untouched = 0 } q = q + 1 }
203 sl_puts(" corrupted save -> rc="); sl_pn(rc); sl_puts(" caller-state-untouched="); sl_pn(untouched); sl_puts("\n" as *u8)
204 if rc == GS_ERR_CHECKSUM { if untouched == 1 { t3 = 1 } }
205 }
206 if t3==1 { sl_puts("T3 PASS corruption REFUSED with a reason and the caller's state left untouched\n" as *u8) }
207 else { fails=fails+1; sl_puts("T3 FAIL corrupt save not properly refused\n" as *u8) }
208
209 // ---- T4 a save from a FUTURE version is refused rather than misparsed ----
210 gs_save(path, stRef, SL_WORDS)
211 let raw2: *u8 = sys_read_file(path, szp)
212 var t4: i64 = 0
213 if (raw2 as i64) != 0 {
214 gs_put64(raw2, 8, 99) // pretend version 99
215 gs_put64(raw2, 0, GS_MAGIC)
216 let fd2: i64 = sys_openat_wr(path, 420)
217 if fd2 >= 0 { sys_write(fd2, raw2, szp[0]); sys_close(fd2) }
218 let rc2: i64 = gs_load(path, stLoad, 32)
219 sl_puts(" version-99 save -> rc="); sl_pn(rc2); sl_puts("\n" as *u8)
220 if rc2 == GS_ERR_VERSION { t4 = 1 }
221 }
222 if t4==1 { sl_puts("T4 PASS unknown save version refused (no silent misparse of a future format)\n" as *u8) }
223 else { fails=fails+1; sl_puts("T4 FAIL version not checked\n" as *u8) }
224
225 // ---- T5 a missing file and an oversized payload both fail cleanly ----
226 let rc3: i64 = gs_load("knowledge/nx_gx9_absent.bin\x00" as *u8, stLoad, 32)
227 gs_save(path, stRef, SL_WORDS)
228 let rc4: i64 = gs_load(path, stLoad, 2) // capacity smaller than the payload
229 sl_puts(" missing-file rc="); sl_pn(rc3); sl_puts(" undersized-buffer rc="); sl_pn(rc4); sl_puts("\n" as *u8)
230 var t5: i64 = 0
231 if rc3 == GS_ERR_UNREADABLE { if rc4 == GS_ERR_TOOBIG { t5 = 1 } }
232 if t5==1 { sl_puts("T5 PASS missing file and undersized buffer each refused with their own code\n" as *u8) }
233 else { fails=fails+1; sl_puts("T5 FAIL clean-failure paths\n" as *u8) }
234
235 // ---- T6 artifact: the resumed frame (this is what the player sees after loading) ----
236 nx_png_write_rgb("knowledge/nx_gx9_resumed.png\x00" as *u8, frameSplit, ww(), hh())
237 let rb: *u8 = sys_read_file("knowledge/nx_gx9_resumed.png\x00" as *u8, szp)
238 var t6: i64 = 0
239 if (rb as i64)!=0 { if szp[0]>1000 { t6=1 } }
240 if t6==1 { sl_puts("T6 PASS resumed-session artifact written ("); sl_pn(szp[0]); sl_puts(" bytes)\n" as *u8) }
241 else { fails=fails+1; sl_puts("T6 FAIL png\n" as *u8) }
242
243 if fails==0 { sl_puts("GX9-SAVELOAD GREEN -- saving and reloading is indistinguishable from never having saved, and damaged saves are refused\n" as *u8); sys_exit(0); return 0 }
244 sl_puts("GX9-SAVELOAD RED fails="); sl_pn(fails); sl_puts("\n" as *u8)
245 sys_exit(1)
246 return 1
247}