nx_photogrambench.nx source
↩ module page · 536 lines · 26154 B
1// nx_photogrambench.nx -- THE PHOTOGRAMMETRY RULER. Measures where the Nishi stack stands on the FULL
2// photo->metric-3D pipeline vs the July-2026 SOTA bar (COLMAP / RealityCapture / Metashape / OpenMVS /
3// MVSNet / VGGT-Pi3 feed-forward / 3DGS), so the build order is CHOSEN BY MEASUREMENT, not by taste.
4//
5// WHY THIS INSTRUMENT AND NOT THE OLD ONE: nx_cadtwin_census scores a photogrammetry-adjacent slice by
6// HAND-TYPED cell values, and its citations drift from what the files actually do -- it credits classical
7// image features to "nx_descriptor", which is a TEXT descriptor profiler for the writing arc (dx_profile
8// over a term lexicon), with no pixel in it. Presence-scoring plus hand-typed cells is exactly the
9// author-optimism the operator liar-killed. So this ruler is built the other way round:
10//
11// 1. EVERY score is COMPUTED FROM DISK, never typed. us = f(source-file exists, gate/test exists).
12// us=0 ABSENT (no source) us=1 PRESENT-UNPROVEN (source, no gate) us=2 PROVEN (source + gate).
13// "Unproven = absent" is softened to 1 rather than 0 only because the source is a real head start;
14// it can never reach 2 without a gate on disk. No file, no credit.
15// 2. A read-grounded CAP per axis: us = min(computed, cap). The cap is the honest ceiling of what the
16// named organ ACTUALLY does, set only after reading it. This is what stops a file-name from voting.
17// Every cap below 2 carries its reason inline. THIS is where the old census leaked.
18// 3. INSTRUMENT SELF-CHECK: a positive probe (a file that must exist) and a negative probe (a file that
19// must not) run every time. If the prober cannot tell present from absent, the census is VOID and
20// the organ exits RED -- a ruler that cannot discriminate must refuse to publish a number.
21// 4. GAP QUEUE: axes ranked by (2-us) * weight * (1+blocks), where `blocks` counts how many OTHER axes
22// are structurally dead until this one exists. Weight alone is not a build order -- graded on weight
23// only, twelve axes tie at the top and the "queue" orders nothing. The blocking term is what makes
24// it an order: it promotes the axis whose absence sterilizes the most of the pipeline. The top of
25// the queue is the next rung, chosen by the ruler and not by me.
26//
27// Run from the nxc2 root (paths are repo-relative). Emits human rows, a summary, the gap queue, and a
28// final one-line JSON envelope for the MCP/API surface.
29// license_tier: ORIGINAL expect_exit: 0
30import "nx_syscalls.nx"
31import "nx_estate_path.nx" // ep_anchor: the CWD must not decide this organ's verdict
32const PGB_MAGIC_1048576: i64 = 1048576
33const PGB_MAGIC_65536: i64 = 65536
34const PGB_MAGIC_917504: i64 = 917504
35
36const PGB_MAX: i64 = 64
37const PGB_NONE: *u8 = "runtime/_pgb_no_such_file.nx"
38
39static pgb_nm: *i64
40static pgb_bar: *i64
41static pgb_us: *i64
42static pgb_wt: *i64
43static pgb_blk: *i64
44static pgb_n: i64
45
46// ---- MEASURED head-to-head totals, loaded from the benchmark's own output ---------------------------
47// Presence-scoring answers "does a file exist". It cannot answer "is it any good", and a read-grounded
48// cap only replaces a filename's opinion with MINE -- still a hand-typed number, still unfalsifiable.
49// For the axes where an industry baseline exists, the score is instead COMPUTED from a real
50// head-to-head against OpenCV SIFT and ORB on the Oxford/VGG sequences, restricted to the HELD-OUT
51// split so sequences that informed the design cannot vote on it.
52static pgb_bench_ok: i64
53static pgb_m_sift: *i64 // [matches, correct, kp]
54static pgb_m_orb: *i64
55static pgb_m_nishi: *i64
56static pgb_booted: i64
57
58func pw(s: *u8) -> i64 {
59 var n: i64 = 0
60 while s[n] != (0 as u8) { n = n + 1 }
61 sys_write(1, s, n)
62 return 0
63}
64
65func pn(v: i64) -> i64 {
66 let bb: *u8 = sys_mmap(32)
67 let t: *u8 = sys_mmap(32)
68 var m: i64 = v
69 var neg: i64 = 0
70 if m < 0 { neg = 1; m = 0 - m }
71 var k: i64 = 0
72 if m == 0 { t[0] = 48 as u8; k = 1 }
73 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
74 var i: i64 = 0
75 if neg == 1 { bb[0] = 45 as u8; i = 1 }
76 var j: i64 = 0
77 while j < k { bb[i + j] = t[k - 1 - j]; j = j + 1 }
78 sys_write(1, bb, i + k)
79 return 0
80}
81
82// pad a string out to width columns so the table reads as a table
83func ppad(s: *u8, width: i64) -> i64 {
84 var n: i64 = 0
85 while s[n] != (0 as u8) { n = n + 1 }
86 sys_write(1, s, n)
87 let sp: *u8 = sys_mmap(8)
88 sp[0] = 32 as u8
89 var i: i64 = n
90 while i < width { sys_write(1, sp, 1); i = i + 1 }
91 return 0
92}
93
94// THE PROBE: 1 iff the path opens for read. This single primitive is the whole evidence base, which is
95// why it is self-checked against a known-present and a known-absent path before any row is scored.
96//
97// TWO ROOTS, deliberately: the organ runs from the repo root during development (sources under
98// runtime/) and from nishihost when invoked as a registered tool on the NAS (the same tree lives under
99// buildroot/runtime/). Probing only the first would make every axis read ABSENT on the NAS and the
100// positive self-check would fail, so the tool would correctly declare its own census VOID -- correct,
101// but useless. Trying the buildroot prefix as a fallback makes the ruler location-independent without
102// weakening it: a path that exists under neither root still scores 0, and the negative control still
103// fails under both.
104func phave(path: *u8) -> i64 {
105 let fd: i64 = sys_openat_rd(path)
106 if fd >= 0 {
107 sys_close(fd)
108 return 1
109 }
110 let buf: *u8 = sys_mmap(512)
111 let pfx: *u8 = "buildroot/" as *u8
112 var i: i64 = 0
113 while pfx[i] != (0 as u8) {
114 buf[i] = pfx[i]
115 i = i + 1
116 }
117 var j: i64 = 0
118 while path[j] != (0 as u8) {
119 let o: i64 = i + j
120 buf[o] = path[j]
121 j = j + 1
122 }
123 let e: i64 = i + j
124 buf[e] = 0 as u8
125 let fd2: i64 = sys_openat_rd(buf)
126 if fd2 >= 0 {
127 sys_close(fd2)
128 return 1
129 }
130 return 0
131}
132
133// Accumulate one TSV row's numbers into the right method bucket.
134// Columns: seq pair nuisance split method kp matches correct prec_permil ms_permil corner_x100
135func pgb_bench_row(buf: *u8, a: i64, b: i64) -> i64 {
136 // walk the tab-separated fields, recording each field's start offset
137 let fs: *i64 = sys_mmap(32 * 8) as *i64
138 var nf: i64 = 0
139 fs[0] = a
140 nf = 1
141 var i: i64 = a
142 while i < b {
143 if buf[i] == (9 as u8) {
144 if nf < 24 {
145 fs[nf] = i + 1
146 nf = nf + 1
147 }
148 }
149 i = i + 1
150 }
151 if nf < 9 { return 0 }
152 // field 3 = split; only HELD-OUT rows may vote
153 let sp: i64 = fs[3]
154 if buf[sp] != (72 as u8) { return 0 }
155 if buf[sp + 1] != (69 as u8) { return 0 }
156 // field 4 = method; discriminate on first two chars: SIft / ORb / NIshi
157 let mp: i64 = fs[4]
158 var which: i64 = 0 - 1
159 if buf[mp] == (83 as u8) { which = 0 }
160 if buf[mp] == (79 as u8) { which = 1 }
161 if buf[mp] == (78 as u8) { which = 2 }
162 if which < 0 { return 0 }
163 // fields 5,6,7 = kp, matches, correct
164 let vals: *i64 = sys_mmap(8 * 8) as *i64
165 var f: i64 = 5
166 while f < 8 {
167 var p: i64 = fs[f]
168 var v: i64 = 0
169 var reading: i64 = 1
170 while reading == 1 {
171 if p >= b { reading = 0 } else {
172 let d: i64 = buf[p] as i64
173 if d >= 48 { if d <= 57 { v = v * 10 + (d - 48); p = p + 1 } else { reading = 0 } } else { reading = 0 }
174 }
175 }
176 vals[f - 5] = v
177 f = f + 1
178 }
179 var tgt: *i64 = pgb_m_sift
180 if which == 1 { tgt = pgb_m_orb }
181 if which == 2 { tgt = pgb_m_nishi }
182 tgt[0] = tgt[0] + vals[1] // matches
183 tgt[1] = tgt[1] + vals[2] // correct
184 tgt[2] = tgt[2] + vals[0] // kp
185 return 1
186}
187
188func pgb_load_bench() -> i64 {
189 pgb_m_sift = sys_mmap(64) as *i64
190 pgb_m_orb = sys_mmap(64) as *i64
191 pgb_m_nishi = sys_mmap(64) as *i64
192 pgb_bench_ok = 0
193 // Four candidate locations, in preference order. The last two matter operationally: nx_ship packs a
194 // source directory into buildroot/runtime, so shipping the benchmark output ALONGSIDE the sources is
195 // what lets the deployed tool score from the same evidence as the local one. Without it the live
196 // instrument honestly reports UNMEASURED while the local one reports numbers, and two rulers
197 // disagreeing about the same stack is worse than either being wrong.
198 var fd: i64 = sys_openat_rd("knowledge/status/photogram_hbench_results.tsv" as *u8)
199 if fd < 0 { fd = sys_openat_rd("buildroot/knowledge/status/photogram_hbench_results.tsv" as *u8) }
200 if fd < 0 { fd = sys_openat_rd("runtime/photogram_hbench_results.tsv" as *u8) }
201 if fd < 0 { fd = sys_openat_rd("buildroot/runtime/photogram_hbench_results.tsv" as *u8) }
202 if fd < 0 { return 0 }
203 let buf: *u8 = sys_mmap(PGB_MAGIC_1048576)
204 var total: i64 = 0
205 var reading: i64 = 1
206 while reading == 1 {
207 let got: i64 = sys_read(fd, buf + total, PGB_MAGIC_65536)
208 if got <= 0 { reading = 0 } else {
209 total = total + got
210 if total > PGB_MAGIC_917504 { reading = 0 }
211 }
212 }
213 sys_close(fd)
214 if total <= 0 { return 0 }
215 var rows: i64 = 0
216 var ls: i64 = 0
217 var i: i64 = 0
218 while i <= total {
219 var eol: i64 = 0
220 if i == total { eol = 1 } else { if buf[i] == (10 as u8) { eol = 1 } }
221 if eol == 1 {
222 if i > ls { rows = rows + pgb_bench_row(buf, ls, i) }
223 ls = i + 1
224 }
225 i = i + 1
226 }
227 if rows > 0 { pgb_bench_ok = 1 }
228 return rows
229}
230
231func pgb_boot() -> i64 {
232 if pgb_booted == 1 { return 0 }
233 pgb_nm = sys_mmap(PGB_MAX * 8) as *i64
234 pgb_bar = sys_mmap(PGB_MAX * 8) as *i64
235 pgb_us = sys_mmap(PGB_MAX * 8) as *i64
236 pgb_wt = sys_mmap(PGB_MAX * 8) as *i64
237 pgb_blk = sys_mmap(PGB_MAX * 8) as *i64
238 pgb_n = 0
239 pgb_booted = 1
240 return 0
241}
242
243// Score and record ONE axis. name/bar are labels; src1/src2 are candidate implementations (either counts);
244// g1/g2 are candidate gate/test files (either counts); w = weight 1..3 (how much a working pipeline needs
245// it); cap = the read-grounded ceiling; blk = how many other axes are structurally dead without this one
246// (counted from the pipeline dependency chain, not guessed). Returns the scored value.
247func pgb_axis(name: *u8, bar: *u8, src1: *u8, src2: *u8, g1: *u8, g2: *u8, w: i64, cap: i64, blk: i64) -> i64 {
248 pgb_boot()
249 var src: i64 = 0
250 if phave(src1) == 1 { src = 1 }
251 if phave(src2) == 1 { src = 1 }
252 var gt: i64 = 0
253 if phave(g1) == 1 { gt = 1 }
254 if phave(g2) == 1 { gt = 1 }
255 var us: i64 = 0
256 if src == 1 { us = 1 }
257 if src == 1 { if gt == 1 { us = 2 } }
258 if us > cap { us = cap }
259 let idx: i64 = pgb_n
260 pgb_nm[idx] = name as i64
261 pgb_bar[idx] = bar as i64
262 pgb_us[idx] = us
263 pgb_wt[idx] = w
264 pgb_blk[idx] = blk
265 pgb_n = idx + 1
266 ppad(name, 44)
267 pw(" w" as *u8)
268 pn(w)
269 pw(" blk" as *u8)
270 pn(blk)
271 pw(" us=" as *u8)
272 pn(us)
273 pw("/2 " as *u8)
274 if us == 0 { pw("ABSENT " as *u8) }
275 if us == 1 { pw("PRESENT-UNPROVEN " as *u8) }
276 if us == 2 { pw("PROVEN " as *u8) }
277 pw(" bar: " as *u8)
278 pw(bar)
279 pw("\n" as *u8)
280 return us
281}
282
283// MEASURED axis: score from the head-to-head instead of from a hand-typed cap.
284// The bar is deliberately the STRONGER baseline: full marks require matching or beating SIFT's
285// matching score on held-out sequences. Parity with ORB alone earns 1. No benchmark data means 0 --
286// unmeasured is not "probably fine", it is unproven, and this ruler already refuses to score anything
287// it cannot check. metric: correct matches per keypoint (yield), which unlike bare precision cannot be
288// inflated by returning a handful of confident matches.
289func pgb_axis_measured(name: *u8, bar: *u8, w: i64, blk: i64) -> i64 {
290 pgb_boot()
291 var us: i64 = 0
292 var ours: i64 = 0
293 var sift: i64 = 0
294 var orb: i64 = 0
295 if pgb_bench_ok == 1 {
296 if pgb_m_nishi[2] > 0 { ours = (pgb_m_nishi[1] * 1000) / pgb_m_nishi[2] }
297 if pgb_m_sift[2] > 0 { sift = (pgb_m_sift[1] * 1000) / pgb_m_sift[2] }
298 if pgb_m_orb[2] > 0 { orb = (pgb_m_orb[1] * 1000) / pgb_m_orb[2] }
299 if ours > 0 { us = 1 }
300 if ours >= orb { us = 1 }
301 if ours >= sift { us = 2 }
302 if ours == 0 { us = 0 }
303 }
304 let idx: i64 = pgb_n
305 pgb_nm[idx] = name as i64
306 pgb_bar[idx] = bar as i64
307 pgb_us[idx] = us
308 pgb_wt[idx] = w
309 pgb_blk[idx] = blk
310 pgb_n = idx + 1
311 ppad(name, 44)
312 pw(" w" as *u8)
313 pn(w)
314 pw(" blk" as *u8)
315 pn(blk)
316 pw(" us=" as *u8)
317 pn(us)
318 pw("/2 " as *u8)
319 if us == 0 { pw("UNMEASURED " as *u8) }
320 if us == 1 { pw("BEHIND-SOTA " as *u8) }
321 if us == 2 { pw("MEETS-SOTA " as *u8) }
322 pw(" MEASURED yield/1000: nishi=" as *u8)
323 pn(ours)
324 pw(" sift=" as *u8)
325 pn(sift)
326 pw(" orb=" as *u8)
327 pn(orb)
328 pw(" " as *u8)
329 pw(bar)
330 pw("\n" as *u8)
331 return us
332}
333
334func main() -> i64 {
335 // ANCHOR FIRST (2026-08-04, nx_cwdguard finding): this organ reads a RELATIVE
336 // knowledge/ path, so its answer depended on where it was launched. No-op when
337 // already at the estate root, so the cron/MCP context is unchanged.
338 ep_anchor()
339 pgb_boot()
340 pgb_load_bench()
341 pw("=== nx_photogrambench -- NISHI photogrammetry stack vs the July-2026 SOTA pipeline ===\n" as *u8)
342 pw("scoring: presence axes COMPUTED from disk (source + gate) under a read-grounded cap; front-end axes\n" as *u8)
343 pw(" scored by a MEASURED head-to-head vs OpenCV SIFT/ORB on held-out Oxford/VGG sequences.\n" as *u8)
344 pw(" Full marks on a measured axis require meeting the STRONGER baseline (SIFT), not merely ORB.\n" as *u8)
345 pw("bar: COLMAP/RealityCapture/Metashape (classical) . OpenMVS/MVSNet (dense) . VGGT/Pi3 (feed-forward) . 3DGS\n\n" as *u8)
346
347 // ---- INSTRUMENT SELF-CHECK: the prober must discriminate, or the whole census is void ----
348 let pos: i64 = phave("runtime/nx_recon3d.nx" as *u8)
349 let neg: i64 = phave("runtime/_pgb_no_such_file.nx" as *u8)
350 pw("-- instrument self-check -- positive-probe(nx_recon3d.nx)=" as *u8)
351 pn(pos)
352 pw(" negative-probe(absent)=" as *u8)
353 pn(neg)
354 var probe_ok: i64 = 0
355 if pos == 1 { if neg == 0 { probe_ok = 1 } }
356 if probe_ok == 1 { pw(" -> DISCRIMINATES (census valid)\n\n" as *u8) }
357 if probe_ok == 0 { pw(" -> BROKEN (census VOID)\n\n" as *u8) }
358
359 pw("-- A. INPUT / CAMERA --\n" as *u8)
360 pgb_axis("input:jpeg-decode-real-photo" as *u8, "read a real camera JPEG" as *u8, "runtime/nx_jpeg_decode_image.nx" as *u8, "runtime/nx_jpeg_decoder.nx" as *u8, "runtime/nx_jpeg_decode_image_test.nx" as *u8, "runtime/nx_jpeg_decode_test.nx" as *u8, 3, 2, 10)
361 pgb_axis("input:png-decode" as *u8, "lossless source frames" as *u8, "runtime/nx_png_decoder.nx" as *u8, "runtime/nx_png.nx" as *u8, "runtime/nx_png_dynamic_huffman_test.nx" as *u8, "runtime/nx_png_real_smoke.nx" as *u8, 1, 2, 0)
362 pgb_axis("input:exif-intrinsics-prior" as *u8, "COLMAP seeds f from EXIF" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 3, 2, 1)
363 pgb_axis("input:lens-distortion-undistort" as *u8, "Brown-Conrady / OPENCV model" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 3, 2, 2)
364 // cap 1: nx_camera_q14 carries a Q14 pinhole basis+focal; no distortion terms, no calibration solve
365 pgb_axis("camera:intrinsics-model-K" as *u8, "full K with principal point + distortion" as *u8, "runtime/nx_camera_q14.nx" as *u8, "runtime/nx_calibrate.nx" as *u8, "runtime/nx_calibrate_test.nx" as *u8, PGB_NONE, 3, 1, 3)
366
367 pw("\n-- B. FRONT END (correspondence) -- the half that decides whether ANY of the back end can run --\n" as *u8)
368 // cap 1: nx_features is Harris/Sobel at ONE scale + Hu moments. No DoG/scale-space pyramid, no
369 // orientation assignment -> not scale-invariant. READ 2026-07-25.
370 // Scale invariance now EXISTS (pf_pyramid) but the honest question is whether it works, and the
371 // head-to-head answers that on the zoom+rotation sequences. Measured, not capped.
372 pgb_axis_measured("front:keypoint-detect-scale-invariant" as *u8, "vs OpenCV SIFT/ORB, Oxford held-out" as *u8, 3, 9)
373 // 2026-07-25: BUILT. nx_photogram_front supplies a 256-bit steered-BRIEF descriptor with
374 // intensity-centroid orientation; gate T4 measures Hamming 0 under a 90-degree rotation against 128
375 // for the unsteered control. (The two files formerly credited here were nx_descriptor = a TEXT
376 // lexicon profiler and nx_image_feature_extract = a 16-element GLOBAL aesthetic signature; neither
377 // is a local patch descriptor. That mis-citation is what the read-grounded cap exists to catch.)
378 // These two axes are no longer scored by file presence under a cap I typed. They are scored by a
379 // real head-to-head against OpenCV SIFT and ORB on held-out Oxford/VGG sequences.
380 pgb_axis_measured("front:local-descriptor-rot-invariant" as *u8, "vs OpenCV SIFT/ORB, Oxford held-out" as *u8, 3, 8)
381 pgb_axis_measured("front:descriptor-match-ratio-test" as *u8, "vs OpenCV SIFT/ORB, Oxford held-out" as *u8, 3, 7)
382 // 2026-07-25: cap RAISED 1 -> 2. nx_imgransac remains capped at 1 on this axis (a real RANSAC, but
383 // over 2-DOF translation / 4-DOF similarity for copy-detection -- it cannot fit E). pf_ransac_e in
384 // nx_photogram_front fits the ESSENTIAL matrix itself, scored by epipolar residual and re-fit on the
385 // full inlier set; gate T5a/T5b measure 5-of-6 planted outliers rejected with the noise control firing.
386 pgb_axis("front:geometric-verify-ransac-epipolar" as *u8, "RANSAC on E/F, inlier epipolar support" as *u8, "runtime/nx_photogram_front.nx" as *u8, PGB_NONE, "runtime/nx_photogram_front_gate.nx" as *u8, PGB_NONE, 3, 2, 4)
387 pgb_axis("front:pair-selection-vocab-tree" as *u8, "vocab tree / retrieval for N^2 blowup" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 2, 2, 0)
388
389 pw("\n-- C. SPARSE SfM (the back end we actually have) --\n" as *u8)
390 pgb_axis("sfm:two-view-essential-8point" as *u8, "normalized 8-point + Hartley norm" as *u8, "runtime/nx_essential3d.nx" as *u8, PGB_NONE, "runtime/nx_essential3d_gate.nx" as *u8, "runtime/_hdl_build/nx_essential3d_gate.nx" as *u8, 3, 2, 5)
391 pgb_axis("sfm:pose-decompose-cheirality" as *u8, "E -> (R,t), 4-way cheirality select" as *u8, "runtime/nx_epose.nx" as *u8, PGB_NONE, "runtime/nx_epose_gate.nx" as *u8, "runtime/_hdl_build/nx_epose_gate.nx" as *u8, 3, 2, 5)
392 pgb_axis("sfm:triangulation-multiview" as *u8, "N-view triangulation" as *u8, "runtime/nx_recon3d.nx" as *u8, PGB_NONE, "runtime/nx_recon3d_gate.nx" as *u8, "runtime/_hdl_build/nx_recon3d_gate.nx" as *u8, 3, 2, 5)
393 pgb_axis("sfm:absolute-pose-pnp-p3p" as *u8, "P3P/EPnP: register view N+1 to cloud" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 3, 2, 3)
394 pgb_axis("sfm:feature-tracks-across-views" as *u8, "union-find tracks over match graph" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 3, 2, 4)
395 pgb_axis("sfm:incremental-reconstruction-loop" as *u8, "seed -> register -> triangulate -> BA -> loop" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 3, 2, 6)
396 // cap 1: nx_bundleadjust is POSE-ONLY 2-view coordinate descent on the epipolar cost. No joint
397 // camera+point refinement, no Jacobians/Schur complement. READ 2026-07-25.
398 pgb_axis("sfm:bundle-adjustment-full" as *u8, "joint cameras+points, Schur (Ceres-class)" as *u8, "runtime/nx_bundleadjust.nx" as *u8, PGB_NONE, "runtime/nx_bundleadjust_gate.nx" as *u8, "runtime/_hdl_build/nx_bundleadjust_gate.nx" as *u8, 3, 1, 2)
399 pgb_axis("sfm:rigid-registration-align" as *u8, "Horn/Kabsch scan-to-scan align" as *u8, "runtime/nx_register3d.nx" as *u8, PGB_NONE, "runtime/nx_register3d_gate.nx" as *u8, "runtime/_hdl_build/nx_register3d_gate.nx" as *u8, 2, 2, 1)
400 pgb_axis("sfm:global-rotation-averaging" as *u8, "drift control on long loops" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 2, 2, 1)
401
402 pw("\n-- D. DENSE / SURFACE (entirely absent: nx_depth_tri is a Z-BUFFER RASTERIZER, not stereo depth) --\n" as *u8)
403 pgb_axis("dense:stereo-match-patchmatch-sgm" as *u8, "PatchMatch / SGM per-pixel depth" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 3, 2, 3)
404 pgb_axis("dense:depth-map-fusion" as *u8, "multi-view fusion + visibility check" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 3, 2, 2)
405 pgb_axis("dense:surface-recon-poisson" as *u8, "Poisson / Delaunay watertight surface" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 3, 2, 1)
406 // cap 1: nx_meshtex exists as mesh texturing, but no multi-view photo-to-UV atlas projection
407 pgb_axis("dense:texture-from-source-photos" as *u8, "per-face view selection + UV atlas" as *u8, "runtime/nx_meshtex.nx" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, 2, 1, 0)
408 pgb_axis("dense:gaussian-splat-radiance" as *u8, "3DGS from posed images" as *u8, "runtime/nx_gsplat.nx" as *u8, PGB_NONE, "runtime/nx_gsplat_gate.nx" as *u8, "runtime/nx_gsplat_photofit_gate.nx" as *u8, 2, 2, 0)
409
410 pw("\n-- E. QUALITY / METROLOGY --\n" as *u8)
411 pgb_axis("qual:chamfer-accuracy-metric" as *u8, "DTU-style Chamfer vs ground truth" as *u8, "runtime/nx_recon3d.nx" as *u8, PGB_NONE, "runtime/nx_recon3d_gate.nx" as *u8, "runtime/_hdl_build/nx_recon3d_gate.nx" as *u8, 3, 2, 2)
412 pgb_axis("qual:reprojection-error-report" as *u8, "per-camera RMS reprojection" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 2, 2, 1)
413 pgb_axis("qual:metric-scale-gcp" as *u8, "scale bar / GCP -> real-world units" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 2, 2, 0)
414
415 pw("\n-- F. PRODUCT SURFACE --\n" as *u8)
416 pgb_axis("prod:end-to-end-cli-organ" as *u8, "one command: images -> reconstruction" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 3, 2, 2)
417 pgb_axis("prod:mcp-tool-live" as *u8, "agent-callable over /mcp" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 3, 2, 0)
418 pgb_axis("prod:http-api-endpoint" as *u8, "sovereign /api route" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, PGB_NONE, 2, 2, 0)
419 // 2026-07-25: filed. Capped at 1 deliberately -- the document exists but nothing MECHANICALLY
420 // enforces it, and a governance doc no gate checks is exactly the kind of unproven artifact this
421 // ruler refuses to score full marks. Raise to 2 when a gate verifies the RACI's cells still resolve.
422 pgb_axis("prod:raci-governance" as *u8, "documented ownership per stage" as *u8, "knowledge/status/photogrammetry_raci_20260725.md" as *u8, PGB_NONE, PGB_NONE, PGB_NONE, 1, 1, 0)
423
424 // ---- SUMMARY ----
425 var absent: i64 = 0
426 var unproven: i64 = 0
427 var proven: i64 = 0
428 var sum_us: i64 = 0
429 var sum_max: i64 = 0
430 var i: i64 = 0
431 while i < pgb_n {
432 let u: i64 = pgb_us[i]
433 let w: i64 = pgb_wt[i]
434 if u == 0 { absent = absent + 1 }
435 if u == 1 { unproven = unproven + 1 }
436 if u == 2 { proven = proven + 1 }
437 sum_us = sum_us + u * w
438 sum_max = sum_max + 2 * w
439 i = i + 1
440 }
441 var permil: i64 = 0
442 if sum_max > 0 { permil = (sum_us * 1000) / sum_max }
443
444 pw("\n=== SUMMARY ===\n" as *u8)
445 pw("axes graded = " as *u8)
446 pn(pgb_n)
447 pw("\nPROVEN (2/2) = " as *u8)
448 pn(proven)
449 pw("\nUNPROVEN(1/2) = " as *u8)
450 pn(unproven)
451 pw("\nABSENT (0/2) = " as *u8)
452 pn(absent)
453 pw("\nHONEST SCORE = " as *u8)
454 pn(permil)
455 pw(" permil [weighted " as *u8)
456 pn(sum_us)
457 pw("/" as *u8)
458 pn(sum_max)
459 pw("]\n" as *u8)
460
461 // ---- GAP QUEUE: deficit*weight desc = the build order. Selection sort over an index array. ----
462 pw("\n=== GAP QUEUE prio = (2-us) x weight x (1+blocks), desc = THE BUILD ORDER ===\n" as *u8)
463 let ord: *i64 = sys_mmap(PGB_MAX * 8) as *i64
464 i = 0
465 while i < pgb_n { ord[i] = i; i = i + 1 }
466 var a: i64 = 0
467 while a < pgb_n {
468 var best: i64 = a
469 var b: i64 = a + 1
470 while b < pgb_n {
471 let ib: i64 = ord[b]
472 let ibest: i64 = ord[best]
473 let pb: i64 = (2 - pgb_us[ib]) * pgb_wt[ib] * (1 + pgb_blk[ib])
474 let pbest: i64 = (2 - pgb_us[ibest]) * pgb_wt[ibest] * (1 + pgb_blk[ibest])
475 if pb > pbest { best = b }
476 b = b + 1
477 }
478 let tmp: i64 = ord[a]
479 ord[a] = ord[best]
480 ord[best] = tmp
481 a = a + 1
482 }
483 var rank: i64 = 1
484 i = 0
485 while i < pgb_n {
486 let ix: i64 = ord[i]
487 let def: i64 = (2 - pgb_us[ix]) * pgb_wt[ix] * (1 + pgb_blk[ix])
488 if def > 0 {
489 if rank <= 12 {
490 pw(" " as *u8)
491 pn(rank)
492 pw(". prio=" as *u8)
493 pn(def)
494 pw(" " as *u8)
495 let nmv: i64 = pgb_nm[ix]
496 let nmp: *u8 = nmv as *u8
497 ppad(nmp, 42)
498 pw(" (us=" as *u8)
499 pn(pgb_us[ix])
500 pw(")\n" as *u8)
501 rank = rank + 1
502 }
503 }
504 i = i + 1
505 }
506
507 // ---- MACHINE ENVELOPE for the MCP / API surface ----
508 pw("\n{\"tool\":\"nx_photogrambench\",\"v\":1,\"axes\":" as *u8)
509 pn(pgb_n)
510 pw(",\"proven\":" as *u8)
511 pn(proven)
512 pw(",\"unproven\":" as *u8)
513 pn(unproven)
514 pw(",\"absent\":" as *u8)
515 pn(absent)
516 pw(",\"weighted_us\":" as *u8)
517 pn(sum_us)
518 pw(",\"weighted_max\":" as *u8)
519 pn(sum_max)
520 pw(",\"permil\":" as *u8)
521 pn(permil)
522 pw(",\"probe_ok\":" as *u8)
523 pn(probe_ok)
524 pw("}\n" as *u8)
525
526 if probe_ok == 0 {
527 pw("\nRED -- prober cannot discriminate present from absent; census VOID\n" as *u8)
528 return 1
529 }
530 pw("\nGREEN -- census valid (prober discriminates; nothing hand-scored)\n" as *u8)
531 if pgb_bench_ok == 0 {
532 pw("NOTE: no head-to-head results on disk, so the measured axes read UNMEASURED (0), not 'probably fine'.\n" as *u8)
533 pw(" Run the benchmark to populate knowledge/status/photogram_hbench_results.tsv.\n" as *u8)
534 }
535 return 0
536}