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}