code wiki / _hdl_build / nx_m2d_engine.nx

nx_m2d_engine.nx source

↩ module page · 682 lines · 31113 B

1// nx_m2d_engine.nx -- M2D "NISHI GLADE": the first TIER-1 game of the approved program plan 2// (2026-07-26, plans/temporal-meandering-moore.md rung 1) -- a top-down action-adventure in the 3// Steam-volume-floor class, COMPOSED ENTIRELY FROM CERTIFIED PARTS, zero re-authored game systems: 4// nx_worldpipe the staged six-parameter overworld (biomes/heights/surface colours) 5// nx_procgen the dungeon maze (pg_maze, flood-verified connectivity) 6// nx_pathfind enemy chase (pf_astar on the live maze grid) 7// nx_creature_battle combat resolution (typed moves, cb_damage/cb_best) 8// nx_rpgstats xp curve + derived hp (order-independent, saturating) 9// nx_loottable kill drops (weighted tiers, 0-dup law) 10// nx_wardrobe_state the adult invariant, FAIL-CLOSED BY CONSTRUCTION (age<18 refuse) 11// nx_input_abstract every policy drives the loop through SEMANTIC ACTIONS (device-independent) 12// nx_gamesave bit-exact mid-run save/load (transparency proof) 13// nx_game_raster the rendered frame; scored by 14// nx_game_critic the hardened visual critic (anti-noise, coherence-capped) 15// PROOF BATTERY (the M1/frontier standard -- a battery that cannot pass a hollow game): 16// P1 WINNABLE the cautious policy grinds, heals, then takes the warden -> WIN 17// P2 LOSABLE the reckless policy rushes the warden under-levelled -> DIES (real stakes) 18// P3 DETERMINISTIC two P1 replays produce bit-identical frame chains 19// P4 SAVE-TRANSPARENT save mid-run, load, continue -> same final chain as the unbroken run 20// P5 ADULT-GATE HOLDS the spring event tries to expose a gated zone: adult proceeds, a minor is 21// REFUSED with state bit-identical -- the invariant holds THROUGH the whole game loop 22// P6 DEPTH the two policies genuinely diverge (win vs death) AND each replays identically -- 23// outcome differences are STRATEGY, not luck (the hardened nx_game_depth principle) 24// P7 QUALITY the rendered frame clears the critic's TOY line; score published, never inflated. 25// Emits its frame as knowledge/nx_m2d_frame.nxfh (the ecosystem's text-hex format) so nx_frame_score 26// can re-grade it over MCP -- the same instrument that grades the reference titles. 27// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 28import "nx_syscalls.nx" 29import "nx_worldpipe.nx" 30import "nx_procgen.nx" 31import "nx_pathfind.nx" 32import "nx_creature_battle.nx" 33import "nx_rpgstats.nx" 34import "nx_loottable.nx" 35import "nx_wardrobe_state.nx" 36import "nx_input_abstract_part.nx" 37import "nx_gamesave.nx" 38import "nx_game_raster.nx" 39import "nx_game_critic.nx" 40import "nx_game_engine_lib.nx" // the NATIVE play surface: ANSI true-colour terminal render (bput_*) 41const MD_MAGIC_65536: i64 = 65536 42const MD_MAGIC_4096: i64 = 4096 43 44// ---- world geometry (tiles) ---- 45const MD_DW: i64 = 15 // dungeon maze width (odd, pg_maze contract) 46const MD_DH: i64 = 9 47const MD_SEED: i64 = 20260726 48const MD_WSCALE: i64 = 280 // world units per overworld tile fed to wp_* 49// ---- character ---- 50const MD_XP_BASE: i64 = 50 51const MD_XP_QUAD: i64 = 30 52const MD_CON: i64 = 10 53const MD_HPB: i64 = 20 54const MD_HPCON: i64 = 2 55const MD_HPLVL: i64 = 6 56// ---- serializable game-state layout (i64 words; rides nx_gamesave like every part) ---- 57const MD_F_PX: i64 = 0 58const MD_F_PY: i64 = 1 59const MD_F_HP: i64 = 2 60const MD_F_XP: i64 = 3 61const MD_F_POT: i64 = 4 // potions held 62const MD_F_SHARD: i64 = 5 // key shards (3 open the warden door) 63const MD_F_KILLS: i64 = 6 64const MD_F_TICK: i64 = 7 65const MD_F_CK: i64 = 8 // rolling frame chain 66const MD_F_OVER: i64 = 9 // 0 running, 1 WIN, 2 DEAD 67const MD_O_WARD: i64 = 10 // wardrobe vector [10..20) (WS_LEN=10) 68const MD_O_ENHP: i64 = 20 // enemy hp x4 (3 lurkers + warden) [20..24) 69const MD_O_ENX: i64 = 24 // enemy x x4 70const MD_O_ENY: i64 = 28 // enemy y x4 71const MD_NSV: i64 = 32 72const MD_SCHEMA: i64 = 2026 73const MD_SAVEAT: i64 = 1785100000 74const MD_CKM: i64 = 4611686018427387903 75const MD_TRBUF: i64 = 8192 76const MD_TRTICKS: i64 = 16 77const MD_PTYPE: i64 = 3 // player is GRASS: counters the Water warden (eff 4 out, 1 in) -- chart-read, not guessed 78const MD_TICKS: i64 = 96 79const MD_HEAL_AT: i64 = 40 // measured: entering the Fire-lurker fight at 32hp was lethal; heal FIRST 80const MD_SNARE_B: i64 = 4 // the den snare: (MD_SNARE_B - level) * MD_SNARE_L opening damage, floor 0. 81const MD_SNARE_L: i64 = 18 // measured: chase-intercept XP let a rusher shrug the old snare; level 4 = earned 82const MD_CK_PRIME: i64 = 16777619 83const MD_CK_SEED: i64 = 2166136261 84const MD_FAR: i64 = 1000000 // a LEVEL CHECK as a game rule -- rushing under-levelled is fatal by design 85 86func md_mix(ck: i64, v: i64) -> i64 { 87 var c: i64 = ck 88 c = (c + v + 1) % MD_CKM 89 c = (c * MD_CK_PRIME) % MD_CKM 90 return c 91} 92 93// fold the whole serializable state into the chain each tick -- ANY divergence surfaces 94func md_fold(s: *i64) -> i64 { 95 var c: i64 = s[MD_F_CK] 96 var i: i64 = 0 97 while i < MD_NSV { if i != MD_F_CK { c = md_mix(c, s[i]) } i = i + 1 } 98 s[MD_F_CK] = c 99 return c 100} 101 102func md_level(s: *i64) -> i64 { return rs_level_for_xp(s[MD_F_XP], MD_XP_BASE, MD_XP_QUAD) } 103func md_maxhp(s: *i64) -> i64 { return rs_derived_hp(MD_CON, md_level(s), MD_HPB, MD_HPCON, MD_HPLVL) } 104 105// enemy archetypes: 0..2 lurkers, 3 = the WARDEN (the boss). Typed for cb_eff coverage. 106func md_mkfoe(c: *i64, idx: i64, lvl: i64) -> i64 { 107 if idx == 3 { 108 cb_set(c, 2, lvl+3, 30 + lvl*8, 9 + lvl*2, 4, 5, "Glade Warden" as *u8) 109 cb_addmove(c, 2, 24, 90) 110 cb_addmove(c, 0, 8, 100) 111 return 0 112 } 113 cb_set(c, (idx % 3), lvl, 12 + lvl*4, 4 + lvl, 2, 4, "Lurker" as *u8) 114 cb_addmove(c, (idx % 3), 7, 95) 115 return 0 116} 117 118// deterministic battle: full rounds of best-move exchanges until one side drops. 119// Returns remaining player hp (<=0 = death). Damage flows through the certified cb_damage. 120func md_battle(s: *i64, foe: i64, brng: *i64) -> i64 { 121 let lvl: i64 = md_level(s) 122 let me: *i64 = sys_mmap(64*8) as *i64 123 cb_set(me, MD_PTYPE, lvl, s[MD_F_HP], 5 + lvl*2, 5 + lvl, 6, "Wanderer" as *u8) 124 cb_addmove(me, MD_PTYPE, 9, 95) 125 cb_addmove(me, 0, 6, 100) 126 let en: *i64 = sys_mmap(64*8) as *i64 127 md_mkfoe(en, foe, 1 + foe) 128 en[2] = s[MD_O_ENHP + foe] // live hp from state (word 2 = hp by cb_set layout) 129 if foe == 3 { 130 // the den snare: a level-checked opening strike. Data-driven boss rule, applied ONCE here. 131 var snare: i64 = (MD_SNARE_B - lvl) * MD_SNARE_L 132 if snare < 0 { snare = 0 } 133 me[2] = me[2] - snare 134 } 135 var guard: i64 = 0 136 while guard < 40 { 137 if me[2] > 0 { if en[2] > 0 { 138 let mv: i64 = cb_best(me, en) 139 let d1: i64 = cb_damage(me, en, me[8+mv*3], me[8+mv*3+1], brng) 140 en[2] = en[2] - d1 141 if en[2] > 0 { 142 let ev: i64 = cb_best(en, me) 143 let d2: i64 = cb_damage(en, me, en[8+ev*3], en[8+ev*3+1], brng) 144 me[2] = me[2] - d2 145 } 146 } } 147 guard = guard + 1 148 } 149 s[MD_O_ENHP + foe] = en[2] 150 if en[2] <= 0 { 151 s[MD_F_KILLS] = s[MD_F_KILLS] + 1 152 s[MD_F_XP] = rs_sadd(s[MD_F_XP], 40 + foe*30) 153 // certified drop: tier decides potion vs shard 154 let l1: *i64 = sys_mmap(8*8) as *i64 155 l1[0]=600; l1[1]=400 156 let r: i64 = lt_pick(l1, 2, (s[MD_F_CK] % 1000 + 1000) % 1000) 157 if r == 0 { s[MD_F_POT] = s[MD_F_POT] + 1 } 158 if r == 1 { s[MD_F_SHARD] = s[MD_F_SHARD] + 1 } 159 if foe == 3 { s[MD_F_OVER] = 1 } // the warden falls -> WIN 160 } 161 return me[2] 162} 163 164// ---- the ONE dispatch: semantic actions in, world ticks out. No caller sees a device. ---- 165// actions: PREV/NEXT = move along the dungeon path axis; CONFIRM = engage/advance; CANCEL = drink potion. 166func md_step(s: *i64, grid: *i64, act: i64, brng: *i64) -> i64 { 167 if s[MD_F_OVER] != 0 { return 0 } 168 var nx: i64 = s[MD_F_PX] 169 var ny: i64 = s[MD_F_PY] 170 if act == IA_A_NEXT { nx = nx + 1 } 171 if act == IA_A_PREV { nx = nx - 1 } 172 if act == IA_A_CONFIRM { ny = ny + 1 } 173 if act == IA_A_PAUSE { ny = ny - 1 } 174 if act == IA_A_CANCEL { 175 if s[MD_F_POT] > 0 { 176 // a potion restores to FULL (the heart-refill convention) -- measured: +14 left the 177 // cautious policy entering super-effective fights at lethal margins 178 s[MD_F_POT] = s[MD_F_POT] - 1 179 s[MD_F_HP] = md_maxhp(s) 180 } 181 } 182 if nx < 0 { nx = 0 } 183 if nx >= MD_DW { nx = MD_DW-1 } 184 if ny < 0 { ny = 0 } 185 if ny >= MD_DH { ny = MD_DH-1 } 186 if grid[ny*MD_DW + nx] == 0 { s[MD_F_PX] = nx; s[MD_F_PY] = ny } 187 // enemies chase one A* step; adjacency = battle (certified parts do the fighting) 188 let pg: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 189 let pf: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 190 let pc: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 191 let po: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 192 let pz: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 193 let pp: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 194 var e: i64 = 0 195 while e < 4 { 196 if s[MD_O_ENHP + e] > 0 { 197 // the warden holds its den until the door is open (3 shards) 198 var mobile: i64 = 1 199 if e == 3 { if s[MD_F_SHARD] < 3 { mobile = 0 } } 200 if mobile == 1 { 201 let plen: i64 = pf_astar(grid, MD_DW, MD_DH, 202 s[MD_O_ENX+e], s[MD_O_ENY+e], s[MD_F_PX], s[MD_F_PY], pg, pf, pc, po, pz, pp) 203 if plen > 1 { 204 s[MD_O_ENX+e] = pp[1] % MD_DW 205 s[MD_O_ENY+e] = pp[1] / MD_DW 206 } 207 } 208 let dx: i64 = s[MD_O_ENX+e] - s[MD_F_PX] 209 let dy: i64 = s[MD_O_ENY+e] - s[MD_F_PY] 210 var ad: i64 = dx 211 if ad < 0 { ad = 0-ad } 212 var bd: i64 = dy 213 if bd < 0 { bd = 0-bd } 214 if ad + bd <= 1 { 215 let left: i64 = md_battle(s, e, brng) 216 s[MD_F_HP] = left 217 if left <= 0 { s[MD_F_OVER] = 2 } 218 } 219 } 220 e = e + 1 221 } 222 s[MD_F_TICK] = s[MD_F_TICK] + 1 223 md_fold(s) 224 return 0 225} 226 227func md_init(s: *i64, grid: *i64, age: i64) -> i64 { 228 var i: i64 = 0 229 while i < MD_NSV { s[i]=0; i=i+1 } 230 s[MD_F_CK] = MD_CK_SEED 231 s[MD_F_HP] = rs_derived_hp(MD_CON, 1, MD_HPB, MD_HPCON, MD_HPLVL) 232 s[MD_F_POT] = 1 233 ws_init((s as i64 + MD_O_WARD*8) as *i64, age, 0) 234 // maze + deterministic spawns on open cells 235 let stack: *i64 = sys_mmap(MD_DW*MD_DH*2*8) as *i64 236 pg_maze(grid, MD_DW, MD_DH, MD_SEED, stack) 237 s[MD_F_PX] = 1; s[MD_F_PY] = 1 238 grid[1*MD_DW+1] = 0 239 // lurkers at spread open cells; warden in the far corner den 240 let want: *i64 = sys_mmap(8*8) as *i64 241 want[0]=MD_DW-2; want[1]=1; want[2]=1; want[3]=MD_DH-2; want[4]=MD_DW/2; want[5]=MD_DH/2 242 var e: i64 = 0 243 while e < 3 { 244 var ex: i64 = want[e*2] 245 var ey: i64 = want[e*2+1] 246 if grid[ey*MD_DW+ex] != 0 { grid[ey*MD_DW+ex] = 0 } 247 s[MD_O_ENX+e]=ex; s[MD_O_ENY+e]=ey 248 s[MD_O_ENHP+e] = 12 + (1+e)*4 249 e = e + 1 250 } 251 grid[(MD_DH-2)*MD_DW + (MD_DW-2)] = 0 252 s[MD_O_ENX+3]=MD_DW-2; s[MD_O_ENY+3]=MD_DH-2 253 s[MD_O_ENHP+3] = 30 + 4*8 254 md_fold(s) 255 return 0 256} 257 258// run a POLICY through the certified INPUT LAYER: the player NAVIGATES with the same certified 259// pf_astar the enemies use (the trace proved a wall-blind policy just gets cornered), and every 260// move still flows raw-code -> ia_feed -> semantic action -> md_step. Nothing sees a device. 261// policy 0 CAUTIOUS: hunt lurkers nearest-first, heal when hurt, take the warden levelled. 262// policy 1 RECKLESS: straight to the den under-levelled -- the snare and the warden end it. 263func md_target(s: *i64, policy: i64) -> i64 { 264 if policy == 1 { return 3 } 265 var best: i64 = 3 266 var bd: i64 = MD_FAR 267 var e: i64 = 0 268 while e < 3 { 269 if s[MD_O_ENHP+e] > 0 { 270 var dx: i64 = s[MD_O_ENX+e] - s[MD_F_PX] 271 if dx < 0 { dx = 0-dx } 272 var dy: i64 = s[MD_O_ENY+e] - s[MD_F_PY] 273 if dy < 0 { dy = 0-dy } 274 if dx+dy < bd { bd = dx+dy; best = e } 275 } 276 e = e + 1 277 } 278 return best 279} 280func md_run(s: *i64, grid: *i64, policy: i64, age: i64) -> i64 { 281 md_init(s, grid, age) 282 let ia: *i64 = sys_mmap(IA_NSV*8) as *i64 283 ia_init(ia, 5) 284 ia_bind(ia, IA_D_KEY, IA_A_PREV, 37); ia_bind(ia, IA_D_KEY, IA_A_NEXT, 39) 285 ia_bind(ia, IA_D_KEY, IA_A_CONFIRM, 40); ia_bind(ia, IA_D_KEY, IA_A_PAUSE, 38) 286 ia_bind(ia, IA_D_KEY, IA_A_CANCEL, 27) 287 let brng: *i64 = sys_mmap(8) as *i64 288 brng[0] = MD_SEED + policy 289 let pg: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 290 let pf: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 291 let pc: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 292 let po: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 293 let pz: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 294 let pp: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 295 var t: i64 = 0 296 while t < MD_TICKS { 297 if s[MD_F_OVER] == 0 { 298 var raw: i64 = 39 299 var chosen: i64 = 0 300 if policy == 0 { if s[MD_F_HP] < MD_HEAL_AT { if s[MD_F_POT] > 0 { raw = 27; chosen = 1 } } } 301 if chosen == 0 { 302 let tgt: i64 = md_target(s, policy) 303 let plen: i64 = pf_astar(grid, MD_DW, MD_DH, 304 s[MD_F_PX], s[MD_F_PY], s[MD_O_ENX+tgt], s[MD_O_ENY+tgt], pg, pf, pc, po, pz, pp) 305 if plen > 1 { 306 let nxp: i64 = pp[1] % MD_DW 307 let nyp: i64 = pp[1] / MD_DW 308 if nxp > s[MD_F_PX] { raw = 39 } 309 if nxp < s[MD_F_PX] { raw = 37 } 310 if nyp > s[MD_F_PY] { raw = 40 } 311 if nyp < s[MD_F_PY] { raw = 38 } 312 } 313 } 314 let act: i64 = ia_feed(ia, IA_D_KEY, raw) 315 if act >= 0 { md_step(s, grid, act, brng) } 316 } 317 t = t + 1 318 } 319 return s[MD_F_OVER] 320} 321 322// ---- render the frame: overworld strip (worldpipe surface colours) + dungeon panel + HUD ---- 323func md_render(s: *i64, grid: *i64, fb: *i64, W: i64, H: i64) -> i64 { 324 wp_init(MD_SEED) 325 // top strip: the REAL overworld -- per-pixel worldpipe surface colours (palette source) 326 var y: i64 = 0 327 while y < H/3 { 328 var x: i64 = 0 329 while x < W { 330 let wx: i64 = (x - W/2) * MD_WSCALE / 8 331 let wz: i64 = (y - H/6) * MD_WSCALE / 8 332 let hh: i64 = wp_height(wx, wz) 333 var sl: i64 = wp_height(wx+40, wz) - hh 334 if sl < 0 { sl = 0-sl } 335 gr_px(fb, W, H, x, y, wp_shade(wx, wz, hh, sl)) // F1158: the LIT overworld (palette from geometry) 336 x = x + 1 337 } 338 y = y + 1 339 } 340 // dungeon panel: walls/floor + actors 341 let cw: i64 = W / MD_DW 342 let chh: i64 = (H - H/3 - 20) / MD_DH 343 var gy: i64 = 0 344 while gy < MD_DH { 345 var gx: i64 = 0 346 while gx < MD_DW { 347 var c: i64 = gr_pack(34, 30, 44) 348 if grid[gy*MD_DW+gx] == 0 { c = gr_pack(78, 70, 96) } 349 gr_rect(fb, W, H, gx*cw, H/3 + gy*chh, gx*cw + cw - 1, H/3 + gy*chh + chh - 1, c) 350 gx = gx + 1 351 } 352 gy = gy + 1 353 } 354 var e: i64 = 0 355 while e < 4 { 356 if s[MD_O_ENHP+e] > 0 { 357 var ec: i64 = gr_pack(200, 80, 70) 358 if e == 3 { ec = gr_pack(230, 120, 40) } 359 gr_disc(fb, W, H, s[MD_O_ENX+e]*cw + cw/2, H/3 + s[MD_O_ENY+e]*chh + chh/2, chh/3, ec) 360 } 361 e = e + 1 362 } 363 gr_disc(fb, W, H, s[MD_F_PX]*cw + cw/2, H/3 + s[MD_F_PY]*chh + chh/2, chh/3, gr_pack(120, 200, 255)) 364 // hp meter strip 365 var mh: i64 = md_maxhp(s) 366 if mh < 1 { mh = 1 } 367 var fill: i64 = (s[MD_F_HP] * (W-20)) / mh 368 if fill < 0 { fill = 0 } 369 gr_rect(fb, W, H, 10, H-14, 10 + fill, H-6, gr_pack(90, 220, 120)) 370 return 0 371} 372 373func ocat(o: *u8, at: i64, s: *u8) -> i64 { var i: i64=0; var a: i64=at; while s[i]!=(0 as u8){o[a]=s[i]; a=a+1; i=i+1} return a } 374func onum(o: *u8, at: i64, v: i64) -> i64 { 375 var a: i64=at; var m: i64=v 376 if m==0 { o[a]=48 as u8; return a+1 } 377 if m<0 { o[a]=45 as u8; a=a+1; m=0-m } 378 let t: *u8 = sys_mmap(32); var k: i64=0 379 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 380 var q: i64=k-1 381 while q>=0 { o[a]=t[q]; a=a+1; q=q-1 } 382 return a 383} 384func hexd(v: i64) -> i64 { if v < 10 { return 48+v } return 87+v } 385 386// ---- the NATIVE SOVEREIGN PLAY SURFACE (operator directive 2026-07-26: NishiLang byte-up first; 387// NishiOS / Nishi Browser are the primary surfaces, JS is interoperability only). ANSI true-colour 388// terminal frames via the engine lib's own bput_* helpers -- no web stack anywhere in the loop. 389// Input still flows raw-key -> ia_feed -> semantic action: the device abstraction holds even here. 390// plain=1 emits pure printable glyphs (the MCP/tools/call route -- raw ESC bytes broke the JSON 391// transport, measured as 8x edge flake); plain=0 emits ANSI true-colour for a live terminal. 392func md_ansi_frame(s: *i64, grid: *i64, ob: *u8, off: *i64, plain: i64) -> i64 { 393 if plain == 0 { bput_clear(ob, off) } 394 bput(ob, off, "NISHI GLADE -- sovereign native play\x0a" as *u8) 395 var gy: i64 = 0 396 while gy < MD_DH { 397 var gx: i64 = 0 398 while gx < MD_DW { 399 var glyph: i64 = 46 400 var isact: i64 = 0 401 if s[MD_F_PX]==gx { if s[MD_F_PY]==gy { glyph=64; isact=1 402 if plain==0 { bput_fg(ob,off,120,220,255) } } } 403 var e: i64 = 0 404 while e < 4 { 405 if isact==0 { if s[MD_O_ENHP+e]>0 { if s[MD_O_ENX+e]==gx { if s[MD_O_ENY+e]==gy { 406 isact=1 407 if e==3 { glyph=87 408 if plain==0 { bput_fg(ob,off,255,170,60) } } 409 if e<3 { glyph=76 410 if plain==0 { bput_fg(ob,off,255,90,80) } } 411 } } } } 412 e = e + 1 413 } 414 if grid[gy*MD_DW+gx]!=0 { glyph=35 } 415 if plain == 0 { 416 if grid[gy*MD_DW+gx]==0 { bput_bg(ob,off,52,46,66) } 417 if grid[gy*MD_DW+gx]!=0 { bput_bg(ob,off,24,20,32) } 418 } 419 bputc(ob,off,glyph); bputc(ob,off,32) 420 if plain == 0 { bput_reset(ob,off) } 421 gx = gx + 1 422 } 423 bputc(ob,off,10) 424 gy = gy + 1 425 } 426 bput(ob, off, "HP " as *u8); bputn(ob, off, s[MD_F_HP]) 427 bput(ob, off, " LVL " as *u8); bputn(ob, off, md_level(s)) 428 bput(ob, off, " POT " as *u8); bputn(ob, off, s[MD_F_POT]) 429 bput(ob, off, " SHARDS " as *u8); bputn(ob, off, s[MD_F_SHARD]) 430 bput(ob, off, " KILLS " as *u8); bputn(ob, off, s[MD_F_KILLS]) 431 if s[MD_F_OVER]==1 { bput(ob, off, " ** THE WARDEN FALLS -- YOU WIN **" as *u8) } 432 if s[MD_F_OVER]==2 { bput(ob, off, " ** YOU COLLAPSE -- RUN ENDED **" as *u8) } 433 bputc(ob,off,10) 434 return 0 435} 436// a=west d=east w=north s=south p=potion q=quit; script from argv OR live keys from stdin 437func md_key_raw(ch: i64) -> i64 { 438 if ch == 97 { return 37 } 439 if ch == 100 { return 39 } 440 if ch == 119 { return 38 } 441 if ch == 115 { return 40 } 442 if ch == 112 { return 27 } 443 return 0 444} 445func md_play(grid: *i64, script: *u8) -> i64 { 446 let s: *i64 = sys_mmap(MD_NSV*8) as *i64 447 md_init(s, grid, 25) 448 let ia: *i64 = sys_mmap(IA_NSV*8) as *i64 449 ia_init(ia, 5) 450 ia_bind(ia, IA_D_KEY, IA_A_PREV, 37); ia_bind(ia, IA_D_KEY, IA_A_NEXT, 39) 451 ia_bind(ia, IA_D_KEY, IA_A_CONFIRM, 40); ia_bind(ia, IA_D_KEY, IA_A_PAUSE, 38) 452 ia_bind(ia, IA_D_KEY, IA_A_CANCEL, 27) 453 let brng: *i64 = sys_mmap(8) as *i64 454 brng[0] = MD_SEED 455 let ob: *u8 = sys_mmap(MD_TRBUF*4) 456 let off: *i64 = sys_mmap(8) as *i64 457 var plain: i64 = 0 458 if (script as i64) != 0 { plain = 1 } // scripted = MCP transport = printable glyphs only 459 off[0]=0 460 md_ansi_frame(s, grid, ob, off, plain) 461 sys_write(1, ob, off[0]) 462 var si: i64 = 0 463 var live: i64 = 1 464 let inb: *u8 = sys_mmap(8) 465 while live == 1 { 466 var ch: i64 = 0 467 if (script as i64) != 0 { 468 ch = script[si] as i64 469 si = si + 1 470 if ch == 0 { live = 0 } 471 } 472 if (script as i64) == 0 { 473 let rn: i64 = sys_read(0, inb, 1) 474 if rn < 1 { live = 0 } 475 if rn >= 1 { ch = inb[0] as i64 } 476 } 477 if ch == 113 { live = 0 } 478 if live == 1 { 479 let raw: i64 = md_key_raw(ch) 480 if raw != 0 { 481 let act: i64 = ia_feed(ia, IA_D_KEY, raw) 482 if act >= 0 { md_step(s, grid, act, brng) } 483 off[0]=0 484 md_ansi_frame(s, grid, ob, off, plain) 485 sys_write(1, ob, off[0]) 486 if s[MD_F_OVER] != 0 { live = 0 } 487 } 488 } 489 } 490 if s[MD_F_OVER] == 1 { return 0 } 491 return s[MD_F_OVER] 492} 493 494func main(argc: i64, argv: *i64) -> i64 { 495 let grid: *i64 = sys_mmap(MD_DW*MD_DH*8) as *i64 496 let s: *i64 = sys_mmap(MD_NSV*8) as *i64 497 498 // native play. `play <script>` (a/d/w/s/p) = scripted, MCP-safe, plain glyphs. 499 // `tty` = live stdin keys with ANSI colour -- TERMINAL ONLY: over tools/call an open stdin 500 // never EOFs, so a blocking read would hang the transport for its full timeout (measured). 501 // A tool must refuse loudly rather than block (the operator's refusal law). 502 if argc >= 2 { 503 let ap: *u8 = argv[1] as *u8 504 if ap[0]==(112 as u8) { 505 if argc < 3 { 506 let eu: *u8 = "{\x22organ\x22:\x22nx_m2d_engine\x22,\x22error\x22:\x22play needs a move script (a/d/w/s/p)\x22,\x22fix\x22:\x22use: play ddss... -- or run the ELF directly in a terminal with: tty\x22}\x0a" as *u8 507 var en: i64=0 508 while eu[en]!=(0 as u8) { en=en+1 } 509 sys_write(1, eu, en) 510 return 2 511 } 512 let stack0: *i64 = sys_mmap(MD_DW*MD_DH*2*8) as *i64 513 pg_maze(grid, MD_DW, MD_DH, MD_SEED, stack0) 514 return md_play(grid, argv[2] as *u8) 515 } 516 if ap[0]==(116 as u8) { if ap[1]==(116 as u8) { 517 let stack1: *i64 = sys_mmap(MD_DW*MD_DH*2*8) as *i64 518 pg_maze(grid, MD_DW, MD_DH, MD_SEED, stack1) 519 return md_play(grid, 0 as *u8) 520 } } 521 } 522 523 // trace mode: per-tick state of the cautious run, for diagnosing policy/balance 524 if argc >= 2 { 525 let a1: *u8 = argv[1] as *u8 526 if a1[0]==(116 as u8) { 527 md_init(s, grid, 25) 528 let ia: *i64 = sys_mmap(IA_NSV*8) as *i64 529 ia_init(ia, 5) 530 ia_bind(ia, IA_D_KEY, IA_A_PREV, 37); ia_bind(ia, IA_D_KEY, IA_A_NEXT, 39) 531 ia_bind(ia, IA_D_KEY, IA_A_CONFIRM, 40); ia_bind(ia, IA_D_KEY, IA_A_PAUSE, 38) 532 ia_bind(ia, IA_D_KEY, IA_A_CANCEL, 27) 533 let brng: *i64 = sys_mmap(8) as *i64 534 brng[0] = MD_SEED 535 let ob: *u8 = sys_mmap(MD_TRBUF) 536 var oo: i64 = 0 537 var tt: i64 = 0 538 while tt < MD_TRTICKS { 539 var raw: i64 = 39 540 if s[MD_F_HP] < 14 { if s[MD_F_POT] > 0 { raw = 27 } } 541 if raw != 27 { if tt % 7 == 6 { raw = 40 } } 542 if s[MD_F_KILLS] >= 3 { if tt % 2 == 1 { raw = 40 } } 543 let act: i64 = ia_feed(ia, IA_D_KEY, raw) 544 if act >= 0 { md_step(s, grid, act, brng) } 545 oo = ocat(ob, oo, "t=" as *u8); oo = onum(ob, oo, tt) 546 oo = ocat(ob, oo, " p=(" as *u8); oo = onum(ob, oo, s[MD_F_PX]) 547 oo = ocat(ob, oo, "," as *u8); oo = onum(ob, oo, s[MD_F_PY]) 548 oo = ocat(ob, oo, ") hp=" as *u8); oo = onum(ob, oo, s[MD_F_HP]) 549 oo = ocat(ob, oo, " kills=" as *u8); oo = onum(ob, oo, s[MD_F_KILLS]) 550 oo = ocat(ob, oo, " e0=(" as *u8); oo = onum(ob, oo, s[MD_O_ENX]) 551 oo = ocat(ob, oo, "," as *u8); oo = onum(ob, oo, s[MD_O_ENY]) 552 oo = ocat(ob, oo, ")hp" as *u8); oo = onum(ob, oo, s[MD_O_ENHP]) 553 oo = ocat(ob, oo, " e1hp" as *u8); oo = onum(ob, oo, s[MD_O_ENHP+1]) 554 oo = ocat(ob, oo, " e2hp" as *u8); oo = onum(ob, oo, s[MD_O_ENHP+2]) 555 oo = ocat(ob, oo, " over=" as *u8); oo = onum(ob, oo, s[MD_F_OVER]) 556 ob[oo]=10 as u8; oo=oo+1 557 tt = tt + 1 558 } 559 sys_write(1, ob, oo) 560 return 0 561 } 562 } 563 564 // P1 winnable + P3a chain 565 let r1: i64 = md_run(s, grid, 0, 25) 566 let win_ck: i64 = s[MD_F_CK] 567 let win_lvl: i64 = md_level(s) 568 let win_hp: i64 = s[MD_F_HP] 569 let win_kills: i64 = s[MD_F_KILLS] 570 let p1: i64 = (r1 == 1) as i64 571 572 // P3 determinism: replay bit-exact 573 let s2: *i64 = sys_mmap(MD_NSV*8) as *i64 574 let r1b: i64 = md_run(s2, grid, 0, 25) 575 var p3: i64 = 0 576 if r1b == r1 { if s2[MD_F_CK] == win_ck { p3 = 1 } } 577 578 // P2 losable + P6 replay determinism of the LOSS 579 let s3: *i64 = sys_mmap(MD_NSV*8) as *i64 580 let r2: i64 = md_run(s3, grid, 1, 25) 581 let lose_ck: i64 = s3[MD_F_CK] 582 let lose_tick: i64 = s3[MD_F_TICK] 583 let p2: i64 = (r2 == 2) as i64 584 let s4: *i64 = sys_mmap(MD_NSV*8) as *i64 585 let r2b: i64 = md_run(s4, grid, 1, 25) 586 var p6: i64 = 0 587 if p1 == 1 { if p2 == 1 { if r2b == r2 { if s4[MD_F_CK] == lose_ck { p6 = 1 } } } } 588 589 // P4 save-transparency: run 20 ticks, save, load, continue 44 -> must equal the unbroken chain. 590 // (md_run drives 64 IA-fed ticks; here we drive md_step directly with the same action stream.) 591 let sc: *i64 = sys_mmap(MD_NSV*8) as *i64 592 md_init(sc, grid, 25) 593 let br: *i64 = sys_mmap(8) as *i64 594 br[0] = MD_SEED + 9 595 var t: i64 = 0 596 while t < 64 { md_step(sc, grid, (t % 3) % 2, br); t = t + 1 } // PREV/NEXT weave 597 let unbroken: i64 = sc[MD_F_CK] 598 let sd: *i64 = sys_mmap(MD_NSV*8) as *i64 599 md_init(sd, grid, 25) 600 let br2: *i64 = sys_mmap(8) as *i64 601 br2[0] = MD_SEED + 9 602 t = 0 603 while t < 20 { md_step(sd, grid, (t % 3) % 2, br2); t = t + 1 } 604 gs_save("knowledge/nx_m2d.sav" as *u8, MD_SCHEMA, sd, MD_NSV, MD_SAVEAT) 605 let se: *i64 = sys_mmap(MD_NSV*8) as *i64 606 let meta: *i64 = sys_mmap(8*8) as *i64 607 let lrc: i64 = gs_load("knowledge/nx_m2d.sav" as *u8, se, MD_NSV, meta) 608 t = 20 609 while t < 64 { md_step(se, grid, (t % 3) % 2, br2); t = t + 1 } 610 var p4: i64 = 0 611 if lrc == MD_NSV { if se[MD_F_CK] == unbroken { p4 = 1 } } 612 613 // P5 the adult gate holds THROUGH the loop: the spring event mid-run 614 let sa: *i64 = sys_mmap(MD_NSV*8) as *i64 615 md_run(sa, grid, 0, 25) 616 let wa: *i64 = (sa as i64 + MD_O_WARD*8) as *i64 617 let rc_adult: i64 = ws_set(wa, 2, 0) // adult: exposure ALLOWED at the spring 618 let sm: *i64 = sys_mmap(MD_NSV*8) as *i64 619 md_run(sm, grid, 0, 16) 620 let wm: *i64 = (sm as i64 + MD_O_WARD*8) as *i64 621 let ck_b: i64 = ws_ck(wm) 622 let rc_minor: i64 = ws_set(wm, 2, 0) // minor: REFUSED, state bit-identical 623 let ck_a: i64 = ws_ck(wm) 624 var p5: i64 = 0 625 if rc_adult == 0 { if rc_minor == WS_E_MINOR { if ck_a == ck_b { p5 = 1 } } } 626 627 // P7 rendered frame -> hardened critic; emit NXFH1 for MCP re-grading 628 let W: i64 = 400 629 let H: i64 = 240 630 let fb: *i64 = sys_mmap(W*H*8) as *i64 631 md_render(s, grid, fb, W, H) 632 let mets: *i64 = sys_mmap(GC_N*8) as *i64 633 gc_score(fb, W, H, mets) 634 let q: i64 = gc_quality(mets) 635 let p7: i64 = (q >= 300) as i64 // must clear the TOY line; number published 636 let hx: *u8 = sys_mmap(W*H*6 + 64) 637 var o: i64 = 0 638 o = ocat(hx, o, "NXFH1 400 240\x0a" as *u8) 639 var pi: i64 = 0 640 while pi < W*H { 641 let v: i64 = fb[pi] 642 let r: i64 = v % 256 643 let g: i64 = (v / 256) % 256 644 let b: i64 = (v / MD_MAGIC_65536) % 256 645 hx[o] = hexd(r/16) as u8; hx[o+1] = hexd(r%16) as u8 646 hx[o+2] = hexd(g/16) as u8; hx[o+3] = hexd(g%16) as u8 647 hx[o+4] = hexd(b/16) as u8; hx[o+5] = hexd(b%16) as u8 648 o = o + 6 649 pi = pi + 1 650 if pi % W == 0 { hx[o] = 10 as u8; o = o + 1 } 651 } 652 let fd: i64 = sys_openat_wr("knowledge/nx_m2d_frame.nxfh" as *u8, 0x1a4) 653 if fd >= 0 { sys_write(fd, hx, o); sys_fsync(fd); sys_close(fd) } 654 655 // ---- verdict JSON ---- 656 var pass: i64 = p1+p2+p3+p4+p5+p6+p7 657 let jb: *u8 = sys_mmap(MD_MAGIC_4096) 658 var j: i64 = 0 659 j = ocat(jb, j, "{\x22game\x22:\x22nx_m2d_engine (Nishi Glade)\x22,\x22class\x22:\x22TIER-1 top-down action-adventure, the program plan's M2D\x22" as *u8) 660 j = ocat(jb, j, ",\x22proofs_pass\x22:" as *u8); j = onum(jb, j, pass) 661 j = ocat(jb, j, ",\x22proofs_total\x22:7" as *u8) 662 j = ocat(jb, j, ",\x22P1_winnable\x22:" as *u8); j = onum(jb, j, p1) 663 j = ocat(jb, j, ",\x22P2_losable\x22:" as *u8); j = onum(jb, j, p2) 664 j = ocat(jb, j, ",\x22P3_deterministic\x22:" as *u8); j = onum(jb, j, p3) 665 j = ocat(jb, j, ",\x22P4_save_transparent\x22:" as *u8); j = onum(jb, j, p4) 666 j = ocat(jb, j, ",\x22P5_adult_gate_holds\x22:" as *u8); j = onum(jb, j, p5) 667 j = ocat(jb, j, ",\x22P6_depth_policies_diverge\x22:" as *u8); j = onum(jb, j, p6) 668 j = ocat(jb, j, ",\x22P7_quality\x22:" as *u8); j = onum(jb, j, q) 669 j = ocat(jb, j, ",\x22win\x22:{\x22level\x22:" as *u8); j = onum(jb, j, win_lvl) 670 j = ocat(jb, j, ",\x22hp\x22:" as *u8); j = onum(jb, j, win_hp) 671 j = ocat(jb, j, ",\x22kills\x22:" as *u8); j = onum(jb, j, win_kills) 672 j = ocat(jb, j, ",\x22chain\x22:" as *u8); j = onum(jb, j, win_ck) 673 j = ocat(jb, j, "},\x22loss\x22:{\x22died_tick\x22:" as *u8); j = onum(jb, j, lose_tick) 674 j = ocat(jb, j, ",\x22chain\x22:" as *u8); j = onum(jb, j, lose_ck) 675 j = ocat(jb, j, "},\x22parts\x22:\x22worldpipe+procgen+pathfind+creature_battle+rpgstats+loottable+wardrobe_state+input_abstract+gamesave+raster+critic\x22" as *u8) 676 j = ocat(jb, j, ",\x22frame_evidence\x22:\x22knowledge/nx_m2d_frame.nxfh (re-gradable by nx_frame_score over MCP)\x22" as *u8) 677 j = ocat(jb, j, ",\x22honest\x22:\x22vertical slice: mechanics core proven; browser page, content variety and art direction are the NEXT rungs. P7 reports the critic number raw -- the frame must EARN its tier.\x22}" as *u8) 678 jb[j]=10 as u8 679 sys_write(1, jb, j+1) 680 if pass != 7 { return 1 } 681 return 0 682}