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>► 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>⚙ 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>✓ 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}