code wiki / _hdl_build / nx_wasm_diablo_gate.nx

nx_wasm_diablo_gate.nx source

↩ module page · 127 lines · 8472 B

1import "nx_gate_gn.nx" 2import "nx_gate_base.nx" 3// nx_wasm_diablo_gate.nx -- NATIVE gate for the playable isometric ARPG (nx_wasm_diablo). Drives click()/tick() 4// the way the browser shim will, and verifies the game + the Diablo-generation render BEFORE lowering to wasm. 5// T1 MOVE: a click sets a destination; the hero walks toward it. 6// T2 COMBAT: chasing a foe and striking it kills it and pays gold. 7// T3 LOOT: walking onto the gold collects it. 8// T4 RENDER + LIGHTING: the torch follows the hero (hero lit, far corner in gloom). PNG. 9// expect_exit: 0 license_tier: ORIGINAL 10import "nx_wasm_diablo.nx" 11import "nx_png.nx" 12import "nx_syscalls.nx" 13import "nx_gate_verdict.nx" 14 15func grow(name: *u8, ok: i64) -> i64 { if ok==1 { gw(" PASS " as *u8) } else { gw(" FAIL " as *u8) } gw(name); gw(" 16" as *u8); return ok } 17func lum(c: i64) -> i64 { return (c & 255) + ((c>>8)&255) + ((c>>16)&255) } 18func red_over_green(c: i64) -> i64 { return (c & 255) - ((c>>8)&255) } 19 20// ★DERIVED FROM THE ACTUAL PALETTE, NOT PICKED. Read out of nx_wasm_diablo's own render: 21// dungeon floor gx_rgb(92+nz, 86+nz, 76+nz) -> red-over-green = 6 (nz shifts all three equally) 22// dungeon walls gx_rgb(106,98,88) / (94,88,80) -> red-over-green = 8 23// hero body gx_rgb(160,44,40) -> red-over-green = 116 24// hero skin gx_rgb(230,186,150) -> red-over-green = 44 25// foe body gx_rgb(70,120,64) -> red-over-green = -50 (green-dominant, not confusable) 26// So the stone is barely warm and the hero is strongly red. 24 sits clear above the stone's 8 and 27// comfortably below the hero's weakest part (skin, 44). The radial torch scales all three channels, 28// which scales the DIFFERENCE too -- that is why the margin is stated against the widest gap in the 29// palette rather than against one lighting condition. The neg-control below proves the stone really 30// does fall under it in the frame as rendered, so this is measured rather than asserted. 31const DBL_RED_MARGIN: i64 = 24 32// far enough sideways to clear the hero sprite, near enough to stay inside the torch radius (116) 33const DBL_FLOOR_DX: i64 = 40 34// ★READ OUT OF gx_sprite_humanoid, NOT GUESSED. With fy at the feet, that sprite lays its TORSO -- 35// the body-coloured part -- across fy-41 .. fy-13, so any offset in that band is on the hero. 36// 38 is deliberately near the SHOULDERS rather than mid-chest: this gate captures a frame RIGHT 37// AFTER A STRIKE, and render_impl paints the slash FX at ((hx+fx)/2, (hy+fy)/2 - 20) in near-white 38// (255,248,205 at alpha 215). A first cut sampled fy-28, landed under that flash, and measured 39// red-over-green 13 -- LESS than the 16 of the stone beside it, because white has no red dominance 40// at all. The tooth caught its own bad sample point by printing both numbers. 41const DBL_TORSO_DY: i64 = 38 42 43func main() -> i64 { 44 gw("=== nx_wasm_diablo_gate: playable isometric ARPG (Diablo generation), verified NATIVE ===\n" as *u8) 45 var pass: i64 = 0; var total: i64 = 0 46 let base: i64 = sys_mmap(4*1024*1024) as i64 47 let mx: *i64 = (base + O_MX) as *i64 48 let my: *i64 = (base + O_MY) as *i64 49 let ma: *i64 = (base + O_MA) as *i64 50 let st: *i64 = (base + O_ST) as *i64 51 52 // ---- T1: move ---- 53 init_impl(base) 54 let hx0: i64 = st[0] 55 click_impl(base, st[0] + 50, st[1]) 56 var t: i64=0; while t < 30 { tick_impl(base, 0); t=t+1 } 57 var t1ok: i64=1; if st[0] <= hx0 + 8 {t1ok=0} 58 total=total+1; if t1ok==1 { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) } 59 gw("T1 click-to-move: hero walked toward the destination (x " as *u8); gn(hx0); gw("->" as *u8); gn(st[0]); gw(")\n" as *u8) 60 61 // ---- T2: combat ---- 62 init_impl(base) 63 let g0: i64 = st[4] 64 t=0; while t < 500 { if ma[0] == 0 { t = 500 } else { click_impl(base, mx[0], my[0]); tick_impl(base, 0); t=t+1 } } 65 var t2ok: i64=1; if ma[0] != 0 {t2ok=0} if st[4] < g0 + 5 {t2ok=0} 66 total=total+1; if t2ok==1 { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) } 67 gw("T2 combat: chased + slew a foe (alive=" as *u8); gn(ma[0]); gw(", gold+=" as *u8); gn(st[4]-g0); gw(")\n" as *u8) 68 69 // ---- T3: loot ---- 70 init_impl(base) 71 t=0; while t < 500 { if st[10] == 1 { t = 500 } else { click_impl(base, st[8], st[9]); tick_impl(base, 0); t=t+1 } } 72 var t3ok: i64=1; if st[10] != 1 {t3ok=0} 73 total=total+1; if t3ok==1 { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) } 74 gw("T3 loot: walked onto the gold and grabbed it (taken=" as *u8); gn(st[10]); gw(", gold=" as *u8); gn(st[4]); gw(")\n" as *u8) 75 76 // ---- render + lighting (capture a combat frame right after a strike: walk pose + attack FX visible) ---- 77 init_impl(base) 78 t=0; while t < 300 { click_impl(base, mx[2], my[2]); tick_impl(base, 0); if st[11] > 3 { t = 300 } else { t = t + 1 } } 79 render_impl(base) 80 let fb: *i64 = (base + O_FB) as *i64 81 write_png(fb, W, H, "knowledge/nx_wasm_diablo_native.png" as *u8) 82 gw(" (live evidence: PNG knowledge/nx_wasm_diablo_native.png -- iso dungeon, hero, foes, torch-follow lighting)\n" as *u8) 83 84 // ---- T4: render + lighting ---- 85 // ⚠SAMPLED ABOVE THE ATTACK FX. This tooth claims the TORCH lit the hero, but render_impl paints 86 // the slash flash AFTER lighting "so they pop", so a pixel under it is bright because of the FX 87 // and not because of the torch -- which would make this tooth pass with the torch removed. Using 88 // the shoulder offset puts it on torch-lit hero only. (Found while fixing T5, which measured the 89 // same contaminated pixel and said so out loud.) 90 let hero: i64 = lum(fb[(st[1]-DBL_TORSO_DY)*W + st[0]]) 91 let corner: i64 = lum(fb[(H-10)*W + 8]) 92 var t4ok: i64=1; if hero <= corner + 100 {t4ok=0} 93 total=total+1; if t4ok==1 { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) } 94 gw("T4 render+lighting: hero lit by the torch (lum=" as *u8); gn(hero); gw(") vs far gloom (lum=" as *u8); gn(corner); gw(")\n" as *u8) 95 96 // ---- T5/T6: THE HERO IS ACTUALLY IN THE PICTURE ---- 97 // ★T4 ABOVE TESTS LIGHTING, NOT PRESENCE, AND CANNOT BE MADE TO DO BOTH. The torch FOLLOWS the 98 // hero, so the floor where he stands is the brightest part of the frame whether or not he is 99 // drawn -- delete the hero sprite and T4 very likely still passes on lit stone. Found 2026-08-14 100 // by nx_vacuity_census; same class as five sibling game gates fixed the same day. 101 // A brightness difference cannot separate hero from lit floor here, so this uses COLOUR instead: 102 // the hero is strongly red-dominant and the stone is barely warm (see DBL_RED_MARGIN above). 103 let heropx: i64 = fb[(st[1]-DBL_TORSO_DY)*W + st[0]] 104 let floorpx: i64 = fb[(st[1]-22)*W + (st[0] + DBL_FLOOR_DX)] 105 total=total+1 106 if red_over_green(heropx) > DBL_RED_MARGIN { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) } 107 gw("T5 hero in frame: the hero cell is red-dominant like his body, not stone (r-g=" as *u8); gn(red_over_green(heropx)); gw(" need >" as *u8); gn(DBL_RED_MARGIN); gw(")\n" as *u8) 108 109 // neg-control: LIT STONE beside him must NOT satisfy the same predicate. Without this, T5 would 110 // be an unproven claim about the palette rather than a measurement of this frame. 111 total=total+1 112 if red_over_green(floorpx) <= DBL_RED_MARGIN { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) } 113 gw("T6 neg-control-lit-stone-is-not-red: floor beside the hero, under the same torch, fails T5's test (r-g=" as *u8); gn(red_over_green(floorpx)); gw(")\n" as *u8) 114 115 gw("\n=== nx_wasm_diablo_gate " as *u8); gn(pass); gw("/" as *u8); gn(total) 116 // D001 ANCHOR: the rich line above stays as this gate's public signature; the canonical verdict 117 // goes LAST because the estate's judge anchors by POSITION, taking only the final line. 118 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 119 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 120 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 121 let ctr__dry: *i64 = gv_ctr() 122 ctr__dry[0] = pass 123 ctr__dry[1] = total 124 let rc__dry: i64 = gv_verdict("WASM-DIABLO-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 125 sys_exit(rc__dry) 126 return rc__dry 127}