code wiki / _hdl_build / nx_eco_graph_png.nx

nx_eco_graph_png.nx source

↩ module page · 330 lines · 13501 B

1// nx_eco_graph_png.nx -- SOVEREIGN VISUAL as a real RASTER PNG (not SVG). Same generational "roots to god" 2// family tree as nx_eco_graph_atlas, but painted pixel-by-pixel into an i64 framebuffer and written as a 3// DEFLATE-COMPRESSED PNG via nx_png.write_png (adaptive filtering + dfe_compress). WHY: a PNG renders 4// BYTE-IDENTICALLY in every browser (Waterfox, Chrome, and our own box-layout sovereign browser) -- no SVG 5// parser, no rendering-quirk, no black box. Proven need: our browser renders the inline-SVG atlas as a black 6// box (self-verified via nx_browser_render_shot), and Waterfox hit an SVG bug too. An <img> sidesteps all of it. 7// Composes nx_eco_graph (graph+ancestry) + nx_font_bitmap_5x7 (labels) + nx_png (compressed encode). 8// Usage: nx_eco_graph_png <store-prefix> <seed-basename> <out.png> license_tier: ORIGINAL expect_exit:0 9import "nx_syscalls.nx" 10import "nx_eco_graph.nx" 11import "nx_font_bitmap_5x7.nx" 12import "nx_png.nx" 13const PG_MAGIC_65536: i64 = 65536 14const PG_MAGIC_6000: i64 = 6000 15const PG_MAGIC_152036: i64 = 152036 16 17const PG_MAXS: i64 = 200 18const PG_W: i64 = 1180 19const PG_LAYH: i64 = 104 20 21func pslen(s: *u8) -> i64 { var n:i64=0; while s[n]!=(0 as u8){n=n+1} return n } 22 23// ---- raster primitives on an i64 framebuffer (one i64/pixel, packed R + G*256 + B*65536) ---- 24func px_set(fb: *i64, W: i64, H: i64, x: i64, y: i64, r: i64, gg: i64, b: i64) -> i64 { 25 if x < 0 { return 0 } 26 if y < 0 { return 0 } 27 if x >= W { return 0 } 28 if y >= H { return 0 } 29 fb[y*W + x] = r + gg*256 + b*PG_MAGIC_65536 30 return 0 31} 32func fill_rect(fb: *i64, W: i64, H: i64, x0: i64, y0: i64, w: i64, h: i64, r: i64, gg: i64, b: i64) -> i64 { 33 var yy: i64 = y0 34 while yy < y0 + h { var xx: i64 = x0; while xx < x0 + w { px_set(fb,W,H,xx,yy,r,gg,b); xx = xx + 1 } yy = yy + 1 } 35 return 0 36} 37func fill_disk(fb: *i64, W: i64, H: i64, cx: i64, cy: i64, rad: i64, r: i64, gg: i64, b: i64) -> i64 { 38 var dy: i64 = 0 - rad 39 while dy <= rad { 40 var dx: i64 = 0 - rad 41 while dx <= rad { 42 if dx*dx + dy*dy <= rad*rad { px_set(fb,W,H,cx+dx,cy+dy,r,gg,b) } 43 dx = dx + 1 44 } 45 dy = dy + 1 46 } 47 return 0 48} 49func ring(fb: *i64, W: i64, H: i64, cx: i64, cy: i64, rad: i64, r: i64, gg: i64, b: i64) -> i64 { 50 let r2hi: i64 = rad*rad 51 let r2lo: i64 = (rad-2)*(rad-2) 52 var dy: i64 = 0 - rad 53 while dy <= rad { 54 var dx: i64 = 0 - rad 55 while dx <= rad { 56 let d: i64 = dx*dx + dy*dy 57 if d <= r2hi { if d >= r2lo { px_set(fb,W,H,cx+dx,cy+dy,r,gg,b) } } 58 dx = dx + 1 59 } 60 dy = dy + 1 61 } 62 return 0 63} 64// Bresenham line 65func line(fb: *i64, W: i64, H: i64, x0: i64, y0: i64, x1: i64, y1: i64, r: i64, gg: i64, b: i64) -> i64 { 66 var x: i64 = x0; var y: i64 = y0 67 var dx: i64 = x1 - x0; if dx < 0 { dx = 0 - dx } 68 var dy: i64 = y1 - y0; if dy < 0 { dy = 0 - dy } 69 var sx: i64 = 1; if x1 < x0 { sx = 0 - 1 } 70 var sy: i64 = 1; if y1 < y0 { sy = 0 - 1 } 71 var err: i64 = dx - dy 72 var guard: i64 = 0 73 while guard < PG_MAGIC_6000 { 74 px_set(fb,W,H,x,y,r,gg,b) 75 if x == x1 { if y == y1 { guard = PG_MAGIC_6000 } } 76 if guard < PG_MAGIC_6000 { 77 let e2: i64 = err + err 78 if e2 > (0 - dy) { err = err - dy; x = x + sx } 79 if e2 < dx { err = err + dx; y = y + sy } 80 guard = guard + 1 81 } 82 } 83 return 0 84} 85// cubic bezier ribbon P0(x1,y1) C(x1,my)(x2,my) P3(x2,y2) sampled into segments 86func ribbon(fb: *i64, W: i64, H: i64, x1: i64, y1: i64, x2: i64, y2: i64, r: i64, gg: i64, b: i64) -> i64 { 87 let my: i64 = (y1 + y2) / 2 88 var px: i64 = x1; var py: i64 = y1 89 var i: i64 = 1 90 while i <= 18 { 91 let t: i64 = i * 1000 / 18 92 let u: i64 = 1000 - t 93 let uu: i64 = u*u/1000 94 let tt: i64 = t*t/1000 95 let uuu: i64 = uu*u/1000 96 let ttt: i64 = tt*t/1000 97 let c0: i64 = uuu 98 let c1: i64 = 3*uu*t/1000 99 let c2: i64 = 3*u*tt/1000 100 let c3: i64 = ttt 101 let bx: i64 = (c0*x1 + c1*x1 + c2*x2 + c3*x2) / 1000 102 let by: i64 = (c0*y1 + c1*my + c2*my + c3*y2) / 1000 103 line(fb,W,H,px,py,bx,by,r,gg,b) 104 px = bx; py = by 105 i = i + 1 106 } 107 return 0 108} 109// 5x7 glyph blit at scale; leftmost pixel = bit4. '_' not in font -> synth bottom row. 110func draw_char(fb: *i64, W: i64, H: i64, x: i64, y: i64, ch: i64, r: i64, gg: i64, b: i64, scale: i64) -> i64 { 111 var row: i64 = 0 112 while row < 7 { 113 var bits: i64 = nx_font_5x7_glyph_row(ch, row) 114 if ch == 95 { if row == 6 { bits = 31 } else { bits = 0 } } 115 if bits < 0 { bits = 0 } 116 var mask: i64 = 16 117 var col: i64 = 0 118 while col < 5 { 119 if (bits / mask) % 2 == 1 { 120 var sy: i64 = 0 121 while sy < scale { var sx: i64 = 0; while sx < scale { px_set(fb,W,H,x+col*scale+sx,y+row*scale+sy,r,gg,b); sx = sx + 1 } sy = sy + 1 } 122 } 123 mask = mask / 2 124 col = col + 1 125 } 126 row = row + 1 127 } 128 return 0 129} 130// draw a graph node NAME (no trailing .nx) centered at (cx, top y). scale px cell. 131func draw_name(fb: *i64, W: i64, H: i64, g: *EcoGraph, idx: i64, cx: i64, y: i64, r: i64, gg: i64, b: i64, scale: i64) -> i64 { 132 let off: i64 = g.node_off[idx] 133 var n: i64 = 0 134 while g.arena[off+n] != (0 as u8) { n = n + 1 } 135 var e: i64 = n 136 if n > 3 { if g.arena[off+n-3]==(46 as u8) { if g.arena[off+n-2]==(110 as u8) { if g.arena[off+n-1]==(120 as u8) { e = n - 3 } } } } 137 let wpx: i64 = e * 6 * scale 138 var x: i64 = cx - wpx/2 139 var k: i64 = 0 140 while k < e { 141 draw_char(fb,W,H,x,y,g.arena[off+k] as i64,r,gg,b,scale) 142 x = x + 6*scale 143 k = k + 1 144 } 145 return 0 146} 147 148func main(argc: i64, argv: *i64) -> i64 { 149 var storep: *u8 = 0 as *u8 150 var seedname: *u8 = 0 as *u8 151 var outp: *u8 = 0 as *u8 152 if argc >= 4 { 153 // explicit CLI mode: caller controls store + out (local/pipeline use) 154 storep = argv[1] as *u8 155 seedname = argv[2] as *u8 156 outp = argv[3] as *u8 157 } 158 if argc == 2 { 159 // MCP/API mode (allowlisted tools/call, CWD=nishihost): SEED ONLY. Store is PINNED and the 160 // output path is DERIVED inside the served docroot -- the caller cannot aim the write anywhere 161 // else (same containment idea as nx_render3d's docroot render). Seed sanitized [A-Za-z0-9_.], 162 // no leading dot, <=100 chars -> no traversal, no absolute paths. 163 seedname = argv[1] as *u8 164 let sl: i64 = pslen(seedname) 165 if sl < 1 { sys_write(1, "ERROR empty seed\n" as *u8, 17); return 5 } 166 if sl > 100 { sys_write(1, "ERROR seed too long\n" as *u8, 20); return 5 } 167 if seedname[0] == (46 as u8) { sys_write(1, "ERROR leading dot\n" as *u8, 18); return 5 } 168 var si: i64 = 0 169 while si < sl { 170 let c: i64 = seedname[si] as i64 171 var ok: i64 = 0 172 if c >= 97 { if c <= 122 { ok = 1 } } 173 if c >= 65 { if c <= 90 { ok = 1 } } 174 if c >= 48 { if c <= 57 { ok = 1 } } 175 if c == 95 { ok = 1 } 176 if c == 46 { ok = 1 } 177 if ok == 0 { sys_write(1, "ERROR bad seed char\n" as *u8, 20); return 5 } 178 si = si + 1 179 } 180 storep = "knowledge/store/ecograph_full\x00" as *u8 181 let ob: *u8 = sys_mmap(256) 182 var oo: i64 = 0 183 let pre: *u8 = "sites/nishifamily/compare/atlas/tree/lineage_\x00" as *u8 184 var pi: i64 = 0 185 while pre[pi] != (0 as u8) { ob[oo] = pre[pi]; oo = oo + 1; pi = pi + 1 } 186 si = 0 187 while si < sl { ob[oo] = seedname[si]; oo = oo + 1; si = si + 1 } 188 let suf: *u8 = ".png\x00" as *u8 189 pi = 0 190 while suf[pi] != (0 as u8) { ob[oo] = suf[pi]; oo = oo + 1; pi = pi + 1 } 191 ob[oo] = 0 as u8 192 outp = ob 193 } 194 if (seedname as i64) == 0 { 195 let us: *u8 = "usage: nx_eco_graph_png <store> <seed> <out.png> | nx_eco_graph_png <seed> (MCP mode: pinned store, docroot out)\n" as *u8 196 sys_write(1, us, pslen(us)) 197 return 2 198 } 199 let g: *EcoGraph = eg_load(storep) 200 if (g as i64) == 0 { sys_write(1, "ERROR store not found\n" as *u8, 22); return 3 } 201 let seed: i64 = eg_find(g, seedname, pslen(seedname)) 202 if seed < 0 { sys_write(1, "ERROR seed not in graph\n" as *u8, 24); return 4 } 203 204 // ancestry subgraph 205 let visited: *u8 = sys_mmap(g.node_count + 2) 206 var vi: i64 = 0 207 while vi < g.node_count { visited[vi] = 0 as u8; vi = vi + 1 } 208 visited[seed] = 1 as u8 209 let anc: *i64 = sys_mmap((PG_MAXS+2)*8) as *i64 210 let ancn: *i64 = sys_mmap(16) as *i64 211 ancn[0] = 0 212 eg_ancestors(g, seed, visited, anc, ancn, PG_MAXS - 1) 213 let sidx: *i64 = sys_mmap((PG_MAXS+2)*8) as *i64 214 sidx[0] = seed 215 var sn: i64 = 1 216 var ai: i64 = 0 217 while ai < ancn[0] { sidx[sn] = anc[ai]; sn = sn + 1; ai = ai + 1 } 218 let g2l: *i64 = sys_mmap((g.node_count+2)*8) as *i64 219 vi = 0 220 while vi < g.node_count { g2l[vi] = 0 - 1; vi = vi + 1 } 221 var li: i64 = 0 222 while li < sn { g2l[sidx[li]] = li; li = li + 1 } 223 224 // generations (longest path to god) 225 let gen: *i64 = sys_mmap((PG_MAXS+2)*8) as *i64 226 li = 0; while li < sn { gen[li] = 0; li = li + 1 } 227 var pass: i64 = 0 228 while pass < sn { 229 li = 0 230 while li < sn { 231 let v: i64 = sidx[li] 232 var p: i64 = g.out_head[v]; let e: i64 = g.out_head[v+1] 233 while p < e { 234 let dl: i64 = g2l[g.out_list[p]] 235 if dl >= 0 { if gen[li] < gen[dl] + 1 { gen[li] = gen[dl] + 1 } } 236 p = p + 1 237 } 238 li = li + 1 239 } 240 pass = pass + 1 241 } 242 var maxgen: i64 = 0 243 li = 0; while li < sn { if gen[li] > maxgen { maxgen = gen[li] } li = li + 1 } 244 245 // positions 246 let px: *i64 = sys_mmap((PG_MAXS+2)*8) as *i64 247 let py: *i64 = sys_mmap((PG_MAXS+2)*8) as *i64 248 var lvl: i64 = 0 249 while lvl <= maxgen { 250 var tot: i64 = 0 251 li = 0; while li < sn { if gen[li] == lvl { tot = tot + 1 } li = li + 1 } 252 var cnt: i64 = 0 253 li = 0 254 while li < sn { 255 if gen[li] == lvl { 256 px[li] = 70 + (cnt+1) * (PG_W - 140) / (tot+1) 257 py[li] = 54 + lvl * PG_LAYH 258 cnt = cnt + 1 259 } 260 li = li + 1 261 } 262 lvl = lvl + 1 263 } 264 let H: i64 = 54 + maxgen * PG_LAYH + 60 265 266 // ---- i64 framebuffer canvas ---- 267 let fb: *i64 = sys_mmap(PG_W * H * 8 + 64) as *i64 268 fill_rect(fb, PG_W, H, 0, 0, PG_W, H, 11, 14, 22) // #0b0e16 269 // generation bands + gen labels 270 var bl: i64 = 0 271 while bl <= maxgen { 272 if bl % 2 == 0 { fill_rect(fb, PG_W, H, 0, 2 + bl*PG_LAYH, PG_W, PG_LAYH, 21, 32, 54) } // #PG_MAGIC_152036 273 var lx: i64 = 14 274 draw_char(fb, PG_W, H, lx, 50 + bl*PG_LAYH, 103, 60, 74, 102, 1); lx = lx + 6 // g 275 draw_char(fb, PG_W, H, lx, 50 + bl*PG_LAYH, 101, 60, 74, 102, 1); lx = lx + 6 // e 276 draw_char(fb, PG_W, H, lx, 50 + bl*PG_LAYH, 110, 60, 74, 102, 1); lx = lx + 8 // n 277 let gnum: i64 = maxgen - bl 278 if gnum >= 10 { draw_char(fb, PG_W, H, lx, 50 + bl*PG_LAYH, 49, 60, 74, 102, 1); lx = lx + 6; draw_char(fb, PG_W, H, lx, 50 + bl*PG_LAYH, 48 + (gnum-10), 60, 74, 102, 1) } 279 if gnum < 10 { draw_char(fb, PG_W, H, lx, 50 + bl*PG_LAYH, 48 + gnum, 60, 74, 102, 1) } 280 bl = bl + 1 281 } 282 // ribbons (edges v -> imports in S) in a dim blue 283 li = 0 284 while li < sn { 285 let v: i64 = sidx[li] 286 var p: i64 = g.out_head[v]; let e: i64 = g.out_head[v+1] 287 while p < e { 288 let dl: i64 = g2l[g.out_list[p]] 289 if dl >= 0 { ribbon(fb, PG_W, H, px[li], py[li], px[dl], py[dl], 43, 79, 130) } 290 p = p + 1 291 } 292 li = li + 1 293 } 294 // nodes + labels 295 li = 0 296 while li < sn { 297 let v: i64 = sidx[li] 298 let x: i64 = px[li]; let y: i64 = py[li] 299 let ca: i64 = eg_ca(g, v) 300 var r: i64 = 5 301 if ca > 3 { r = 6 } 302 if ca > 12 { r = 8 } 303 if ca > 40 { r = 10 } 304 if ca > 150 { r = 13 } 305 var cr: i64 = 111; var cg: i64 = 139; var cb: i64 = 216 // #6f8bd8 ancestor 306 var isgod: i64 = 0 307 if ca >= 20 { cr = 143; cg = 180; cb = 255 } // #8fb4ff hub 308 if gen[li] == 0 { isgod = 1; cr = 255; cg = 212; cb = 121; if r < 8 { r = 8 } } // #ffd479 root 309 if li == 0 { cr = 201; cg = 160; cb = 255; if r < 11 { r = 11 } } // #c9a0ff seed 310 fill_disk(fb, PG_W, H, x, y, r, cr, cg, cb) 311 ring(fb, PG_W, H, x, y, r, 10, 13, 22) // #0a0d16 outline 312 var showlbl: i64 = 0 313 if li == 0 { showlbl = 1 } 314 if isgod == 1 { showlbl = 1 } 315 if ca >= 6 { showlbl = 1 } 316 if showlbl == 1 { draw_name(fb, PG_W, H, g, v, x, y - r - 18, 174, 185, 207, 2) } // #aeb9cf 317 li = li + 1 318 } 319 320 write_png(fb, PG_W, H, outp) 321 sys_write(1, "wrote compressed PNG " as *u8, 21); sys_write(1, outp, pslen(outp)); sys_write(1, "\n" as *u8, 1) 322 if argc == 2 { 323 // MCP mode: hand the caller the PUBLIC URL of what was just rendered 324 let up: *u8 = "URL https://nishifamily.com/compare/atlas/tree/lineage_\x00" as *u8 325 sys_write(1, up, pslen(up)) 326 sys_write(1, seedname, pslen(seedname)) 327 sys_write(1, ".png\n" as *u8, 5) 328 } 329 return 0 330}