code wiki / _hdl_build / nx_game_publish.nx

nx_game_publish.nx source

↩ module page · 265 lines · 13585 B

1// nx_game_publish.nx -- the AUTO game/test PUBLISHER (operator: "we should be publishing these tests 2// so i can tell you where they are -- get us to an auto s-class-exceed publishing"). It AUTO-DISCOVERS 3// (no hand-list) every game + every test gate and PUBLISHES a discoverable dashboard so the operator 4// can find them: 5// - GAMES: scans knowledge/specs/ (getdents64) for game_*.spec, reads each title + detects engine. 6// - TESTS: scans knowledge/status/ for gate logs (contain "verdict="), reads each LIVE verdict. 7// - HOSTED: the games already playable in the browser (served by nx_arcade_serve). 8// Outputs (the published LOCATIONS): 9// - web_assets/_game_build/games.html (the human dashboard; served at http://localhost:7700/games) 10// - knowledge/status/published_catalog.tsv (the machine manifest) 11// Re-run anytime to RE-PUBLISH current state (idempotent, law#10) -> "auto". SELF-GATE (no args, the 12// nx_sov_build_run way): PUBLISH-GATE GREEN iff it discovered >=2 games + >=3 tests and wrote both 13// outputs -> knowledge/status/game_publish.log. S-class-exceed is MEASURED in a separate bench rung 14// (sovereignty + zero-config auto-discovery + inline test-proof are the differentiators vs itch.io/ 15// GitHub-Pages; no exceed asserted here -- no-wave law). license_tier: ORIGINAL 16import "nx_syscalls.nx" 17import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 18const K_MAGIC_65536: i64 = 65536 19const K_MAGIC_131072: i64 = 131072 20 21func gp_w(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 } 22func gp_c(fd: i64, c: i64) -> i64 { let b: *u8 = sys_mmap(8); b[0] = c as u8; sys_write(fd, b, 1); return 0 } 23// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 24// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 25// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 26// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 27func gp_wn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 28func gp_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 29func gp_starts(s: *u8, pre: *u8) -> i64 { 30 var i: i64 = 0 31 while pre[i] != (0 as u8) { if s[i] != pre[i] { return 0 } i = i + 1 } 32 return 1 33} 34func gp_ends(s: *u8, suf: *u8) -> i64 { 35 let sl: i64 = gp_len(s) 36 let fl: i64 = gp_len(suf) 37 if fl > sl { return 0 } 38 var i: i64 = 0 39 while i < fl { if s[sl - fl + i] != suf[i] { return 0 } i = i + 1 } 40 return 1 41} 42func gp_read(path: *u8, buf: *u8, cap: i64) -> i64 { 43 let fd: i64 = sys_openat_rd(path) 44 if fd < 0 { return 0 - 1 } 45 var n: i64 = 0 46 var r: i64 = sys_read(fd, buf, cap - 1) 47 while r > 0 { n = n + r; r = sys_read(fd, buf + n, cap - 1 - n) } 48 sys_close(fd) 49 return n 50} 51func gp_match(buf: *u8, pos: i64, len: i64, pat: *u8) -> i64 { 52 var i: i64 = 0 53 while pat[i] != (0 as u8) { if pos + i >= len { return 0 } if buf[pos + i] != pat[i] { return 0 } i = i + 1 } 54 return 1 55} 56func gp_contains(buf: *u8, len: i64, pat: *u8) -> i64 { 57 var i: i64 = 0 58 while i < len { if gp_match(buf, i, len, pat) == 1 { return 1 } i = i + 1 } 59 return 0 60} 61// last verdict in a gate log: 0 none, 1 GREEN, 2 RED (later position wins as i increases) 62func gp_last_verdict(buf: *u8, len: i64) -> i64 { 63 var last: i64 = 0 64 var i: i64 = 0 65 while i < len { 66 if gp_match(buf, i, len, "verdict=GREEN" as *u8) == 1 { last = 1 } 67 if gp_match(buf, i, len, "verdict=RED" as *u8) == 1 { last = 2 } 68 i = i + 1 69 } 70 return last 71} 72// extract "title <rest-of-line>" into out (NUL-term); 0 if absent 73func gp_title(buf: *u8, len: i64, out: *u8) -> i64 { 74 var i: i64 = 0 75 while i < len { 76 var ls: i64 = 0 77 if i == 0 { ls = 1 } 78 if i > 0 { if buf[i - 1] == (10 as u8) { ls = 1 } } 79 if ls == 1 { if gp_match(buf, i, len, "title " as *u8) == 1 { 80 var p: i64 = i + 6 81 var w: i64 = 0 82 var go: i64 = 1 83 while go == 1 { 84 if p >= len { go = 0 } else { 85 let c: i64 = buf[p] as i64 86 if c == 10 { go = 0 } else { if c != 13 { out[w] = c as u8; w = w + 1 } p = p + 1 } 87 } 88 } 89 out[w] = 0 as u8 90 return w 91 } } 92 i = i + 1 93 } 94 out[0] = 0 as u8 95 return 0 96} 97func gp_catpath(dir: *u8, name: *u8, out: *u8) -> i64 { 98 var w: i64 = 0; var i: i64 = 0 99 while dir[i] != (0 as u8) { out[w] = dir[i]; w = w + 1; i = i + 1 } 100 out[w] = 47 as u8; w = w + 1 101 i = 0 102 while name[i] != (0 as u8) { out[w] = name[i]; w = w + 1; i = i + 1 } 103 out[w] = 0 as u8 104 return w 105} 106func gp_html_row3(fd: i64, a: *u8, b: *u8, c: *u8) -> i64 { 107 gp_w(fd, "<tr><td>" as *u8); gp_w(fd, a) 108 gp_w(fd, "</td><td>" as *u8); gp_w(fd, b) 109 gp_w(fd, "</td><td>" as *u8); gp_w(fd, c) 110 gp_w(fd, "</td></tr>\n" as *u8) 111 return 0 112} 113func gp_html_link_row(fd: i64, title: *u8, engine: *u8, url: *u8) -> i64 { 114 gp_w(fd, "<tr><td>" as *u8); gp_w(fd, title) 115 gp_w(fd, "</td><td>" as *u8); gp_w(fd, engine) 116 gp_w(fd, "</td><td><a href='" as *u8); gp_w(fd, url); gp_w(fd, "'>" as *u8); gp_w(fd, url) 117 gp_w(fd, "</a></td></tr>\n" as *u8) 118 return 0 119} 120func gp_man(fd: i64, kind: *u8, name: *u8, ev: *u8, loc: *u8) -> i64 { 121 gp_w(fd, kind); gp_c(fd, 9); gp_w(fd, name); gp_c(fd, 9); gp_w(fd, ev); gp_c(fd, 9); gp_w(fd, loc); gp_c(fd, 10) 122 return 0 123} 124 125// scan knowledge/specs for game_*.spec -> html game rows + manifest rows; returns count 126func gp_scan_games(hf: i64, mf: i64) -> i64 { 127 let dfd: i64 = sys_openat_rd("knowledge/specs" as *u8) 128 if dfd < 0 { return 0 } 129 let dbuf: *u8 = sys_mmap(K_MAGIC_65536) 130 let fbuf: *u8 = sys_mmap(K_MAGIC_131072) 131 let path: *u8 = sys_mmap(512) 132 let title: *u8 = sys_mmap(256) 133 var count: i64 = 0 134 var n: i64 = sys_getdents64(dfd, dbuf, K_MAGIC_65536) 135 while n > 0 { 136 var off: i64 = 0 137 while off < n { 138 let rec: *u8 = ((dbuf as i64) + off) as *u8 139 let reclen: i64 = dirent_reclen(rec) 140 if reclen <= 0 { off = n } else { 141 let name: *u8 = dirent_name(rec) 142 if gp_starts(name, "game_" as *u8) == 1 { if gp_ends(name, ".spec" as *u8) == 1 { 143 gp_catpath("knowledge/specs" as *u8, name, path) 144 let fn: i64 = gp_read(path, fbuf, K_MAGIC_131072) 145 var eng: *u8 = "dungeon" as *u8 146 if fn > 0 { if gp_contains(fbuf, fn, "n_player" as *u8) == 1 { eng = "tactics" as *u8 } } 147 title[0] = 0 as u8 148 if fn > 0 { gp_title(fbuf, fn, title) } 149 if title[0] == (0 as u8) { var z: i64 = 0; while name[z] != (0 as u8) { title[z] = name[z]; z = z + 1 } title[z] = 0 as u8 } 150 gp_html_row3(hf, title, eng, path) 151 gp_man(mf, "game" as *u8, title, eng, path) 152 count = count + 1 153 } } 154 off = off + reclen 155 } 156 } 157 n = sys_getdents64(dfd, dbuf, K_MAGIC_65536) 158 } 159 sys_close(dfd) 160 return count 161} 162// scan knowledge/status for gate logs (contain "verdict=") -> html test rows + manifest; returns count. 163// greenp[0] += number GREEN. 164func gp_scan_tests(hf: i64, mf: i64, greenp: *i64) -> i64 { 165 let dfd: i64 = sys_openat_rd("knowledge/status" as *u8) 166 if dfd < 0 { return 0 } 167 let dbuf: *u8 = sys_mmap(K_MAGIC_65536) 168 let fbuf: *u8 = sys_mmap(K_MAGIC_131072) 169 let path: *u8 = sys_mmap(512) 170 var count: i64 = 0 171 var n: i64 = sys_getdents64(dfd, dbuf, K_MAGIC_65536) 172 while n > 0 { 173 var off: i64 = 0 174 while off < n { 175 let rec: *u8 = ((dbuf as i64) + off) as *u8 176 let reclen: i64 = dirent_reclen(rec) 177 if reclen <= 0 { off = n } else { 178 let name: *u8 = dirent_name(rec) 179 if gp_ends(name, ".log" as *u8) == 1 { 180 gp_catpath("knowledge/status" as *u8, name, path) 181 let fn: i64 = gp_read(path, fbuf, K_MAGIC_131072) 182 if fn > 0 { if gp_contains(fbuf, fn, "verdict=" as *u8) == 1 { 183 let v: i64 = gp_last_verdict(fbuf, fn) 184 var vs: *u8 = "RED" as *u8 185 if v == 1 { vs = "GREEN" as *u8; greenp[0] = greenp[0] + 1 } 186 gp_w(hf, "<tr><td>" as *u8); gp_w(hf, name) 187 gp_w(hf, "</td><td class='" as *u8) 188 if v == 1 { gp_w(hf, "green" as *u8) } else { gp_w(hf, "red" as *u8) } 189 gp_w(hf, "'>" as *u8); gp_w(hf, vs) 190 gp_w(hf, "</td><td>" as *u8); gp_w(hf, path); gp_w(hf, "</td></tr>\n" as *u8) 191 gp_man(mf, "test" as *u8, name, vs, path) 192 count = count + 1 193 } } 194 } 195 off = off + reclen 196 } 197 } 198 n = sys_getdents64(dfd, dbuf, K_MAGIC_65536) 199 } 200 sys_close(dfd) 201 return count 202} 203 204func main() -> i64 { 205 let hf: i64 = sys_openat_wr("web_assets/_game_build/games.html" as *u8, 0x1a4) 206 if hf < 0 { gp_w(1, "PUBLISH-GATE verdict=RED (cannot open games.html)\n" as *u8); sys_exit(1); return 1 } 207 let mf: i64 = sys_openat_wr("knowledge/status/published_catalog.tsv" as *u8, 0x1a4) 208 if mf < 0 { gp_w(1, "PUBLISH-GATE verdict=RED (cannot open manifest)\n" as *u8); sys_exit(1); return 1 } 209 210 gp_w(hf, "<!doctype html><html><head><meta charset='utf-8'><title>NISHI Game Ecosystem -- Published Catalog</title>\n" as *u8) 211 gp_w(hf, "<style>body{font-family:system-ui,monospace;background:#0f1117;color:#e6e6e6;margin:24px}h1{color:#7ad}h2{color:#9c6;border-bottom:1px solid #333;padding-bottom:4px;margin-top:28px}table{border-collapse:collapse;width:100%;margin:8px 0}td,th{border:1px solid #333;padding:6px 10px;text-align:left}a{color:#6cf}.green{color:#5d5;font-weight:bold}.red{color:#f66;font-weight:bold}.muted{color:#888}</style></head><body>\n" as *u8) 212 gp_w(hf, "<h1>NISHI Game Ecosystem -- Published Catalog</h1>\n" as *u8) 213 gp_w(hf, "<p class='muted'>Auto-published by nx_game_publish (sovereign nx server). Re-run to refresh.</p>\n" as *u8) 214 215 gp_w(mf, "# published_catalog.tsv -- AUTO-generated by nx_game_publish (re-run to refresh)\n" as *u8) 216 gp_w(mf, "kind" as *u8); gp_c(mf, 9); gp_w(mf, "name" as *u8); gp_c(mf, 9); gp_w(mf, "engine_or_verdict" as *u8); gp_c(mf, 9); gp_w(mf, "location" as *u8); gp_c(mf, 10) 217 218 // HOSTED (playable in browser now) 219 gp_w(hf, "<h2>&#9658; Playable in browser now</h2><table><tr><th>Game</th><th>Engine</th><th>Open</th></tr>\n" as *u8) 220 gp_html_link_row(hf, "Pets (creature-collector)" as *u8, "pets-wasm" as *u8, "http://localhost:7700/pets" as *u8) 221 gp_html_link_row(hf, "Pets 3D view" as *u8, "pets-wasm" as *u8, "http://localhost:7700/pets3dview" as *u8) 222 gp_html_link_row(hf, "Dungeon Crawler" as *u8, "dungeon-wasm" as *u8, "http://localhost:7700/" as *u8) 223 gp_w(hf, "</table>\n" as *u8) 224 gp_man(mf, "hosted" as *u8, "Pets" as *u8, "pets-wasm" as *u8, "http://localhost:7700/pets" as *u8) 225 gp_man(mf, "hosted" as *u8, "Pets3D" as *u8, "pets-wasm" as *u8, "http://localhost:7700/pets3dview" as *u8) 226 gp_man(mf, "hosted" as *u8, "Dungeon" as *u8, "dungeon-wasm" as *u8, "http://localhost:7700/" as *u8) 227 228 // GAMES (auto-discovered specs) 229 gp_w(hf, "<h2>&#9881; Generatable games (auto-discovered specs)</h2><table><tr><th>Title</th><th>Engine</th><th>Spec</th></tr>\n" as *u8) 230 let games: i64 = gp_scan_games(hf, mf) 231 gp_w(hf, "</table>\n" as *u8) 232 233 // TESTS (auto-discovered gate logs, live verdicts) 234 gp_w(hf, "<h2>&#10003; Test gates (auto-discovered, live verdicts)</h2><table><tr><th>Test log</th><th>Verdict</th><th>Location</th></tr>\n" as *u8) 235 let greenp: *i64 = sys_mmap(16) as *i64 236 greenp[0] = 0 237 let tests: i64 = gp_scan_tests(hf, mf, greenp) 238 gp_w(hf, "</table>\n" as *u8) 239 240 gp_w(hf, "<p class='muted'>Published: " as *u8); gp_wn(hf, games); gp_w(hf, " games, " as *u8); gp_wn(hf, tests) 241 gp_w(hf, " tests (" as *u8); gp_wn(hf, greenp[0]); gp_w(hf, " GREEN). Served at http://localhost:7700/games -- sovereign nx_arcade_serve.</p>\n" as *u8) 242 gp_w(hf, "</body></html>\n" as *u8) 243 sys_close(hf) 244 sys_close(mf) 245 246 var ok: i64 = 1 247 if games < 2 { ok = 0 } 248 if tests < 3 { ok = 0 } 249 250 let lf: i64 = sys_openat_append("knowledge/status/game_publish.log" as *u8, 420) 251 gp_w(1, "PUBLISH-GATE authored=organ auto-discovered games=" as *u8); gp_wn(1, games) 252 gp_w(1, " tests=" as *u8); gp_wn(1, tests); gp_w(1, " green=" as *u8); gp_wn(1, greenp[0]) 253 gp_w(1, " -> web_assets/_game_build/games.html + knowledge/status/published_catalog.tsv (serve http://localhost:7700/games)" as *u8) 254 if ok == 1 { gp_w(1, " verdict=GREEN\n" as *u8) } else { gp_w(1, " verdict=RED\n" as *u8) } 255 if lf >= 0 { 256 gp_w(lf, "PUBLISH-GATE authored=organ auto-discovered games=" as *u8); gp_wn(lf, games) 257 gp_w(lf, " tests=" as *u8); gp_wn(lf, tests); gp_w(lf, " green=" as *u8); gp_wn(lf, greenp[0]) 258 gp_w(lf, " published=web_assets/_game_build/games.html+knowledge/status/published_catalog.tsv" as *u8) 259 if ok == 1 { gp_w(lf, " verdict=GREEN\n" as *u8) } else { gp_w(lf, " verdict=RED\n" as *u8) } 260 sys_close(lf) 261 } 262 if ok == 1 { sys_exit(0); return 0 } 263 sys_exit(1) 264 return 1 265}