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}