code wiki / _hdl_build / nx_wire_nethack_gate.nx

nx_wire_nethack_gate.nx source

↩ module page · 1051 lines · 43733 B

1// nx_wire_nethack_gate.nx -- TITLE WIRING #1: NetHack (roguelike), the first nx_gamebench title wired 2// through the certified parts. Until now every gamebench readiness number was SELF-GRADED (titles_wired=0): 3// the parts exist, but nothing proved they COMPOSE into this title's genre loop. This gate is that proof -- 4// an actual playable roguelike loop (procgen dungeon -> descend -> fight -> loot rations -> level up -> 5// save/resume) built ONLY from certified parts, with every exercised capability measured by a tooth. 6// 7// CAPS EXERCISED (bit = nx_gamebench capability index, set ONLY if its tooth passes; the mask is written 8// into the wiring artifact payload[0] so the board's declared mask can be cross-checked by nx_gamebench_gate): 9// bit 3 procgen-world (deterministic carved dungeon, seed-diverse, stairs reachable) 10// bit 7 pathfinding-ai (nx_pathfind pf_astar drives the player every move turn) 11// bit 6 entity-component-sim (nx_entity_store holds monsters; stale handle of a killed monster refused) 12// bit 9 rpg-stats-progression(nx_rpgstats XP curve/level inverse/derived HP drive combat + level-ups) 13// bit 11 inventory-crafting (rations picked up, 2 rations CRAFT a feast, ledger conserves exactly) 14// bit 15 save-load-persistence(nx_gamesave mid-run save -> load into ZEROED state -> resume TRANSPARENT) 15// bit 18 ui-menus-hud (nx_gamehud: the eat decision goes THROUGH a modal menu -- open/wrap/select 16// CAUSES the eat, it is not painted after the fact; HUD slots bind live 17// hp/lvl/depth/kills/food per turn, render-chain deterministic across runs) 18// NOT exercised (title requires, this loop does not prove -> the board must down-score them for NetHack): 19// bit 14 text-render-typography (the HUD blits glyphs, but typography = layout/wrap/paragraphs; honest no-claim). 20// 21// FLAGSHIP PROPERTY (mutation target): save/load TRANSPARENCY -- play N..M continuously vs play N, save, 22// load into a zeroed world, resume to M: EVERY per-turn checksum identical (the GX-9 test most engines 23// cannot run; it catches the whole "the save forgot a field" class). Non-vacuity guard: the resumed world 24// must be non-trivial (live monsters, xp earned, items picked) or the equality proves nothing. 25// license_tier: ORIGINAL expect_exit: 0 26import "nx_syscalls.nx" 27import "nx_gamesave.nx" 28import "nx_rpgstats.nx" 29import "nx_entity_store.nx" 30import "nx_pathfind.nx" 31import "nx_gamehud.nx" 32import "nx_wire_harness.nx" 33import "nx_gate_verdict.nx" 34import "nx_trimesh.nx" 35import "nx_objload.nx" 36 37// ---- world shape (data at the top, per rule 11) ---- 38const NH_W: i64 = 24 39const NH_H: i64 = 12 40const NH_DEPTH: i64 = 3 41const NH_NMON: i64 = 6 42const NH_NITEM: i64 = 5 43const NH_CAP: i64 = 16 // entity arena capacity 44const NH_NCOMP: i64 = 4 // components: 0=x 1=y 2=hp 3=xp_reward 45const NH_CARVE: i64 = 140 // carve steps per level 46const NH_AGGRO: i64 = 8 // monster chase radius (manhattan) 47const NH_MAXT: i64 = 600 48// rpg tuning (feeds the data-driven nx_rpgstats curves) 49const NH_XB: i64 = 20 // xp curve base 50const NH_XQ: i64 = 10 // xp curve quad 51const NH_HPB: i64 = 10 52const NH_HPC: i64 = 2 53const NH_HPL: i64 = 3 54const NH_CON: i64 = 14 55const NH_BITE: i64 = 2 56const NH_MHPB: i64 = 12 // monster base hp (tanky enough that the save point sees a LIVE world) 57const NH_HEAL_R: i64 = 10 // ration heal 58const NH_HEAL_F: i64 = 25 // feast heal (crafted from 2 rations -> crafting is worth it) 59// save shape 60const NH_HDRN: i64 = 26 // game header fields serialized 61const NH_NSV: i64 = 106 // 26 hdr + 1 nmon + 16*4 monsters + 5*3 items 62const NH_SCHEMA: i64 = 7307 63const NH_ART_SCHEMA: i64 = 7317 // the wiring-evidence artifact schema 64// transparency test window (the full run wins at ~turn 62; the window must sit fully before the win) 65const NH_T1W: i64 = 20 66const NH_T2W: i64 = 45 67// ---- game arena offsets (i64 words; grid 288, scratch blocks padded to 296) ---- 68const NG_O_GRID: i64 = 32 69const NG_O_ITEM: i64 = 320 70const NG_O_ENT: i64 = 336 // en_words(16,4) = 156, padded to 160 71const NG_O_PATH: i64 = 496 72const NG_O_G: i64 = 792 73const NG_O_F: i64 = 1088 74const NG_O_CAME: i64 = 1384 75const NG_O_OPENF: i64 = 1680 76const NG_O_CLOSED: i64 = 1976 77const NG_O_CARVE: i64 = 2272 78const NG_O_HUD: i64 = 2576 // nx_gamehud arena (GH_WORDS=64) APPENDED -- UI state rides the game 79 // arena but stays OUT of the save (transient UI, re-init on restore; 80 // the PART's own gate proves hud state CAN persist -- the TITLE keeps 81 // it ephemeral, which is what real roguelikes do with menu state) 82const NG_WORDS: i64 = 2640 83const NH_HUDINK: i64 = 50 // min ink px for a non-vacuous HUD frame (T10 guard) 84// header field indices inside the game arena 85const F_WS: i64 = 0 86const F_DEPTH: i64 = 1 87const F_TURN: i64 = 2 88const F_PX: i64 = 3 89const F_PY: i64 = 4 90const F_XP: i64 = 5 91const F_LVL: i64 = 6 92const F_HP: i64 = 7 93const F_KILLS: i64 = 8 94const F_RATION: i64 = 9 95const F_FEAST: i64 = 10 96const F_PICKED: i64 = 11 97const F_USED: i64 = 12 98const F_WIN: i64 = 13 99const F_COMB: i64 = 14 100const F_EATR: i64 = 15 101const F_EATF: i64 = 16 102const F_PSTEPS: i64 = 17 103const F_ATT: i64 = 18 104const F_BITES: i64 = 19 105const F_SPAWNED: i64 = 20 106const F_SX: i64 = 21 107const F_SY: i64 = 22 108const F_NCARVE: i64 = 23 109const F_GENRNG: i64 = 24 110const F_ABAND: i64 = 25 // monsters left alive on a level when the player descended 111 112func ww(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 113func wn(v: i64) -> i64 { 114 if v==0 { sys_write(1,"0" as *u8,1); return 0 } 115 var m: i64=v; if m<0 { sys_write(1,"-" as *u8,1); m=0-m } 116 let t: *u8=sys_mmap(32); var k: i64=0 117 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 118 let o: *u8=sys_mmap(32); var q: i64=k-1; var i: i64=0 119 while q>=0 { o[i]=t[q]; i=i+1; q=q-1 } 120 sys_write(1,o,i); return 0 121} 122func nbit(i: i64) -> i64 { var v: i64=1; var k: i64=0; while k<i { v=v*2; k=k+1 } return v } 123func nrng(g: *i64) -> i64 { 124 var x: i64 = g[F_GENRNG] 125 x = x ^ (x << 13) 126 x = x ^ (x >> 7) 127 x = x ^ (x << 17) 128 g[F_GENRNG] = x 129 if x < 0 { return 0 - x } 130 return x 131} 132func nabs(v: i64) -> i64 { if v<0 { return 0-v } return v } 133 134// ---- level structure: PURE function of (worldseed, depth). Restore regenerates it exactly. ---- 135func nh_genstruct(g: *i64) -> i64 { 136 g[F_GENRNG] = g[F_WS]*1000 + g[F_DEPTH]*7919 137 if g[F_GENRNG]==0 { g[F_GENRNG]=1 } 138 let grid: *i64 = (g as i64 + NG_O_GRID*8) as *i64 139 let carve: *i64 = (g as i64 + NG_O_CARVE*8) as *i64 140 var i: i64=0 141 while i<NH_W*NH_H { grid[i]=1; i=i+1 } 142 var cx: i64=1 143 var cy: i64=1 144 grid[cy*NH_W+cx]=0 145 carve[0]=cy*NH_W+cx 146 var nc: i64=1 147 var steps: i64=0 148 while steps<NH_CARVE { 149 let d: i64 = nrng(g)%4 150 var tx: i64=cx 151 var ty: i64=cy 152 if d==0 { tx=cx+1 } 153 if d==1 { tx=cx-1 } 154 if d==2 { ty=cy+1 } 155 if d==3 { ty=cy-1 } 156 if tx>=1 { if tx<=NH_W-2 { if ty>=1 { if ty<=NH_H-2 { 157 cx=tx; cy=ty 158 if grid[cy*NH_W+cx]==1 { grid[cy*NH_W+cx]=0; carve[nc]=cy*NH_W+cx; nc=nc+1 } 159 } } } } 160 steps=steps+1 161 } 162 g[F_SX]=cx 163 g[F_SY]=cy 164 g[F_NCARVE]=nc 165 return nc 166} 167// place monsters + items on carved floor, reset player to the entrance (level entry only, never on restore) 168func nh_populate(g: *i64) -> i64 { 169 let carve: *i64 = (g as i64 + NG_O_CARVE*8) as *i64 170 let item: *i64 = (g as i64 + NG_O_ITEM*8) as *i64 171 let a: *i64 = (g as i64 + NG_O_ENT*8) as *i64 172 en_init(a, NH_CAP, NH_NCOMP) 173 let nc: i64 = g[F_NCARVE] 174 let d: i64 = g[F_DEPTH] 175 var m: i64=0 176 while m<NH_NMON { 177 var placed: i64=0 178 var guard: i64=0 179 while placed==0 { 180 guard=guard+1 181 if guard>200 { placed=1 } 182 let pick: i64 = 1 + (nrng(g) % (nc-1)) 183 let cell: i64 = carve[pick] 184 let mx: i64 = cell % NH_W 185 let my: i64 = cell / NH_W 186 // not on another monster 187 var clash: i64=0 188 var k: i64=0 189 while k<en_count(a) { 190 let h: i64 = en_nth(a,k) 191 let ox: i64 = en_get(a,h,0) 192 let oy: i64 = en_get(a,h,1) 193 if ox==mx { if oy==my { clash=1 } } 194 k=k+1 195 } 196 if clash==0 { if placed==0 { 197 let h2: i64 = en_spawn(a) 198 if h2!=EN_NULL { 199 en_set(a,h2,0,mx) 200 en_set(a,h2,1,my) 201 let mhp: i64 = NH_MHPB + d*2 202 en_set(a,h2,2,mhp) 203 let mxp: i64 = 15*d 204 en_set(a,h2,3,mxp) 205 g[F_SPAWNED]=g[F_SPAWNED]+1 206 placed=1 207 } 208 } } 209 } 210 m=m+1 211 } 212 var it: i64=0 213 while it<NH_NITEM { 214 var placed2: i64=0 215 var guard2: i64=0 216 while placed2==0 { 217 guard2=guard2+1 218 if guard2>200 { placed2=1 } 219 let pick2: i64 = 1 + (nrng(g) % (nc-1)) 220 let cell2: i64 = carve[pick2] 221 let ix: i64 = cell2 % NH_W 222 let iy: i64 = cell2 / NH_W 223 var clash2: i64=0 224 var q: i64=0 225 while q<it { if item[q*3]==ix { if item[q*3+1]==iy { clash2=1 } } q=q+1 } 226 // not on the stairs (items on stairs would be fine, keep them distinct for clarity) 227 if ix==g[F_SX] { if iy==g[F_SY] { clash2=1 } } 228 if clash2==0 { if placed2==0 { 229 item[it*3]=ix 230 item[it*3+1]=iy 231 item[it*3+2]=0 232 placed2=1 233 } } 234 } 235 it=it+1 236 } 237 g[F_PX]=1 238 g[F_PY]=1 239 return 0 240} 241func nh_new(g: *i64, ws: i64) -> i64 { 242 var i: i64=0 243 while i<32 { g[i]=0; i=i+1 } 244 g[F_WS]=ws 245 g[F_DEPTH]=1 246 let l1: i64 = rs_level_for_xp(0, NH_XB, NH_XQ) 247 g[F_LVL]=l1 248 let hp0: i64 = rs_derived_hp(NH_CON, l1, NH_HPB, NH_HPC, NH_HPL) 249 g[F_HP]=hp0 250 nh_genstruct(g) 251 nh_populate(g) 252 let hud: *i64 = (g as i64 + NG_O_HUD*8) as *i64 253 gh_init(hud, 5) 254 return 0 255} 256// serialize game-visible state into S[NH_NSV] (monsters via DENSE order so restore preserves iteration order) 257func nh_serialize(g: *i64, S: *i64) -> i64 { 258 var i: i64=0 259 while i<NH_HDRN { S[i]=g[i]; i=i+1 } 260 let a: *i64 = (g as i64 + NG_O_ENT*8) as *i64 261 let n: i64 = en_count(a) 262 S[NH_HDRN]=n 263 var k: i64=0 264 while k<NH_CAP*4 { S[NH_HDRN+1+k]=0; k=k+1 } 265 k=0 266 while k<n { 267 let h: i64 = en_nth(a,k) 268 let b: i64 = NH_HDRN+1+k*4 269 let v0: i64 = en_get(a,h,0) 270 let v1: i64 = en_get(a,h,1) 271 let v2: i64 = en_get(a,h,2) 272 let v3: i64 = en_get(a,h,3) 273 S[b]=v0 274 S[b+1]=v1 275 S[b+2]=v2 276 S[b+3]=v3 277 k=k+1 278 } 279 let item: *i64 = (g as i64 + NG_O_ITEM*8) as *i64 280 let ib: i64 = NH_HDRN+1+NH_CAP*4 281 var q: i64=0 282 while q<NH_NITEM*3 { S[ib+q]=item[q]; q=q+1 } 283 return NH_NSV 284} 285// restore a game from S into a (possibly ZEROED) arena: header, regenerated structure, monsters, items 286func nh_restore(g: *i64, S: *i64) -> i64 { 287 var i: i64=0 288 while i<32 { g[i]=0; i=i+1 } 289 g[F_WS]=S[F_WS] 290 g[F_DEPTH]=S[F_DEPTH] 291 nh_genstruct(g) // grid + stairs: pure function of (ws,depth), matches by determinism 292 // copy the FULL header AFTER genstruct: genstruct clobbers F_GENRNG (it re-runs the carve), and the 293 // saved value is the post-populate stream -- restoring it after keeps the checksum bit-exact. 294 i=0 295 while i<NH_HDRN { g[i]=S[i]; i=i+1 } 296 let a: *i64 = (g as i64 + NG_O_ENT*8) as *i64 297 en_init(a, NH_CAP, NH_NCOMP) 298 let n: i64 = S[NH_HDRN] 299 var k: i64=0 300 while k<n { 301 let b: i64 = NH_HDRN+1+k*4 302 let h: i64 = en_spawn(a) 303 en_set(a,h,0,S[b]) 304 en_set(a,h,1,S[b+1]) 305 en_set(a,h,2,S[b+2]) 306 en_set(a,h,3,S[b+3]) 307 k=k+1 308 } 309 let item: *i64 = (g as i64 + NG_O_ITEM*8) as *i64 310 let ib: i64 = NH_HDRN+1+NH_CAP*4 311 var q: i64=0 312 while q<NH_NITEM*3 { item[q]=S[ib+q]; q=q+1 } 313 let hud: *i64 = (g as i64 + NG_O_HUD*8) as *i64 314 gh_init(hud, 5) // UI state is transient: fresh menu machine on restore, game state untouched 315 return 0 316} 317func nh_ckv(ck: i64, v0: i64) -> i64 { 318 var v: i64 = v0 319 if v<0 { v = (0-v)*2+1 } else { v = v*2 } 320 return (ck*131 + v) & 0x7FFFFFFFFFFFFFFF 321} 322// order-sensitive rolling checksum of the FULL game-visible state. 323// DELIBERATELY INDEPENDENT of nh_serialize: it walks the live arena directly, so a serializer that 324// "forgets a field" produces a restore whose checksum diverges -- the T6 transparency tooth catches it. 325// (A ck routed THROUGH the serializer would inherit the same amnesia and prove nothing.) 326func nh_ck(g: *i64) -> i64 { 327 var ck: i64 = 1469598103 328 var i: i64=0 329 while i<NH_HDRN { ck = nh_ckv(ck, g[i]); i=i+1 } 330 let a: *i64 = (g as i64 + NG_O_ENT*8) as *i64 331 let n: i64 = en_count(a) 332 ck = nh_ckv(ck, n) 333 var k: i64=0 334 while k<n { 335 let h: i64 = en_nth(a,k) 336 var c: i64=0 337 while c<4 { 338 let v: i64 = en_get(a,h,c) 339 ck = nh_ckv(ck, v) 340 c=c+1 341 } 342 k=k+1 343 } 344 let item: *i64 = (g as i64 + NG_O_ITEM*8) as *i64 345 var q: i64=0 346 while q<NH_NITEM*3 { ck = nh_ckv(ck, item[q]); q=q+1 } 347 return ck 348} 349// is any OTHER monster on (x,y)? 350func nh_moncell(a: *i64, x: i64, y: i64, skip: i64) -> i64 { 351 var k: i64=0 352 while k<en_count(a) { 353 let h: i64 = en_nth(a,k) 354 if h!=skip { 355 let ox: i64 = en_get(a,h,0) 356 let oy: i64 = en_get(a,h,1) 357 if ox==x { if oy==y { return 1 } } 358 } 359 k=k+1 360 } 361 return 0 362} 363// one game turn. returns the handle of a monster KILLED this turn (0 if none) -- the stale-handle witness. 364func nh_tick(g: *i64) -> i64 { 365 if g[F_WIN]==1 { return 0 } 366 let grid: *i64 = (g as i64 + NG_O_GRID*8) as *i64 367 let a: *i64 = (g as i64 + NG_O_ENT*8) as *i64 368 let item: *i64 = (g as i64 + NG_O_ITEM*8) as *i64 369 var killed: i64 = 0 370 let px: i64 = g[F_PX] 371 let py: i64 = g[F_PY] 372 // ---- player action: attack an adjacent monster, else descend on stairs, else A* step toward stairs ---- 373 var adj: i64 = 0 374 var k: i64=0 375 while k<en_count(a) { 376 if adj==0 { 377 let h: i64 = en_nth(a,k) 378 let mx: i64 = en_get(a,h,0) 379 let my: i64 = en_get(a,h,1) 380 let dd: i64 = nabs(mx-px)+nabs(my-py) 381 if dd==1 { adj=h } 382 } 383 k=k+1 384 } 385 // DIALECT NOTE: no `else if` / `||` / `else` in this file from here on -- multiline else-branches 386 // DESYNC the nx_cc parser (found by THIS gate; minimal repro nx_wire_probe.nx aux2: the next func 387 // fuses into this one as an empty stub and the binary runs silent exit-0). The certified parts are 388 // mostly plain-if code; this file uses flag vars instead of else-chains. 389 var acted: i64=0 390 if adj!=0 { 391 acted=1 392 g[F_ATT]=g[F_ATT]+1 393 let dmg: i64 = 4 + g[F_LVL]*2 394 let mhp: i64 = en_get(a,adj,2) - dmg 395 if mhp<=0 { 396 let reward: i64 = en_get(a,adj,3) 397 en_destroy(a,adj) 398 killed=adj 399 g[F_KILLS]=g[F_KILLS]+1 400 g[F_XP]=g[F_XP]+reward 401 let nl: i64 = rs_level_for_xp(g[F_XP], NH_XB, NH_XQ) 402 if nl>g[F_LVL] { 403 g[F_LVL]=nl 404 let mh: i64 = rs_derived_hp(NH_CON, nl, NH_HPB, NH_HPC, NH_HPL) 405 g[F_HP]=mh 406 } 407 } 408 if mhp>0 { en_set(a,adj,2,mhp) } 409 } 410 if acted==0 { if px==g[F_SX] { if py==g[F_SY] { 411 acted=1 412 g[F_DEPTH]=g[F_DEPTH]+1 413 if g[F_DEPTH]>NH_DEPTH { g[F_WIN]=1 } 414 if g[F_DEPTH]<=NH_DEPTH { 415 let leftover: i64 = en_count(a) 416 g[F_ABAND]=g[F_ABAND]+leftover 417 nh_genstruct(g) 418 nh_populate(g) 419 } 420 } } } 421 if acted==0 { if g[F_WIN]==0 { 422 let pg: *i64 = (g as i64 + NG_O_G*8) as *i64 423 let pf: *i64 = (g as i64 + NG_O_F*8) as *i64 424 let pc: *i64 = (g as i64 + NG_O_CAME*8) as *i64 425 let po: *i64 = (g as i64 + NG_O_OPENF*8) as *i64 426 let pz: *i64 = (g as i64 + NG_O_CLOSED*8) as *i64 427 let pp: *i64 = (g as i64 + NG_O_PATH*8) as *i64 428 let plen: i64 = pf_astar(grid, NH_W, NH_H, px, py, g[F_SX], g[F_SY], pg, pf, pc, po, pz, pp) 429 if plen>=1 { 430 let nxt: i64 = pp[1] 431 let nx: i64 = nxt % NH_W 432 let ny: i64 = nxt / NH_W 433 // never step ONTO a monster (an adjacent one would have been attacked; this is belt+braces) 434 let occ: i64 = nh_moncell(a, nx, ny, 0) 435 if occ==0 { g[F_PX]=nx; g[F_PY]=ny; g[F_PSTEPS]=g[F_PSTEPS]+1 } 436 } 437 } } 438 // ---- pickup ---- 439 let qx: i64 = g[F_PX] 440 let qy: i64 = g[F_PY] 441 var q: i64=0 442 while q<NH_NITEM { 443 if item[q*3+2]==0 { if item[q*3]==qx { if item[q*3+1]==qy { 444 item[q*3+2]=1 445 g[F_RATION]=g[F_RATION]+1 446 g[F_PICKED]=g[F_PICKED]+1 447 } } } 448 q=q+1 449 } 450 // ---- craft: 2 rations -> 1 feast (one combine per turn) ---- 451 if g[F_RATION]>=2 { 452 g[F_RATION]=g[F_RATION]-2 453 g[F_FEAST]=g[F_FEAST]+1 454 g[F_COMB]=g[F_COMB]+1 455 } 456 // ---- eat when hurt, THROUGH THE MENU (bit 18): the selection CAUSES the eat. Feast-first policy 457 // is encoded as menu ORDER (feast listed first, cursor selects item 0 after a FULL WRAP -- the wrap 458 // exercises gh_menu_move's both-directions arithmetic on every single eat of every run). Same 459 // decisions as the old inline chain => game-state evolution and every banked checksum unchanged. ---- 460 let maxhp: i64 = rs_derived_hp(NH_CON, g[F_LVL], NH_HPB, NH_HPC, NH_HPL) 461 let hud: *i64 = (g as i64 + NG_O_HUD*8) as *i64 462 if g[F_HP] < maxhp { 463 var edible: i64=0 464 if g[F_FEAST]>0 { edible=edible+1 } 465 if g[F_RATION]>0 { edible=edible+1 } 466 if edible>0 { 467 gh_menu_open(hud, edible) 468 var slot: i64=0 469 if g[F_FEAST]>0 { gh_menu_item(hud, slot, 1, g[F_FEAST]); slot=slot+1 } 470 if g[F_RATION]>0 { gh_menu_item(hud, slot, 2, g[F_RATION]); slot=slot+1 } 471 let choice: i64 = wh_menu_wrapsel(hud, edible) // MIGRATED: shared variable-menu wrap+select 472 if choice==1 { 473 g[F_FEAST]=g[F_FEAST]-1 474 g[F_HP]=g[F_HP]+NH_HEAL_F 475 g[F_USED]=g[F_USED]+1 476 g[F_EATF]=g[F_EATF]+1 477 } 478 if choice==2 { 479 g[F_RATION]=g[F_RATION]-1 480 g[F_HP]=g[F_HP]+NH_HEAL_R 481 g[F_USED]=g[F_USED]+1 482 g[F_EATR]=g[F_EATR]+1 483 } 484 } 485 if g[F_HP]>maxhp { g[F_HP]=maxhp } 486 } 487 // ---- monsters: bite when adjacent, chase when in aggro range ---- 488 // ⚠nx_cc PARSER TRAP (found by THIS gate, minimal repro nx_wire_probe.nx aux2): a var/let declared 489 // DIRECTLY inside an `else if` branch DESYNCS the parser -- later funcs fuse into this one as empty 490 // stubs and the binary exits 0 silently. Workaround: hoist ALL decls above the if/else-if. 491 var mI: i64=0 492 var msx: i64=0 493 var msy: i64=0 494 var mtx: i64=0 495 var mty: i64=0 496 var moved: i64=0 497 var occ2: i64=0 498 var occ3: i64=0 499 let mtot: i64 = en_count(a) 500 while mI<mtot { 501 let h2: i64 = en_nth(a,mI) 502 let mx2: i64 = en_get(a,h2,0) 503 let my2: i64 = en_get(a,h2,1) 504 let da: i64 = nabs(mx2-g[F_PX]) 505 let db: i64 = nabs(my2-g[F_PY]) 506 let d2: i64 = da+db 507 if d2==1 { 508 g[F_HP]=g[F_HP]-NH_BITE 509 g[F_BITES]=g[F_BITES]+1 510 } 511 if d2>1 { if d2<=NH_AGGRO { 512 msx=0 513 if g[F_PX]>mx2 { msx=1 } 514 if g[F_PX]<mx2 { msx=0-1 } 515 moved=0 516 if msx!=0 { 517 mtx=mx2+msx 518 if grid[my2*NH_W+mtx]==0 { 519 occ2 = nh_moncell(a,mtx,my2,h2) 520 var onp: i64=0 521 if mtx==g[F_PX] { if my2==g[F_PY] { onp=1 } } 522 if occ2==0 { if onp==0 { en_set(a,h2,0,mtx); moved=1 } } 523 } 524 } 525 if moved==0 { 526 msy=0 527 if g[F_PY]>my2 { msy=1 } 528 if g[F_PY]<my2 { msy=0-1 } 529 if msy!=0 { 530 mty=my2+msy 531 if grid[mty*NH_W+mx2]==0 { 532 occ3 = nh_moncell(a,mx2,mty,h2) 533 var onp2: i64=0 534 if mx2==g[F_PX] { if mty==g[F_PY] { onp2=1 } } 535 if occ3==0 { if onp2==0 { en_set(a,h2,1,mty) } } 536 } 537 } 538 } 539 } } 540 mI=mI+1 541 } 542 g[F_TURN]=g[F_TURN]+1 543 return killed 544} 545// T10 instrument: replay a real game for `turns`, binding the HUD to live state and rendering every 546// turn; returns the rolling chain of frame checksums. out[0]=frames that CHANGED vs the previous 547// (a state-blind renderer scores 0), out[1]=final-frame ink px, out[2]=menu selections made in-loop. 548func nh_hudchain(seed: i64, turns: i64, out: *i64) -> i64 { 549 let g: *i64 = sys_mmap(NG_WORDS*8) as *i64 550 nh_new(g, seed) 551 let font: *u8 = font8x8_table() 552 let labels: *i64 = sys_mmap(8*8) as *i64 553 labels[0]="HP" as i64 554 labels[1]="LVL" as i64 555 labels[2]="DEPTH" as i64 556 labels[3]="KILLS" as i64 557 labels[4]="FOOD" as i64 558 let mlabels: *i64 = sys_mmap(8*8) as *i64 559 mlabels[0]="feast" as i64 560 mlabels[1]="ration" as i64 561 let fb: *u8 = sys_mmap(72000) 562 let hud: *i64 = (g as i64 + NG_O_HUD*8) as *i64 563 var chain: i64 = 1469598103 564 var prev: i64 = 0 565 var moved: i64 = 0 566 var t: i64 = 0 567 while t < turns { 568 nh_tick(g) 569 gh_slot_set(hud, 0, g[F_HP]) 570 gh_slot_set(hud, 1, g[F_LVL]) 571 gh_slot_set(hud, 2, g[F_DEPTH]) 572 gh_slot_set(hud, 3, g[F_KILLS]) 573 gh_slot_set(hud, 4, g[F_RATION]+g[F_FEAST]) 574 var z: i64=0 575 while z<72000 { fb[z]=0 as u8; z=z+1 } 576 gh_render(hud, fb, 200, 120, font, labels, mlabels) 577 let fk: i64 = gh_frame_ck(fb, 200, 120) 578 if t>0 { if fk!=prev { moved=moved+1 } } 579 prev=fk 580 chain = (chain*131 + (fk & 0xFFFFFFFF)) & 0x7FFFFFFFFFFFFFFF 581 t=t+1 582 } 583 out[0]=moved 584 out[1]=gh_ink(fb, 200, 120) 585 out[2]=hud[GH_F_NSEL] 586 return chain 587} 588// T11/T12 helpers. nh_decint writes v0 in decimal at b[at..], returns the new cursor. 589// nh_clearfb / nh_filled / nh_fbck operate on the i64 trimesh framebuffer (same shapes the 590// certified nx_bunny_gate uses on this exact part chain). 591func nh_decint(b: *u8, at: i64, v0: i64) -> i64 { 592 var a: i64 = at 593 var v: i64 = v0 594 if v < 0 { b[a]=45 as u8; a=a+1; v=0-v } 595 let t: *u8 = sys_mmap(24) 596 var k: i64 = 0 597 if v == 0 { t[0]=48 as u8; k=1 } 598 while v > 0 { t[k]=(48+(v%10)) as u8; v=v/10; k=k+1 } 599 var q: i64 = k-1 600 while q >= 0 { b[a]=t[q]; a=a+1; q=q-1 } 601 return a 602} 603func nh_clearfb(fb: *i64, n: i64, c: i64) -> i64 { var i: i64=0; while i<n { fb[i]=c; i=i+1 } return 0 } 604func nh_filled(fb: *i64, n: i64, bg: i64) -> i64 { var c: i64=0; var i: i64=0; while i<n { if fb[i]!=bg { c=c+1 } i=i+1 } return c } 605func nh_fbck(fb: *i64, n: i64) -> i64 { var ck: i64=1469598103; var i: i64=0; while i<n { ck = nh_ckv(ck, fb[i]); i=i+1 } return ck } 606// run until win or maxturns; returns first-killed handle (the stale witness) 607func nh_run_full(g: *i64) -> i64 { 608 var firstkill: i64=0 609 var go: i64=1 610 while go==1 { 611 if g[F_WIN]==1 { go=0 } 612 if g[F_TURN]>=NH_MAXT { go=0 } 613 if g[F_HP]<=0 { go=0 } 614 if go==1 { 615 let kh: i64 = nh_tick(g) 616 if kh!=0 { if firstkill==0 { firstkill=kh } } 617 } 618 } 619 return firstkill 620} 621 622func main() -> i64 { 623 ww("=== nx_wire_nethack_gate: does NetHack's genre loop actually run on the certified parts? ===\n\n") 624 var pass: i64 = 0 625 var checks: i64 = 0 626 var mask: i64 = 0 627 let SEED: i64 = 4242 628 let g1: *i64 = sys_mmap(NG_WORDS*8) as *i64 629 let g2: *i64 = sys_mmap(NG_WORDS*8) as *i64 630 let S: *i64 = sys_mmap(NH_NSV*8+64) as *i64 631 let S2: *i64 = sys_mmap(NH_NSV*8+64) as *i64 632 633 // ---------- T1 procgen-world (bit 3): deterministic, seed-diverse, stairs reachable ---------- 634 checks=checks+1 635 nh_new(g1, SEED) 636 nh_new(g2, SEED) 637 let cka: i64 = nh_ck(g1) 638 let ckb: i64 = nh_ck(g2) 639 nh_new(g2, SEED+1) 640 let grid1: *i64 = (g1 as i64 + NG_O_GRID*8) as *i64 641 let grid2: *i64 = (g2 as i64 + NG_O_GRID*8) as *i64 642 var diff: i64=0 643 var i: i64=0 644 while i<NH_W*NH_H { if grid1[i]!=grid2[i] { diff=diff+1 } i=i+1 } 645 let pg1: *i64 = (g1 as i64 + NG_O_G*8) as *i64 646 let pf1: *i64 = (g1 as i64 + NG_O_F*8) as *i64 647 let pc1: *i64 = (g1 as i64 + NG_O_CAME*8) as *i64 648 let po1: *i64 = (g1 as i64 + NG_O_OPENF*8) as *i64 649 let pz1: *i64 = (g1 as i64 + NG_O_CLOSED*8) as *i64 650 let pp1: *i64 = (g1 as i64 + NG_O_PATH*8) as *i64 651 let plen0: i64 = pf_astar(grid1, NH_W, NH_H, 1, 1, g1[F_SX], g1[F_SY], pg1, pf1, pc1, po1, pz1, pp1) 652 // every monster and item sits on a floor cell 653 let a1: *i64 = (g1 as i64 + NG_O_ENT*8) as *i64 654 let item1: *i64 = (g1 as i64 + NG_O_ITEM*8) as *i64 655 var badcell: i64=0 656 var k: i64=0 657 while k<en_count(a1) { 658 let h: i64 = en_nth(a1,k) 659 let mx: i64 = en_get(a1,h,0) 660 let my: i64 = en_get(a1,h,1) 661 if grid1[my*NH_W+mx]!=0 { badcell=badcell+1 } 662 k=k+1 663 } 664 k=0 665 while k<NH_NITEM { 666 let ix: i64=item1[k*3] 667 let iy: i64=item1[k*3+1] 668 if grid1[iy*NH_W+ix]!=0 { badcell=badcell+1 } 669 k=k+1 670 } 671 var t1: i64=0 672 if cka==ckb { if diff>=30 { if plen0>=1 { if badcell==0 { 673 ww("T1 GREEN procgen: same seed identical (ck "); wn(cka) 674 ww("), seed+1 differs in "); wn(diff); ww(" cells, stairs reachable (path "); wn(plen0) 675 ww("), 0 off-floor placements\n") 676 t1=1; pass=pass+1 677 mask=mask+nbit(3) 678 } } } } 679 if t1==0 { 680 ww("T1 RED procgen: ckA="); wn(cka); ww(" ckB="); wn(ckb) 681 ww(" diff="); wn(diff); ww(" plen="); wn(plen0); ww(" badcell="); wn(badcell); ww("\n") 682 } 683 684 // ---------- T2 pathfinding-ai (bit 7): the A* path is valid cell-by-cell ---------- 685 checks=checks+1 686 var pathok: i64=1 687 i=1 688 while i<=plen0 { 689 let c0: i64 = pp1[i-1] 690 let c1: i64 = pp1[i] 691 let x0: i64 = c0 % NH_W 692 let y0: i64 = c0 / NH_W 693 let x1: i64 = c1 % NH_W 694 let y1: i64 = c1 / NH_W 695 let ad: i64 = nabs(x1-x0)+nabs(y1-y0) 696 if ad!=1 { pathok=0 } 697 if grid1[c1]!=0 { pathok=0 } 698 i=i+1 699 } 700 var t2: i64=0 701 if plen0>=1 { if pathok==1 { 702 ww("T2 GREEN pathfinding: pf_astar path of "); wn(plen0) 703 ww(" steps is 4-adjacent floor cell-by-cell (the part drives every player move)\n") 704 t2=1; pass=pass+1 705 mask=mask+nbit(7) 706 } } 707 if t2==0 { ww("T2 RED pathfinding: plen="); wn(plen0); ww(" pathok="); wn(pathok); ww("\n") } 708 709 // ---------- full deterministic run (the actual game) ---------- 710 nh_new(g1, SEED) 711 let fk: i64 = nh_run_full(g1) 712 let a1b: *i64 = (g1 as i64 + NG_O_ENT*8) as *i64 713 let fck: i64 = nh_ck(g1) 714 let runwin: i64 = g1[F_WIN] 715 let runturn: i64 = g1[F_TURN] 716 let runkills: i64 = g1[F_KILLS] 717 let runxp: i64 = g1[F_XP] 718 let runlvl: i64 = g1[F_LVL] 719 ww(" [run] turns="); wn(g1[F_TURN]); ww(" depth="); wn(g1[F_DEPTH]); ww(" win="); wn(g1[F_WIN]) 720 ww(" kills="); wn(g1[F_KILLS]); ww(" xp="); wn(g1[F_XP]); ww(" level="); wn(g1[F_LVL]) 721 ww(" hp="); wn(g1[F_HP]); ww(" picked="); wn(g1[F_PICKED]); ww(" used="); wn(g1[F_USED]) 722 ww(" combines="); wn(g1[F_COMB]); ww(" psteps="); wn(g1[F_PSTEPS]); ww(" bites="); wn(g1[F_BITES]) 723 ww(" ck="); wn(fck); ww("\n") 724 725 // ---------- T3 entity-component-sim (bit 6): stale handle refused, census exact ---------- 726 checks=checks+1 727 var t3: i64=0 728 var stale: i64=0-1 729 if fk!=0 { stale = en_valid(a1b, fk) } 730 let alive: i64 = en_count(a1b) 731 let census: i64 = g1[F_SPAWNED] - g1[F_KILLS] - g1[F_ABAND] 732 if fk!=0 { if stale==0 { if alive==census { 733 ww("T3 GREEN entity store: first-killed monster's handle "); wn(fk) 734 ww(" is DETECTABLY STALE (en_valid=0); census exact: spawned "); wn(g1[F_SPAWNED]) 735 ww(" - kills "); wn(g1[F_KILLS]); ww(" - abandoned-on-descend "); wn(g1[F_ABAND]) 736 ww(" = alive "); wn(alive); ww("\n") 737 t3=1; pass=pass+1 738 mask=mask+nbit(6) 739 } } } 740 if t3==0 { 741 ww("T3 RED entity: firstkill="); wn(fk); ww(" stale-valid="); wn(stale) 742 ww(" alive="); wn(alive); ww(" census="); wn(census); ww("\n") 743 } 744 745 // ---------- T4 rpg-stats-progression (bit 9): curve honored, level inverse exact, hp derived ---------- 746 checks=checks+1 747 let wantlvl: i64 = rs_level_for_xp(g1[F_XP], NH_XB, NH_XQ) 748 let maxhp: i64 = rs_derived_hp(NH_CON, g1[F_LVL], NH_HPB, NH_HPC, NH_HPL) 749 let thresh: i64 = rs_xp_for_level(g1[F_LVL], NH_XB, NH_XQ) 750 var t4: i64=0 751 if g1[F_XP]>0 { if g1[F_LVL]>1 { if g1[F_LVL]==wantlvl { if g1[F_HP]<=maxhp { if g1[F_XP]>=thresh { 752 ww("T4 GREEN rpgstats: xp "); wn(g1[F_XP]); ww(" -> level "); wn(g1[F_LVL]) 753 ww(" == rs_level_for_xp EXACT (threshold "); wn(thresh); ww(" <= xp), hp "); wn(g1[F_HP]) 754 ww(" <= derived max "); wn(maxhp); ww("\n") 755 t4=1; pass=pass+1 756 mask=mask+nbit(9) 757 } } } } } 758 if t4==0 { 759 ww("T4 RED rpgstats: xp="); wn(g1[F_XP]); ww(" lvl="); wn(g1[F_LVL]) 760 ww(" want="); wn(wantlvl); ww(" hp="); wn(g1[F_HP]); ww(" maxhp="); wn(maxhp); ww("\n") 761 } 762 763 // ---------- T5 inventory-crafting (bit 11): the ledger conserves exactly ---------- 764 checks=checks+1 765 // picked rations = held + eaten + 2*crafted ; crafted feasts = held + eaten 766 let led1: i64 = g1[F_RATION] + g1[F_EATR] + 2*g1[F_COMB] 767 let led2: i64 = g1[F_FEAST] + g1[F_EATF] 768 var t5: i64=0 769 if g1[F_PICKED]>=3 { if g1[F_USED]>=1 { if g1[F_COMB]>=1 { 770 if g1[F_PICKED]==led1 { if g1[F_COMB]==led2 { 771 ww("T5 GREEN inventory-crafting: picked "); wn(g1[F_PICKED]); ww(" == held ") 772 wn(g1[F_RATION]); ww(" + eaten "); wn(g1[F_EATR]); ww(" + 2*crafted "); wn(g1[F_COMB]) 773 ww("; feasts "); wn(g1[F_COMB]); ww(" == held "); wn(g1[F_FEAST]); ww(" + eaten "); wn(g1[F_EATF]) 774 ww(" (conserved, "); wn(g1[F_USED]); ww(" consumed)\n") 775 t5=1; pass=pass+1 776 mask=mask+nbit(11) 777 } } 778 } } } 779 if t5==0 { 780 ww("T5 RED inventory: picked="); wn(g1[F_PICKED]); ww(" led1="); wn(led1) 781 ww(" comb="); wn(g1[F_COMB]); ww(" led2="); wn(led2); ww(" used="); wn(g1[F_USED]); ww("\n") 782 } 783 784 // ---------- T6 save-load TRANSPARENCY (bit 15, FLAGSHIP) ---------- 785 checks=checks+1 786 // run A: continuous 0..T2W, banking a checksum EVERY turn in the window T1W..T2W 787 let bank: *i64 = sys_mmap((NH_T2W-NH_T1W+2)*8) as *i64 788 nh_new(g1, SEED) 789 var t: i64=0 790 while t<NH_T1W { nh_tick(g1); t=t+1 } 791 // non-vacuity guard: the world at the save point must be NON-TRIVIAL 792 let g1a: *i64 = (g1 as i64 + NG_O_ENT*8) as *i64 793 let nv_alive: i64 = en_count(g1a) 794 let nv_xp: i64 = g1[F_XP] 795 let nv_picked: i64 = g1[F_PICKED] 796 let ckt1: i64 = nh_ck(g1) 797 bank[0]=ckt1 798 t=NH_T1W 799 while t<NH_T2W { nh_tick(g1); t=t+1; let c: i64 = nh_ck(g1); bank[t-NH_T1W]=c } 800 let contwin: i64 = g1[F_WIN] 801 // run B: fresh game to T1W, SAVE, load into a ZEROED arena, resume to T2W comparing every turn 802 nh_new(g2, SEED) 803 t=0 804 while t<NH_T1W { nh_tick(g2); t=t+1 } 805 nh_serialize(g2,S2) 806 let sr: i64 = gs_save("knowledge/nx_wire_nethack_mid.sav" as *u8, NH_SCHEMA, S2, NH_NSV, 20260720) 807 var zi: i64=0 808 while zi<NG_WORDS { g2[zi]=0; zi=zi+1 } // ZERO the whole arena: nothing survives but the file 809 let Sl: *i64 = sys_mmap(NH_NSV*8+64) as *i64 810 let lr: i64 = gs_load("knowledge/nx_wire_nethack_mid.sav" as *u8, Sl, NH_NSV, 0 as *i64) 811 nh_restore(g2, Sl) 812 let ckr: i64 = nh_ck(g2) 813 var mismatch: i64=0 814 if ckr!=bank[0] { mismatch=mismatch+1 } 815 t=NH_T1W 816 while t<NH_T2W { 817 nh_tick(g2) 818 t=t+1 819 let c2: i64 = nh_ck(g2) 820 if c2!=bank[t-NH_T1W] { mismatch=mismatch+1 } 821 } 822 // field-by-field final compare 823 nh_serialize(g1,S) 824 nh_serialize(g2,S2) 825 var fdiff: i64=0 826 i=0 827 while i<NH_NSV { if S[i]!=S2[i] { fdiff=fdiff+1 } i=i+1 } 828 var t6: i64=0 829 if sr>0 { if lr==NH_NSV { if mismatch==0 { if fdiff==0 { if nv_alive>=1 { if nv_xp>0 { if nv_picked>=1 { if contwin==0 { 830 ww("T6 GREEN save/load TRANSPARENT: save at turn "); wn(NH_T1W) 831 ww(" -> load into ZEROED arena -> resume to "); wn(NH_T2W) 832 ww(": all "); wn(NH_T2W-NH_T1W+1); ww(" per-turn checksums identical, 0/"); wn(NH_NSV) 833 ww(" state fields differ (non-trivial world: alive="); wn(nv_alive) 834 ww(" xp="); wn(nv_xp); ww(" picked="); wn(nv_picked); ww(")\n") 835 t6=1; pass=pass+1 836 mask=mask+nbit(15) 837 } } } } } } } } 838 if t6==0 { 839 ww("T6 RED transparency: save="); wn(sr); ww(" load="); wn(lr) 840 ww(" ck-mismatches="); wn(mismatch); ww(" field-diffs="); wn(fdiff) 841 ww(" alive="); wn(nv_alive); ww(" xp="); wn(nv_xp); ww(" picked="); wn(nv_picked) 842 ww(" contwin="); wn(contwin); ww("\n") 843 } 844 845 // ---------- T7 corruption refused, state untouched ---------- 846 checks=checks+1 847 let lenp: *i64 = sys_mmap(8) as *i64 848 lenp[0]=0 849 let raw: *u8 = sys_read_file("knowledge/nx_wire_nethack_mid.sav" as *u8, lenp) 850 var t7: i64=0 851 if (raw as i64)!=0 { 852 let rl: i64 = lenp[0] 853 let flip: i64 = 48+24 // a payload byte (past the 48B header) 854 raw[flip] = (raw[flip] as i64 ^ 255) as u8 855 let cfd: i64 = sys_openat_wr("knowledge/nx_wire_nethack_corrupt.sav" as *u8, 0x1a4) 856 if cfd>=0 { 857 sys_write(cfd, raw, rl) 858 sys_close(cfd) 859 let Sc: *i64 = sys_mmap(NH_NSV*8+64) as *i64 860 Sc[0]=777777 861 let cr: i64 = gs_load("knowledge/nx_wire_nethack_corrupt.sav" as *u8, Sc, NH_NSV, 0 as *i64) 862 if cr==(0-6) { if Sc[0]==777777 { t7=1 } } 863 if t7==1 { 864 ww("T7 GREEN corruption refused LOUD (rc=-6 CHECKSUM-CORRUPT) and caller state untouched\n") 865 pass=pass+1 866 } else { 867 ww("T7 RED corrupt load rc="); wn(cr); ww(" sentinel="); wn(Sc[0]); ww("\n") 868 } 869 } else { ww("T7 RED cannot write corrupt copy\n") } 870 } else { ww("T7 RED cannot read the mid-save back\n") } 871 872 // ---------- T8 determinism: two independent full runs, bit-identical ---------- 873 checks=checks+1 874 nh_new(g2, SEED) 875 nh_run_full(g2) 876 let fck2: i64 = nh_ck(g2) 877 var t8: i64=0 878 if fck==fck2 { if runwin==g2[F_WIN] { 879 ww("T8 GREEN deterministic: two independent full runs -> identical final checksum "); wn(fck) 880 ww(" at turn "); wn(g2[F_TURN]); ww("\n") 881 t8=1; pass=pass+1 882 } } 883 if t8==0 { ww("T8 RED run1 ck="); wn(fck); ww(" run2 ck="); wn(fck2); ww("\n") } 884 885 // ---------- T9 anti-vacuity: the LOOP is a real game that was actually played and WON ---------- 886 checks=checks+1 887 var t9: i64=0 888 if g2[F_WIN]==1 { if g2[F_TURN]>20 { if g2[F_TURN]<NH_MAXT { if g2[F_KILLS]>=4 { if g2[F_DEPTH]==NH_DEPTH+1 { if g2[F_HP]>0 { 889 ww("T9 GREEN the game was PLAYED and WON: "); wn(NH_DEPTH); ww(" dungeon levels cleared in ") 890 wn(g2[F_TURN]); ww(" turns, "); wn(g2[F_KILLS]); ww(" kills, survived at hp "); wn(g2[F_HP]); ww("\n") 891 t9=1; pass=pass+1 892 } } } } } } 893 if t9==0 { 894 ww("T9 RED loop hollow: win="); wn(g2[F_WIN]); ww(" turns="); wn(g2[F_TURN]) 895 ww(" kills="); wn(g2[F_KILLS]); ww(" depth="); wn(g2[F_DEPTH]); ww(" hp="); wn(g2[F_HP]); ww("\n") 896 } 897 898 // ---------- T10 ui-menus-hud (bit 18): menu CAUSES the eats; HUD binds live state per turn ---------- 899 checks=checks+1 900 let hud2: *i64 = (g2 as i64 + NG_O_HUD*8) as *i64 901 let menusel: i64 = hud2[GH_F_NSEL] 902 let o10a: *i64 = sys_mmap(4*8) as *i64 903 let o10b: *i64 = sys_mmap(4*8) as *i64 904 let ch10a: i64 = nh_hudchain(SEED, NH_T2W, o10a) 905 let ch10b: i64 = nh_hudchain(SEED, NH_T2W, o10b) 906 var t10: i64=0 907 if ch10a==ch10b { if o10a[0]>=2 { if o10a[1]>=NH_HUDINK { if menusel>=1 { if menusel==g2[F_USED] { 908 ww("T10 GREEN ui-menus-hud: "); wn(menusel); ww(" eats ALL caused by menu selections (nsel==used), HUD ") 909 ww("render chain deterministic over "); wn(NH_T2W); ww(" turns ("); wn(o10a[0]) 910 ww(" frame changes, "); wn(o10a[1]); ww(" ink px)\n") 911 t10=1; pass=pass+1 912 mask=mask+nbit(18) 913 } } } } } 914 if t10==0 { 915 ww("T10 RED ui-menus-hud: chA="); wn(ch10a); ww(" chB="); wn(ch10b) 916 ww(" changes="); wn(o10a[0]); ww(" ink="); wn(o10a[1]) 917 ww(" nsel="); wn(menusel); ww(" used="); wn(g2[F_USED]); ww("\n") 918 } 919 920 // ---------- T11 text-render-typography (bit 14): the status line is GLYPH TEXT bound to LIVE 921 // run state, with EXACT ink accounting DERIVED FROM THE FONT TABLE ITSELF (zero picked numbers: 922 // expected ink = sum of set bits over the exact glyphs drawn; cells disjoint at 9px pitch = 8px 923 // glyph + 1px gap, drawn inside 200x120 with no clipping, so equality is exact by construction). 924 // A stub that blits a cached bitmap or a constant blob cannot match the per-state glyph 925 // population, and a text renderer not bound to state cannot make the mid-state frame differ. 926 checks=checks+1 927 let font11: *u8 = font8x8_table() 928 let fb11: *u8 = sys_mmap(72000) 929 let fb11b: *u8 = sys_mmap(72000) 930 let fb11c: *u8 = sys_mmap(72000) 931 let s11: *u8 = sys_mmap(64) 932 var sl: i64 = 0 933 s11[sl]=84 as u8; sl=sl+1 934 sl = nh_decint(s11, sl, g2[F_TURN]) 935 s11[sl]=88 as u8; sl=sl+1 936 sl = nh_decint(s11, sl, g2[F_XP]) 937 s11[sl]=75 as u8; sl=sl+1 938 sl = nh_decint(s11, sl, g2[F_KILLS]) 939 s11[sl]=0 as u8 940 let s11c: *u8 = sys_mmap(64) 941 var slc: i64 = 0 942 s11c[slc]=84 as u8; slc=slc+1 943 slc = nh_decint(s11c, slc, NH_T1W) 944 s11c[slc]=88 as u8; slc=slc+1 945 slc = nh_decint(s11c, slc, nv_xp) 946 s11c[slc]=0 as u8 947 var exp11: i64 = 0 948 var ci11: i64 = 0 949 while s11[ci11]!=(0 as u8) { 950 let ch11: i64 = s11[ci11] & 0xff 951 let go11: i64 = (ch11-0x20)*8 952 var r11: i64 = 0 953 while r11<8 { 954 var b11: i64 = font11[go11+r11] & 0xff 955 while b11>0 { exp11 = exp11 + (b11%2); b11=b11/2 } 956 r11=r11+1 957 } 958 gh_glyph(fb11, 200, 120, font11, ch11, 4+ci11*9, 8) 959 gh_glyph(fb11b, 200, 120, font11, ch11, 4+ci11*9, 8) 960 ci11=ci11+1 961 } 962 var cc11: i64 = 0 963 while s11c[cc11]!=(0 as u8) { 964 gh_glyph(fb11c, 200, 120, font11, s11c[cc11] & 0xff, 4+cc11*9, 8) 965 cc11=cc11+1 966 } 967 let ink11: i64 = gh_ink(fb11, 200, 120) 968 let cka11: i64 = gh_frame_ck(fb11, 200, 120) 969 let ckb11: i64 = gh_frame_ck(fb11b, 200, 120) 970 let ckc11: i64 = gh_frame_ck(fb11c, 200, 120) 971 var t11: i64=0 972 if exp11>0 { if ink11==exp11 { if cka11==ckb11 { if ckc11!=cka11 { if sl>=6 { if slc>=4 { 973 ww("T11 GREEN text-render: status line "); ww(s11); ww(" drawn as "); wn(sl) 974 ww(" glyphs, ink "); wn(ink11); ww(" px == font-table-derived expectation EXACT, deterministic, ") 975 ww("and the mid-save state renders a DIFFERENT frame (state-bound, not a cached bitmap)\n") 976 t11=1; pass=pass+1 977 mask=mask+nbit(14) 978 } } } } } } 979 if t11==0 { 980 ww("T11 RED text-render: exp="); wn(exp11); ww(" ink="); wn(ink11) 981 ww(" ckA="); wn(cka11); ww(" ckB="); wn(ckb11); ww(" ckC="); wn(ckc11) 982 ww(" sl="); wn(sl); ww(" slc="); wn(slc); ww("\n") 983 } 984 985 // ---------- T12 asset-pipeline-import (bit 25): the WON game's victory screen imports and 986 // renders the standard-corpus mesh through the certified OBJ import chain (nx_objload -> 987 // nx_trimesh), the exact part nx_bunny_gate certifies. Provenance is proven by the EXACT 988 // banked Stanford canonical counts (35947 vertices / 69451 triangles); the scale/camera/floor 989 // values are nx_bunny_gate's own calibrated view, reused not re-picked (composition reuses the 990 // incumbent's calibration). Composition condition: this only renders on a WON run -- the way a 991 // real title imports display assets for its win screen. 992 checks=checks+1 993 let tw12: i64 = 300 994 let th12: i64 = 300 995 let npx12: i64 = tw12*th12 996 let bg12: i64 = 24 + 26*256 + 34*65536 997 let fb12: *i64 = sys_mmap(npx12*8) as *i64 998 let zb12: *i64 = sys_mmap(npx12*8) as *i64 999 tm_set_spec(0) 1000 tm_set_tex(0) 1001 let ok12: i64 = obj_load("knowledge/stdassets/bunny.obj" as *u8, 1300, 205+200*256+195*65536) 1002 let nv12: i64 = tm_nv() 1003 let nt12: i64 = tm_nt() 1004 nh_clearfb(fb12, npx12, bg12) 1005 trimesh_zclear(zb12, npx12) 1006 trimesh_render(fb12, zb12, tw12, th12, 1300, 0-250, 2300, 520, 2) 1007 let fil12: i64 = nh_filled(fb12, npx12, bg12) 1008 let rck12: i64 = nh_fbck(fb12, npx12) 1009 nh_clearfb(fb12, npx12, bg12) 1010 trimesh_zclear(zb12, npx12) 1011 trimesh_render(fb12, zb12, tw12, th12, 1300, 0-250, 2300, 520, 2) 1012 let rck12b: i64 = nh_fbck(fb12, npx12) 1013 var t12: i64=0 1014 if g2[F_WIN]==1 { if ok12==1 { if nv12==35947 { if nt12==69451 { if tm_ovf()==0 { if fil12>npx12/12 { if rck12==rck12b { 1015 ww("T12 GREEN asset-import: WON run's victory screen renders the imported standard mesh -- ") 1016 ww("EXACT canonical Stanford counts (35947/69451, provenance by the numbers), ") 1017 wn(fil12); ww(" px rasterized (floor npx/12 = the part's own gate calibration), deterministic\n") 1018 t12=1; pass=pass+1 1019 mask=mask+nbit(25) 1020 } } } } } } } 1021 if t12==0 { 1022 ww("T12 RED asset-import: win="); wn(g2[F_WIN]); ww(" ok="); wn(ok12) 1023 ww(" nv="); wn(nv12); ww(" nt="); wn(nt12); ww(" ovf="); wn(tm_ovf()) 1024 ww(" filled="); wn(fil12); ww(" ckA="); wn(rck12); ww(" ckB="); wn(rck12b); ww("\n") 1025 } 1026 1027 // ---------- wiring evidence artifact: measured mask + run summary (board cross-checks payload[0]) ---------- 1028 let art: *i64 = sys_mmap(16*8) as *i64 1029 art[0]=mask 1030 art[1]=fck 1031 art[2]=runturn 1032 art[3]=runkills 1033 art[4]=runxp 1034 art[5]=runlvl 1035 art[6]=NH_DEPTH 1036 art[7]=runwin 1037 let aw: i64 = gs_save("knowledge/nx_wire_nethack.sav" as *u8, NH_ART_SCHEMA, art, 8, 20260720) 1038 ww("\nwiring artifact knowledge/nx_wire_nethack.sav bytes="); wn(aw) 1039 ww(" exercised_mask="); wn(mask); ww(" (bits 3,6,7,9,11,14,15,18,25 when all their teeth pass)\n") 1040 1041 ww("\n=== nx_wire_nethack_gate "); wn(pass); ww("/"); wn(checks) 1042 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 1043 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 1044 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 1045 let ctr__dry: *i64 = gv_ctr() 1046 ctr__dry[0] = pass 1047 ctr__dry[1] = checks 1048 let rc__dry: i64 = gv_verdict("WIRE-NETHACK-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 1049 sys_exit(rc__dry) 1050 return rc__dry 1051}