code wiki / _hdl_build / nx_browser.nx
nx_browser.nx source
↩ module page · 1470 lines · 77361 B
1// nx_browser.nx -- the LIVE, NAVIGABLE sovereign browser WINDOW. Fetches a URL over our own DNS+TLS1.3+
2// HTTP, renders it with the full upgraded pipeline (chrome/hidden-drop -> complex-selector cascade ->
3// float/grid/table-cell/word-wrap layout), and shows it in a real scrollable X11 window (WSLg). CLICK a
4// link to navigate (own URL resolver), 'b' = back, scroll wheel / arrows, q quits. 100% sovereign -- no
5// Chrome/V8/curl/gcc. Initial URL from _url.txt. license_tier: ORIGINAL
6import "nx_syscalls.nx"
7import "nx_tier.nx" // defines `type nx_size = i64`. br_comp_border casts NX_CSS_COLOR_BYTES to it,
8 // and WITHOUT this import that cast resolves to an UNKNOWN TYPE, which desyncs
9 // the parser and surfaces far away as "unexpected operator token kind=46" (TK_EQ).
10 // Bisected 2026-08-16: adding this import removes that desync outright.
11import "nx_x509_trust_store.nx"
12import "nx_trust_store_load_from_certdata.nx"
13import "nx_https_get.nx"
14import "nx_http_response_parse.nx"
15import "nx_html_tokenizer.nx"
16import "nx_js_eval.nx" // R-JS-DOMWRITE: js_render_page + js_has_inline_script (run page JS, render DOM mutations)
17import "nx_dom_query.nx"
18import "nx_html_entities.nx"
19import "nx_url_resolve.nx"
20import "nx_render_html.nx"
21import "nx_css_color_decode.nx"
22import "nx_layout_box.nx"
23import "nx_layout_default_display.nx"
24import "nx_layout_from_dom.nx"
25import "nx_layout_block.nx"
26import "nx_x11_connect.nx"
27import "nx_x11_window.nx"
28import "nx_x11_paint.nx"
29import "nx_http_get_ua.nx"
30import "nx_dns_resolve_a_record.nx"
31import "nx_paint_solid_rect.nx"
32import "nx_paint_text.nx"
33import "nx_png_write.nx"
34import "nx_signal.nx"
35import "nx_browser_buyguard.nx" // "before you buy" pass: splice an opportunity-cost overlay into the page
36import "nx_intake_profile.nx" // read the operator's own intake numbers (their real $ vs what it costs)
37import "nx_media_harvest.nx" // 'm' key: panel emitter + kind labels for the media source list
38import "nx_media_deepcrawl.nx" // 'm' key: DEEP crawl -- follow the page's css/js (incl. JS-constructed URLs) and harvest their media
39
40struct Page {
41 buf: *u8,
42 tree: *LayoutTree,
43 computed: *CssComputedDecl,
44 ncomp: i64,
45 page_h: i64,
46 bhref_off: *i64, // per box_idx: href byte-offset into buf, or -1
47 bhref_len: *i64,
48 ok: i64,
49 raw: *u8, // fetched response body (kept so resize can re-layout WITHOUT re-fetching)
50 raw_len: i64
51}
52const NX_PAGE_BYTES: i64 = 80
53const NX_CHROME_H: i64 = 44 // browser chrome (toolbar + address bar) height; page viewport is below it
54
55// ---- headless-mode constants, NAMED FOR PURPOSE (rule 11; the magic ratchet refused these as inline
56// literals on 2026-08-27 and it was right to). BR_PATH_MAX is the OS path bound, not a guess: the
57// sweep composes <compare-root>/<domain>/index.html into it and the domain comes from a list whose
58// rows are already shorter than a path. BR_DEFAULT_VW matches the window's own WW so a headless shot
59// lays out at the SAME width the windowed browser would use -- two different numbers there would make
60// the headless render a measurement of something the user never sees.
61const BR_PATH_MAX: i64 = 4096
62const BR_DEFAULT_VW: i64 = 1000
63const BR_ASCII_NL: i64 = 10
64const BR_ASCII_CR: i64 = 13
65const BR_ASCII_HASH: i64 = 35
66const BR_NAME_COL: i64 = 20 // report column width for a domain name
67
68func br_slen(s: *u8) -> i64 { var k: i64=0; while s[k]!=(0 as u8){k=k+1} return k }
69func br_p(fd: i64, s: *u8) -> i64 { sys_write(fd, s, br_slen(s)); return 0 }
70func br_n(fd: i64, v0: i64) -> i64 { var v: i64=v0; if v<0 { let m: *u8=sys_mmap(8); m[0]=45 as u8; sys_write(fd,m,1); v=0-v } let b: *u8=sys_mmap(24); var k: i64=0; if v==0{b[0]=48;k=1} while v>0{b[k]=(48+(v%10)) as u8;v=v/10;k=k+1} let o: *u8=sys_mmap(24); var j: i64=0; while j<k{o[j]=b[k-1-j];j=j+1} sys_write(fd,o,k); return 0 }
71
72// write buf[0..n) to path (O_CREAT|O_TRUNC|O_WRONLY, 0644). returns bytes written or -1.
73func br_write_file(path: *u8, buf: *u8, n: i64) -> i64 {
74 let fd: i64 = __syscall(257, 0-100, path as i64, 577, 420, 0, 0) // openat(AT_FDCWD, path, O_WRONLY|O_CREAT|O_TRUNC, 0644)
75 if fd < 0 { return 0 - 1 }
76 var done: i64 = 0
77 while done < n { let w: i64 = sys_write(fd, ((buf as i64)+done) as *u8, n-done); if w <= 0 { done = n } else { done = done + w } }
78 __syscall(3, fd, 0, 0, 0, 0, 0) // close
79 return n
80}
81
82// 'm' key: DEEP-harvest every image/gif/video/stream/audio SOURCE URL reachable through THIS page's resource
83// graph -- it follows the CSS/JS the page pulls in, INCLUDING asset URLs the JS constructs from string constants
84// (reconstructed by nx_js_urlfold WITHOUT executing anything), so runtime-loaded art on Unity/heavy-JS pages
85// surfaces with no manual network capture. Prints the list to the terminal + (re)writes the shared media panel.
86// Budget-bounded (16 fetches / depth 1) so the window only blocks briefly. Never runs the page's JS.
87func br_media_dump(store: *TrustStore, cur_url: *u8, ulen: i64) -> i64 {
88 br_p(1, "nx_browser: harvesting media (deep crawl -- following this page's css/js)...\n" as *u8)
89 let M: *MediaSet = nx_mediaset_new()
90 let fetched: i64 = nx_media_deepcrawl(cur_url, ulen, store, M, 16, 1, 1, "knowledge/status/media_panel_raw.html" as *u8)
91 br_p(1, "nx_browser: fetched " as *u8); br_n(1, fetched); br_p(1, " resources, media sources = " as *u8); br_n(1, M.cnt); br_p(1, "\n" as *u8)
92 var d: i64 = 0
93 while d < M.cnt {
94 let bp: *u8 = ((M.buf as i64) + M.off[d]) as *u8
95 br_p(1, " [" as *u8); br_p(1, nx_media_kind_label(M.kind[d])); br_p(1, "] " as *u8)
96 sys_write(1, bp, M.len[d]); br_p(1, "\n" as *u8)
97 d = d + 1
98 }
99 let H: *u8 = sys_mmap(12582912)
100 let hlen: i64 = nx_media_emit_panel(H, cur_url, ulen, M.buf, M.off, M.len, M.kind, M.cnt)
101 br_write_file("knowledge/status/media_panel.html" as *u8, H, hlen)
102 br_p(1, "nx_browser: media panel -> knowledge/status/media_panel.html (open in any browser)\n" as *u8)
103 return 0
104}
105
106func br_comp_int(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, prop: *u8, plen: i64, dflt: i64) -> i64 {
107 var j: i64 = ncomp - 1
108 while j >= 0 {
109 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl
110 if cd.element_idx == box_idx { if cd.prop_len == plen {
111 var ok: i64 = 1; var k: i64 = 0
112 while k < plen { if (src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { ok=0; k=plen } else { k=k+1 } }
113 if ok == 1 {
114 var v: i64 = 0; var any: i64 = 0; var i: i64 = 0
115 while i < cd.val_len { let c: i64=src[cd.val_off+i]&0xff; if c>=48 { if c<=57 { v=v*10+(c-48); any=1 } } i=i+1 }
116 if any == 1 { return v }
117 return dflt
118 }
119 } }
120 j = j - 1
121 }
122 return dflt
123}
124func br_comp_color(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, prop: *u8, plen: i64) -> i64 {
125 var j: i64 = ncomp - 1
126 while j >= 0 {
127 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl
128 if cd.element_idx == box_idx { if cd.prop_len == plen {
129 var ok: i64 = 1; var k: i64 = 0
130 while k < plen { if (src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { ok=0; k=plen } else { k=k+1 } }
131 if ok == 1 {
132 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor
133 var got: i64 = 0
134 if cd.val_kind == NX_CSS_VAL_HEXCOLOR { got = nx_css_color_decode(src, cd.val_off, cd.val_len, col) }
135 else { if cd.val_kind == NX_CSS_VAL_IDENT { got = nx_css_color_named(src, cd.val_off, cd.val_len, col); if got != 1 { got = nx_css_color_rgb_func(src, cd.val_off, cd.val_len, col) } } }
136 if got == 1 { if col.a != 0 { return (col.r << 16) | (col.g << 8) | col.b } }
137 return 0 - 1
138 }
139 } }
140 j = j - 1
141 }
142 return 0 - 1
143}
144
145// Extract elements (tag/id/class) AND per-element href, mirroring rh_extract's chrome-skip EXACTLY (so the
146// element order == the layout box order). ehref_off/ehref_len get the <a href> span, or -1.
147func br_extract(html: *u8, hlen: i64, elements: *CssElement, ehref_off: *i64, ehref_len: *i64, maxn: i64) -> i64 {
148 let c: *HtmlCursor=sys_mmap(24) as *HtmlCursor; nx_html_cursor_init(c,html,hlen)
149 let tok: *HtmlToken=sys_mmap(56) as *HtmlToken
150 let ao: *i64=sys_mmap(8) as *i64; let al: *i64=sys_mmap(8) as *i64; let bo: *i64=sys_mmap(8) as *i64; let bl: *i64=sys_mmap(8) as *i64
151 let ho: *i64=sys_mmap(8) as *i64; let hl: *i64=sys_mmap(8) as *i64
152 var n: i64=0
153 var cskip: i64=0
154 while 1==1 {
155 nx_html_next_token(c,tok)
156 if tok.kind==NX_HTML_TOK_EOF { return n }
157 if n>=maxn { return n }
158 var ie: i64=0
159 if cskip > 0 {
160 if tok.kind==NX_HTML_TOK_START_TAG { cskip = cskip + 1 }
161 if tok.kind==NX_HTML_TOK_END_TAG { cskip = cskip - 1 }
162 } else {
163 if tok.kind==NX_HTML_TOK_START_TAG {
164 if _lfd_is_chrome_start(html, tok.name_off, tok.name_len, tok.src_off, tok.src_len) == 1 { cskip = 1 }
165 else { ie = 1 }
166 }
167 if tok.kind==NX_HTML_TOK_SELF_CLOSING { ie = 1 }
168 }
169 if ie==1 {
170 let e: *CssElement=((elements as i64)+n*NX_CSS_ELEMENT_BYTES) as *CssElement
171 e.src=html; e.tag_off=tok.name_off; e.tag_len=tok.name_len
172 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"id\x00" as *u8,ao,al)==1 { e.id_off=ao[0]; e.id_len=al[0] } else { e.id_off=0; e.id_len=0 }
173 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"class\x00" as *u8,bo,bl)==1 { e.class_off=bo[0]; e.class_len=bl[0] } else { e.class_off=0; e.class_len=0 }
174 ehref_off[n] = 0 - 1; ehref_len[n] = 0 - 1
175 if tok.name_len==1 { if (html[tok.name_off]&0xff)==97 { // <a>
176 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"href\x00" as *u8,ho,hl)==1 { ehref_off[n]=ho[0]; ehref_len[n]=hl[0] }
177 } }
178 n=n+1
179 }
180 }
181 return n
182}
183
184// Fetch `url` (own DNS+TLS+HTTP), run the full pipeline, fill `page`. page.ok=1 on success.
185func br_isdigit(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 }
186
187// "http://" (plain) but NOT "https://" -> 1
188func br_is_plain_http(url: *u8, ulen: i64) -> i64 {
189 if ulen < 7 { return 0 }
190 if (url[0] & 0xff) != 104 { return 0 } // h
191 if (url[1] & 0xff) != 116 { return 0 } // t
192 if (url[2] & 0xff) != 116 { return 0 } // t
193 if (url[3] & 0xff) != 112 { return 0 } // p
194 if (url[4] & 0xff) != 58 { return 0 } // :
195 if (url[5] & 0xff) != 47 { return 0 } // /
196 if (url[6] & 0xff) != 47 { return 0 } // /
197 return 1
198}
199
200// dotted-quad "a.b.c.d" -> packed (a<<24|b<<16|c<<8|d), or -1 if not a literal IPv4
201func br_parse_ipv4(s: *u8, len: i64) -> i64 {
202 var parts: i64 = 0
203 var cur: i64 = 0
204 var any: i64 = 0
205 var packed: i64 = 0
206 var i: i64 = 0
207 while i < len {
208 let c: i64 = s[i] & 0xff
209 if c == 46 {
210 if any == 0 { return 0 - 1 }
211 if cur > 255 { return 0 - 1 }
212 packed = (packed << 8) | cur
213 parts = parts + 1; cur = 0; any = 0
214 } else {
215 if br_isdigit(c) == 0 { return 0 - 1 }
216 cur = cur * 10 + (c - 48); any = 1
217 }
218 i = i + 1
219 }
220 if any == 0 { return 0 - 1 }
221 if cur > 255 { return 0 - 1 }
222 packed = (packed << 8) | cur
223 parts = parts + 1
224 if parts != 4 { return 0 - 1 }
225 return packed
226}
227
228// Sovereign plain-HTTP/1.1 fetch of an http:// URL into out (<=cap). IP-literal
229// hosts (localhost daemons) parsed directly; hostnames resolved via our own DNS.
230// Returns bytes read (full raw response, headers+body) or -1. NO TLS, NO 3rd party.
231func br_http_fetch(url: *u8, ulen: i64, out: *u8, cap: i64) -> i64 {
232 var p: i64 = 7 // skip "http://"
233 let host: *u8 = sys_mmap(512)
234 var hlen: i64 = 0
235 var go: i64 = 1
236 while go == 1 {
237 if p >= ulen { go = 0 }
238 else {
239 let c: i64 = url[p] & 0xff
240 if c == 58 { go = 0 } else { if c == 47 { go = 0 } else { host[hlen] = url[p]; hlen = hlen + 1; p = p + 1 } }
241 }
242 }
243 host[hlen] = 0 as u8
244 if hlen == 0 { return 0 - 1 }
245
246 var port: i64 = 80
247 if p < ulen { if (url[p] & 0xff) == 58 {
248 p = p + 1; port = 0
249 var g2: i64 = 1
250 while g2 == 1 {
251 if p >= ulen { g2 = 0 }
252 else {
253 let c: i64 = url[p] & 0xff
254 if br_isdigit(c) == 1 { port = port * 10 + (c - 48); p = p + 1 } else { g2 = 0 }
255 }
256 }
257 } }
258
259 let path: *u8 = sys_mmap(2048)
260 var plen: i64 = 0
261 while p < ulen { path[plen] = url[p]; plen = plen + 1; p = p + 1 }
262 if plen == 0 { path[0] = 47 as u8; plen = 1 } // "/"
263 path[plen] = 0 as u8
264
265 var ipv4: i64 = br_parse_ipv4(host, hlen)
266 if ipv4 < 0 {
267 let dns: *DnsResolveResult = nx_dns_resolve_default(host, hlen, 305419896)
268 if dns.verdict != NX_DNS_R_OK { return 0 - 1 }
269 if dns.ipv4_packed == 0 { return 0 - 1 }
270 ipv4 = dns.ipv4_packed
271 }
272 let sa: *u8 = sys_mmap(16)
273 nx_https_build_sockaddr(sa, ipv4, port)
274 let status: *i64 = sys_mmap(8) as *i64
275 let ua: *u8 = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0\x00"
276 return nx_http_get_ua(sa, path, plen, host, hlen, ua, br_slen(ua), out, cap, status)
277}
278
279// layout-only pass: (re)build the style buffer + lay out page.raw at viewport width `vw`.
280// Split from the fetch so a window RESIZE re-lays-out at the new width with NO network round-trip.
281func br_layout(page: *Page, vw: i64) -> i64 {
282 page.ok = 0
283 let body: *u8 = page.raw
284 let body_len: i64 = page.raw_len
285
286 let css: *u8 = "h1{background-color:#a8c8ff;margin-top:24px;margin-bottom:10px;font-size:40px}h2{background-color:#d0d0d0;margin-top:20px;margin-bottom:8px;font-size:28px}h3{background-color:#e4e4e4;margin-top:16px;margin-bottom:6px;font-size:24px}body{background-color:#ffffff;padding-left:40px;padding-right:40px;padding-top:14px;max-width:900px}p{margin-top:12px;margin-bottom:12px}ul{padding-left:16px;margin-top:3px;margin-bottom:3px}ol{padding-left:24px;margin-top:3px;margin-bottom:3px}li{margin-bottom:2px}tr{display:flex}th{font-weight:bold;padding-left:4px;padding-right:4px}td{padding-left:4px;padding-right:4px}a{color:#3366cc}b{font-weight:bold}strong{font-weight:bold}i{font-style:italic}em{font-style:italic}.vector-menu-content-list{display:none}.vector-dropdown{display:none}.mw-interlanguage-selector{display:none}.wbc-editpage{display:none}.infobox{float:right;width:300px;margin-left:8px;background-color:#f8f8f8}.nishi-buyguard{background-color:#fff3cd;color:#332701;padding-left:14px;padding-right:14px;padding-top:8px;padding-bottom:8px;margin-bottom:10px;font-size:18px}\x00"
287 let clen: i64 = br_slen(css)
288 let STYCAP: i64 = 262144
289 let buf: *u8 = sys_mmap(body_len + STYCAP + clen + 16)
290 var bi: i64 = 0
291 while bi < body_len { buf[bi]=body[bi]; bi=bi+1 }
292 // CASCADE ORIGIN ORDER (07-09 root fix, twin of nx_browser_render.nx): UA defaults FIRST, author
293 // <style> AFTER -- the last-match resolvers now let author rules override the defaults (correct CSS
294 // origin order); before this the defaults silently beat every author color on styled pages.
295 var ci: i64 = 0
296 while ci < clen { buf[body_len + ci] = css[ci]; ci=ci+1 }
297 let rawsty: *u8 = sys_mmap(STYCAP)
298 let rawstylen: i64 = rh_extract_styles(body, body_len, rawsty, STYCAP)
299 // VIEWPORT-CORRECT @media (2026-08-25): VW is now computed BEFORE the media filter, so @media rules are
300 // selected at the SAME width the page is laid out at. This call used to pass a literal 1000 while VW was
301 // derived two lines BELOW it, so every responsive page was STYLED for a 1000px viewport and LAID OUT at
302 // the real window width -- and because br_layout is re-run on resize (see the docstring), a resize
303 // re-laid-out without ever re-selecting the styles. The sibling render core already passed the real vw
304 // (nx_browser_render.nx:567); this is that half of the same fix landing in the shipped browser.
305 var VW: i64 = vw
306 if VW < 200 { VW = 200 }
307 let stylen: i64 = rh_filter_media(rawsty, 0, rawstylen, ((buf as i64)+body_len+clen) as *u8, STYCAP, VW)
308 let total: i64 = body_len + clen + stylen
309 let boxes: *LayoutBox = (sys_mmap(NX_LAYOUT_BOX_BYTES*16384+16)) as *LayoutBox
310 let tree: *LayoutTree = (sys_mmap(64)) as *LayoutTree
311 nx_layout_tree_init(tree, boxes, 16384)
312 let stk: *LayoutFromDomStack = (sys_mmap(NX_LAYOUT_FROM_DOM_STACK_BYTES+8)) as *LayoutFromDomStack
313 let sidx: *i64 = (sys_mmap(8*128)) as *i64
314 nx_layout_from_dom_stack_init(stk, sidx, 128)
315 nx_layout_from_dom(buf, body_len, tree, stk)
316
317 let ext: *CssElement = (sys_mmap(NX_CSS_ELEMENT_BYTES*16384 as nx_size)) as *CssElement
318 let ehoff: *i64 = sys_mmap(8*16384) as *i64
319 let ehlen: *i64 = sys_mmap(8*16384) as *i64
320 let n_ext: i64 = br_extract(buf, body_len, ext, ehoff, ehlen, 16384)
321 let boxel: *CssElement = (sys_mmap((NX_CSS_ELEMENT_BYTES*(tree.count+1)) as nx_size)) as *CssElement
322 let bhoff: *i64 = sys_mmap(8*(tree.count+1)) as *i64
323 let bhlen: *i64 = sys_mmap(8*(tree.count+1)) as *i64
324 var k: i64 = 0
325 var ii: i64 = 0
326 while ii < tree.count {
327 let b: *LayoutBox = ((tree.boxes as i64)+ii*NX_LAYOUT_BOX_BYTES) as *LayoutBox
328 let e: *CssElement = ((boxel as i64)+ii*NX_CSS_ELEMENT_BYTES) as *CssElement
329 e.src = buf
330 bhoff[ii] = 0 - 1; bhlen[ii] = 0 - 1
331 var mapped: i64 = 0
332 if rh_is_elem(b.kind)==1 { if b.parent_idx != (0-1) { if k < n_ext {
333 let s: *CssElement = ((ext as i64)+k*NX_CSS_ELEMENT_BYTES) as *CssElement
334 e.tag_off=s.tag_off; e.tag_len=s.tag_len; e.id_off=s.id_off; e.id_len=s.id_len; e.class_off=s.class_off; e.class_len=s.class_len
335 bhoff[ii] = ehoff[k]; bhlen[ii] = ehlen[k]
336 k=k+1; mapped=1
337 } } }
338 if mapped==0 { e.tag_off=0; e.tag_len=0; e.id_off=0; e.id_len=0; e.class_off=0; e.class_len=0 }
339 ii = ii + 1
340 }
341
342 let cur: *CssCursor = (sys_mmap(NX_CSS_CURSOR_BYTES) as nx_size) as *CssCursor
343 nx_css_cursor_init(cur, buf, total); cur.pos = body_len
344 let tok: *CssToken = (sys_mmap(NX_CSS_TOKEN_BYTES) as nx_size) as *CssToken
345 let rules: *CssRule = (sys_mmap(NX_CSS_RULE_BYTES*8192 as nx_size)) as *CssRule
346 let decls: *CssDeclaration = (sys_mmap(NX_CSS_DECLARATION_BYTES*32768 as nx_size)) as *CssDeclaration
347 let st: *CssParseState = (sys_mmap(128) as nx_size) as *CssParseState
348 nx_css_parse_state_init(st, cur, tok, rules, 8192, decls, 32768)
349 nx_css_parse(st)
350 let computed: *CssComputedDecl = (sys_mmap(NX_CSS_COMPUTED_DECL_BYTES*262144 as nx_size)) as *CssComputedDecl
351 let cb: *i64 = (sys_mmap(8)) as *i64
352 rh_cascade(buf, rules, st.rule_count, decls, st.decl_count, boxel, tree.count, tree, computed, 262144, cb)
353 let ncomp: i64 = cb[0]
354 rh_promote_display_kinds(tree, computed, ncomp, buf) // inline + display:flex/grid/block -> BLOCK (sized + painted)
355
356 let pbuf: *u8 = sys_mmap(256)
357 let ltable: *LayoutPropTable = (sys_mmap(NX_LAYOUT_PROP_TABLE_BYTES)) as *LayoutPropTable
358 nx_layout_prop_table_init(ltable, pbuf)
359 let resolve: *CssResolveCtx = (sys_mmap(NX_CSS_RESOLVE_CTX_BYTES)) as *CssResolveCtx
360 resolve.root_font_size_px = 16; resolve.parent_font_size_px = 16; resolve.parent_dimension_px = VW
361 let lctx: *LayoutCtx = (sys_mmap(NX_LAYOUT_CTX_BYTES)) as *LayoutCtx
362 nx_layout_ctx_init(lctx, tree, computed, ncomp, buf, VW, resolve)
363 let page_h: i64 = nx_layout_block_layout(lctx, ltable, 0)
364
365 page.buf = buf; page.tree = tree; page.computed = computed; page.ncomp = ncomp
366 page.page_h = page_h; page.bhref_off = bhoff; page.bhref_len = bhlen; page.ok = 1
367 return 0
368}
369
370// load the operator's OWN financial profile (from their intake survey) into the buyguard profile array.
371// This is the local browser on the operator's machine -> respondent "operator"; ip_get_pos falls back to
372// safe defaults for any field they have not entered yet (so the guard works before the survey is filled).
373func bg_load_profile(prof: *i64) -> i64 {
374 let P: *u8 = "knowledge/store/intake-\x00" as *u8
375 let who: *u8 = "operator\x00" as *u8
376 prof[BGW_AVAIL] = ip_get_pos(P, who, "available_cash\x00" as *u8, 50000)
377 prof[BGW_BAL] = ip_get_pos(P, who, "debt_balance\x00" as *u8, 2000000)
378 prof[BGW_APR] = mny_rate_from_pct(ip_get_pos(P, who, "debt_apr_pct\x00" as *u8, 24), 1)
379 prof[BGW_PAY] = ip_get_pos(P, who, "monthly_payment\x00" as *u8, 60000)
380 prof[BGW_WAGE] = ip_get_pos(P, who, "hourly_wage\x00" as *u8, 2500)
381 prof[BGW_GOAL] = ip_get_pos(P, who, "buffer_goal\x00" as *u8, 100000)
382 prof[BGW_THRESH] = ip_get_pos(P, who, "pause_threshold\x00" as *u8, 10000)
383 return 0
384}
385
386// ---- DOCUMENT OMNITOOL: the browser natively opens documents (.docx/.md/.csv/...) it navigates to ----
387// A registered document extension diverts the fetched bytes through the nx_doc_view dispatch (data-driven
388// registry knowledge/doc_viewers.conf -> focused sovereign renderer organ), and the browser paints the
389// renderer's 0-JS HTML preview inline instead of dead-ending on a download. The renderers stay OUT of this
390// binary (fork-exec'd on demand) -- supporting more formats never grows the browser core.
391
392// lowercased extension of the URL's LAST path segment (after the host, before ?/#) into ext (NUL-term).
393// Returns 0 for: no path, no dot in the last segment, ext>12 chars, or html/htm (normal HTML pipeline).
394func br_doc_ext(url: *u8, ulen: i64, ext: *u8) -> i64 {
395 // end of path = first '?' or '#'
396 var pend: i64 = ulen
397 var i: i64 = 0
398 while i < ulen {
399 let c: i64 = url[i] & 0xff
400 if c == 63 { pend = i; i = ulen } else { if c == 35 { pend = i; i = ulen } else { i = i + 1 } }
401 }
402 // host starts after "://" (when present); a REAL path needs a '/' at/after hstart (bare hosts opt out)
403 var hstart: i64 = 0
404 var scanning: i64 = 1
405 i = 0
406 while scanning == 1 {
407 if i + 2 >= pend { scanning = 0 } else {
408 if url[i] == (58 as u8) { if url[i+1] == (47 as u8) { if url[i+2] == (47 as u8) { hstart = i + 3; scanning = 0 } } }
409 if scanning == 1 { i = i + 1 }
410 }
411 }
412 var slash: i64 = 0 - 1
413 var dot: i64 = 0 - 1
414 i = hstart
415 while i < pend {
416 let c: i64 = url[i] & 0xff
417 if c == 47 { slash = i; dot = 0 - 1 } else { if c == 46 { dot = i } }
418 i = i + 1
419 }
420 if slash < 0 { return 0 }
421 if dot < 0 { return 0 }
422 if dot <= slash { return 0 }
423 var k: i64 = 0
424 i = dot + 1
425 while i < pend {
426 if k >= 12 { return 0 }
427 var c2: i64 = url[i] & 0xff
428 if c2 >= 65 { if c2 <= 90 { c2 = c2 + 32 } }
429 ext[k] = c2 as u8
430 k = k + 1
431 i = i + 1
432 }
433 ext[k] = 0 as u8
434 if k == 0 { return 0 }
435 if k == 4 { if ext[0] == (104 as u8) { if ext[1] == (116 as u8) { if ext[2] == (109 as u8) { if ext[3] == (108 as u8) { return 0 } } } } }
436 if k == 3 { if ext[0] == (104 as u8) { if ext[1] == (116 as u8) { if ext[2] == (109 as u8) { return 0 } } } }
437 return k
438}
439
440// build the staging paths: pin = "/tmp/nx_bdoc_in.<ext>", pout = "/tmp/nx_bdoc_<ext>.html"
441func br_doc_paths(ext: *u8, extn: i64, pin: *u8, pout: *u8) -> i64 {
442 let a: *u8 = "/tmp/nx_bdoc_in.\x00" as *u8
443 var o: i64 = 0
444 while a[o] != 0 as u8 { pin[o] = a[o]; o = o + 1 }
445 var k: i64 = 0
446 while k < extn { pin[o + k] = ext[k]; k = k + 1 }
447 pin[o + k] = 0 as u8
448 let b: *u8 = "/tmp/nx_bdoc_\x00" as *u8
449 o = 0
450 while b[o] != 0 as u8 { pout[o] = b[o]; o = o + 1 }
451 k = 0
452 while k < extn { pout[o + k] = ext[k]; k = k + 1 }
453 o = o + k
454 let s: *u8 = ".html\x00" as *u8
455 k = 0
456 while s[k] != 0 as u8 { pout[o + k] = s[k]; k = k + 1 }
457 pout[o + k] = 0 as u8
458 return 0
459}
460
461// stage the fetched bytes and run the dispatch: 0 = converted (/tmp/nx_bdoc_<ext>.html written);
462// 3 = extension not registered; anything else = converter failure. All nonzero -> caller falls through.
463func br_doc_conv(ext: *u8, extn: i64, body: *u8, blen: i64, url: *u8) -> i64 {
464 let pin: *u8 = sys_mmap(64)
465 let pout: *u8 = sys_mmap(64)
466 br_doc_paths(ext, extn, pin, pout)
467 let fd: i64 = sys_openat_wr(pin, 0x1a4)
468 if fd < 0 { return 0 - 1 }
469 sys_write(fd, body, blen)
470 sys_close(fd)
471 let pid: i64 = sys_fork()
472 if pid == 0 {
473 let lfd: i64 = sys_openat_wr("/tmp/nx_bdoc.log\x00" as *u8, 0x1a4)
474 if lfd >= 0 { sys_dup3(lfd, 1, 0); sys_dup3(lfd, 2, 0) }
475 let argv: *i64 = sys_mmap(64) as *i64
476 argv[0] = "_offc/nx_doc_view.elf\x00" as *u8 as i64
477 argv[1] = pin as i64
478 argv[2] = pout as i64
479 argv[3] = url as i64
480 argv[4] = 0
481 let envp: *i64 = sys_mmap(16) as *i64
482 envp[0] = 0
483 sys_execve("_offc/nx_doc_view.elf\x00" as *u8, argv, envp)
484 sys_exit(127)
485 }
486 let st: *i64 = sys_mmap(16) as *i64
487 sys_wait4(pid, st, 0)
488 let sig: i64 = st[0] & 0x7f
489 if sig != 0 { return 128 + sig }
490 return (st[0] >> 8) & 0xff
491}
492
493// fetch <url> over our own DNS/TLS/HTTP, store the raw body in `page`, then lay it out at width `vw`.
494func load_page(url: *u8, ulen: i64, store: *TrustStore, cr: *u8, priv: *u8, page: *Page, vw: i64) -> i64 {
495 page.ok = 0
496 let now: i64 = sys_now_realtime_sec()
497 let cap: i64 = 8388608
498 let rbuf: *u8 = sys_mmap(cap)
499 // mutable current URL so we can FOLLOW REDIRECTS (apex->www, http->https, 30x Location) like a browser
500 let cururl: *u8 = sys_mmap(4096)
501 var curlen: i64 = ulen
502 if curlen > 4094 { curlen = 4094 }
503 var ci: i64 = 0
504 while ci < curlen { cururl[ci] = url[ci]; ci = ci + 1 }
505 cururl[curlen] = 0 as u8
506 let r: *i64 = nx_http_resp_alloc()
507 var n: i64 = 0 - 1
508 var pv: i64 = 0 - 1
509 var hops: i64 = 0
510 var go: i64 = 1
511 while go == 1 {
512 go = 0
513 n = 0 - 1
514 if br_is_plain_http(cururl, curlen) == 1 { n = br_http_fetch(cururl, curlen, rbuf, cap) }
515 else { n = nx_https_get(cururl, cr, priv, store, now, rbuf, cap) }
516 if n < 0 { return n } // propagate the specific failure code (-2 url, -3 connect/DNS, -4 handshake, -5 fetch)
517 pv = nx_http_response_parse(rbuf, n, r)
518 if pv == NX_HTTP_RESP_OK { if r[1] >= 300 { if r[1] < 400 { if hops < 6 {
519 let vo: *i64 = sys_mmap(16) as *i64
520 let vl: *i64 = sys_mmap(16) as *i64
521 if nx_http_response_header(rbuf, n, r, "location" as *u8, 8, vo, vl) == 1 {
522 let nb: *u8 = sys_mmap(4096)
523 let nl: *i64 = sys_mmap(16) as *i64
524 if nx_url_resolve(cururl, curlen, ((rbuf as i64) + vo[0]) as *u8, vl[0], nb, 4096, nl) == NX_URL_RESOLVE_OK {
525 var z: i64 = 0
526 while z < nl[0] { cururl[z] = nb[z]; z = z + 1 }
527 cururl[nl[0]] = 0 as u8; curlen = nl[0]; hops = hops + 1; go = 1
528 br_p(2, "nx_browser: redirect -> \x00" as *u8); sys_write(2, cururl, curlen); br_p(2, "\n\x00" as *u8)
529 }
530 }
531 } } } }
532 }
533 var body: *u8 = rbuf
534 var body_len: i64 = n
535 if pv == NX_HTTP_RESP_OK {
536 let bo: i64 = r[6]; let kind: i64 = r[8]
537 body = ((rbuf as i64) + bo) as *u8
538 body_len = n - bo
539 if kind == NX_HTTP_BODY_CONTENT_LENGTH { body_len = r[7]; if body_len > n - bo { body_len = n - bo } }
540 if kind == NX_HTTP_BODY_CHUNKED { let dcap: i64 = 8388608; let dbuf: *u8 = sys_mmap(dcap); let dl: i64 = nx_http_dechunk(body, n - bo, dbuf, dcap); if dl > 0 { body = dbuf; body_len = dl } }
541 }
542 // DOCUMENT OMNITOOL: registered doc extension -> convert via the nx_doc_view dispatch and paint the
543 // HTML preview inline (the browser opens .docx/.md/.csv/... natively). html/htm, unregistered exts and
544 // any converter failure fall through to the normal pipeline untouched -- graceful by construction.
545 let dext: *u8 = sys_mmap(32)
546 let dextn: i64 = br_doc_ext(cururl, curlen, dext)
547 if dextn > 0 {
548 let drc: i64 = br_doc_conv(dext, dextn, body, body_len, cururl)
549 if drc == 0 {
550 let pin2: *u8 = sys_mmap(64)
551 let pout2: *u8 = sys_mmap(64)
552 br_doc_paths(dext, dextn, pin2, pout2)
553 let dlp: *i64 = sys_mmap(16) as *i64
554 let dhtml: *u8 = sys_read_file(pout2, dlp)
555 if dlp[0] > 0 {
556 body = dhtml
557 body_len = dlp[0]
558 br_p(1, "nx_browser: doc omnitool rendered .\x00" as *u8)
559 sys_write(1, dext, dextn)
560 br_p(1, " inline\n\x00" as *u8)
561 }
562 }
563 }
564 // R-JS-DOMWRITE: if the page has an INLINE <script>, EXECUTE it against a mutable DOM and lay out the
565 // MUTATED html -- so JS-built content renders. RCDATA-safe round-trip; script-less pages skip this.
566 var rbody: *u8 = body
567 var rblen: i64 = body_len
568 if js_has_inline_script(body, body_len) == 1 {
569 let jscap: i64 = body_len + 1048576
570 let jsbuf: *u8 = sys_mmap(jscap)
571 let jslen: i64 = js_render_page(body, body_len, jsbuf, jscap)
572 if jslen > 0 { rbody = jsbuf; rblen = jslen }
573 }
574 // BUYGUARD: run the before-you-buy pass on the fetched page and splice the opportunity-cost overlay in
575 // as a top-of-page box (no price detected -> body returned untouched). Uses the operator's real numbers.
576 let bgprof: *i64 = sys_mmap(8 * 8) as *i64
577 bg_load_profile(bgprof)
578 let injcap: i64 = rblen + 65536
579 let injbuf: *u8 = sys_mmap(injcap)
580 let injlen: i64 = bgw_inject(rbody, rblen, bgprof, injbuf, injcap)
581 page.raw = injbuf; page.raw_len = injlen
582 return br_layout(page, vw)
583}
584
585// one toolbar button: a bordered face with a centered single-char label
586func br_button(c: *X11Conn, wid: i64, gc: i64, x: i64, label: *u8, fbody: i64) -> i64 {
587 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, x, 8, 30, 28) // border
588 nx_x11_set_gc_foreground(c, gc, 0x00F6F8FA); nx_x11_fill_rect(c, wid, gc, x + 1, 9, 28, 26) // face
589 nx_x11_set_gc_font(c, gc, fbody); nx_x11_set_gc_foreground(c, gc, 0x00202124)
590 nx_x11_poly_text8(c, wid, gc, x + 11, 27, label, 1)
591 return 0
592}
593
594// the browser chrome: toolbar background + Back/Forward/Reload buttons + the address bar (URL display)
595func br_chrome(c: *X11Conn, wid: i64, gc: i64, win_w: i64, cur_url: *u8, ulen: i64, fbody: i64) -> i64 {
596 nx_x11_set_gc_foreground(c, gc, 0x00DDE1E6); nx_x11_fill_rect(c, wid, gc, 0, 0, win_w, NX_CHROME_H)
597 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, 0, NX_CHROME_H - 1, win_w, 1)
598 br_button(c, wid, gc, 8, "<\x00" as *u8, fbody)
599 br_button(c, wid, gc, 44, ">\x00" as *u8, fbody)
600 br_button(c, wid, gc, 80, "R\x00" as *u8, fbody)
601 // address bar field (bordered white box) + the current URL
602 let bx: i64 = 118
603 let bw: i64 = win_w - bx - 10
604 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, bx, 8, bw, 28)
605 nx_x11_set_gc_foreground(c, gc, 0x00FFFFFF); nx_x11_fill_rect(c, wid, gc, bx + 1, 9, bw - 2, 26)
606 var maxc: i64 = (bw - 14) / 7
607 if maxc < 0 { maxc = 0 }
608 var n: i64 = ulen
609 if n > maxc { n = maxc }
610 if n > 250 { n = 250 }
611 nx_x11_set_gc_font(c, gc, fbody); nx_x11_set_gc_foreground(c, gc, 0x00202124)
612 if n > 0 { nx_x11_poly_text8(c, wid, gc, bx + 7, 27, cur_url, n) }
613 return 0
614}
615
616// chrome hit-test: 1=Back, 2=Forward, 3=Reload, 0=none (call only when click y < NX_CHROME_H)
617func br_chrome_click(ex: i64) -> i64 {
618 if ex >= 8 { if ex < 38 { return 1 } }
619 if ex >= 44 { if ex < 74 { return 2 } }
620 if ex >= 80 { if ex < 110 { return 3 } }
621 return 0
622}
623
624// INHERITED color resolve (CSS: `color` inherits) -- twin of nx_browser_render.nx's helper; walk up the
625// parent chain to the nearest computed color decl (author body/.doc colors now reach p/li/td text).
626func br_comp_color_inh(src: *u8, computed: *CssComputedDecl, ncomp: i64, tree: *LayoutTree, box_idx: i64) -> i64 {
627 var cur: i64 = box_idx
628 var guard: i64 = 0
629 while cur >= 0 {
630 if guard > 64 { return 0 - 1 }
631 guard = guard + 1
632 let c: i64 = br_comp_color(src, computed, ncomp, cur, "color\x00" as *u8, 5)
633 if c >= 0 { return c }
634 let b: *LayoutBox = ((tree.boxes as i64) + cur * NX_LAYOUT_BOX_BYTES) as *LayoutBox
635 cur = b.parent_idx
636 }
637 return 0 - 1
638}
639
640// background-color, else the `background` shorthand's single-hex value (twin of nx_browser_render.nx).
641func br_comp_bg(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64) -> i64 {
642 let c: i64 = br_comp_color(src, computed, ncomp, box_idx, "background-color\x00" as *u8, 16)
643 if c >= 0 { return c }
644 return br_comp_color(src, computed, ncomp, box_idx, "background\x00" as *u8, 10)
645}
646
647// `border` shorthand: parse "<w>px ... #rrggbb" from the swept value span (twin of nx_browser_render.nx).
648func br_comp_border(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, wout: *i64) -> i64 {
649 var j: i64 = ncomp - 1
650 while j >= 0 {
651 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl
652 if cd.element_idx == box_idx { if cd.prop_len == 6 {
653 let P: *u8 = "border\x00" as *u8
654 var ok: i64 = 1
655 var k: i64 = 0
656 while k < 6 { if (src[cd.prop_off+k]&0xff) != (P[k]&0xff) { ok=0; k=6 } else { k=k+1 } }
657 if ok == 1 {
658 var wv: i64 = 0
659 var i2: i64 = cd.val_off
660 let e2: i64 = cd.val_off + cd.val_len
661 while i2 < e2 { let ch: i64 = src[i2]&0xff; if ch>=48 { if ch<=57 { wv=wv*10+(ch-48); i2=i2+1 } else { i2=e2 } } else { i2=e2 } }
662 if wv <= 0 { wv = 1 }
663 if wv > 8 { wv = 8 }
664 var hp: i64 = 0 - 1
665 var i3: i64 = cd.val_off
666 while i3 < e2 { if src[i3]==(35 as u8) { hp=i3; i3=e2 } else { i3=i3+1 } }
667 if hp >= 0 {
668 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor
669 if nx_css_color_decode(src, hp+1, 6, col) == 1 {
670 wout[0] = wv
671 return (col.r << 16) | (col.g << 8) | col.b
672 }
673 }
674 return 0 - 1
675 }
676 } }
677 j = j - 1
678 }
679 return 0 - 1
680}
681
682func br_draw(c: *X11Conn, wid: i64, gc: i64, paper: i64, page: *Page, win_w: i64, win_h: i64, scroll_y: i64,
683 fbody: i64, fh2: i64, fh1: i64, cur_url: *u8, ulen: i64) -> i64 {
684 let tree: *LayoutTree = page.tree
685 let src: *u8 = page.buf
686 let computed: *CssComputedDecl = page.computed
687 let ncomp: i64 = page.ncomp
688 let decbuf: *u8 = sys_mmap(1100) // reused per text box: decode HTML entities before painting
689 nx_x11_fill_rect(c, wid, paper, 0, 0, win_w, win_h)
690 var i: i64 = 0
691 let bwp: *i64 = sys_mmap(16) as *i64 // border width out-slot (hoisted: no mmap in the loop)
692 while i < tree.count {
693 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox
694 if b.kind == NX_LAYOUT_BOX_BLOCK { if b.w > 0 { if b.h > 0 {
695 let bg: i64 = br_comp_bg(src, computed, ncomp, i)
696 if bg >= 0 { if bg != 0x00FFFFFF {
697 let sy: i64 = b.y - scroll_y + NX_CHROME_H
698 if sy + b.h >= NX_CHROME_H { if sy < win_h { nx_x11_set_gc_foreground(c, gc, bg); nx_x11_fill_rect(c, wid, gc, b.x, sy, b.w, b.h) } }
699 } }
700 // CSS borders (the `border` shorthand): stroke the 4 edges
701 let bc: i64 = br_comp_border(src, computed, ncomp, i, bwp)
702 if bc >= 0 {
703 let sy3: i64 = b.y - scroll_y + NX_CHROME_H
704 if sy3 + b.h >= NX_CHROME_H { if sy3 < win_h {
705 let bw2: i64 = bwp[0]
706 nx_x11_set_gc_foreground(c, gc, bc)
707 nx_x11_fill_rect(c, wid, gc, b.x, sy3, b.w, bw2)
708 nx_x11_fill_rect(c, wid, gc, b.x, sy3 + b.h - bw2, b.w, bw2)
709 nx_x11_fill_rect(c, wid, gc, b.x, sy3, bw2, b.h)
710 nx_x11_fill_rect(c, wid, gc, b.x + b.w - bw2, sy3, bw2, b.h)
711 } }
712 }
713 } } }
714 i = i + 1
715 }
716 i = 0
717 while i < tree.count {
718 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox
719 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len > 0 {
720 let sy: i64 = b.y - scroll_y + NX_CHROME_H
721 if sy >= NX_CHROME_H - 40 { if sy < win_h {
722 let par: i64 = b.parent_idx
723 let fs: i64 = br_comp_int(src, computed, ncomp, par, "font-size\x00" as *u8, 9, 16)
724 var fid: i64 = fbody; var asc: i64 = 12
725 if fs > 18 { fid = fh2; asc = 16 }
726 if fs > 28 { fid = fh1; asc = 19 }
727 var col: i64 = br_comp_color_inh(src, computed, ncomp, tree, par) // CSS-inherited color
728 if col < 0 { col = 0 }
729 nx_x11_set_gc_font(c, gc, fid); nx_x11_set_gc_foreground(c, gc, col)
730 var nn: i64 = b.text_len
731 if nn > 200 { nn = 200 }
732 let dn: i64 = nx_html_decode_entities(src, b.text_off, nn, decbuf, 1024)
733 nx_x11_poly_text8(c, wid, gc, b.x + 4, sy + asc, decbuf, dn)
734 if col == 0x3366cc { nx_x11_fill_rect(c, wid, gc, b.x + 4, sy + asc + 2, b.w, 1) } // underline links
735 } }
736 } }
737 i = i + 1
738 }
739 br_chrome(c, wid, gc, win_w, cur_url, ulen, fbody)
740 nx_x11_flush_via_get_input_focus(c)
741 return 0
742}
743
744func br_color_from_int(c: i64) -> *CssColor {
745 let col: *CssColor = sys_mmap(NX_CSS_COLOR_BYTES) as *CssColor
746 col.r = (c >> 16) & 0xff
747 col.g = (c >> 8) & 0xff
748 col.b = c & 0xff
749 col.a = 255
750 return col
751}
752
753// 2x-scaled 5x7 text for the SHOT framebuffer (each glyph pixel -> 2x2 block = 10x14 glyph, 9px advance)
754// so the capture ~matches the live window's 9x15 font + the 9px layout metric (faithful self-verify).
755func br_paint_text2x(fb: *Framebuffer, x: i64, y: i64, text: *u8, text_len: i64, color: *CssColor) -> i64 {
756 var i: i64 = 0
757 while i < text_len {
758 let ch: i64 = text[i] & 0xff
759 let cx: i64 = x + i * 9
760 var row: i64 = 0
761 while row < 7 {
762 let bits: i64 = nx_font_5x7_glyph_row(ch, row)
763 if bits >= 0 {
764 var col: i64 = 0
765 while col < 5 {
766 if ((bits >> (4 - col)) & 1) == 1 { nx_paint_solid_rect(fb, cx + col * 2, y + row * 2, 2, 2, color) }
767 col = col + 1
768 }
769 }
770 row = row + 1
771 }
772 i = i + 1
773 }
774 return 0
775}
776
777// OFF-SCREEN render: rasterize the same page into an in-memory RGBA framebuffer (no X server) so the
778// render can be captured to a PNG and self-reviewed. Mirrors br_draw; uses the 5x7 bitmap-font paint
779// primitives (so headings aren't scaled here -- this is for verifying colors/layout/chrome, not pixel-parity).
780func br_draw_fb(fb: *Framebuffer, page: *Page, win_w: i64, cur_url: *u8, ulen: i64) -> i64 {
781 let tree: *LayoutTree = page.tree
782 let src: *u8 = page.buf
783 let computed: *CssComputedDecl = page.computed
784 let ncomp: i64 = page.ncomp
785 let fdec: *u8 = sys_mmap(1100) // reused per text box: decode HTML entities before painting
786 let ffold: *u8 = sys_mmap(1100) // reused: fold decoded UTF-8 -> ASCII (accents+punct) for the 5x7 font
787 nx_paint_solid_rect(fb, 0, 0, win_w, fb.height, br_color_from_int(0x00FFFFFF))
788 var i: i64 = 0
789 let bwp2: *i64 = sys_mmap(16) as *i64 // border width out-slot (hoisted: no mmap in the loop)
790 while i < tree.count {
791 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox
792 if b.kind == NX_LAYOUT_BOX_BLOCK { if b.w > 0 { if b.h > 0 {
793 let bg: i64 = br_comp_bg(src, computed, ncomp, i)
794 if bg >= 0 { if bg != 0x00FFFFFF { nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, b.w, b.h, br_color_from_int(bg)) } }
795 // CSS borders (the `border` shorthand): stroke the 4 edges
796 let bc: i64 = br_comp_border(src, computed, ncomp, i, bwp2)
797 if bc >= 0 {
798 let bw2: i64 = bwp2[0]
799 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, b.w, bw2, br_color_from_int(bc))
800 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H + b.h - bw2, b.w, bw2, br_color_from_int(bc))
801 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, bw2, b.h, br_color_from_int(bc))
802 nx_paint_solid_rect(fb, b.x + b.w - bw2, b.y + NX_CHROME_H, bw2, b.h, br_color_from_int(bc))
803 }
804 } } }
805 i = i + 1
806 }
807 i = 0
808 while i < tree.count {
809 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox
810 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len > 0 {
811 let par: i64 = b.parent_idx
812 var col: i64 = br_comp_color_inh(src, computed, ncomp, tree, par) // CSS-inherited color
813 if col < 0 { col = 0 }
814 var nn: i64 = b.text_len
815 if nn > 200 { nn = 200 }
816 let dn: i64 = nx_html_decode_entities(src, b.text_off, nn, fdec, 1024)
817 let fdn: i64 = nx_fold_ascii(fdec, 0, dn, ffold, 1100) // UTF-8 -> ASCII: punctuation + accents render, non-Latin skips cleanly (no blank gaps)
818 br_paint_text2x(fb, b.x + 4, b.y + NX_CHROME_H + 1, ffold, fdn, br_color_from_int(col))
819 if col == 0x3366cc { nx_paint_solid_rect(fb, b.x + 4, b.y + NX_CHROME_H + 16, b.w, 1, br_color_from_int(col)) } // underline links
820 } }
821 i = i + 1
822 }
823 // chrome
824 nx_paint_solid_rect(fb, 0, 0, win_w, NX_CHROME_H, br_color_from_int(0x00DDE1E6))
825 nx_paint_solid_rect(fb, 0, NX_CHROME_H - 1, win_w, 1, br_color_from_int(0x00B0B4BA))
826 nx_paint_solid_rect(fb, 8, 8, 30, 28, br_color_from_int(0x00F6F8FA))
827 nx_paint_solid_rect(fb, 44, 8, 30, 28, br_color_from_int(0x00F6F8FA))
828 nx_paint_solid_rect(fb, 80, 8, 30, 28, br_color_from_int(0x00F6F8FA))
829 br_paint_text2x(fb, 18, 16, "<\x00" as *u8, 1, br_color_from_int(0x00202124))
830 br_paint_text2x(fb, 54, 16, ">\x00" as *u8, 1, br_color_from_int(0x00202124))
831 br_paint_text2x(fb, 90, 16, "R\x00" as *u8, 1, br_color_from_int(0x00202124))
832 let bx: i64 = 118
833 nx_paint_solid_rect(fb, bx, 8, win_w - bx - 10, 28, br_color_from_int(0x00FFFFFF))
834 var un: i64 = ulen
835 if un > 96 { un = 96 }
836 br_paint_text2x(fb, bx + 6, 14, cur_url, un, br_color_from_int(0x00202124))
837 return 0
838}
839
840// render the laid-out page to a 1000xSH RGBA framebuffer and write it as a PNG at `path`. SH tracks the FULL
841// page height (chrome + laid-out content) so a self-verify shot captures the whole page, not a fixed top crop
842// (the 1000px crop made tall pages -- e.g. /office at 2765px -- look empty below the fold). Clamped for safety.
843func br_shot_png(page: *Page, cur_url: *u8, ulen: i64, path: *u8) -> i64 {
844 let SW: i64 = 1000
845 var SH: i64 = page.page_h + NX_CHROME_H + 40
846 if SH < 400 { SH = 400 }
847 if SH > 6000 { SH = 6000 }
848 let fb: *Framebuffer = sys_mmap(NX_FRAMEBUFFER_BYTES) as *Framebuffer
849 let px: *u8 = sys_mmap(SW * SH * 4 + 64)
850 nx_framebuffer_init(fb, px, SW, SH)
851 br_draw_fb(fb, page, SW, cur_url, ulen)
852 let rgb: *u8 = sys_mmap(SW * SH * 3 + 64)
853 var p: i64 = 0
854 while p < SW * SH {
855 rgb[p * 3 + 0] = px[p * 4 + 0]
856 rgb[p * 3 + 1] = px[p * 4 + 1]
857 rgb[p * 3 + 2] = px[p * 4 + 2]
858 p = p + 1
859 }
860 nx_png_write_rgb(path, rgb, SW, SH)
861 return 0
862}
863
864// hit-test: find a box with an href whose rect contains doc-point (dx,dy). 1 + sets off/len, else 0.
865// CONTAINER ANCHORS (2026-07-03): an <a> wrapping block children lays out with a ZERO own-rect --
866// hit-test the UNION of its descendants' rects (mirrors nx_browser_render.nx; found by
867// nx_aw_browser_gate on the real andelinwest card links). Purely additive.
868func br_hit_link(page: *Page, dx: i64, dy: i64, off: *i64, len: *i64) -> i64 {
869 let tree: *LayoutTree = page.tree
870 var i: i64 = 0
871 while i < tree.count {
872 if page.bhref_off[i] >= 0 {
873 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox
874 var bx: i64 = b.x
875 var by: i64 = b.y
876 var bw: i64 = b.w
877 var bh: i64 = b.h
878 if bh <= 0 {
879 // SIBLING-SPAN effective rect (mirrors nx_browser_render.nx): union the rects of the
880 // boxes AFTER i while they stay inside i's parent's subtree, stopping at the next
881 // href box -- the layout flattens container-anchor children to siblings.
882 let par: i64 = b.parent_idx
883 var x0: i64 = 0
884 var y0: i64 = 0
885 var x1: i64 = 0
886 var y1: i64 = 0
887 var any: i64 = 0
888 var j: i64 = i + 1
889 var span: i64 = 1
890 while span == 1 {
891 if j >= tree.count { span = 0 }
892 else { if page.bhref_off[j] >= 0 { span = 0 }
893 else {
894 let cb: *LayoutBox = ((tree.boxes as i64) + j * NX_LAYOUT_BOX_BYTES) as *LayoutBox
895 var p: i64 = cb.parent_idx
896 var hop: i64 = 0
897 var inside: i64 = 0
898 while hop < 64 {
899 if p == (0 - 1) { hop = 64 }
900 else { if p == par { inside = 1; hop = 64 }
901 else { let pb: *LayoutBox = ((tree.boxes as i64) + p * NX_LAYOUT_BOX_BYTES) as *LayoutBox; p = pb.parent_idx; hop = hop + 1 } }
902 }
903 if inside == 0 { span = 0 }
904 else {
905 if cb.w > 0 { if cb.h > 0 {
906 if any == 0 { x0 = cb.x; y0 = cb.y; x1 = cb.x + cb.w; y1 = cb.y + cb.h; any = 1 }
907 else {
908 if cb.x < x0 { x0 = cb.x }
909 if cb.y < y0 { y0 = cb.y }
910 if cb.x + cb.w > x1 { x1 = cb.x + cb.w }
911 if cb.y + cb.h > y1 { y1 = cb.y + cb.h }
912 }
913 } }
914 j = j + 1
915 }
916 } }
917 }
918 if any == 1 { bx = x0; by = y0; bw = x1 - x0; bh = y1 - y0 }
919 }
920 var hitw: i64 = bw // layout metric now matches the 9px render font, so b.w is the true width
921 if hitw < 12 { hitw = 12 }
922 if dx >= bx { if dx < bx + hitw { if dy >= by { if dy < by + bh {
923 off[0] = page.bhref_off[i]; len[0] = page.bhref_len[i]; return 1
924 } } } }
925 }
926 i = i + 1
927 }
928 return 0
929}
930
931// ---- sovereign-game pages (nishi-game marker) -> run the NATIVE client, zero wasm ----
932// Extract the `<meta name="nishi-game" content="...">` organ name from raw page bytes.
933// SECURITY (rule 12): the value is PAGE-CONTROLLED and becomes an exec path, so this is strict
934// fail-closed validation, not parsing: the name must be nx_[a-z0-9_]{1,60} and anything else --
935// a slash, a dot, an uppercase byte, overlength -- REJECTS the whole marker (no truncate-accept).
936func br_game_organ(raw: *u8, n: i64, out: *u8) -> i64 {
937 let pat: *u8 = "name=\"nishi-game\"\x00" as *u8
938 let cpat: *u8 = "content=\"\x00" as *u8
939 var i: i64 = 0
940 while i < n - 17 {
941 var m: i64 = 0
942 while m < 17 { if (raw[i+m] & 0xff) != (pat[m] & 0xff) { m = 99 } else { m = m + 1 } }
943 if m == 17 {
944 // find content=" within the next 80 bytes
945 var j: i64 = i + 17
946 let jend: i64 = i + 97
947 while j < jend {
948 if j < n - 9 {
949 var c: i64 = 0
950 while c < 9 { if (raw[j+c] & 0xff) != (cpat[c] & 0xff) { c = 99 } else { c = c + 1 } }
951 if c == 9 {
952 var k: i64 = 0
953 var p: i64 = j + 9
954 var badc: i64 = 0
955 var closed: i64 = 0
956 while closed == 0 {
957 if p >= n { badc = 1; closed = 1 }
958 if closed == 0 {
959 let ch: i64 = raw[p] & 0xff
960 if ch == 34 { closed = 1 }
961 if closed == 0 {
962 var okc: i64 = 0
963 if ch >= 97 { if ch <= 122 { okc = 1 } }
964 if ch >= 48 { if ch <= 57 { okc = 1 } }
965 if ch == 95 { okc = 1 }
966 if okc == 0 { badc = 1 }
967 if k >= 60 { badc = 1 }
968 if badc == 0 { out[k] = ch as u8; k = k + 1 }
969 if badc == 1 { closed = 1 }
970 p = p + 1
971 }
972 }
973 }
974 if badc == 1 { return 0 }
975 if k < 4 { return 0 }
976 if (out[0] & 0xff) != 110 { return 0 } // n
977 if (out[1] & 0xff) != 120 { return 0 } // x
978 if (out[2] & 0xff) != 95 { return 0 } // _
979 out[k] = 0 as u8
980 return k
981 }
982 }
983 j = j + 1
984 }
985 return 0
986 }
987 i = i + 1
988 }
989 return 0
990}
991
992// fork+exec the native client `_build/<name>.sov.elf` (name pre-validated by br_game_organ).
993// selftest=1: pass "selftest" and WAIT, returning the child's exit code (the chain proof).
994// selftest=0: fire-and-forget (the game gets its own window; zombie reaped at browser exit).
995func br_launch_game(name: *u8, nlen: i64, selftest: i64) -> i64 {
996 let path: *u8 = sys_mmap(128)
997 let pre: *u8 = "_build/\x00" as *u8
998 var k: i64 = 0
999 while k < 7 { path[k] = pre[k]; k = k + 1 }
1000 var q: i64 = 0
1001 while q < nlen { path[7+q] = name[q]; q = q + 1 }
1002 let suf: *u8 = ".sov.elf\x00" as *u8
1003 var t: i64 = 0
1004 while t < 9 { path[7+nlen+t] = suf[t]; t = t + 1 }
1005 let av: *i64 = sys_mmap(32) as *i64
1006 av[0] = path as i64
1007 av[1] = 0
1008 av[2] = 0
1009 if selftest == 1 { av[1] = ("selftest\x00" as *u8) as i64 }
1010 let ep: *i64 = sys_mmap(16) as *i64
1011 ep[0] = 0
1012 let pid: i64 = sys_fork()
1013 if pid == 0 {
1014 sys_execve(path, av, ep)
1015 sys_exit(127)
1016 }
1017 if pid < 0 { return 0 - 1 }
1018 if selftest == 1 {
1019 let st: *i64 = sys_mmap(16) as *i64
1020 sys_wait4(pid, st, 0)
1021 return (st[0] >> 8) & 0xff
1022 }
1023 return 0
1024}
1025
1026func br_seq(a: *u8, b: *u8) -> i64 {
1027 var i: i64 = 0
1028 while 1 == 1 {
1029 let ca: i64 = a[i] & 0xff
1030 let cb: i64 = b[i] & 0xff
1031 if ca != cb { return 0 }
1032 if ca == 0 { return 1 }
1033 i = i + 1
1034 }
1035 return 1
1036}
1037
1038func main(argc: i64, argv: *i64) -> i64 {
1039 nx_signal_ignore(NX_SIGPIPE) // a server reset mid-write must NOT kill us (was exit 141); fetch fails gracefully
1040
1041 // ---- HEADLESS SHOT, ADDED 2026-08-26: nx_browser shot <html-file> <png-out> [viewport-px] ----
1042 // Renders a LOCAL FILE through the FULL pipeline -- UA defaults + author <style> + @media filtered
1043 // at the REAL viewport width -> DOM -> layout tree -> cascade -> block layout -> painted
1044 // framebuffer -> PNG. It returns BEFORE _url.txt, the CA bundle and X11 are touched, so it runs on
1045 // a headless NAS where the windowed browser structurally cannot.
1046 //
1047 // WHY THIS AND NOT THE TEXT RENDER TEST: nx_pagerender_lib proves text extraction and paints ONE
1048 // line -- useful, and honest about its scope. It does NOT run the cascade or lay out a single box,
1049 // so it cannot see a stylesheet that fails to apply, a box tree that collapses, or a page that lays
1050 // out to zero height. The render test's own header flagged full CSS-cascade layout as "the NEXT
1051 // rung -- flagged, not claimed". This is that rung, and it REPORTS THE LAYOUT NUMBERS (boxes,
1052 // computed declarations, laid-out page height) rather than a pass bit, because a boolean cannot
1053 // tell you that a page rendered to 40 boxes when it used to render to 400.
1054 // Called with no arguments the browser behaves EXACTLY as before (rule 19: add, never break).
1055 if argc >= 4 {
1056 if br_seq(argv[1] as *u8, "shot\x00" as *u8) == 1 {
1057 let hp: *u8 = argv[2] as *u8
1058 let op: *u8 = argv[3] as *u8
1059 var vw: i64 = 1000
1060 if argc >= 5 {
1061 let vs: *u8 = argv[4] as *u8
1062 var vv: i64 = 0
1063 var vi: i64 = 0
1064 var vbad: i64 = 0
1065 while vs[vi] != (0 as u8) {
1066 let c: i64 = vs[vi] & 0xff
1067 if c < 48 { vbad = 1 }
1068 if c > 57 { vbad = 1 }
1069 if vbad == 0 { vv = vv * 10 + (c - 48) }
1070 vi = vi + 1
1071 }
1072 // A NON-NUMERIC VIEWPORT IS REFUSED, NOT SILENTLY READ AS 1000. Quietly defaulting would
1073 // lay the page out at a width nobody asked for and report it as if it were the request.
1074 if vbad == 1 { br_p(2, "nx_browser shot: viewport must be numeric\n\x00" as *u8); return 2 }
1075 vw = vv
1076 }
1077 let flp: *i64 = sys_mmap(16) as *i64
1078 let fbody: *u8 = sys_read_file(hp, flp)
1079 if (fbody as i64) == 0 {
1080 br_p(2, "nx_browser shot: cannot read \x00" as *u8); br_p(2, hp); br_p(2, "\n\x00" as *u8)
1081 return 4
1082 }
1083 let spage: *Page = sys_mmap(NX_PAGE_BYTES) as *Page
1084 spage.raw = fbody
1085 spage.raw_len = flp[0]
1086 br_layout(spage, vw)
1087 if spage.ok == 0 { br_p(2, "nx_browser shot: LAYOUT FAILED -- no PNG written\n\x00" as *u8); return 5 }
1088 let stree: *LayoutTree = spage.tree
1089 // A PAGE THAT LAYS OUT TO ZERO HEIGHT OR ZERO BOXES IS A FAILED RENDER THAT WOULD OTHERWISE
1090 // WRITE A CLEAN-LOOKING BLANK PNG. Refuse before writing, so a blank artifact can never be
1091 // mistaken for a successful screenshot.
1092 if stree.count <= 0 { br_p(2, "nx_browser shot: ZERO BOXES -- refusing to write a blank PNG\n\x00" as *u8); return 6 }
1093 if spage.page_h <= 0 { br_p(2, "nx_browser shot: ZERO page height -- refusing to write a blank PNG\n\x00" as *u8); return 7 }
1094 // A PNG OUT-PATH OF "-" MEANS METRICS ONLY, AND IT IS NOT AN OPTIMISATION -- IT IS A
1095 // RESOURCE FACT. nx_png_write_rgb emits essentially UNCOMPRESSED PNG: this page produced
1096 // 18,007,438 bytes for one 1000x6000 shot (measured 2026-08-26), so a sweep over the 84
1097 // published boards would write roughly 1.5 GB to the array for a regression check whose
1098 // ACTUAL signal is three integers. Hammering the disk is a defect even when the feature
1099 // works, so the fleet path takes the numbers and the image stays on demand.
1100 // The layout still runs IN FULL either way -- nothing is skipped except the write.
1101 if br_seq(op, "-\x00" as *u8) == 0 { br_shot_png(spage, hp, br_slen(hp), op) }
1102 br_p(1, "BROWSER-SHOT ok html=\x00" as *u8); br_n(1, spage.raw_len)
1103 br_p(1, "B boxes=\x00" as *u8); br_n(1, stree.count)
1104 br_p(1, " computed_decls=\x00" as *u8); br_n(1, spage.ncomp)
1105 br_p(1, " page_h=\x00" as *u8); br_n(1, spage.page_h)
1106 br_p(1, "px vw=\x00" as *u8); br_n(1, vw)
1107 br_p(1, " -> \x00" as *u8); br_p(1, op); br_p(1, "\n\x00" as *u8)
1108 return 0
1109 }
1110 }
1111
1112 // ---- LAYOUT SWEEP, ADDED 2026-08-27: nx_browser layoutsweep <domain-list> ----
1113 // Lays out EVERY published /compare board through the same br_layout the windowed browser uses --
1114 // full cascade, real box tree, real block layout -- and prints the NUMBERS per board plus a
1115 // partition that SUMS. Two boards laid out is a spot check; this is the population.
1116 // NO PNG IS WRITTEN: nx_png_write_rgb emits essentially uncompressed PNG (18,007,438 bytes for one
1117 // 1000x6000 shot, measured 2026-08-26), so imaging 84 boards would put ~1.5 GB on the array for a
1118 // check whose real signal is three integers per row.
1119 // ALLOCATION PROFILE, NAMED RATHER THAN DISCOVERED AS AN OOM: br_layout mmaps a fresh 16384-box
1120 // tree, a 256 KB style buffer and a computed-decl arena PER CALL and frees none of it, so this loop
1121 // accumulates address space linearly in the number of boards. mmap faults pages in on demand so the
1122 // RESIDENT cost is far below the address-space cost, and the SITETEST batch in this same organ
1123 // already loops layouts in one process -- but if this ever sweeps thousands of pages rather than
1124 // dozens, the fix is a per-iteration arena reset, not a bigger box.
1125 if argc >= 3 {
1126 if br_seq(argv[1] as *u8, "layoutsweep\x00" as *u8) == 1 {
1127 let lpath: *u8 = argv[2] as *u8
1128 let llp: *i64 = sys_mmap(16) as *i64
1129 let lbuf: *u8 = sys_read_file(lpath, llp)
1130 if (lbuf as i64) == 0 {
1131 br_p(2, "nx_browser layoutsweep: cannot read \x00" as *u8); br_p(2, lpath); br_p(2, "\n\x00" as *u8)
1132 return 4
1133 }
1134 let ln: i64 = llp[0]
1135 br_p(1, "=== NX-BROWSER LAYOUT SWEEP -- every published board through the real cascade ===\n\x00" as *u8)
1136 let dpath: *u8 = sys_mmap(BR_PATH_MAX)
1137 let dname: *u8 = sys_mmap(BR_PATH_MAX)
1138 var listed: i64 = 0
1139 var okc: i64 = 0
1140 var absent: i64 = 0
1141 var badc: i64 = 0
1142 var min_boxes: i64 = 0 - 1
1143 var min_h: i64 = 0 - 1
1144 var sum_boxes: i64 = 0
1145 var sum_decls: i64 = 0
1146 var lp2: i64 = 0
1147 while lp2 < ln {
1148 var lq: i64 = lp2
1149 var lsc: i64 = 1
1150 while lsc == 1 {
1151 if lq >= ln { lsc = 0 } else { if lbuf[lq] == (BR_ASCII_NL as u8) { lsc = 0 } else { lq = lq + 1 } }
1152 }
1153 var dl: i64 = 0
1154 var di: i64 = lp2
1155 while di < lq {
1156 if lbuf[di] != (BR_ASCII_CR as u8) { if dl < (BR_PATH_MAX - 1) { dname[dl] = lbuf[di]; dl = dl + 1 } }
1157 di = di + 1
1158 }
1159 dname[dl] = 0 as u8
1160 var use: i64 = 1
1161 if dl == 0 { use = 0 }
1162 if dl > 0 { if dname[0] == (BR_ASCII_HASH as u8) { use = 0 } }
1163 if use == 1 {
1164 listed = listed + 1
1165 var dp: i64 = 0
1166 let pre: *u8 = "sites/nishifamily/compare/\x00" as *u8
1167 var pi2: i64 = 0
1168 while pre[pi2] != (0 as u8) { dpath[dp] = pre[pi2]; dp = dp + 1; pi2 = pi2 + 1 }
1169 var di2: i64 = 0
1170 while dname[di2] != (0 as u8) { dpath[dp] = dname[di2]; dp = dp + 1; di2 = di2 + 1 }
1171 let suf: *u8 = "/index.html\x00" as *u8
1172 var si2: i64 = 0
1173 while suf[si2] != (0 as u8) { dpath[dp] = suf[si2]; dp = dp + 1; si2 = si2 + 1 }
1174 dpath[dp] = 0 as u8
1175 let dlp: *i64 = sys_mmap(16) as *i64
1176 let dbody: *u8 = sys_read_file(dpath, dlp)
1177 br_p(1, " \x00" as *u8); br_p(1, dname)
1178 var padn: i64 = dl
1179 while padn < BR_NAME_COL { br_p(1, " \x00" as *u8); padn = padn + 1 }
1180 if (dbody as i64) == 0 {
1181 absent = absent + 1
1182 br_p(1, "SKIP-absent\n\x00" as *u8)
1183 } else {
1184 let dpg: *Page = sys_mmap(NX_PAGE_BYTES) as *Page
1185 dpg.raw = dbody
1186 dpg.raw_len = dlp[0]
1187 br_layout(dpg, BR_DEFAULT_VW)
1188 if dpg.ok == 0 {
1189 badc = badc + 1
1190 br_p(1, "FAIL-layout\n\x00" as *u8)
1191 } else {
1192 let dt: *LayoutTree = dpg.tree
1193 if dt.count <= 0 { badc = badc + 1; br_p(1, "FAIL-zero-boxes\n\x00" as *u8) }
1194 else {
1195 if dpg.page_h <= 0 { badc = badc + 1; br_p(1, "FAIL-zero-height\n\x00" as *u8) }
1196 else {
1197 okc = okc + 1
1198 sum_boxes = sum_boxes + dt.count
1199 sum_decls = sum_decls + dpg.ncomp
1200 if min_boxes < 0 { min_boxes = dt.count }
1201 if dt.count < min_boxes { min_boxes = dt.count }
1202 if min_h < 0 { min_h = dpg.page_h }
1203 if dpg.page_h < min_h { min_h = dpg.page_h }
1204 br_p(1, "OK boxes=\x00" as *u8); br_n(1, dt.count)
1205 br_p(1, " decls=\x00" as *u8); br_n(1, dpg.ncomp)
1206 br_p(1, " page_h=\x00" as *u8); br_n(1, dpg.page_h)
1207 br_p(1, "px\n\x00" as *u8)
1208 }
1209 }
1210 }
1211 }
1212 }
1213 lp2 = lq + 1
1214 }
1215 let tot: i64 = okc + absent + badc
1216 br_p(1, "\nboards_listed=\x00" as *u8); br_n(1, listed)
1217 br_p(1, " laid_out=\x00" as *u8); br_n(1, okc)
1218 br_p(1, " skipped=\x00" as *u8); br_n(1, absent)
1219 br_p(1, " failed=\x00" as *u8); br_n(1, badc)
1220 br_p(1, " sum=\x00" as *u8); br_n(1, tot)
1221 if tot == listed { br_p(1, " partition=RECONCILES\n\x00" as *u8) } else { br_p(1, " partition=LEAK\n\x00" as *u8) }
1222 br_p(1, "total_boxes=\x00" as *u8); br_n(1, sum_boxes)
1223 br_p(1, " total_computed_decls=\x00" as *u8); br_n(1, sum_decls)
1224 br_p(1, " WORST_boxes=\x00" as *u8); br_n(1, min_boxes)
1225 br_p(1, " WORST_page_h=\x00" as *u8); br_n(1, min_h)
1226 br_p(1, "px\n\nWORST IS PRINTED, NEVER THE MEAN: an average hides the one board that laid out to almost nothing.\n\x00" as *u8)
1227 if tot != listed { return 1 }
1228 if badc > 0 { return 1 }
1229 return 0
1230 }
1231 }
1232
1233 let lp: *i64 = sys_mmap(16) as *i64
1234 let urlraw: *u8 = sys_read_file("_url.txt\x00" as *u8, lp)
1235 var ulen: i64 = *lp
1236 if ulen <= 0 { br_p(2, "nx_browser: no _url.txt\n\x00" as *u8); return 1 }
1237 var trimming: i64 = 1
1238 while trimming == 1 { if ulen <= 0 { trimming = 0 } else { let ch: i64 = urlraw[ulen-1]&0xff; if ch==10 { ulen=ulen-1 } else { if ch==13 { ulen=ulen-1 } else { if ch==32 { ulen=ulen-1 } else { if ch==9 { ulen=ulen-1 } else { trimming=0 } } } } } }
1239 let URLCAP: i64 = 4096
1240 let cur_url: *u8 = sys_mmap(URLCAP)
1241 var u: i64 = 0
1242 while u < ulen { cur_url[u]=urlraw[u]; u=u+1 }
1243 cur_url[ulen] = 0 as u8
1244
1245 var tr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt\x00" as *u8, 300, 4194304)
1246 if tr <= 0 { tr = nx_trust_store_load_from_certdata("/tmp/mozilla_certdata.txt\x00" as *u8, 300, 4194304) }
1247 if tr <= 0 { br_p(2, "nx_browser: CA bundle load failed\n\x00" as *u8); return 3 }
1248 let store: *TrustStore = tr as *TrustStore
1249 let cr: *u8 = sys_mmap(32); let priv: *u8 = sys_mmap(32)
1250 let ufd: i64 = sys_openat_rd("/dev/urandom\x00" as *u8)
1251 if ufd >= 0 { sys_read(ufd, cr, 32); sys_read(ufd, priv, 32); sys_close(ufd) } else { var z: i64=0; while z<32 { cr[z]=(0xC0+z) as u8; priv[z]=(0xA0+z) as u8; z=z+1 } }
1252
1253 let page: *Page = sys_mmap(NX_PAGE_BYTES) as *Page
1254 var WW: i64 = 1000
1255 var WH: i64 = 760
1256
1257 // SITE-TEST BATCH: if /tmp/nxsites exists (one URL per line), fetch+layout each through the REAL
1258 // pipeline and print per-site metrics (box count = did real content render; FETCH-FAIL = TLS/network/
1259 // redirect), then exit. This is how we measure whether the render fixes generalize across the web.
1260 let stfd: i64 = sys_openat_rd("/tmp/nxsites\x00" as *u8)
1261 if stfd >= 0 {
1262 sys_close(stfd)
1263 let slp: *i64 = sys_mmap(16) as *i64
1264 let sbuf: *u8 = sys_read_file("/tmp/nxsites\x00" as *u8, slp)
1265 let sblen: i64 = slp[0]
1266 let one: *u8 = sys_mmap(URLCAP)
1267 var si: i64 = 0
1268 var idx: i64 = 0
1269 while si < sblen {
1270 var k: i64 = 0
1271 var go: i64 = 1
1272 while go == 1 {
1273 if si >= sblen { go = 0 }
1274 else {
1275 let ch: i64 = sbuf[si] & 0xff
1276 si = si + 1
1277 if ch == 10 { go = 0 } else { if ch != 13 { if k < URLCAP - 1 { one[k] = ch as u8; k = k + 1 } } }
1278 }
1279 }
1280 one[k] = 0 as u8
1281 if k > 0 {
1282 idx = idx + 1
1283 let rcL: i64 = load_page(one, k, store, cr, priv, page, WW)
1284 br_p(1, "SITETEST n=\x00" as *u8); br_n(1, idx)
1285 if rcL == 0 { br_p(1, " boxes=\x00" as *u8); br_n(1, page.tree.count); br_p(1, " page_h=\x00" as *u8); br_n(1, page.page_h) }
1286 else { br_p(1, " FETCH-FAIL rc=\x00" as *u8); br_n(1, rcL) }
1287 br_p(1, " url=\x00" as *u8); sys_write(1, one, k); br_p(1, "\n\x00" as *u8)
1288 }
1289 }
1290 br_p(1, "SITETEST done\n\x00" as *u8)
1291 return 0
1292 }
1293
1294 br_p(1, "nx_browser: fetching \x00" as *u8); sys_write(1, cur_url, ulen); br_p(1, "\n\x00" as *u8)
1295 if load_page(cur_url, ulen, store, cr, priv, page, WW) < 0 { br_p(2, "nx_browser: initial fetch failed\n\x00" as *u8); return 4 }
1296 if page.ok != 1 { return 4 }
1297 br_p(1, "nx_browser: laid out \x00" as *u8); br_n(1, page.tree.count); br_p(1, " boxes, page_h=\x00" as *u8); br_n(1, page.page_h); br_p(1, "\n\x00" as *u8)
1298
1299 // sovereign-game page? (nishi-game marker) -> the NATIVE client replaces the wasm entirely
1300 let gname: *u8 = sys_mmap(80)
1301 var glen: i64 = br_game_organ(page.raw, page.raw_len, gname)
1302 if glen > 0 {
1303 br_p(1, "nx_browser: SOVEREIGN GAME page -- press g to play NATIVE (\x00" as *u8)
1304 sys_write(1, gname, glen); br_p(1, "), zero wasm\n\x00" as *u8)
1305 }
1306 // chain-proof mode: knowledge/status/nxgame_chain present -> fetch->detect->exec selftest->exit
1307 let chfd: i64 = sys_openat_rd("knowledge/status/nxgame_chain\x00" as *u8)
1308 if chfd >= 0 {
1309 sys_close(chfd)
1310 if glen <= 0 { br_p(1, "NATIVE-CHAIN no-marker rc=90\n\x00" as *u8); return 90 }
1311 let crc: i64 = br_launch_game(gname, glen, 1)
1312 br_p(1, "NATIVE-CHAIN organ=\x00" as *u8); sys_write(1, gname, glen)
1313 br_p(1, " rc=\x00" as *u8); br_n(1, crc); br_p(1, "\n\x00" as *u8)
1314 return crc
1315 }
1316
1317 // SHOT MODE (self-verify, no X): if /tmp/nxshot exists, rasterize the page to an in-memory
1318 // framebuffer and write a PNG, then exit. Works headless -- sidesteps WSLg's GetImage limits.
1319 let shotfd: i64 = sys_openat_rd("/tmp/nxshot\x00" as *u8)
1320 if shotfd >= 0 {
1321 sys_close(shotfd)
1322 br_shot_png(page, cur_url, br_slen(cur_url), "/tmp/nxbrowser.png\x00" as *u8)
1323 br_p(1, "nx_browser: shot -> /tmp/nxbrowser.png\n\x00" as *u8)
1324 return 0
1325 }
1326
1327 let rc: i64 = nx_x11_connect_local(0)
1328 if rc < 0 { br_p(2, "nx_browser: no X display\n\x00" as *u8); return 6 }
1329 let cc: *X11Conn = rc as *X11Conn
1330 let mask: i64 = NX_X11_EVENT_EXPOSURE | NX_X11_EVENT_KEY_PRESS | NX_X11_EVENT_BUTTON_PRESS | NX_X11_EVENT_STRUCTURE
1331 let wid: i64 = nx_x11_create_window(cc, 40, 40, WW, WH, 0x00FFFFFF, mask)
1332 if wid < 0 { return 7 }
1333 let title: *u8 = "Nishi Browser (LIVE, sovereign) -- click links, b=back, m=media, q=quit\x00"
1334 nx_x11_set_wm_name(cc, wid, title, br_slen(title))
1335 let gc: i64 = nx_x11_create_gc(cc, wid, 0x00000000)
1336 let paper: i64 = nx_x11_create_gc(cc, wid, 0x00FFFFFF)
1337 let fbody: i64 = nx_x11_open_font(cc, "9x15\x00" as *u8, 4)
1338 let fh2: i64 = nx_x11_open_font(cc, "10x20\x00" as *u8, 5)
1339 let fh1: i64 = nx_x11_open_font(cc, "12x24\x00" as *u8, 5)
1340 if nx_x11_map_window(cc, wid) < 0 { return 8 }
1341 nx_x11_flush_via_get_input_focus(cc)
1342 br_p(1, "nx_browser: WINDOW OPEN -- click a link to navigate, b=back, m=media sources, scroll wheel/arrows, q quits\n\x00" as *u8)
1343
1344 // history stacks of URLs (each a fixed URLCAP slot): back + forward
1345 let HISTMAX: i64 = 64
1346 let hist: *u8 = sys_mmap(URLCAP * HISTMAX)
1347 let histlen: *i64 = sys_mmap(8 * HISTMAX) as *i64
1348 var histn: i64 = 0
1349 let fhist: *u8 = sys_mmap(URLCAP * HISTMAX)
1350 let fhistlen: *i64 = sys_mmap(8 * HISTMAX) as *i64
1351 var fhistn: i64 = 0
1352
1353 var scroll_y: i64 = 0
1354 var max_scroll: i64 = page.page_h - (WH - NX_CHROME_H)
1355 if max_scroll < 0 { max_scroll = 0 }
1356 br_draw(cc, wid, gc, paper, page, WW, WH, scroll_y, fbody, fh2, fh1, cur_url, br_slen(cur_url))
1357 let ev: *u8 = sys_mmap(64)
1358 let hoff: *i64 = sys_mmap(8) as *i64; let hlen2: *i64 = sys_mmap(8) as *i64
1359 let absbuf: *u8 = sys_mmap(URLCAP); let abslen: *i64 = sys_mmap(8) as *i64
1360 var running: i64 = 1
1361 while running == 1 {
1362 if nx_x11_read_event(cc, ev) != 0 { running = 0 }
1363 else {
1364 let kind: i64 = (ev[0] & 0x7f) as i64
1365 var dirty: i64 = 0
1366 var navurl: i64 = 0 // 1 if cur_url changed -> reload
1367 if kind == 12 { dirty = 1 }
1368 if kind == 22 { // ConfigureNotify: window resized -> reflow
1369 let nw: i64 = ((ev[20]&0xff) | ((ev[21]&0xff)<<8)) as i64
1370 let nh: i64 = ((ev[22]&0xff) | ((ev[23]&0xff)<<8)) as i64
1371 if nw > 0 { if nh > 0 {
1372 WH = nh
1373 if nw != WW {
1374 WW = nw
1375 br_layout(page, WW) // re-lay-out at the new width (NO refetch)
1376 max_scroll = page.page_h - (WH - NX_CHROME_H)
1377 if max_scroll < 0 { max_scroll = 0 }
1378 }
1379 dirty = 1
1380 } }
1381 }
1382 if kind == 4 {
1383 let btn: i64 = (ev[1] & 0xff) as i64
1384 if btn == 5 { scroll_y = scroll_y + 120; dirty = 1 }
1385 if btn == 4 { scroll_y = scroll_y - 120; dirty = 1 }
1386 if btn == 1 {
1387 // left click: window coords in ev[24..27]=x, ev[26..]=y per X event layout (event-x at 24, event-y at 26)
1388 let ex: i64 = ((ev[24]&0xff) | ((ev[25]&0xff)<<8)) as i64
1389 let ey: i64 = ((ev[26]&0xff) | ((ev[27]&0xff)<<8)) as i64
1390 if ey < NX_CHROME_H {
1391 // chrome click: Back / Forward / Reload
1392 let act: i64 = br_chrome_click(ex)
1393 if act == 1 { if histn > 0 {
1394 if fhistn < HISTMAX { var z: i64=0; let cl: i64=br_slen(cur_url); while z<cl { fhist[fhistn*URLCAP+z]=cur_url[z]; z=z+1 } fhistlen[fhistn]=cl; fhistn=fhistn+1 }
1395 histn = histn - 1; let cl2: i64=histlen[histn]; var z2: i64=0; while z2<cl2 { cur_url[z2]=hist[histn*URLCAP+z2]; z2=z2+1 } cur_url[cl2]=0 as u8; navurl = 1
1396 } }
1397 if act == 2 { if fhistn > 0 {
1398 if histn < HISTMAX { var z: i64=0; let cl: i64=br_slen(cur_url); while z<cl { hist[histn*URLCAP+z]=cur_url[z]; z=z+1 } histlen[histn]=cl; histn=histn+1 }
1399 fhistn = fhistn - 1; let cl2: i64=fhistlen[fhistn]; var z2: i64=0; while z2<cl2 { cur_url[z2]=fhist[fhistn*URLCAP+z2]; z2=z2+1 } cur_url[cl2]=0 as u8; navurl = 1
1400 } }
1401 if act == 3 { navurl = 1 }
1402 } else {
1403 let dx: i64 = ex
1404 let dy: i64 = (ey - NX_CHROME_H) + scroll_y
1405 if br_hit_link(page, dx, dy, hoff, hlen2) == 1 {
1406 if nx_url_resolve(cur_url, br_slen(cur_url), ((page.buf as i64)+hoff[0]) as *u8, hlen2[0], absbuf, URLCAP, abslen) == NX_URL_RESOLVE_OK {
1407 // only follow http(s) links
1408 if (absbuf[0]&0xff)==104 { // 'h'
1409 br_p(1, "nx_browser: -> \x00" as *u8); sys_write(1, absbuf, abslen[0]); br_p(1, "\n\x00" as *u8)
1410 // push current to back history; a NEW navigation clears the forward stack
1411 if histn < HISTMAX { var z: i64=0; let cl: i64=br_slen(cur_url); while z<cl { hist[histn*URLCAP+z]=cur_url[z]; z=z+1 } histlen[histn]=cl; histn=histn+1 }
1412 fhistn = 0
1413 var z2: i64=0; while z2<abslen[0] { cur_url[z2]=absbuf[z2]; z2=z2+1 } cur_url[abslen[0]]=0 as u8
1414 navurl = 1
1415 }
1416 }
1417 }
1418 }
1419 }
1420 }
1421 if kind == 2 {
1422 let kc: i64 = (ev[1] & 0xff) as i64
1423 if kc == 116 { scroll_y = scroll_y + 120; dirty = 1 }
1424 if kc == 111 { scroll_y = scroll_y - 120; dirty = 1 }
1425 if kc == 117 { scroll_y = scroll_y + 600; dirty = 1 }
1426 if kc == 112 { scroll_y = scroll_y - 600; dirty = 1 }
1427 if kc == 65 { scroll_y = scroll_y + 600; dirty = 1 }
1428 if kc == 56 { // 'b' = back
1429 if histn > 0 {
1430 if fhistn < HISTMAX { var zf: i64=0; let clf: i64=br_slen(cur_url); while zf<clf { fhist[fhistn*URLCAP+zf]=cur_url[zf]; zf=zf+1 } fhistlen[fhistn]=clf; fhistn=fhistn+1 }
1431 histn = histn - 1; let cl: i64=histlen[histn]; var z: i64=0; while z<cl { cur_url[z]=hist[histn*URLCAP+z]; z=z+1 } cur_url[cl]=0 as u8; navurl = 1
1432 }
1433 }
1434 if kc == 58 { br_media_dump(store, cur_url, br_slen(cur_url)) } // 'm' = deep-harvest + panel the gif/vid/image sources
1435 if kc == 42 { // 'g' = play this page's game NATIVELY
1436 if glen > 0 {
1437 br_p(1, "nx_browser: launching native \x00" as *u8); sys_write(1, gname, glen); br_p(1, "\n\x00" as *u8)
1438 br_launch_game(gname, glen, 0)
1439 }
1440 if glen <= 0 { br_p(1, "nx_browser: no sovereign game on this page\n\x00" as *u8) }
1441 }
1442 if kc == 24 { running = 0 } // 'q'
1443 if kc == 9 { running = 0 } // Esc
1444 }
1445 if kind == 17 { running = 0 }
1446 if navurl == 1 {
1447 br_p(1, "nx_browser: loading...\n\x00" as *u8)
1448 if load_page(cur_url, br_slen(cur_url), store, cr, priv, page, WW) == 0 { if page.ok == 1 {
1449 scroll_y = 0
1450 max_scroll = page.page_h - (WH - NX_CHROME_H)
1451 if max_scroll < 0 { max_scroll = 0 }
1452 dirty = 1
1453 glen = br_game_organ(page.raw, page.raw_len, gname)
1454 if glen > 0 {
1455 br_p(1, "nx_browser: SOVEREIGN GAME page -- press g to play NATIVE (\x00" as *u8)
1456 sys_write(1, gname, glen); br_p(1, ")\n\x00" as *u8)
1457 }
1458 } }
1459 }
1460 if dirty == 1 {
1461 if scroll_y < 0 { scroll_y = 0 }
1462 if scroll_y > max_scroll { scroll_y = max_scroll }
1463 br_draw(cc, wid, gc, paper, page, WW, WH, scroll_y, fbody, fh2, fh1, cur_url, br_slen(cur_url))
1464 }
1465 }
1466 }
1467 sys_close(cc.fd)
1468 br_p(1, "nx_browser: closed\n\x00" as *u8)
1469 return 0
1470}