nx_nif_gate.nx source
↩ module page · 349 lines · 17835 B
1// nx_nif_gate.nx -- BITE-PROOF for the NIF structural probe + geometry leg, both directions.
2//
3// IT DRIVES THE SHIPPING CODE: every tooth calls nx_nif_lib directly -- the same nif_probe,
4// nif_types and nif_geom the CLI calls. FIXTURES ARE ASSEMBLED AT RUNTIME under /tmp/<gate>/ by a
5// byte builder (binary format; no literal fixture bytes exist in this source). Idempotent.
6//
7// FIXTURE HONESTY, DECLARED: the good fixtures are SYNTHETIC minimal modern-layout NIFs derived
8// from the mirrored nif.xml and the hex-measured real SE + FO76 files; the REAL corpus (nifly test
9// nifs, hex-verified 2026-08-30) rides the CLI smoke and the banked-mirror watch row.
10//
11// license_tier: ORIGINAL expect_exit: 0
12import "nx_syscalls.nx"
13import "nx_gate_verdict.nx"
14import "nx_nif_lib.nx"
15
16const NG_DIR: *u8 = "/tmp/nx_nif_gate" as *u8
17const NG_F_GOOD: *u8 = "/tmp/nx_nif_gate/good.nif" as *u8
18const NG_F_BS155: *u8 = "/tmp/nx_nif_gate/bs155.nif" as *u8
19const NG_F_OOB: *u8 = "/tmp/nx_nif_gate/oob.nif" as *u8
20const NG_F_JUNK: *u8 = "/tmp/nx_nif_gate/junk.bin" as *u8
21const NG_F_OLDVER: *u8 = "/tmp/nx_nif_gate/oldver.nif" as *u8
22const NG_F_ABSURD: *u8 = "/tmp/nx_nif_gate/absurd.nif" as *u8
23const NG_F_EMPTY: *u8 = "/tmp/nx_nif_gate/empty.nif" as *u8
24const NG_BUFCAP: i64 = 8192
25const NG_VER_OLD: i64 = 335544323 // 0x14000003 = 20.0.0.3 -- recognized magic, unsupported layout
26// the geometry block: desc low nibble 5 dwords/vert, 2 verts, 1 tri -> datasize 5*2*4 + 1*6 = 46
27const NG_GEO_DESCLO: i64 = 5
28const NG_GEO_VERTS: i64 = 2
29const NG_GEO_TRIS: i64 = 1
30const NG_GEO_DSIZE: i64 = 46
31const NG_GEO_BLOCK: i64 = 162 // 100 prefix (0 extras) + 8 desc + 2 tri + 2 vert + 4 dsize + 46 data
32const NG_F32_ONE: i64 = 1065353216 // 1.0f -- vertex 0 x
33const NG_F32_ONEP5: i64 = 1069547520 // 1.5f -- vertex 1 y
34const NG_F32_TWO: i64 = 1073741824 // 2.0f -- the good NiBound radius (covers both verts)
35const NG_F32_HALF: i64 = 1056964608 // 0.5f -- the out-of-bound radius (covers neither)
36const NG_BLOCKBYTES: i64 = 212 // 20 (NiNode) + 162 (real BSTriShape) + 30 (undersized BSTriShape)
37
38func ng_u8(dst: *u8, at: i64, v: i64) -> i64 { dst[at] = v as u8; return at + 1 }
39func ng_u16(dst: *u8, at: i64, v: i64) -> i64 { dst[at] = (v & 0xff) as u8; dst[at + 1] = ((v >> 8) & 0xff) as u8; return at + 2 }
40func ng_u32(dst: *u8, at: i64, v: i64) -> i64 {
41 dst[at] = (v & 0xff) as u8
42 dst[at + 1] = ((v >> 8) & 0xff) as u8
43 dst[at + 2] = ((v >> 16) & 0xff) as u8
44 dst[at + 3] = ((v >> 24) & 0xff) as u8
45 return at + 4
46}
47func ng_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
48func ng_raw(dst: *u8, at: i64, s: *u8) -> i64 {
49 var i: i64 = 0
50 var o: i64 = at
51 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 }
52 return o
53}
54func ng_sized(dst: *u8, at: i64, s: *u8) -> i64 {
55 var o: i64 = ng_u32(dst, at, ng_slen(s))
56 return ng_raw(dst, o, s)
57}
58// export string as observed on the real SE file: length byte counts the terminating nul
59func ng_export_empty(dst: *u8, at: i64) -> i64 {
60 var o: i64 = ng_u8(dst, at, 1)
61 return ng_u8(dst, o, 0)
62}
63func ng_zeros(dst: *u8, at: i64, count: i64) -> i64 {
64 var i: i64 = 0
65 var o: i64 = at
66 while i < count { dst[o] = 0 as u8; o = o + 1; i = i + 1 }
67 return o
68}
69func ng_write(path: *u8, body: *u8, n: i64) -> i64 {
70 sys_unlinkat(path)
71 let fd: i64 = sys_openat_wr(path, MODE_0644)
72 if fd < 0 { return 0 - 1 }
73 if n > 0 { sys_write(fd, body, n) }
74 sys_close(fd)
75 return n
76}
77
78// minimal modern NIF: 3 blocks over 2 types (NiNode, BSTriShape x2), 1 string, 0 groups.
79// Block 1 carries a REAL BSTriShape tuple satisfying the nif.xml datasize formula; block 2 is an
80// undersized BSTriShape that the geometry leg must count UNVERIFIED.
81func ng_build_good(dst: *u8, ver: i64, rbits: i64) -> i64 {
82 var o: i64 = ng_raw(dst, 0, "Gamebryo File Format, Version 20.2.0.7" as *u8)
83 o = ng_u8(dst, o, NF_CH_NL)
84 o = ng_u32(dst, o, ver)
85 o = ng_u8(dst, o, 1) // little endian
86 o = ng_u32(dst, o, 12) // user version
87 o = ng_u32(dst, o, 3) // num blocks
88 o = ng_u32(dst, o, 100) // BS version (SSE): Author + ProcessScript + ExportScript, no MaxFilepath
89 o = ng_export_empty(dst, o)
90 o = ng_export_empty(dst, o)
91 o = ng_export_empty(dst, o)
92 o = ng_u16(dst, o, 2) // num block types
93 o = ng_sized(dst, o, "NiNode" as *u8)
94 o = ng_sized(dst, o, "BSTriShape" as *u8)
95 o = ng_u16(dst, o, 0) // block 0 -> NiNode
96 o = ng_u16(dst, o, 1) // block 1 -> BSTriShape (real tuple)
97 o = ng_u16(dst, o, 1) // block 2 -> BSTriShape (undersized)
98 o = ng_u32(dst, o, 20)
99 o = ng_u32(dst, o, NG_GEO_BLOCK)
100 o = ng_u32(dst, o, 30)
101 o = ng_u32(dst, o, 1) // num strings
102 o = ng_u32(dst, o, 5) // max string length
103 o = ng_sized(dst, o, "hello" as *u8)
104 o = ng_u32(dst, o, 0) // num groups
105 // block 0: NiNode, 20 opaque bytes
106 o = ng_zeros(dst, o, 20)
107 // block 1: BSTriShape -- 72-byte NiAVObject fixed part (num extras at +4 stays 0), then the
108 // block's OWN NiBound oracle (center 0,0,0 + the caller's radius), refs, tuple, and REAL
109 // vertex records: v0 at (1, 0, 0), v1 at (0, 1.5, 0), stride 5 dwords, then the triangle.
110 o = ng_zeros(dst, o, NF_BOUND_OFF)
111 o = ng_u32(dst, o, 0) // bound center x
112 o = ng_u32(dst, o, 0) // bound center y
113 o = ng_u32(dst, o, 0) // bound center z
114 o = ng_u32(dst, o, rbits) // bound radius
115 o = ng_zeros(dst, o, 12) // skin + shader + alpha refs
116 o = ng_u32(dst, o, NG_GEO_DESCLO) // desc low dword (low nibble = dwords per vertex)
117 o = ng_u32(dst, o, 0) // desc high dword
118 o = ng_u16(dst, o, NG_GEO_TRIS)
119 o = ng_u16(dst, o, NG_GEO_VERTS)
120 o = ng_u32(dst, o, NG_GEO_DSIZE)
121 o = ng_u32(dst, o, NG_F32_ONE) // v0 x = 1.0
122 o = ng_u32(dst, o, 0) // v0 y
123 o = ng_u32(dst, o, 0) // v0 z
124 o = ng_zeros(dst, o, 8) // v0 tail of the 5-dword record
125 o = ng_u32(dst, o, 0) // v1 x
126 o = ng_u32(dst, o, NG_F32_ONEP5) // v1 y = 1.5
127 o = ng_u32(dst, o, 0) // v1 z
128 o = ng_zeros(dst, o, 8) // v1 tail
129 o = ng_zeros(dst, o, 6) // the triangle
130 // block 2: BSTriShape but only 30 bytes -- the tuple cannot fit past the prefix
131 o = ng_zeros(dst, o, 30)
132 return o
133}
134// bsver-155 minimal header (FO76 layout: Author + extra u32 + ExportScript + MaxFilepath), 1 block
135func ng_build_bs155(dst: *u8) -> i64 {
136 var o: i64 = ng_raw(dst, 0, "Gamebryo File Format, Version 20.2.0.7" as *u8)
137 o = ng_u8(dst, o, NF_CH_NL)
138 o = ng_u32(dst, o, NF_VER_SSE)
139 o = ng_u8(dst, o, 1)
140 o = ng_u32(dst, o, 12)
141 o = ng_u32(dst, o, 1) // num blocks
142 o = ng_u32(dst, o, 155) // BS version (FO76)
143 o = ng_export_empty(dst, o) // Author
144 o = ng_u32(dst, o, 0) // the extra u32 (BS > 130)
145 o = ng_export_empty(dst, o) // Export Script (no Process Script at BS >= 131)
146 o = ng_export_empty(dst, o) // Max Filepath (BS >= 103)
147 o = ng_u16(dst, o, 1)
148 o = ng_sized(dst, o, "NiNode" as *u8)
149 o = ng_u16(dst, o, 0)
150 o = ng_u32(dst, o, 20)
151 o = ng_u32(dst, o, 0) // num strings
152 o = ng_u32(dst, o, 0) // max string length
153 o = ng_u32(dst, o, 0) // num groups
154 o = ng_zeros(dst, o, 20)
155 return o
156}
157// absurd type-table count spliced in: bounds must refuse, never misparse
158func ng_build_absurd(dst: *u8) -> i64 {
159 var o: i64 = ng_raw(dst, 0, "Gamebryo File Format, Version 20.2.0.7" as *u8)
160 o = ng_u8(dst, o, NF_CH_NL)
161 o = ng_u32(dst, o, NF_VER_SSE)
162 o = ng_u8(dst, o, 1)
163 o = ng_u32(dst, o, 12)
164 o = ng_u32(dst, o, 3)
165 o = ng_u32(dst, o, 100)
166 o = ng_export_empty(dst, o)
167 o = ng_export_empty(dst, o)
168 o = ng_export_empty(dst, o)
169 o = ng_u16(dst, o, 65535) // absurd num block types
170 return o
171}
172
173func main(argc: i64, argv: *i64) -> i64 {
174 let ctr: *i64 = gv_ctr()
175 gv_head("=== NX-NIF GATE -- the Bethesda mesh container must census exactly, and abstain by name on everything else ===" as *u8)
176
177 sys_mkdir(NG_DIR, MODE_0755)
178 let asmbuf: *u8 = sys_mmap(NG_BUFCAP)
179 let sc: *i64 = sys_mmap(64) as *i64
180
181 let n_good: i64 = ng_build_good(asmbuf, NF_VER_SSE, NG_F32_TWO)
182 let w_good: i64 = ng_write(NG_F_GOOD, asmbuf, n_good)
183 let n_oob: i64 = ng_build_good(asmbuf, NF_VER_SSE, NG_F32_HALF)
184 let w_oob: i64 = ng_write(NG_F_OOB, asmbuf, n_oob)
185 let n_155: i64 = ng_build_bs155(asmbuf)
186 let w_155: i64 = ng_write(NG_F_BS155, asmbuf, n_155)
187 let n_old: i64 = ng_build_good(asmbuf, NG_VER_OLD, NG_F32_TWO)
188 let w_old: i64 = ng_write(NG_F_OLDVER, asmbuf, n_old)
189 let n_abs: i64 = ng_build_absurd(asmbuf)
190 let w_abs: i64 = ng_write(NG_F_ABSURD, asmbuf, n_abs)
191 let n_junk: i64 = ng_raw(asmbuf, 0, "these bytes are neither gamebryo nor netimmerse and never will be, padded to pass the length floor" as *u8)
192 let w_junk: i64 = ng_write(NG_F_JUNK, asmbuf, n_junk)
193 let w_empty: i64 = ng_write(NG_F_EMPTY, asmbuf, 0)
194
195 gv_puts(" planted: good=" as *u8); gv_num(w_good)
196 gv_puts(" bs155=" as *u8); gv_num(w_155)
197 gv_puts(" oob=" as *u8); gv_num(w_oob)
198 gv_puts(" oldver=" as *u8); gv_num(w_old)
199 gv_puts(" absurd=" as *u8); gv_num(w_abs)
200 gv_puts(" junk=" as *u8); gv_num(w_junk)
201 gv_puts(" empty=" as *u8); gv_num(w_empty); gv_puts("\n" as *u8)
202
203 var built: i64 = 0
204 if w_good == n_good { if w_good > 300 { if w_155 == n_155 { if w_oob == n_oob { if w_old == n_old { if w_abs == n_abs {
205 if w_junk == n_junk { if w_junk >= 40 { if w_empty == 0 { built = 1 } } } } } } } } }
206 gv_need("every fixture reached its planted condition (written == built, good past 300 B with its geometry block, empty empty)" as *u8, built, ctr)
207
208 let good: *u8 = sys_read_file(NG_F_GOOD, sc)
209 let good_n: i64 = sc[0]
210 let facts: *i64 = sys_mmap(NF_N_SLOTS * 8) as *i64
211
212 // ---- T1 THE BITE
213 nif_probe(good, good_n, good_n, facts)
214 var fired_good: i64 = 0
215 if facts[0] == 1 { fired_good = 1 }
216 let f_ver: i64 = facts[1]
217 let f_bsv: i64 = facts[2]
218 let f_blk: i64 = facts[3]
219 let f_typ: i64 = facts[4]
220 let f_tri: i64 = facts[5]
221 let f_tot: i64 = facts[6]
222 let f_str: i64 = facts[7]
223 let f_hdr: i64 = facts[8]
224 let junk: *u8 = sys_read_file(NG_F_JUNK, sc)
225 let junk_n: i64 = sc[0]
226 nif_probe(junk, junk_n, junk_n, facts)
227 var fired_junk: i64 = 0
228 if facts[0] != 0 { fired_junk = 1 }
229 gv_bite("T1 nif_probe parses the modern container and reads prose as NOT A NIF" as *u8, fired_good, fired_junk, ctr)
230
231 // ---- T2 census exact against the built values
232 gv_puts(" census: ver=" as *u8); gv_num(f_ver)
233 gv_puts(" bsver=" as *u8); gv_num(f_bsv)
234 gv_puts(" blocks=" as *u8); gv_num(f_blk)
235 gv_puts(" types=" as *u8); gv_num(f_typ)
236 gv_puts(" trishapes=" as *u8); gv_num(f_tri)
237 gv_puts(" blockbytes=" as *u8); gv_num(f_tot)
238 gv_puts(" strings=" as *u8); gv_num(f_str)
239 gv_puts(" headerbytes=" as *u8); gv_num(f_hdr); gv_puts("\n" as *u8)
240 var t2: i64 = 0
241 if f_ver == NF_VER_SSE { if f_bsv == 100 { if f_blk == 3 { if f_typ == 2 { if f_str == 1 { t2 = 1 } } } } }
242 gv_check("T2 header census exact: version 20.2.0.7, BS 100, 3 blocks, 2 types, 1 string" as *u8, t2, ctr)
243
244 // ---- T3 the TriShape count discriminates by TYPE, not by block count
245 var t3: i64 = 0
246 if f_tri == 2 { if f_blk == 3 { t3 = 1 } }
247 gv_check("T3 trishape census counts the two BSTriShape blocks and not the NiNode (2 of 3)" as *u8, t3, ctr)
248
249 // ---- T4 the size table sums and the header ends where the payload begins
250 var t4: i64 = 0
251 if f_tot == NG_BLOCKBYTES { if f_hdr > 0 { if f_hdr + f_tot == good_n { t4 = 1 } } }
252 gv_check("T4 block bytes sum to the declared 212 and headerbytes + blockbytes == the file (the partition SUMS)" as *u8, t4, ctr)
253
254 // ---- T5 the type-table verb agrees with the probe
255 let toffs: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64
256 let tlens: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64
257 let tcounts: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64
258 let meta: *i64 = sys_mmap(16) as *i64
259 let st: i64 = nif_types(good, good_n, good_n, toffs, tlens, tcounts, NF_MAX_TYPES, meta)
260 var t5: i64 = 0
261 if st == 1 { if meta[0] == 2 { if tcounts[0] == 1 { if tcounts[1] == 2 { t5 = 1 } } } }
262 gv_check("T5 nif_types agrees: 2 types, NiNode carries 1 block and BSTriShape carries 2" as *u8, t5, ctr)
263
264 // ---- T5g THE GEOMETRY LEG: the schema's own datasize arithmetic separates a true tuple from
265 // an undersized block in the SAME file -- both signals present at once.
266 let vouts: *i64 = sys_mmap(64 * 8) as *i64
267 let touts: *i64 = sys_mmap(64 * 8) as *i64
268 let vflags: *i64 = sys_mmap(64 * 8) as *i64
269 let gmeta: *i64 = sys_mmap(32) as *i64
270 let shapes: i64 = nif_geom(good, good_n, good_n, vouts, touts, vflags, 64, gmeta)
271 gv_puts(" geom: shapes=" as *u8); gv_num(shapes)
272 gv_puts(" verified=" as *u8); gv_num(gmeta[0])
273 gv_puts(" verts=" as *u8); gv_num(gmeta[1])
274 gv_puts(" tris=" as *u8); gv_num(gmeta[2])
275 gv_puts(" flags=" as *u8); gv_num(vflags[0]); gv_puts("," as *u8); gv_num(vflags[1]); gv_puts("\n" as *u8)
276 var t5g: i64 = 0
277 if shapes == 2 { if gmeta[0] == 1 { if gmeta[1] == NG_GEO_VERTS { if gmeta[2] == NG_GEO_TRIS {
278 if vflags[0] == 1 { if vflags[1] == 0 { t5g = 1 } } } } } }
279 gv_check("T5g nif_geom verifies the true tuple by the nif.xml formula (2 verts, 1 tri) and counts the undersized sibling UNVERIFIED" as *u8, t5g, ctr)
280
281 // ---- T5v THE VERTEX LEG: positions decode to exact permil and land inside the block's OWN bound
282 let pxs: *i64 = sys_mmap(64 * 8) as *i64
283 let pys: *i64 = sys_mmap(64 * 8) as *i64
284 let pzs: *i64 = sys_mmap(64 * 8) as *i64
285 let vmeta: *i64 = sys_mmap(32) as *i64
286 let nv: i64 = nif_verts(good, good_n, good_n, 0, pxs, pys, pzs, 64, vmeta)
287 gv_puts(" verts: n=" as *u8); gv_num(nv)
288 gv_puts(" inside=" as *u8); gv_num(vmeta[1])
289 gv_puts(" radius=" as *u8); gv_num(vmeta[2])
290 gv_puts(" v0=" as *u8); gv_num(pxs[0]); gv_puts("," as *u8); gv_num(pys[0]); gv_puts("," as *u8); gv_num(pzs[0])
291 gv_puts(" v1=" as *u8); gv_num(pxs[1]); gv_puts("," as *u8); gv_num(pys[1]); gv_puts("," as *u8); gv_num(pzs[1]); gv_puts("\n" as *u8)
292 var t5v: i64 = 0
293 if nv == 2 { if pxs[0] == 1000 { if pys[0] == 0 { if pys[1] == 1500 { if pxs[1] == 0 {
294 if vmeta[1] == 2 { if vmeta[2] == 2000 { t5v = 1 } } } } } } }
295 gv_check("T5v vertex positions decode exactly (1.0 -> 1000, 1.5 -> 1500 permil) and both land inside the declared radius-2000 bound" as *u8, t5v, ctr)
296
297 // ---- T5w CONTAINMENT BITES: the SAME geometry under a shrunk bound must read OUTSIDE
298 let oobf: *u8 = sys_read_file(NG_F_OOB, sc)
299 let oobf_n: i64 = sc[0]
300 let nv2: i64 = nif_verts(oobf, oobf_n, oobf_n, 0, pxs, pys, pzs, 64, vmeta)
301 var contain_good: i64 = 0
302 if nv == 2 { contain_good = 1 }
303 var contain_oob_fires: i64 = 0
304 if nv2 == 2 { if vmeta[1] > 0 { contain_oob_fires = 1 } }
305 gv_puts(" oob: n=" as *u8); gv_num(nv2)
306 gv_puts(" inside=" as *u8); gv_num(vmeta[1]); gv_puts("\n" as *u8)
307 gv_bite("T5w the bound containment discriminates: identical verts read inside at radius 2.0 and OUTSIDE at radius 0.5" as *u8, contain_good, contain_oob_fires, ctr)
308
309 // ---- T5h the FO76-layout header (bsver 155: extra u32, no Process Script, Max Filepath) parses
310 let b155: *u8 = sys_read_file(NG_F_BS155, sc)
311 let b155_n: i64 = sc[0]
312 nif_probe(b155, b155_n, b155_n, facts)
313 var t5h: i64 = 0
314 if facts[0] == 1 { if facts[2] == 155 { if facts[3] == 1 { if facts[8] + facts[6] == b155_n { t5h = 1 } } } }
315 gv_check("T5h the bsver-155 header variant parses: state 1, 1 block, and its partition sums too" as *u8, t5h, ctr)
316
317 // ---- T6 an unsupported version is RECOGNIZED-UNSUPPORTED (2), never misparsed into counts
318 let old: *u8 = sys_read_file(NG_F_OLDVER, sc)
319 let old_n: i64 = sc[0]
320 nif_probe(old, old_n, old_n, facts)
321 var t6: i64 = 0
322 if facts[0] == 2 { if facts[3] == (0 - 1) { if facts[1] == NG_VER_OLD { t6 = 1 } } }
323 gv_check("T6 neg-control-an-unsupported-version-reads-state-2-with-counts-abstained-and-the-version-still-reported" as *u8, t6, ctr)
324
325 // ---- T7 an absurd bound is a named abstention, not a crash or a confident garbage census
326 let absf: *u8 = sys_read_file(NG_F_ABSURD, sc)
327 let absf_n: i64 = sc[0]
328 nif_probe(absf, absf_n, absf_n, facts)
329 var t7: i64 = 0
330 if facts[0] == 2 { if facts[3] == (0 - 1) { t7 = 1 } }
331 gv_check("T7 neg-control-a-65535-entry-type-table-refuses-into-state-2-abstention (bounds, not guesses)" as *u8, t7, ctr)
332
333 // ---- T8 truncation honesty: a partial read of a real container abstains
334 nif_probe(good, 60, good_n, facts)
335 var t8: i64 = 0
336 if facts[0] == 2 { if facts[3] == (0 - 1) { t8 = 1 } }
337 gv_check("T8 a truncated read is RECOGNIZED-UNSUPPORTED with every count abstained, never a small number" as *u8, t8, ctr)
338
339 // ---- T9 empty refused
340 let emp: *u8 = sys_read_file(NG_F_EMPTY, sc)
341 let emp_n: i64 = sc[0]
342 nif_probe(emp, emp_n, emp_n, facts)
343 var t9: i64 = 0
344 if facts[0] == 0 { t9 = 1 }
345 gv_check("T9 neg-control-a-zero-byte-file-is-not-a-nif" as *u8, t9, ctr)
346
347 return gv_verdict("NIFFACTS" as *u8, ctr,
348 "the Bethesda mesh container censuses exactly on the modern layout incl. the bsver-155 header, self-verifies TriShape geometry AND vertex positions by the file's own declared oracles, and abstains BY NAME on anything it cannot parse" as *u8)
349}