nx_roundtrip.nx
buildroot/runtime/nx_roundtrip.nx
about
nx_roundtrip.nx -- THE SELF-ORACLE: nishi -> glTF -> nishi, and NAME WHAT THE TRIP LOSES.
WHY THIS EXISTS. Every other fidelity oracle in this estate is currently blocked on ground truth: the
rigged reference corpus was measured 2026-08-22 and is DEGENERATE (eleven .nxa files byte-identical in
geometry), and D:\rigs is empty. A ROUND TRIP needs none of that. It compares our own ingest against our
own export, so it is available today, it carries no licensing question, and it measures the one thing an
external corpus can never tell us: what OUR OWN PIPELINE destroys.
IT COMPOSES, IT DOES NOT REIMPLEMENT. It forks nx_mesh2glb (NXMSH2 -> glb) then nx_gltf2mesh
(glb -> NXMSH2) and compares the input with the output. There is exactly one glTF writer and one glTF
reader in the estate and this organ is neither of them.
WHAT "IDENTICAL" MEANS HERE, AND WHY NOT BYTE IDENTITY. glTF is JSON plus a binary blob; key order,
float formatting and accessor packing are all free to change without meaning changing. Byte-comparing
the containers would report LOSSY on a lossless trip. So the assertion is SEMANTIC: triangle count,
layer count, layer NAMES, and vertex POSITIONS.
THE POSITION TOLERANCE IS ZERO, AND THAT IS DERIVED, NOT CHOSEN. nx_mesh2glb's own header states
positions "PASS THROUGH as raw f32 bits (zero re-quantisation)", and its code copies the 4-byte word
with mg_w32 without decoding it. A pass-through has no error term, so the correct tolerance is EXACT BIT
EQUALITY. Picking an epsilon would have hidden a real requantisation bug behind a number nobody derived.
If positions ever stop matching bit-for-bit, that is a REGRESSION IN THE WRITER, not rounding.
NORMALS AND COLOURS ARE KNOWN-LOSSY BY DESIGN and are deliberately NOT asserted: the writer transcodes
per-vertex normals f32 -> normalized i16 and per-tri colour f32 -> u8. Asserting on them would make this
organ permanently RED for a documented, intended conversion, and a detector that is permanently red is
one everyone learns to ignore. They are REPORTED, never scored.
EXITS: 0 IDENTICAL | 1 LOSSY | 2 usage | 3 UNOBSERVABLE
3 IS NOT A SOFTER 1. A missing tool, a fork that returns 127, an empty output, or an unreadable container
means the trip WAS NOT OBSERVED -- reporting that as LOSSY would blame the format for a harness failure.
This estate has been bitten by exactly that confusion, so the two are kept structurally apart.
license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
dependencies 2 imports · 0 importers
imports: nx_syscalls.nxnx_tool_run.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 38 | const RT_EXIT_IDENTICAL: i64 = 0 |
| 39 | const RT_EXIT_LOSSY: i64 = 1 |
| 40 | const RT_EXIT_USAGE: i64 = 2 |
| 41 | const RT_EXIT_UNOBSERVABLE: i64 = 3 |
| 46 | const RT_MAGIC_N: i64 = 78 // 'N' at offset 0 |
| 47 | const RT_MAGIC_2: i64 = 50 // '2' at offset 5 |
| 48 | const RT_MAGIC_2_OFF: i64 = 5 |
| 49 | const RT_NLAY_OFF: i64 = 8 // u32 layer count |
| 50 | const RT_NTRI_OFF: i64 = 12 // u32 triangle count |
| 51 | const RT_HDR_BASE: i64 = 16 // first layer record |
| 52 | const RT_LAYER_STRIDE: i64 = 24 // name[16] + u32 off + u32 count |
| 53 | const RT_LAYER_NAME_LEN: i64 = 16 |
| 54 | const RT_LAYER_OFF_FLD: i64 = 16 |
| 55 | const RT_LAYER_CNT_FLD: i64 = 20 |
| 56 | const RT_TRI_STRIDE: i64 = 84 // 36 position + 36 normal + 12 colour |
| 57 | const RT_POS_BYTES: i64 = 36 // the only bytes asserted; see header |
| 58 | const RT_MIN_CONTAINER: i64 = 44 // the smallest readable NXMSH2, per the writer's own guard |
| 60 | const RT_WORD: i64 = 8 |
| 61 | const RT_U32_BYTES: i64 = 4 |
| 62 | const RT_BYTE_MASK: i64 = 255 |
| 63 | const RT_STDOUT: i64 = 1 |
| 64 | const RT_PATH_CAP: i64 = 4096 |
| 65 | const RT_CAP_CAP: i64 = 262144 // capture buffer for a forked tool's stdout |
| 66 | const RT_ARGV_SLOTS: i64 = 8 |
| 67 | const RT_SCRATCH: i64 = 64 |
| 68 | const RT_DECIMAL: i64 = 10 |
| 69 | const RT_ASCII_ZERO: i64 = 48 |
| 70 | const RT_ASCII_MINUS: i64 = 45 |
| 71 | const RT_ASCII_NL: i64 = 10 |
| 72 | const RT_ASCII_SLASH: i64 = 47 |
| 73 | const RT_EXEC_NOT_FOUND: i64 = 127 // execve failure surfaces here; NOT a lossy trip |
| 74 | const RT_VERB_CHECK: i64 = 99 // 'c' |
| 78 | const RT_NAME_REPORT_MAX: i64 = 12 |
| 80 | const RT_M2G_DEFAULT: *u8 = "/volume1/homes/elderwesto/nishihost/_offc/nx_mesh2glb.elf" |
| 81 | const RT_G2M_DEFAULT: *u8 = "/volume1/homes/elderwesto/nishihost/_offc/nx_gltf2mesh.elf" |
| 82 | const RT_MID_NAME: *u8 = "rt_mid.glb" |
| 83 | const RT_BACK_NAME: *u8 = "rt_back.nxmesh" |
functions
| 85 | func rt_out(s: *u8) -> i64 |
| 91 | func rt_u32(b: *u8, o: i64) -> i64 called by 1: rt_hdr |
| 94 | func rt_bputs(buf: *u8, off: i64, s: *u8) -> i64 called by 1: rt_say |
| 100 | func rt_bputnum(buf: *u8, off: i64, v: i64, scratch: *u8) -> i64 called by 1: rt_say |
| 115 | func rt_say(label: *u8, v: i64, scratch: *u8, line: *u8) -> i64 |
| 124 | func rt_join(dst: *u8, dir: *u8, name: *u8) -> i64 called by 1: rt_check |
| 145 | func rt_run2(tool: *u8, a1: *u8, a2: *u8, cap: *u8, olen: *i64) -> i64 |
| 154 | func rt_hdr(b: *u8, n: i64, out: *i64) -> i64 |
| 171 | func rt_name_eq(a: *u8, ao: i64, b: *u8, bo: i64) -> i64 called by 1: rt_check |
| 179 | func rt_print_name(b: *u8, off: i64) -> i64 |
| 189 | func rt_usage() -> i64 |
| 199 | func rt_check(inp: *u8, scratch_dir: *u8, m2g: *u8, g2m: *u8) -> i64 |
| 377 | func main(argc: i64, argv: *i64) -> i64 |