nx_games_publish.nx source
↩ module page · 189 lines · 7390 B
1// nx_games_publish.nx -- sovereign emitter: content/games.cards ->
2// dist/games/index.html (the arcade page).
3//
4// Data-driven sibling of nx_hub_emit (see [[project-nishi-hub-games-publish-2026-06-30]]).
5// Each published game renders with a version badge (the value the game-
6// update API channels off) + a Play CTA; "soon" games render disabled.
7// File-I/O only -> imports the leaf x86_64 syscalls ONLY (builds clean).
8// Run from the nishi-pages checkout; dist/games must exist.
9//
10// Build: ./nxc2.exe --target x86_64 runtime/nx_games_publish.nx > /tmp/gp.s
11// wsl bash -c "gcc -no-pie /tmp/gp.s -o /tmp/nx_games_publish"
12// Run: cd nishi-pages && mkdir -p dist/games && /tmp/nx_games_publish
13//
14// nx_capability_claims:
15// needs: [x86_64_syscalls, pipe_delimited_parse]
16// provides: [games_arcade_emit, version_badge, data_driven_publish]
17// safety: [html_escape_text_fields, no_floating_point, fixed_output_path]
18// verdict: [exit_0_ok]
19// license: ORIGINAL
20// kind: nishi_pages_specialist
21// layer: L3
22// raci: [R=nishi_pages_builder, A=elder_west, C=games_cards_owner, I=conductor]
23
24import "nx_syscalls_x86_64.nx"
25const K_MAGIC_262144: i64 = 262144
26
27func gp_puts(buf: *u8, off: i64, s: *u8) -> i64 {
28 var o: i64 = off
29 var i: i64 = 0
30 while s[i] != 0 {
31 buf[o] = s[i]
32 o = o + 1
33 i = i + 1
34 }
35 return o
36}
37
38func gp_putb(buf: *u8, off: i64, src: *u8, n: i64) -> i64 {
39 var o: i64 = off
40 var i: i64 = 0
41 while i < n {
42 buf[o] = src[i]
43 o = o + 1
44 i = i + 1
45 }
46 return o
47}
48
49func gp_put_esc(buf: *u8, off: i64, src: *u8, n: i64) -> i64 {
50 var o: i64 = off
51 var i: i64 = 0
52 while i < n {
53 let c: i64 = src[i] as i64
54 if c == 0x26 {
55 o = gp_puts(buf, o, "&" as *u8)
56 } else {
57 if c == 0x3c {
58 o = gp_puts(buf, o, "<" as *u8)
59 } else {
60 if c == 0x3e {
61 o = gp_puts(buf, o, ">" as *u8)
62 } else {
63 buf[o] = src[i]
64 o = o + 1
65 }
66 }
67 }
68 i = i + 1
69 }
70 return o
71}
72
73func gp_eq(buf: *u8, off: i64, n: i64, lit: *u8) -> i64 {
74 var i: i64 = 0
75 while lit[i] != 0 {
76 if i >= n { return 0 }
77 if (buf[off + i] as i64) != (lit[i] as i64) { return 0 }
78 i = i + 1
79 }
80 if i != n { return 0 }
81 return 1
82}
83
84func gp_split(buf: *u8, ls: i64, le: i64, foff: *i64, flen: *i64) -> i64 {
85 var fi: i64 = 0
86 var s: i64 = ls
87 var i: i64 = ls
88 while i < le {
89 if (buf[i] as i64) == 0x7c {
90 if fi < 6 {
91 foff[fi] = s
92 flen[fi] = i - s
93 fi = fi + 1
94 }
95 s = i + 1
96 }
97 i = i + 1
98 }
99 if fi < 6 {
100 foff[fi] = s
101 flen[fi] = le - s
102 fi = fi + 1
103 }
104 return fi
105}
106
107func main() -> i64 {
108 let len_p: *i64 = sys_mmap(8) as *i64
109 let data: *u8 = sys_read_file_x86_64("content/games.cards" as *u8, len_p)
110 if data == (0 as *u8) { return 5 }
111 let dn: i64 = len_p[0]
112
113 let out: *u8 = sys_mmap(K_MAGIC_262144)
114 var o: i64 = 0
115
116 o = gp_puts(out, o, "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Games · Nishi Family</title><link rel=\"stylesheet\" href=\"/assets/style.css\"><style>.app-soon{opacity:.55}.gver{display:inline-block;margin-left:8px;padding:1px 7px;font-size:.6em;vertical-align:middle;border:1px solid #2f6f4f;border-radius:999px;color:#7fd9a6;background:#10231a}.gplay{display:inline-block;margin-top:10px;padding:6px 14px;border-radius:8px;background:#2f6f4f;color:#eafff3;font-weight:600;font-size:.9em}.gnote{color:#8a8d98;max-width:640px;margin:0 auto 22px}</style></head><body><header class=\"site-header\"><a class=\"site-title\" href=\"/\">Nishi Family</a><nav><a href=\"/hub\">Hub</a><a href=\"/games\">Games</a><a href=\"/video/\">Video</a><a href=\"/pages/about\">About</a></nav></header><main><section class=\"hero\"><h1>Games</h1><p class=\"tagline\">Sovereign WebAssembly games. No install, no store account — just open and play.</p></section><p class=\"gnote\">Every game is compiled bits-up to WebAssembly and pinned to a version. Published games update automatically when a new version ships (watch the badge).</p><section class=\"apps\"><div class=\"app-grid\" id=\"gamegrid\">" as *u8)
117
118 let foff: *i64 = sys_mmap(64) as *i64
119 let flen: *i64 = sys_mmap(64) as *i64
120
121 var ls: i64 = 0
122 while ls < dn {
123 var le: i64 = 0 - 1
124 var k: i64 = ls
125 while k < dn {
126 if le < 0 {
127 if (data[k] as i64) == 0x0a { le = k }
128 }
129 k = k + 1
130 }
131 if le < 0 { le = dn }
132
133 var skip: i64 = 0
134 if le <= ls { skip = 1 }
135 if skip == 0 {
136 if (data[ls] as i64) == 0x23 { skip = 1 }
137 }
138
139 if skip == 0 {
140 let nf: i64 = gp_split(data, ls, le, foff, flen)
141 if nf >= 6 {
142 let is_soon: i64 = gp_eq(data, foff[0], flen[0], "soon" as *u8)
143 o = gp_puts(out, o, "<a class=\"app-card" as *u8)
144 if is_soon == 1 { o = gp_puts(out, o, " app-soon" as *u8) }
145 o = gp_puts(out, o, "\" href=\"" as *u8)
146 if is_soon == 1 {
147 o = gp_puts(out, o, "#" as *u8)
148 } else {
149 o = gp_putb(out, o, ((data as i64) + foff[3]) as *u8, flen[3])
150 }
151 o = gp_puts(out, o, "\" data-kw=\"" as *u8)
152 o = gp_put_esc(out, o, ((data as i64) + foff[2]) as *u8, flen[2])
153 o = gp_puts(out, o, " " as *u8)
154 o = gp_put_esc(out, o, ((data as i64) + foff[5]) as *u8, flen[5])
155 o = gp_puts(out, o, "\"><span class=\"app-emoji\">" as *u8)
156 o = gp_putb(out, o, ((data as i64) + foff[1]) as *u8, flen[1])
157 o = gp_puts(out, o, "</span><h3>" as *u8)
158 o = gp_put_esc(out, o, ((data as i64) + foff[2]) as *u8, flen[2])
159 if is_soon == 0 {
160 o = gp_puts(out, o, " <span class=\"gver\">" as *u8)
161 o = gp_putb(out, o, ((data as i64) + foff[4]) as *u8, flen[4])
162 o = gp_puts(out, o, "</span>" as *u8)
163 }
164 o = gp_puts(out, o, "</h3><p>" as *u8)
165 o = gp_put_esc(out, o, ((data as i64) + foff[5]) as *u8, flen[5])
166 o = gp_puts(out, o, "</p>" as *u8)
167 if is_soon == 0 {
168 o = gp_puts(out, o, "<span class=\"gplay\">▶ Play</span>" as *u8)
169 }
170 o = gp_puts(out, o, "</a>" as *u8)
171 }
172 }
173
174 ls = le + 1
175 }
176
177 o = gp_puts(out, o, "</div></section></main><footer><p>© 2026 Elder West. Served by NishiVM — sovereign substrate.</p></footer></body></html>" as *u8)
178
179 let fd: i64 = sys_openat_wr("dist/games/index.html" as *u8, 420)
180 if fd < 0 { return 6 }
181 sys_write(fd, out, o)
182 sys_close(fd)
183
184 let banner: *u8 = "nx_games_publish: wrote dist/games/index.html\n" as *u8
185 var bn: i64 = 0
186 while banner[bn] != 0 { bn = bn + 1 }
187 sys_write(1, banner, bn)
188 return 0
189}