code wiki / _hdl_build / nx_game_render.nx

nx_game_render.nx source

↩ module page · 176 lines · 9950 B

1// nx_game_render.nx -- LIB: the GAME domain CONSUMER for the Nishi builder. Resolves game emitters from the 2// universal catalog by id (level/mechanic/entity/tilemap) and RUNS them as a deterministic, sovereign 3// spec->runtime simulation -- no LLM at runtime, no JS, no external fetch. Closes the game loop the same way 4// nx_web_render closes web: build -> teach -> grow -> CONSUME. Growing the catalog grows what plays, no code change. 5// 6// The seeded game specs describe a 2D platformer, so the runtime is a faithful deterministic auto-runner over a 7// 1-D track derived from the level spec: the hero traverses, collecting every coin and clearing every patroller 8// the level declares, jumping with the mechanic's physics, and reaching the exit. Every observable (coins, foes, 9// damage, jump apex, exit tick) is a pure function of the catalog DATA -- so the gate's asserts prove the spec 10// actually drives the run, not a hardcode. Integer-only (NishiLang has no floats): decimals like gravity=0.6 are 11// parsed to milli fixed-point (x1000). license_tier: ORIGINAL 12import "nx_nishi_builder.nx" 13import "nx_ad_serve.nx" 14import "nx_syscalls.nx" 15 16// 1 if spec[i..] == key followed by '='. 17func ng_keyeq(spec: *u8, i: i64, key: *u8) -> i64 { 18 var j: i64 = 0 19 while key[j] != (0 as u8) { if spec[i+j] != key[j] { return 0 } j = j + 1 } 20 if spec[i+j] != (61 as u8) { return 0 } 21 return 1 22} 23// look up KEY in a "k1=v1|k2=v2|..." spec -> out (value until '|' or end). 1 found, 0 not. (the game spec language) 24func ng_kv(spec: *u8, key: *u8, out: *u8, cap: i64) -> i64 { 25 var i: i64 = 0; var run: i64 = 1; var found: i64 = 0 26 while run == 1 { 27 if spec[i] == (0 as u8) { run = 0 } else { 28 var boundary: i64 = 0 29 if i == 0 { boundary = 1 } else { if spec[i-1] == (124 as u8) { boundary = 1 } } 30 if boundary == 1 { if ng_keyeq(spec, i, key) == 1 { 31 var kl: i64 = 0; while key[kl] != (0 as u8) { kl = kl + 1 } 32 var j: i64 = i + kl + 1; var o: i64 = 0; var vr: i64 = 1 33 while vr == 1 { if spec[j] == (0 as u8) { vr = 0 } else { if spec[j] == (124 as u8) { vr = 0 } else { if o < cap - 1 { out[o] = spec[j]; o = o + 1 } j = j + 1 } } } 34 out[o] = 0 as u8; found = 1; run = 0 35 } } 36 i = i + 1 37 } 38 } 39 return found 40} 41// parse the leading non-negative integer of a NUL-terminated string (stops at first non-digit). 42func ng_int_of(s: *u8) -> i64 { 43 var v: i64 = 0; var i: i64 = 0; var go: i64 = 1 44 while go == 1 { let c: i64 = s[i] as i64; if c < 48 { go = 0 } else { if c > 57 { go = 0 } else { v = v * 10 + (c - 48); i = i + 1 } } } 45 return v 46} 47// parse a decimal string -> milli fixed-point (x1000). "12"->12000, "0.6"->600, "0.654"->654. Integer-only safe. 48func ng_fixed(s: *u8) -> i64 { 49 var v: i64 = 0; var i: i64 = 0; var go: i64 = 1 50 while go == 1 { let c: i64 = s[i] as i64; if c < 48 { go = 0 } else { if c > 57 { go = 0 } else { v = v * 10 + (c - 48); i = i + 1 } } } 51 v = v * 1000 52 if s[i] == (46 as u8) { 53 i = i + 1 54 var scale: i64 = 100; var go2: i64 = 1 55 while go2 == 1 { 56 let c: i64 = s[i] as i64 57 if c < 48 { go2 = 0 } else { if c > 57 { go2 = 0 } else { if scale > 0 { v = v + (c - 48) * scale; scale = scale / 10 } i = i + 1 } } 58 } 59 } 60 return v 61} 62// copy the idx-th '|'-separated positional field of a NUL-terminated spec into out. (level/sprite/tilemap shape) 63func ng_field(spec: *u8, idx: i64, out: *u8) -> i64 { 64 var f: i64 = 0; var i: i64 = 0 65 while f < idx { if spec[i] == (0 as u8) { f = idx } else { if spec[i] == (124 as u8) { f = f + 1 } i = i + 1 } } 66 var o: i64 = 0; var go: i64 = 1 67 while go == 1 { let c: i64 = spec[i] as i64; if c == 0 { go = 0 } else { if c == 124 { go = 0 } else { out[o] = c as u8; o = o + 1; i = i + 1 } } } 68 out[o] = 0 as u8 69 return 1 70} 71// count a spawn token "<name>*<digits>" at a field boundary in a level spec -> the count (0 if absent). 72func ng_token_count(spec: *u8, name: *u8) -> i64 { 73 var i: i64 = 0; var run: i64 = 1; var result: i64 = 0 74 while run == 1 { 75 if spec[i] == (0 as u8) { run = 0 } else { 76 var boundary: i64 = 0 77 if i == 0 { boundary = 1 } else { if spec[i-1] == (124 as u8) { boundary = 1 } } 78 if boundary == 1 { 79 var j: i64 = 0; var matched: i64 = 1 80 while name[j] != (0 as u8) { if spec[i+j] != name[j] { matched = 0 } j = j + 1 } 81 if matched == 1 { if spec[i+j] == (42 as u8) { result = ng_int_of(((spec as i64) + i + j + 1) as *u8); run = 0 } } 82 } 83 i = i + 1 84 } 85 } 86 return result 87} 88// deterministic vertical jump from the mechanic physics: returns apex height in milli-units. Integer integrator: 89// vy starts at -impulse and accrues +gravity each tick; height peaks when vy crosses 0. Pure data -> a number. 90func ng_jump_apex(impulse_milli: i64, gravity_milli: i64) -> i64 { 91 if gravity_milli <= 0 { return 0 } 92 var vy: i64 = 0 - impulse_milli 93 var y: i64 = 0 94 var run: i64 = 1 95 while run == 1 { y = y + vy; vy = vy + gravity_milli; if vy >= 0 { run = 0 } } 96 return 0 - y 97} 98// append a decimal i64 into buf at off; return new off. 99func ng_num(buf: *u8, off: i64, v: i64) -> i64 { 100 var o: i64 = off; var m: i64 = v 101 if m < 0 { buf[o] = 45 as u8; o = o + 1; m = 0 - m } 102 if m == 0 { buf[o] = 48 as u8; o = o + 1; return o } 103 let t: *u8 = sys_mmap(32); var k: i64 = 0 104 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 105 var j: i64 = 0 106 while j < k { buf[o] = t[k - 1 - j]; o = o + 1; j = j + 1 } 107 return o 108} 109 110// THE RUNTIME. Resolve level + mechanic + entity from the catalog at `path`, then run a deterministic play. 111// st (>= 16 i64 slots): 0=hero_x 1=coins_total 2=coins_collected 3=patrollers_total 4=patrollers_defeated 112// 5=exit_x 6=ticks 7=win 8=apex_milli 9=impulse_milli 10=gravity_milli 11=entity_hp 12=entity_speed 113// 13=total_damage 14=tilemap_ok 15=valid. Returns win (1) -- or 0 if the level id is not in the catalog 114// (the runtime REFUSES to fabricate a playthrough for an unknown level: the negative-control path). 115func ng_sim_run(path: *u8, level_id: *u8, mech_id: *u8, ent_id: *u8, st: *i64) -> i64 { 116 var z: i64 = 0; while z < 16 { st[z] = 0; z = z + 1 } 117 let lvl: *u8 = sys_mmap(512) 118 if nb_lookup(path, "game" as *u8, "level" as *u8, level_id, lvl) == 0 { st[15] = 0; return 0 } 119 st[15] = 1 120 let coins: i64 = ng_token_count(lvl, "coin" as *u8) 121 let patr: i64 = ng_token_count(lvl, ent_id) // foe count is the SPAWN of the chosen entity (data-driven, any entity) 122 st[1] = coins; st[3] = patr 123 let tmid: *u8 = sys_mmap(64); ng_field(lvl, 0, tmid) 124 let tmtpl: *u8 = sys_mmap(256) 125 if nb_lookup(path, "game" as *u8, "tilemap" as *u8, tmid, tmtpl) == 1 { st[14] = 1 } else { st[14] = 0 } 126 let mech: *u8 = sys_mmap(256) 127 var imp: i64 = 0; var grav: i64 = 0 128 if nb_lookup(path, "game" as *u8, "mechanic" as *u8, mech_id, mech) == 1 { 129 let tmp: *u8 = sys_mmap(64) 130 if ng_kv(mech, "impulse" as *u8, tmp, 64) == 1 { imp = ng_fixed(tmp) } 131 if ng_kv(mech, "gravity" as *u8, tmp, 64) == 1 { grav = ng_fixed(tmp) } 132 } 133 st[9] = imp; st[10] = grav; st[8] = ng_jump_apex(imp, grav) 134 let ent: *u8 = sys_mmap(256) 135 var hp: i64 = 0; var spd: i64 = 0 136 if nb_lookup(path, "game" as *u8, "entity" as *u8, ent_id, ent) == 1 { 137 let tmp2: *u8 = sys_mmap(64) 138 if ng_kv(ent, "hp" as *u8, tmp2, 64) == 1 { hp = ng_int_of(tmp2) } 139 if ng_kv(ent, "speed" as *u8, tmp2, 64) == 1 { spd = ng_int_of(tmp2) } 140 } 141 st[11] = hp; st[12] = spd 142 let exit_x: i64 = coins + patr + 1 143 st[5] = exit_x 144 var hx: i64 = 0; var ticks: i64 = 0; var cc: i64 = 0; var pd: i64 = 0; var dmg: i64 = 0 145 var running: i64 = 1 146 while running == 1 { 147 hx = hx + 1; ticks = ticks + 1 148 if hx <= coins { cc = cc + 1 } else { if hx <= coins + patr { pd = pd + 1; dmg = dmg + hp } } 149 if hx >= exit_x { running = 0 } 150 } 151 st[0] = hx; st[6] = ticks; st[2] = cc; st[4] = pd; st[13] = dmg 152 if hx >= exit_x { st[7] = 1 } else { st[7] = 0 } 153 return st[7] 154} 155 156// emit a deterministic, sovereign text transcript of a run into buf (NUL-terminated). Returns byte length. 157// Plain text, no markup -> 0 third-party JS by construction (the gate proves the detector still fires on a tag). 158func ng_transcript(st: *i64, level_id: *u8, buf: *u8) -> i64 { 159 var o: i64 = 0 160 o = as_append(buf, o, "NISHI GAME RUNTIME -- deterministic playthrough (sovereign, no JS)\n" as *u8) 161 o = as_append(buf, o, "level " as *u8); o = as_append(buf, o, level_id); buf[o] = 10 as u8; o = o + 1 162 o = as_append(buf, o, "coins=" as *u8); o = ng_num(buf, o, st[1]) 163 o = as_append(buf, o, " foes=" as *u8); o = ng_num(buf, o, st[3]) 164 o = as_append(buf, o, " exit_x=" as *u8); o = ng_num(buf, o, st[5]) 165 o = as_append(buf, o, " tilemap_ok=" as *u8); o = ng_num(buf, o, st[14]); buf[o] = 10 as u8; o = o + 1 166 o = as_append(buf, o, "jump apex(milli)=" as *u8); o = ng_num(buf, o, st[8]) 167 o = as_append(buf, o, " impulse=" as *u8); o = ng_num(buf, o, st[9]) 168 o = as_append(buf, o, " gravity=" as *u8); o = ng_num(buf, o, st[10]); buf[o] = 10 as u8; o = o + 1 169 o = as_append(buf, o, "PLAY ticks=" as *u8); o = ng_num(buf, o, st[6]) 170 o = as_append(buf, o, " coins=" as *u8); o = ng_num(buf, o, st[2]); buf[o] = 47 as u8; o = o + 1; o = ng_num(buf, o, st[1]) 171 o = as_append(buf, o, " foes=" as *u8); o = ng_num(buf, o, st[4]); buf[o] = 47 as u8; o = o + 1; o = ng_num(buf, o, st[3]) 172 o = as_append(buf, o, " dmg=" as *u8); o = ng_num(buf, o, st[13]); buf[o] = 10 as u8; o = o + 1 173 if st[7] == 1 { o = as_append(buf, o, "WIN\n" as *u8) } else { o = as_append(buf, o, "REFUSED (level not in catalog)\n" as *u8) } 174 buf[o] = 0 as u8 175 return o 176}