nx_fbx_texm.nx
buildroot/runtime/nx_fbx_texm.nx
about
nx_fbx_texm.nx -- AUTHORED FBX MATERIALS -> TEXC + TEXM. The donor half of the clay fix.
THE MEASURED GAP THIS CLOSES. The four Fab donors (toon3d8 / dark_witch / dark_knight /
stylized_paladin) were ingested rigged (nx_fbx2nxa + nx_nxa_skin) but FLAT: their AUTHORED
texture maps were never carried, so the shipped cw_npc_atlas sampling path (TEXC+TEXM ->
world shader) has nothing to sample on them. nx_nxa_texm ship-mode BAKES maps from the genome;
a donor's surface is not bakeable -- it is authored, and the honest carry is the author's own
pixels behind the author's own UVs.
RECON-MEASURED FACTS THIS DESIGN RESTS ON (2026-08-23, full 4-donor census, not sampled):
- Every geometry's UV layer is ByPolygonVertex + IndexToDirect. One resolution path.
- LayerElementMaterial is AllSame on 75 of 79 geometries; ByPolygon on 4. Policy for
ByPolygon: FIRST POLYGON WINS per control point -- a declared approximation at material
boundaries, never a fabricated map.
- Map roles come from OP connections (DiffuseColor / ShininessExponent / ReflectionFactor /
NormalMap / TransparencyFactor), never from filename guessing.
- Albedo sources: PNG-8 (ctype 0/2/3/6) and BASELINE JPEG (every toon JPG is SOF0).
Depth-16 PNG appears ONLY in normal maps -> the normal map is declared absent by depth.
- Knight hair material has no DiffuseColor texture -> authored scalar diffuse (P prop) fills
its chart. Authored scalar, not a fabricated pixel.
SHAPE: one chart per DISTINCT diffuse source (scalar-filled charts get their own cell), grid
g = smallest g with g*g >= ncharts (derived), atlas res from knowledge/texture_ship_budget.conf
(the SAME owner nx_nxa_texm ship-mode reads -- one owner per number). TEXC stores FINAL
atlas-space Q16 UVs with chart ids, exactly the section shape nx_nxa_texc_lib writes, so the
shipped world sampler needs NO new convention. FBX v (origin bottom-left) is flipped to the
atlas row space (origin top-left) at ingest; declared here, applied once. Tiling UVs outside
[0,1] wrap by modulo before the cell mapping (a clamp would smear the border texel).
GRAMMAR: nx_fbx_texm <in.fbx> <texdir> <in_rigged.nxa> <out.nxa>
EXITS: 0 OK | 1 io | 2 usage | 3 bad-input | 4 missing-section | 5 desync-refused
REFUSALS ARE NAMED. Every skipped map and every unreadable source prints WHICH rule fired.
dependencies 6 imports · 0 importers
imports: nx_syscalls.nxnx_nxa.nxnx_fbx_core.nxnx_inflate.nxnx_png.nxnx_jpeg_ascii.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
| 42 | const FT_OK: i64 = 0 |
| 43 | const FT_E_IO: i64 = 1 |
| 44 | const FT_E_USAGE: i64 = 2 |
| 45 | const FT_E_BAD: i64 = 3 |
| 46 | const FT_E_NOSEC: i64 = 4 |
| 47 | const FT_E_DESYNC: i64 = 5 |
| 50 | const FT_HDR: i64 = 32 |
| 51 | const FT_TOCE: i64 = 32 |
| 52 | const FT_MAXSEC: i64 = 64 |
| 53 | const FT_MODE: i64 = 0x1a4 |
| 54 | const FT_PAD: i64 = 4096 |
| 55 | const FT_WORD: i64 = 8 |
| 58 | const FT_TX_PHDR: i64 = 4 |
| 59 | const FT_TX_STRIDE: i64 = 3 |
| 60 | const FT_Q16: i64 = 65536 |
| 63 | const FT_TM_HDRW: i64 = 6 |
| 64 | const FT_TM_RECW: i64 = 3 |
| 65 | const FT_TM_NMAP: i64 = 4 |
| 66 | const FT_TM_MODEL_METALROUGH: i64 = 2 |
| 69 | const FT_SLOT_ALBEDO: i64 = 0 |
| 70 | const FT_SLOT_METAL: i64 = 1 |
| 71 | const FT_SLOT_ROUGH: i64 = 2 |
| 74 | const FT_SHIP_CONF: *u8 = "knowledge/texture_ship_budget.conf" |
| 79 | const FT_MAXGEOM: i64 = 128 |
| 80 | const FT_MAXMAT: i64 = 128 |
| 81 | const FT_MAXTEX: i64 = 256 |
| 82 | const FT_MAXCONN: i64 = 65536 |
| 83 | const FT_MAXMODEL: i64 = 1024 |
| 84 | const FT_MAXCHART: i64 = 64 |
| 85 | const FT_MAXSLOT: i64 = 64 |
| 86 | const FT_NAMECAP: i64 = 256 |
| 90 | const FT_STACK_MIN: i64 = 1024 |
| 92 | const FT_FX_ONE: i64 = 1024 // fbxc_f64_fx unit (nx_fbx_core law) |
functions
| 94 | func ft_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 95 | func ft_err(s: *u8) -> i64 { sys_write(2, s, ft_slen(s)); return 0 } |
| 96 | func ft_out(s: *u8) -> i64 { sys_write(1, s, ft_slen(s)); return 0 } |
| 97 | func ft_outn(v: i64) -> i64 |
| 116 | func ft_nl() -> i64 { let b: *u8 = sys_mmap(8); b[0] = 10 as u8; sys_write(1, b, 1); sys_munmap(b, 8); return 0 } |
| 118 | func ft_streq(a: *u8, b: *u8) -> i64 called by 1: main |
| 126 | func ft_basename(src: *u8, srclen: i64, dst: *u8) -> i64 called by 1: main |
| 144 | func ft_pathjoin(dir: *u8, base: *u8, dst: *u8) -> i64 called by 1: main |
| 160 | func ft_png_load(path: *u8, prgb: *i64, pw: *i64, ph: *i64) -> i64 |
| 274 | func ft_img_load(path: *u8, prgb: *i64, pw: *i64, ph: *i64) -> i64 called by 1: ft_cell_load calls 7: sys_mmapsys_read_fileft_png_loadnx_jpeg_decode_rgbft_errft_outn+1 |
| 299 | func ft_cell_load(path: *u8, dst: *u8, res: i64, cx0: i64, cy0: i64, cell: i64, tmpp: *u8) -> i64 |
| 354 | func ft_downsample_into(src: *u8, w: i64, h: i64, atlas: *u8, res: i64, cx0: i64, cy0: i64, cell: i64) -> i64 called by 1: ft_cell_load |
| 398 | func ft_conf_at(path: *u8, key: *u8) -> i64 |
| 454 | func ft_ship_res() -> i64 |
| 472 | func ft_prop_i64(b: *u8, p: i64) -> i64 |
| 480 | func ft_prop_str(b: *u8, p: i64, out: *i64) -> i64 |
| 492 | func ft_str_is(s: *u8, n: i64, t: *u8) -> i64 |
| 504 | func ft_prop_dcolor(b: *u8, p0: i64, rr: *i64, gg: *i64, bb: *i64, mi: i64) -> i64 |
| 538 | func ft_conn_row(b: *u8, p0: i64, ca: *i64, cb: *i64, cp: *i64, n: i64) -> i64 |
| 574 | func nt2_rd64(b: *u8, off: i64) -> i64 |
| 580 | func nt2_wr64(b: *u8, off: i64, v: i64) -> i64 called by 1: ft_write_out |
| 586 | func nt2_tageq(b: *u8, off: i64, t: *u8) -> i64 |
| 592 | func ft_write_out(xb: *u8, xns: i64, uvq: *i64, uvwords: i64, res: i64, grid: i64, |
| 687 | func main(argc: i64, argv: *i64) -> i64 |