code wiki / _hdl_build / nx_game_gen2.nx
nx_game_gen2.nx source
↩ module page · 297 lines · 13323 B
1// nx_game_gen2.nx -- the POLYMORPHIC game-system generator (X-GAME-GEN-001: "generate-a-game:
2// game systems as emitter SHAPES -> GAME SPEC -> Builder emits the game"). Generalizes
3// nx_game_gen (which hard-codes the dungenc table, dungeon-only) to ALL SIX proven game-system
4// FSM types by SOURCING each type's transition table from its build_<type>.spec -- the same DATA
5// the hand-authored _pe_<type> FSM (shape 18 STATE_MACHINE) was gated on. So the emitted game's
6// FSM is, by construction, the PROVEN one (DRY: the table is never duplicated here).
7//
8// CLI: nx_game_gen2 <name> <THEME> <type> [difficulty]
9// type = dungenc | twrwave | abround | stealth | datesim | fightfsm
10// writes knowledge/specs/<name>.spec (name/title/economy + fsm+rows from build_<type>.spec)
11// SELF-GATE (run with no args, the way nx_sov_build_run invokes it): generate all 6 types to
12// scratch, re-parse each emitted game-spec, assert its FSM == the source build_<type>.spec FSM,
13// assert the 6 tables are DISTINCT (polymorphic, not all-dungenc), and assert an UNKNOWN type is
14// REFUSED. GAMEGEN2-GATE verdict=GREEN iff all sourced-ok + distinct>=2 + neg refused.
15//
16// HONEST SCOPE: the economy shell (walls/foes/hp, difficulty-scaled -- the deterministic tier
17// table) is the dungeon default; the FSM (the game SYSTEM) is the polymorphic shape. Per-type
18// economy templates are a refinement. Sovereign syscalls only. license_tier: ORIGINAL
19import "nx_syscalls.nx"
20import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
21const K_MAGIC_65536: i64 = 65536
22const K_MAGIC_2048: i64 = 2048
23
24func gg_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 }
25func gg_c(fd: i64, c: i64) -> i64 { let b: *u8 = sys_mmap(8); b[0] = c as u8; sys_write(fd, b, 1); return 0 }
26// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
27// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
28// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
29// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
30func gg_wn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 }
31func gg_streq(a: *u8, b: *u8) -> i64 {
32 var i: i64 = 0
33 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
34 if b[i] != (0 as u8) { return 0 }
35 return 1
36}
37func gg_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i }
38
39func gg_read(path: *u8, buf: *u8, cap: i64) -> i64 {
40 let fd: i64 = sys_openat_rd(path)
41 if fd < 0 { return 0 - 1 }
42 var n: i64 = 0
43 var r: i64 = sys_read(fd, buf, cap - 1)
44 while r > 0 { n = n + r; r = sys_read(fd, buf + n, cap - 1 - n) }
45 sys_close(fd)
46 return n
47}
48
49// parse every signed int on line [ls,le) into tmp; returns count (the word "param"/"row"/"fsm" has no digits, skipped)
50func gg_line_ints(buf: *u8, ls: i64, le: i64, tmp: *i64) -> i64 {
51 var cnt: i64 = 0
52 var i: i64 = ls
53 while i < le {
54 let c: i64 = buf[i] as i64
55 var start: i64 = 0
56 if c == 45 { start = 1 }
57 if c >= 48 { if c <= 57 { start = 1 } }
58 if start == 1 {
59 var neg: i64 = 0
60 if c == 45 { neg = 1; i = i + 1 }
61 var v: i64 = 0
62 var go: i64 = 1
63 while go == 1 {
64 if i >= le { go = 0 } else {
65 let d: i64 = buf[i] as i64
66 if d >= 48 { if d <= 57 { v = v * 10 + (d - 48); i = i + 1 } else { go = 0 } } else { go = 0 }
67 }
68 }
69 if neg == 1 { v = 0 - v }
70 tmp[cnt] = v; cnt = cnt + 1
71 } else { i = i + 1 }
72 }
73 return cnt
74}
75
76// extract the FSM table from a spec buffer. key=1 -> build spec ("param" lines: 1st=hdr ns ne s0, rest=rows);
77// key=0 -> game spec ("fsm ns ne" line + "row" lines). out[0]=ns out[1]=ne out[3+r*ne+c]=cell. returns 1 ok.
78func gg_extract_fsm(buf: *u8, n: i64, key: i64, out: *i64) -> i64 {
79 let tmp: *i64 = sys_mmap(64 * 8) as *i64
80 var ns: i64 = 0; var ne: i64 = 0
81 var pcount: i64 = 0 // build: param-lines seen ; game: rows seen
82 var have_hdr: i64 = 0
83 var i: i64 = 0
84 while i < n {
85 var le: i64 = i
86 var s: i64 = 1
87 while s == 1 { if le >= n { s = 0 } else { if buf[le] == (10 as u8) { s = 0 } else { le = le + 1 } } }
88 if le > i { if buf[i] != (35 as u8) {
89 // first non-space token
90 var t0: i64 = i
91 while t0 < le { if buf[t0] == (32 as u8) { t0 = le } else { t0 = t0 + 1 } } // t0 = end of first token (or le)
92 // identify keyword by first char(s)
93 if key == 1 {
94 // build spec: "param ..." lines only
95 if buf[i] == (112 as u8) { // 'p' -> param
96 let m: i64 = gg_line_ints(buf, i, le, tmp)
97 if pcount == 0 { ns = tmp[0]; ne = tmp[1]; out[0] = ns; out[1] = ne; out[2] = tmp[2]; have_hdr = 1 }
98 else {
99 let rr: i64 = pcount - 1
100 var c: i64 = 0
101 while c < ne { out[3 + rr * ne + c] = tmp[c]; c = c + 1 }
102 }
103 pcount = pcount + 1
104 }
105 } else {
106 // game spec: "fsm ns ne" then "row ..."
107 if buf[i] == (102 as u8) { // 'f' -> fsm
108 let m: i64 = gg_line_ints(buf, i, le, tmp)
109 ns = tmp[0]; ne = tmp[1]; out[0] = ns; out[1] = ne; out[2] = 0; have_hdr = 1
110 } else { if buf[i] == (114 as u8) { // 'r' -> row
111 let m: i64 = gg_line_ints(buf, i, le, tmp)
112 var c: i64 = 0
113 while c < ne { out[3 + pcount * ne + c] = tmp[c]; c = c + 1 }
114 pcount = pcount + 1
115 } }
116 }
117 } }
118 i = le + 1
119 }
120 if have_hdr == 0 { return 0 }
121 var rows_needed: i64 = ns
122 if key == 1 { if pcount == ns + 1 { return 1 } return 0 }
123 if pcount == rows_needed { return 1 }
124 return 0
125}
126
127// read knowledge/specs/build_<type>.spec -> out fsm table. returns 1 ok / 0 missing-or-malformed.
128func gg_build_fsm(typ: *u8, out: *i64) -> i64 {
129 let path: *u8 = sys_mmap(256)
130 var o: i64 = 0
131 o = gg_cat(path, o, "knowledge/specs/build_" as *u8); o = gg_cat(path, o, typ); o = gg_cat(path, o, ".spec" as *u8); path[o] = 0 as u8
132 let buf: *u8 = sys_mmap(K_MAGIC_65536)
133 let n: i64 = gg_read(path, buf, K_MAGIC_65536)
134 if n <= 0 { return 0 }
135 return gg_extract_fsm(buf, n, 1, out)
136}
137
138// difficulty tier 0..3
139func gg_tier(d: *u8) -> i64 {
140 if gg_streq(d, "easy" as *u8) == 1 { return 0 }
141 if gg_streq(d, "normal" as *u8) == 1 { return 1 }
142 if gg_streq(d, "hard" as *u8) == 1 { return 2 }
143 if gg_streq(d, "nightmare" as *u8) == 1 { return 3 }
144 return 1
145}
146
147func gg_kv(fd: i64, key: *u8, v: i64) -> i64 { gg_w(fd, key); gg_c(fd, 32); gg_wn(fd, v); gg_c(fd, 10); return 0 }
148
149// write a complete game-spec: name/title/economy(tier-scaled)/needs + fsm+rows from ftab.
150func gg_emit_spec(fd: i64, name: *u8, theme: *u8, typ: *u8, tier: i64, ftab: *i64) -> i64 {
151 let walls: i64 = 16 + tier * 6
152 let eb: i64 = 2 + tier
153 let hb: i64 = 14 + tier * 6
154 let hpl: i64 = 3 + tier
155 var loot: i64 = 4 - tier
156 if loot < 1 { loot = 1 }
157 var db: i64 = 10 - tier
158 if db < 6 { db = 6 }
159 gg_w(fd, "# generated by nx_game_gen2 (polymorphic intent->spec); FSM sourced from build_" as *u8); gg_w(fd, typ); gg_w(fd, ".spec\n" as *u8)
160 gg_w(fd, "name " as *u8); gg_w(fd, name); gg_c(fd, 10)
161 gg_w(fd, "title THE " as *u8); gg_w(fd, theme); gg_c(fd, 10)
162 gg_kv(fd, "walls" as *u8, walls)
163 gg_kv(fd, "enemy_base" as *u8, eb)
164 gg_kv(fd, "enemy_per_level" as *u8, 1)
165 gg_kv(fd, "ehp_base" as *u8, hb)
166 gg_kv(fd, "ehp_per_level" as *u8, hpl)
167 gg_kv(fd, "loot" as *u8, loot)
168 gg_kv(fd, "dmg_base" as *u8, db)
169 gg_kv(fd, "dmg_rand" as *u8, 8)
170 gg_w(fd, "needs save\n" as *u8)
171 let ns: i64 = ftab[0]; let ne: i64 = ftab[1]
172 gg_w(fd, "fsm " as *u8); gg_wn(fd, ns); gg_c(fd, 32); gg_wn(fd, ne); gg_c(fd, 10)
173 var r: i64 = 0
174 while r < ns {
175 gg_w(fd, "row" as *u8)
176 var c: i64 = 0
177 while c < ne { gg_c(fd, 32); gg_wn(fd, ftab[3 + r * ne + c]); c = c + 1 }
178 gg_c(fd, 10)
179 r = r + 1
180 }
181 return 0
182}
183
184// compare two extracted fsm tables (ns,ne,cells). 1 equal / 0 differ.
185func gg_fsm_eq(a: *i64, b: *i64) -> i64 {
186 if a[0] != b[0] { return 0 }
187 if a[1] != b[1] { return 0 }
188 let cells: i64 = a[0] * a[1]
189 var i: i64 = 0
190 while i < cells { if a[3 + i] != b[3 + i] { return 0 } i = i + 1 }
191 return 1
192}
193// a cheap order-sensitive signature of an fsm table (for distinctness)
194func gg_fsm_sig(a: *i64) -> i64 {
195 var sig: i64 = a[0] * 131 + a[1]
196 let cells: i64 = a[0] * a[1]
197 var i: i64 = 0
198 while i < cells { sig = sig * 31 + a[3 + i] + 7; i = i + 1 }
199 return sig
200}
201
202func gg_gate_report(fd: i64, navail: i64, sourced: i64, distinct: i64, neg: i64, ok: i64) -> i64 {
203 gg_w(fd, "GAMEGEN2-GATE authored=organ types_available=" as *u8); gg_wn(fd, navail)
204 gg_w(fd, " fsm_sourced_ok=" as *u8); gg_wn(fd, sourced)
205 gg_w(fd, " distinct_tables=" as *u8); gg_wn(fd, distinct)
206 gg_w(fd, " neg_unknown_refused=" as *u8); gg_wn(fd, neg)
207 if ok == 1 { gg_w(fd, " verdict=GREEN\n" as *u8) } else { gg_w(fd, " verdict=RED\n" as *u8) }
208 return 0
209}
210
211func main(argc: i64, argv: *i64) -> i64 {
212 if argc >= 4 {
213 // CLI generate
214 let name: *u8 = argv[1] as *u8
215 let theme: *u8 = argv[2] as *u8
216 let typ: *u8 = argv[3] as *u8
217 var diff: *u8 = "normal" as *u8
218 if argc >= 5 { diff = argv[4] as *u8 }
219 let ftab: *i64 = sys_mmap(K_MAGIC_2048) as *i64
220 if gg_build_fsm(typ, ftab) == 0 { gg_w(1, "GAMEGEN2 REFUSED: unknown type (no build_" as *u8); gg_w(1, typ); gg_w(1, ".spec)\n" as *u8); sys_exit(1); return 1 }
221 let path: *u8 = sys_mmap(256)
222 var o: i64 = gg_cat(path, 0, "knowledge/specs/" as *u8); o = gg_cat(path, o, name); o = gg_cat(path, o, ".spec" as *u8); path[o] = 0 as u8
223 let fd: i64 = sys_openat_wr(path, 0x1a4)
224 if fd < 0 { gg_w(1, "GAMEGEN2 REFUSED: cannot open spec path\n" as *u8); sys_exit(1); return 1 }
225 gg_emit_spec(fd, name, theme, typ, gg_tier(diff), ftab)
226 sys_close(fd)
227 gg_w(1, "GAMEGEN2 GREEN name=" as *u8); gg_w(1, name); gg_w(1, " type=" as *u8); gg_w(1, typ)
228 gg_w(1, " -> " as *u8); gg_w(1, path); gg_c(1, 10)
229 sys_exit(0); return 0
230 }
231
232 // SELF-GATE: generate all 6 proven types, prove each emitted FSM == its build-spec source.
233 let N: i64 = 6
234 let types: *i64 = sys_mmap(N * 8) as *i64
235 types[0] = "dungenc" as *u8 as i64
236 types[1] = "twrwave" as *u8 as i64
237 types[2] = "abround" as *u8 as i64
238 types[3] = "stealth" as *u8 as i64
239 types[4] = "datesim" as *u8 as i64
240 types[5] = "fightfsm" as *u8 as i64
241
242 let src: *i64 = sys_mmap(K_MAGIC_2048) as *i64
243 let gen: *i64 = sys_mmap(K_MAGIC_2048) as *i64
244 let sigs: *i64 = sys_mmap(N * 8) as *i64
245 var navail: i64 = 0
246 var sourced: i64 = 0
247 var t: i64 = 0
248 while t < N {
249 let typ: *u8 = types[t] as *u8
250 if gg_build_fsm(typ, src) == 1 {
251 navail = navail + 1
252 // emit to scratch game-spec
253 let sp: *u8 = sys_mmap(256)
254 var o: i64 = gg_cat(sp, 0, "/tmp/_ggtest_" as *u8); o = gg_cat(sp, o, typ); o = gg_cat(sp, o, ".spec" as *u8); sp[o] = 0 as u8
255 let fd: i64 = sys_openat_wr(sp, 0x1a4)
256 if fd >= 0 {
257 gg_emit_spec(fd, "_ggtest" as *u8, "PROOF" as *u8, typ, 1, src)
258 sys_close(fd)
259 // re-parse the emitted game-spec FSM
260 let buf: *u8 = sys_mmap(K_MAGIC_65536)
261 let nn: i64 = gg_read(sp, buf, K_MAGIC_65536)
262 if nn > 0 { if gg_extract_fsm(buf, nn, 0, gen) == 1 {
263 if gg_fsm_eq(src, gen) == 1 { sourced = sourced + 1; sigs[t] = gg_fsm_sig(gen) }
264 } }
265 }
266 } else { sigs[t] = 0 }
267 t = t + 1
268 }
269 // distinctness over the sourced tables
270 var distinct: i64 = 0
271 var a: i64 = 0
272 while a < N {
273 if sigs[a] != 0 {
274 var seen: i64 = 0
275 var b: i64 = 0
276 while b < a { if sigs[b] == sigs[a] { seen = 1 } b = b + 1 }
277 if seen == 0 { distinct = distinct + 1 }
278 }
279 a = a + 1
280 }
281 // negative control: an unknown type must NOT yield a build fsm
282 let bog: *i64 = sys_mmap(K_MAGIC_2048) as *i64
283 var neg: i64 = 1
284 if gg_build_fsm("nosuchtype" as *u8, bog) == 1 { neg = 0 }
285
286 var ok: i64 = 1
287 if sourced != navail { ok = 0 }
288 if navail < 2 { ok = 0 }
289 if distinct < 2 { ok = 0 }
290 if neg != 1 { ok = 0 }
291
292 gg_gate_report(1, navail, sourced, distinct, neg, ok)
293 let lf: i64 = sys_openat_append("knowledge/status/game_gen2.log" as *u8, 420)
294 if lf >= 0 { gg_gate_report(lf, navail, sourced, distinct, neg, ok); sys_close(lf) }
295 if ok == 1 { sys_exit(0); return 0 }
296 sys_exit(1); return 1
297}