code wiki / _hdl_build / nx_struct_diff.nx

nx_struct_diff.nx source

↩ module page · 107 lines · 4666 B

1// nx_struct_diff.nx -- STRUCTURAL exactness differ (the operator ratchet's metric, 2026-07-28): 2// extracts TEXT-LINE BANDS (consecutive inky rows) from two screenshots of the same page region 3// (oracle vs nishi), matches bands by vertical proximity, and reports the numbers OCR cannot see: 4// band counts, matched permille, mean |dy| (vertical drift), mean |dx| (line-start drift). Exact 5// structural match (the bar) = matched 1000 with dy/dx ~ 0; every miss is a nameable layout defect. 6// usage: nx_struct_diff <oracle.png> <nishi.png> <ay> <by> <h> 7import "nx_syscalls.nx" 8import "nx_visual_diff.nx" 9const SD_MAGIC_100000: i64 = 100000 10 11func sdw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 12func sdn(v: i64) -> i64 { let b: *u8=sys_mmap(28); var m: i64=v; if m<0{sdw("-" as *u8);m=0-m} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 } 13func sd_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while (s[i]&0xff)>=48 { if (s[i]&0xff)<=57 { v=v*10+((s[i]&0xff)-48); i=i+1 } else { return v } } return v } 14 15const SD_MAXBANDS: i64 = 2048 16// extract line bands of im rows [off, off+h): band = consecutive rows with >=3 ink px 17// (ink = mean rgb < 160, x in [8, w-8)). Writes ymid/xs into out arrays; returns band count. 18func sd_bands(im: *VdImg, off: i64, h: i64, ymid: *i64, xs: *i64) -> i64 { 19 var nb: i64 = 0 20 var in_band: i64 = 0 21 var b_y0: i64 = 0 22 var b_xs: i64 = SD_MAGIC_100000 23 var y: i64 = 0 24 while y < h { 25 let ry: i64 = off + y 26 var ink: i64 = 0 27 var firstx: i64 = SD_MAGIC_100000 28 if ry < im.h { 29 var x: i64 = 8 30 while x < im.w - 8 { 31 let pi: i64 = (ry * im.w + x) * im.nc 32 let lum: i64 = ((im.px[pi]&0xff) + (im.px[pi+1]&0xff) + (im.px[pi+2]&0xff)) / 3 33 if lum < 160 { ink = ink + 1; if x < firstx { firstx = x } } 34 x = x + 2 35 } 36 } 37 if ink >= 3 { 38 if in_band == 0 { in_band = 1; b_y0 = y; b_xs = firstx } 39 else { if firstx < b_xs { b_xs = firstx } } 40 } else { 41 if in_band == 1 { 42 if nb < SD_MAXBANDS { ymid[nb] = (b_y0 + y) / 2; xs[nb] = b_xs; nb = nb + 1 } 43 in_band = 0 44 } 45 } 46 y = y + 1 47 } 48 if in_band == 1 { if nb < SD_MAXBANDS { ymid[nb] = (b_y0 + h) / 2; xs[nb] = b_xs; nb = nb + 1 } } 49 return nb 50} 51 52func main(argc: i64, argv: *i64) -> i64 { 53 if argc < 6 { sdw("usage: nx_struct_diff <oracle.png> <nishi.png> <ay> <by> <h>\n" as *u8); sys_exit(2); return 2 } 54 let a: *VdImg = vd_load(argv[1] as *u8) 55 let b: *VdImg = vd_load(argv[2] as *u8) 56 if (a as i64) == 0 { sdw("STRUCT-DIFF FAIL: cannot load A\n" as *u8); sys_exit(3); return 3 } 57 if (b as i64) == 0 { sdw("STRUCT-DIFF FAIL: cannot load B\n" as *u8); sys_exit(3); return 3 } 58 let ay: i64 = sd_atoi(argv[3] as *u8) 59 let by: i64 = sd_atoi(argv[4] as *u8) 60 let h: i64 = sd_atoi(argv[5] as *u8) 61 let aym: *i64 = sys_mmap(8*SD_MAXBANDS) as *i64 62 let axs: *i64 = sys_mmap(8*SD_MAXBANDS) as *i64 63 let bym: *i64 = sys_mmap(8*SD_MAXBANDS) as *i64 64 let bxs: *i64 = sys_mmap(8*SD_MAXBANDS) as *i64 65 let bused: *i64 = sys_mmap(8*SD_MAXBANDS) as *i64 66 let na: i64 = sd_bands(a, ay, h, aym, axs) 67 let nb2: i64 = sd_bands(b, by, h, bym, bxs) 68 var matched: i64 = 0 69 var sumdy: i64 = 0 70 var sumdx: i64 = 0 71 var i: i64 = 0 72 while i < na { 73 var best: i64 = 0 - 1 74 var bestd: i64 = 26 // match tolerance: 25px vertical 75 var j: i64 = 0 76 while j < nb2 { 77 if bused[j] == 0 { 78 var d: i64 = bym[j] - aym[i] 79 if d < 0 { d = 0 - d } 80 if d < bestd { bestd = d; best = j } 81 } 82 j = j + 1 83 } 84 if best >= 0 { 85 bused[best] = 1 86 matched = matched + 1 87 sumdy = sumdy + bestd 88 var dx: i64 = bxs[best] - axs[i] 89 if dx < 0 { dx = 0 - dx } 90 sumdx = sumdx + dx 91 } 92 i = i + 1 93 } 94 sdw("STRUCT bandsA=" as *u8); sdn(na) 95 sdw(" bandsB=" as *u8); sdn(nb2) 96 var pm: i64 = 0 97 if na > 0 { pm = (matched * 1000) / na } 98 sdw(" matched=" as *u8); sdn(matched) 99 sdw(" (" as *u8); sdn(pm); sdw(" permille)" as *u8) 100 if matched > 0 { 101 sdw(" mean_dy=" as *u8); sdn(sumdy / matched) 102 sdw(" mean_dx=" as *u8); sdn(sumdx / matched) 103 } 104 sdw("\n" as *u8) 105 sys_exit(0) 106 return 0 107}