nx_rigfloor.nx source
↩ module page · 675 lines · 33090 B
1// nx_rigfloor.nx -- DERIVE THE ASSET FLOOR FROM THE REFERENCE CORPUS INSTEAD OF DECLARING IT.
2//
3// WHY THIS EXISTS. nx_asset_floor_gate is THE ruler for "is this character game-ready", and it
4// carried five HAND-PICKED bounds: AF_VERT_MIN 4000, AF_TRI_MIN 8000, AF_TRI_MAX 120000,
5// AF_JOINT_MIN 40, AF_MIDLINE 3000. A picked bound structurally cannot answer the question the
6// operator actually asks -- can we emit something that MATCHES THE MODELS WE HAVE -- because it
7// is not a function of the models we have. This organ makes the bound a MEASUREMENT of the
8// reference population, so the gate stops ASSERTING a floor and starts REPORTING one.
9//
10// THIS IS NOT A SECOND RULER. nx_asset_floor_gate remains the one ruler; this is the PRODUCER
11// that derives its bounds into a conf the gate reads. Producer and consumer cannot disagree
12// because there is exactly one of each. nx_spendgate ranked nx_asset_floor_gate as the top
13// incumbent for this intent -- extending it is the entire point, and a rival floor organ would
14// be the duplicate-ruler defect.
15//
16// ROSTER-DRIVEN, NOT DIRECTORY-WALKING, ON PURPOSE. sys_getdents64 exists and a bounded walker
17// is proven in nx_wiki_content_loader. It is still the wrong primitive HERE: the corpus IS the
18// thing under measurement, so a silently-truncated listing would not merely lose a file, it would
19// corrupt the derived bound itself and then publish that bound as a fact. A roster is complete by
20// construction and its coverage is DECIDABLE -- rows declared vs rows read, both printed, and if
21// they do not sum then no floor is written at all.
22//
23// METHOD: OBSERVED RANGE OVER THE FULL POPULATION. No percentile index is chosen, because
24// choosing one would reintroduce exactly the picked constant this organ exists to remove. The
25// band is [min, max] of what the reference assets actually are.
26//
27// WHY n>=2 IS NOT A MAGIC NUMBER. A range needs two observations to HAVE WIDTH. At n=1 the band
28// is a point and would refuse every asset except that one file -- a bar that is not a bar. So the
29// organ abstains at n<2. That is the arity of the operation, not a threshold anyone selected.
30//
31// EXITS: 0 OK | 1 OUT-OF-BAND | 2 usage | 3 UNOBSERVABLE (no corpus / degenerate / partial read)
32// A partial roster read NEVER degrades to a floor: it abstains, because a floor derived from an
33// unknown fraction of the corpus is worse than no floor -- it looks authoritative and is not.
34//
35// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
36import "nx_syscalls.nx"
37
38// ---- exit contract ----
39const RF_EXIT_OK: i64 = 0
40const RF_EXIT_OUT_OF_BAND: i64 = 1
41const RF_EXIT_USAGE: i64 = 2
42const RF_EXIT_UNOBSERVABLE: i64 = 3
43
44// ---- NXA container layout (mirrors nx_asset_floor_gate; one format, one reader shape) ----
45const RF_HDR_BYTES: i64 = 32
46const RF_SECHDR_BYTES: i64 = 32
47const RF_SEC_OFF_FIELD: i64 = 8
48const RF_NSEC_FIELD: i64 = 16
49const RF_MAGIC_BYTES: i64 = 8
50const RF_TAG_BYTES: i64 = 4
51const RF_WORD_BYTES: i64 = 8
52const RF_PERMIL: i64 = 1000
53const RF_BYTE_RADIX: i64 = 256
54const RF_BYTE_MASK: i64 = 255
55
56// ---- ASCII ----
57const RF_ASCII_ZERO: i64 = 48
58const RF_ASCII_NINE: i64 = 57
59const RF_ASCII_MINUS: i64 = 45
60const RF_ASCII_EQ: i64 = 61
61const RF_ASCII_NL: i64 = 10
62const RF_ASCII_CR: i64 = 13
63const RF_ASCII_HASH: i64 = 35
64const RF_VERB_DERIVE: i64 = 100
65const RF_VERB_SCORE: i64 = 115
66const RF_DECIMAL: i64 = 10
67
68// ---- buffers. Every cap here REFUSES rather than truncating (no silent caps). ----
69const RF_CONF_CAP: i64 = 65536
70const RF_SCRATCH_CAP: i64 = 64
71const RF_PATH_CAP: i64 = 4096
72const RF_MAX_ROWS: i64 = 4096
73const RF_STDOUT: i64 = 1
74const RF_PAIR: i64 = 2
75
76// ---- CONTAINERS. Two live in this estate and BOTH START WITH 'N', so one byte cannot tell them
77// apart -- the estate's own readers check only b[0] and therefore cannot either. NXANIM01 is an
78// exact EIGHT-byte check, so testing it FIRST is unambiguous and everything else beginning with
79// 'N' falls through to NXMSH2.
80const RF_CONTAINER_UNKNOWN: i64 = 0
81const RF_CONTAINER_NXA: i64 = 1 // NXANIM01 -- our shipped rigged asset
82const RF_CONTAINER_MSH: i64 = 2 // NXMSH2 -- what nx_gltf2mesh emits from a glTF donor
83// The magic is EIGHT bytes, "NXMSH2\0\0". Every shipping consumer validates byte 0 and byte 5 --
84// checking byte 0 alone is NOT enough here, because 'NXANIM01' also begins with 'N'. NXANIM01 is
85// tested exhaustively first, so byte 5 is belt-and-braces against any other N-file.
86const RF_MSH_MAGIC_BYTE: i64 = 78 // 'N' at offset 0
87const RF_MSH_VER_OFF: i64 = 5
88const RF_MSH_VER_BYTE: i64 = 50 // '2' at offset 5 -- the version discriminator
89const RF_MSH_NLAYER_FIELD: i64 = 8 // u32 LE layer count
90const RF_MSH_NTRI_FIELD: i64 = 12 // u32 LE triangle count
91const RF_MSH_HDR_BYTES: i64 = 16 // FIXED header; the FULL header is 16 + nlay*24
92const RF_MSH_LAYROW_BYTES: i64 = 24
93const RF_U32_BYTES: i64 = 4
94// A triangle record is 84 BYTES = 21 float32: nine position (three vertices x xyz), nine normal,
95// and THREE per-triangle colour. Positions are unindexed -- there is no shared-vertex table and no
96// joint section anywhere in the format. That is a property of the container, not of any one asset.
97const RF_MSH_TRIREC_BYTES: i64 = 84
98
99// ---- AXES. Seven exist; WHICH ONES A CONTAINER CAN ANSWER IS A PROPERTY OF THE CONTAINER, and an
100// axis a container cannot answer is reported UNAVAILABLE rather than filled with a plausible
101// substitute. Deriving "verts" for NXMSH2 as tris*3 would be arithmetic dressed as a measurement:
102// it would be the SAME number for every mesh in existence and would silently add a zero-width axis
103// that dilutes axes_with_width. Name a bucket for what was measured, never for what you wish it was.
104const RF_AXES: i64 = 7
105const RF_AX_VERTS: i64 = 0
106const RF_AX_TRIS: i64 = 1
107const RF_AX_JOINTS: i64 = 2
108const RF_AX_TPV_PERMIL: i64 = 3
109const RF_AX_VPJ: i64 = 4
110const RF_AX_PARTS: i64 = 5
111const RF_AX_TPP: i64 = 6 // triangles per named part -- the NXMSH2 shape signal
112
113func rf_out(s: *u8) -> i64 {
114 var n: i64 = 0
115 while s[n] != 0 { n = n + 1 }
116 sys_write(RF_STDOUT, s, n)
117 return 0
118}
119// little-endian i64 read, byte by byte -- the container is not alignment-guaranteed
120func rf_rd64(b: *u8, off: i64) -> i64 {
121 var v: i64 = 0
122 var i: i64 = RF_WORD_BYTES - 1
123 while i >= 0 { v = v*RF_BYTE_RADIX + ((b[off + i] & RF_BYTE_MASK) as i64); i = i - 1 }
124 return v
125}
126func rf_tag_eq(b: *u8, off: i64, t: *u8) -> i64 {
127 var i: i64 = 0
128 while i < RF_TAG_BYTES { if b[off + i] != t[i] { return 0 } i = i + 1 }
129 return 1
130}
131// section byte-offset by tag, or -1. Walks EVERY declared section: the table is small and a
132// partial scan would report a capability absent that the asset actually carries.
133func rf_find(b: *u8, nsec: i64, t: *u8) -> i64 {
134 var s: i64 = 0
135 while s < nsec {
136 let e: i64 = RF_HDR_BYTES + s*RF_SECHDR_BYTES
137 if rf_tag_eq(b, e, t) == 1 { return rf_rd64(b, e + RF_SEC_OFF_FIELD) }
138 s = s + 1
139 }
140 return 0 - 1
141}
142
143// ---- text emission into a buffer (build the line, then ONE write) ----
144func rf_bputs(buf: *u8, off: i64, s: *u8) -> i64 {
145 var o: i64 = off
146 var i: i64 = 0
147 while s[i] != 0 { buf[o] = s[i]; o = o + 1; i = i + 1 }
148 return o
149}
150// scratch is passed IN, never allocated here -- this runs per row per axis
151func rf_bputnum(buf: *u8, off: i64, v: i64, scratch: *u8) -> i64 {
152 var o: i64 = off
153 var n: i64 = v
154 if n < 0 { buf[o] = RF_ASCII_MINUS; o = o + 1; n = 0 - n }
155 var k: i64 = 0
156 if n == 0 { scratch[k] = RF_ASCII_ZERO; k = 1 }
157 while n > 0 {
158 let q: i64 = n / RF_DECIMAL
159 scratch[k] = RF_ASCII_ZERO + (n - q*RF_DECIMAL)
160 n = q
161 k = k + 1
162 }
163 while k > 0 { k = k - 1; buf[o] = scratch[k]; o = o + 1 }
164 return o
165}
166func rf_kv(buf: *u8, off: i64, key: *u8, v: i64, scratch: *u8) -> i64 {
167 var o: i64 = rf_bputs(buf, off, key)
168 buf[o] = RF_ASCII_EQ
169 o = o + 1
170 o = rf_bputnum(buf, o, v, scratch)
171 buf[o] = RF_ASCII_NL
172 return o + 1
173}
174// Emit one axis band ONLY if the corpus's container could answer it. An axis the container cannot
175// answer must be ABSENT from the conf, never present-with-a-plausible-value: rf_score treats an
176// absent bound as UNOBSERVABLE, so omission propagates the honest answer instead of inventing one.
177func rf_emit_band(cb: *u8, off: i64, kmin: *u8, kmax: *u8, idx: i64,
178 lo: *i64, hi: *i64, corpus_av: *i64, scratch: *u8) -> i64 {
179 if corpus_av[idx] == 0 { return off }
180 var o: i64 = rf_kv(cb, off, kmin, lo[idx], scratch)
181 o = rf_kv(cb, o, kmax, hi[idx], scratch)
182 return o
183}
184func rf_say(label: *u8, v: i64, scratch: *u8, line: *u8) -> i64 {
185 var o: i64 = rf_bputs(line, 0, label)
186 o = rf_bputnum(line, o, v, scratch)
187 line[o] = RF_ASCII_NL
188 sys_write(RF_STDOUT, line, o + 1)
189 return 0
190}
191
192// ---- conf reading. LINE-ANCHORED: a key matched mid-line would read a number out of a comment
193// and answer confidently with the wrong bound. Returns `miss` when absent, so an ABSENT key stays
194// distinguishable from a key whose value is legitimately zero.
195func rf_conf_i64(buf: *u8, n: i64, key: *u8, miss: i64) -> i64 {
196 var i: i64 = 0
197 var at_line_start: i64 = 1
198 var found: i64 = 0
199 var result: i64 = miss
200 while i < n {
201 if found == 0 { if at_line_start == 1 {
202 var k: i64 = 0
203 var matched: i64 = 1
204 while key[k] != 0 {
205 if i + k >= n { matched = 0 }
206 if matched == 1 { if buf[i + k] != key[k] { matched = 0 } }
207 k = k + 1
208 }
209 if matched == 1 { if i + k < n { if buf[i + k] == RF_ASCII_EQ {
210 var o: i64 = i + k + 1
211 var neg: i64 = 0
212 if o < n { if buf[o] == RF_ASCII_MINUS { neg = 1; o = o + 1 } }
213 var v: i64 = 0
214 var digits: i64 = 0
215 var scanning: i64 = 1
216 while scanning == 1 {
217 if o >= n { scanning = 0 }
218 if scanning == 1 {
219 let c: i64 = (buf[o] & RF_BYTE_MASK) as i64
220 if c < RF_ASCII_ZERO { scanning = 0 }
221 if c > RF_ASCII_NINE { scanning = 0 }
222 if scanning == 1 {
223 v = v*RF_DECIMAL + (c - RF_ASCII_ZERO)
224 digits = digits + 1
225 o = o + 1
226 }
227 }
228 }
229 if digits > 0 {
230 if neg == 1 { v = 0 - v }
231 result = v
232 found = 1
233 }
234 } } }
235 } }
236 if buf[i] == RF_ASCII_NL { at_line_start = 1 } else { at_line_start = 0 }
237 i = i + 1
238 }
239 return result
240}
241
242// little-endian u32 -- NXMSH2 uses 32-bit counts where NXANIM01 uses 64
243func rf_rd32(b: *u8, off: i64) -> i64 {
244 var v: i64 = 0
245 var i: i64 = RF_U32_BYTES - 1
246 while i >= 0 { v = v*RF_BYTE_RADIX + ((b[off + i] & RF_BYTE_MASK) as i64); i = i - 1 }
247 return v
248}
249
250// ---- one asset measurement ----
251// Fills out[] on the seven axes and avail[] with 1 for each axis this CONTAINER can answer.
252// Returns the container kind (RF_CONTAINER_NXA / _MSH), or RF_CONTAINER_UNKNOWN when the asset is
253// unmeasurable -- and it SAYS WHICH, because an unmeasurable asset and a failing asset demand
254// opposite actions.
255func rf_measure(path: *u8, out: *i64, avail: *i64) -> i64 {
256 var a: i64 = 0
257 while a < RF_AXES { out[a] = 0; avail[a] = 0; a = a + 1 }
258 let lp: *i64 = sys_mmap(RF_WORD_BYTES*RF_PAIR) as *i64
259 let b: *u8 = sys_read_file(path, lp)
260 if (b as i64) == 0 { rf_out(" UNREADABLE (cannot open)\n" as *u8); return RF_CONTAINER_UNKNOWN }
261 let n: i64 = lp[0]
262 if n < RF_HDR_BYTES { rf_out(" UNREADABLE (shorter than a container header)\n" as *u8); return RF_CONTAINER_UNKNOWN }
263
264 // NXANIM01 FIRST, on all eight bytes. Both containers start with 'N', so the exact check must
265 // run before the one-byte one or every NXA would be misread as an NXMSH2.
266 let mg: *u8 = "NXANIM01" as *u8
267 var magic: i64 = 1
268 var i: i64 = 0
269 while i < RF_MAGIC_BYTES { if b[i] != mg[i] { magic = 0 } i = i + 1 }
270
271 if magic == 1 {
272 let nsec: i64 = rf_rd64(b, RF_NSEC_FIELD)
273 let ov: i64 = rf_find(b, nsec, "VERT" as *u8)
274 let ot: i64 = rf_find(b, nsec, "TRIS" as *u8)
275 let ok: i64 = rf_find(b, nsec, "SKEL" as *u8)
276 if ov < 0 { rf_out(" UNREADABLE (no VERT section)\n" as *u8); return RF_CONTAINER_UNKNOWN }
277 if ot < 0 { rf_out(" UNREADABLE (no TRIS section)\n" as *u8); return RF_CONTAINER_UNKNOWN }
278 if ok < 0 { rf_out(" UNREADABLE (no SKEL section -- not a RIGGED asset)\n" as *u8); return RF_CONTAINER_UNKNOWN }
279 let nv: i64 = rf_rd64(b, ov)
280 let nt: i64 = rf_rd64(b, ot)
281 let nj: i64 = rf_rd64(b, ok)
282 if nv <= 0 { rf_out(" UNREADABLE (VERT count is zero)\n" as *u8); return RF_CONTAINER_UNKNOWN }
283 if nj <= 0 { rf_out(" UNREADABLE (SKEL count is zero)\n" as *u8); return RF_CONTAINER_UNKNOWN }
284 out[RF_AX_VERTS] = nv
285 out[RF_AX_TRIS] = nt
286 out[RF_AX_JOINTS] = nj
287 // SCALE-FREE axes: these answer "does it match", because a larger or smaller character is
288 // still the same KIND of character. Raw counts are scale-bound.
289 out[RF_AX_TPV_PERMIL] = nt*RF_PERMIL/nv
290 out[RF_AX_VPJ] = nv/nj
291 avail[RF_AX_VERTS] = 1
292 avail[RF_AX_TRIS] = 1
293 avail[RF_AX_JOINTS] = 1
294 avail[RF_AX_TPV_PERMIL] = 1
295 avail[RF_AX_VPJ] = 1
296 return RF_CONTAINER_NXA
297 }
298
299 var msh: i64 = 0
300 if (b[0] & RF_BYTE_MASK) == RF_MSH_MAGIC_BYTE {
301 if (b[RF_MSH_VER_OFF] & RF_BYTE_MASK) == RF_MSH_VER_BYTE { msh = 1 }
302 }
303 if msh == 1 {
304 let nl: i64 = rf_rd32(b, RF_MSH_NLAYER_FIELD)
305 let nt: i64 = rf_rd32(b, RF_MSH_NTRI_FIELD)
306 if nt <= 0 { rf_out(" UNREADABLE (NXMSH2 triangle count is zero)\n" as *u8); return RF_CONTAINER_UNKNOWN }
307 // SIZE CHECK against the format's own arithmetic: 16 + nlay*24 + nt*84. A truncated file
308 // whose header still parses would otherwise report a confident count for data that is not
309 // there -- the same class as a partial scan publishing its prefix as a total.
310 let need: i64 = RF_MSH_HDR_BYTES + nl*RF_MSH_LAYROW_BYTES + nt*RF_MSH_TRIREC_BYTES
311 if n < need {
312 rf_out(" UNREADABLE (NXMSH2 shorter than its own declared header+records)\n" as *u8)
313 return RF_CONTAINER_UNKNOWN
314 }
315 // ONLY WHAT THE CONTAINER ACTUALLY HOLDS. NXMSH2 has no vertex table and no joint section,
316 // so verts/joints/ratios stay UNAVAILABLE. Deriving verts as a fixed multiple of tris would
317 // be identical for every mesh ever written -- a zero-width axis masquerading as a
318 // measurement, which would also dilute axes_with_width.
319 out[RF_AX_TRIS] = nt
320 avail[RF_AX_TRIS] = 1
321 // A ZERO-LAYER MESH IS VALID, NOT BROKEN. nx_bvhfk is a shipping producer that writes
322 // nlay=0. Rejecting it would call a conformant file unreadable; dividing by it would be a
323 // SIGFPE. The honest answer is that the parts axes are simply UNAVAILABLE for such a file.
324 if nl > 0 {
325 out[RF_AX_PARTS] = nl
326 out[RF_AX_TPP] = nt/nl
327 avail[RF_AX_PARTS] = 1
328 avail[RF_AX_TPP] = 1
329 }
330 return RF_CONTAINER_MSH
331 }
332
333 rf_out(" UNREADABLE (magic is neither NXANIM01 nor NXMSH2)\n" as *u8)
334 return RF_CONTAINER_UNKNOWN
335}
336
337func rf_usage() -> i64 {
338 rf_out("usage: nx_rigfloor derive <roster> <out.conf> -- measure the FULL roster, write the floor\n" as *u8)
339 rf_out(" nx_rigfloor score <asset.nxa> <floor.conf> -- score one asset against that floor\n" as *u8)
340 rf_out(" roster: one NXA path per line; blank lines and # comments ignored.\n" as *u8)
341 rf_out(" exits: 0 OK | 1 OUT-OF-BAND | 2 usage | 3 UNOBSERVABLE\n" as *u8)
342 return RF_EXIT_USAGE
343}
344
345func rf_derive(roster: *u8, outconf: *u8) -> i64 {
346 let line: *u8 = sys_mmap(RF_PATH_CAP)
347 let scratch: *u8 = sys_mmap(RF_SCRATCH_CAP)
348 let pathbuf: *u8 = sys_mmap(RF_PATH_CAP)
349 let m: *i64 = sys_mmap(RF_WORD_BYTES*RF_AXES) as *i64
350 let lo: *i64 = sys_mmap(RF_WORD_BYTES*RF_AXES) as *i64
351 let hi: *i64 = sys_mmap(RF_WORD_BYTES*RF_AXES) as *i64
352 let av: *i64 = sys_mmap(RF_WORD_BYTES*RF_AXES) as *i64
353 let corpus_av: *i64 = sys_mmap(RF_WORD_BYTES*RF_AXES) as *i64
354 var ai: i64 = 0
355 while ai < RF_AXES { corpus_av[ai] = 0; ai = ai + 1 }
356 var container: i64 = RF_CONTAINER_UNKNOWN
357 var mixed: i64 = 0
358
359 let rp: *i64 = sys_mmap(RF_WORD_BYTES*RF_PAIR) as *i64
360 let rb: *u8 = sys_read_file(roster, rp)
361 if (rb as i64) == 0 {
362 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=roster-unreadable path=" as *u8)
363 rf_out(roster)
364 rf_out("\n" as *u8)
365 return RF_EXIT_UNOBSERVABLE
366 }
367 let rn: i64 = rp[0]
368 rf_out("NX-RIGFLOOR derive\n roster: " as *u8)
369 rf_out(roster)
370 rf_out("\n" as *u8)
371
372 var declared: i64 = 0
373 var read_ok: i64 = 0
374 var failed: i64 = 0
375 var capped: i64 = 0
376 var first: i64 = 1
377 var i: i64 = 0
378 while i < rn {
379 var e: i64 = i
380 var eol: i64 = 0
381 while eol == 0 {
382 if e >= rn { eol = 1 }
383 if eol == 0 { if rb[e] == RF_ASCII_NL { eol = 1 } else { e = e + 1 } }
384 }
385 var len: i64 = e - i
386 if len > 0 { if rb[i + len - 1] == RF_ASCII_CR { len = len - 1 } }
387 var skip: i64 = 0
388 if len <= 0 { skip = 1 }
389 if skip == 0 { if rb[i] == RF_ASCII_HASH { skip = 1 } }
390 if skip == 0 { if len >= RF_PATH_CAP { skip = 1; capped = 1 } }
391 if skip == 0 {
392 declared = declared + 1
393 if declared > RF_MAX_ROWS { capped = 1 }
394 if capped == 0 {
395 var c: i64 = 0
396 while c < len { pathbuf[c] = rb[i + c]; c = c + 1 }
397 pathbuf[len] = 0
398 rf_out(" - " as *u8)
399 rf_out(pathbuf)
400 rf_out("\n" as *u8)
401 let kind: i64 = rf_measure(pathbuf, m, av)
402 if kind != RF_CONTAINER_UNKNOWN {
403 read_ok = read_ok + 1
404 // A MIXED-CONTAINER CORPUS IS NOT A CORPUS. NXANIM01 answers verts/joints;
405 // NXMSH2 answers parts and cannot answer either. Ranging across both would put
406 // two different quantities in one band and publish it as a bound.
407 if container == RF_CONTAINER_UNKNOWN { container = kind }
408 if container != kind { mixed = 1 }
409 if kind == RF_CONTAINER_NXA {
410 rf_say(" container=NXANIM01 verts=" as *u8, m[RF_AX_VERTS], scratch, line)
411 rf_say(" tris=" as *u8, m[RF_AX_TRIS], scratch, line)
412 rf_say(" joints=" as *u8, m[RF_AX_JOINTS], scratch, line)
413 rf_say(" tris_per_vert_permil=" as *u8, m[RF_AX_TPV_PERMIL], scratch, line)
414 rf_say(" verts_per_joint=" as *u8, m[RF_AX_VPJ], scratch, line)
415 }
416 if kind == RF_CONTAINER_MSH {
417 rf_say(" container=NXMSH2 tris=" as *u8, m[RF_AX_TRIS], scratch, line)
418 rf_say(" parts=" as *u8, m[RF_AX_PARTS], scratch, line)
419 rf_say(" tris_per_part=" as *u8, m[RF_AX_TPP], scratch, line)
420 }
421 var a: i64 = 0
422 while a < RF_AXES {
423 if av[a] == 1 {
424 // an axis enters the corpus set the first time ANY row answers it, and
425 // its band opens on that row -- not on row zero, which may not have it
426 if corpus_av[a] == 0 { corpus_av[a] = 1; lo[a] = m[a]; hi[a] = m[a] }
427 else {
428 if m[a] < lo[a] { lo[a] = m[a] }
429 if m[a] > hi[a] { hi[a] = m[a] }
430 }
431 }
432 a = a + 1
433 }
434 first = 0
435 } else { failed = failed + 1 }
436 }
437 }
438 i = e + 1
439 }
440
441 rf_say(" n_declared=" as *u8, declared, scratch, line)
442 rf_say(" n_read=" as *u8, read_ok, scratch, line)
443 rf_say(" n_failed=" as *u8, failed, scratch, line)
444 // A PARTITION IS A CLAIM: CHECK THE PARTS SUM.
445 var sums: i64 = 0
446 if read_ok + failed == declared { sums = 1 }
447 rf_say(" partition_reconciles=" as *u8, sums, scratch, line)
448
449 if capped == 1 {
450 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=roster-hit-the-row-or-path-cap -- RAISE IT DELIBERATELY, a floor is NOT written from a prefix\n" as *u8)
451 return RF_EXIT_UNOBSERVABLE
452 }
453 if sums == 0 {
454 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=partition-does-not-reconcile\n" as *u8)
455 return RF_EXIT_UNOBSERVABLE
456 }
457 if failed > 0 {
458 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=corpus-incomplete -- a floor derived from an unknown fraction of the corpus looks authoritative and is not\n" as *u8)
459 return RF_EXIT_UNOBSERVABLE
460 }
461 if read_ok < RF_PAIR {
462 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=degenerate-band-too-few -- a range needs two observations to have width; at n<2 the band is a point and would refuse every asset but one\n" as *u8)
463 return RF_EXIT_UNOBSERVABLE
464 }
465 // n>=2 IS NECESSARY BUT NOT SUFFICIENT, AND REAL DATA REFUTED THE FIRST VERSION OF THIS GUARD.
466 // Measured 2026-08-22 over the estate's own corpus: eleven rigged NXA files that are BYTE
467 // IDENTICAL in geometry (verts=14164 tris=28092 joints=104, every one) satisfy n>=2 and still
468 // produce a ZERO-WIDTH band on every axis -- a bar that admits only the reference itself and
469 // rejects every other asset in existence. The invariant is not the COUNT of observations, it
470 // is the WIDTH those observations produce. Counting was the wrong SHAPE, not the wrong number.
471 if mixed == 1 {
472 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=mixed-container-corpus -- this roster holds both NXANIM01 and NXMSH2 rows. They answer DIFFERENT axes, so a band across them would range over two different quantities and publish the result as one bound. Split the roster by container.\n" as *u8)
473 return RF_EXIT_UNOBSERVABLE
474 }
475 var axes_available: i64 = 0
476 var axes_with_width: i64 = 0
477 var w: i64 = 0
478 while w < RF_AXES {
479 if corpus_av[w] == 1 {
480 axes_available = axes_available + 1
481 if hi[w] > lo[w] { axes_with_width = axes_with_width + 1 }
482 }
483 w = w + 1
484 }
485 rf_say(" axes_available=" as *u8, axes_available, scratch, line)
486 rf_say(" axes_with_width=" as *u8, axes_with_width, scratch, line)
487 if axes_with_width == 0 {
488 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=degenerate-corpus-every-asset-identical -- n>=2 but ZERO axes have width. This corpus is ONE distinct shape repeated, so it cannot express a range and a floor from it would admit only itself. This is a CORPUS defect, not an organ defect: acquire distinct reference assets.\n" as *u8)
489 return RF_EXIT_UNOBSERVABLE
490 }
491
492 let cb: *u8 = sys_mmap(RF_CONF_CAP)
493 var o: i64 = 0
494 o = rf_bputs(cb, o, "# nx_rigfloor derived asset floor -- GENERATED ARTEFACT, DO NOT HAND-EDIT\n" as *u8)
495 o = rf_bputs(cb, o, "# Every bound below is a MEASUREMENT of the reference corpus, never a chosen number.\n" as *u8)
496 o = rf_bputs(cb, o, "# method: observed range over the FULL population. No percentile index is selected,\n" as *u8)
497 o = rf_bputs(cb, o, "# because selecting one would reintroduce the picked constant this file exists to remove.\n" as *u8)
498 o = rf_bputs(cb, o, "method=observed-range-full-population\n" as *u8)
499 o = rf_bputs(cb, o, "roster=" as *u8)
500 o = rf_bputs(cb, o, roster)
501 cb[o] = RF_ASCII_NL
502 o = o + 1
503 o = rf_kv(cb, o, "n_declared" as *u8, declared, scratch)
504 o = rf_kv(cb, o, "n_read" as *u8, read_ok, scratch)
505 o = rf_kv(cb, o, "n_failed" as *u8, failed, scratch)
506 o = rf_kv(cb, o, "corpus_complete" as *u8, 1, scratch)
507 // published so a consumer can see HOW MANY axes actually discriminate. A floor whose bands are
508 // mostly zero-width is technically valid and nearly useless; that has to be visible, not buried.
509 o = rf_kv(cb, o, "axes_total" as *u8, RF_AXES, scratch)
510 o = rf_kv(cb, o, "axes_available" as *u8, axes_available, scratch)
511 o = rf_kv(cb, o, "axes_with_width" as *u8, axes_with_width, scratch)
512 // the container is part of the claim: a floor derived from NXMSH2 donors says nothing about
513 // joints, and a consumer must be able to see that WITHOUT inferring it from missing keys
514 o = rf_kv(cb, o, "container" as *u8, container, scratch)
515 o = rf_emit_band(cb, o, "verts_min" as *u8, "verts_max" as *u8, RF_AX_VERTS, lo, hi, corpus_av, scratch)
516 o = rf_emit_band(cb, o, "tris_min" as *u8, "tris_max" as *u8, RF_AX_TRIS, lo, hi, corpus_av, scratch)
517 o = rf_emit_band(cb, o, "joints_min" as *u8, "joints_max" as *u8, RF_AX_JOINTS, lo, hi, corpus_av, scratch)
518 o = rf_emit_band(cb, o, "tris_per_vert_permil_min" as *u8, "tris_per_vert_permil_max" as *u8, RF_AX_TPV_PERMIL, lo, hi, corpus_av, scratch)
519 o = rf_emit_band(cb, o, "verts_per_joint_min" as *u8, "verts_per_joint_max" as *u8, RF_AX_VPJ, lo, hi, corpus_av, scratch)
520 o = rf_emit_band(cb, o, "parts_min" as *u8, "parts_max" as *u8, RF_AX_PARTS, lo, hi, corpus_av, scratch)
521 o = rf_emit_band(cb, o, "tris_per_part_min" as *u8, "tris_per_part_max" as *u8, RF_AX_TPP, lo, hi, corpus_av, scratch)
522
523 let fd: i64 = sys_openat_wr(outconf, MODE_0644)
524 if fd < 0 {
525 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=cannot-open-out-conf path=" as *u8)
526 rf_out(outconf)
527 rf_out("\n" as *u8)
528 return RF_EXIT_UNOBSERVABLE
529 }
530 let wr: i64 = sys_write(fd, cb, o)
531 sys_close(fd)
532 // ANNOUNCE THE WRITE: an unannounced publish makes "did it land" a hunt instead of a number.
533 rf_say(" conf_bytes_built=" as *u8, o, scratch, line)
534 rf_say(" conf_bytes_written=" as *u8, wr, scratch, line)
535 if wr != o {
536 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=short-write\n" as *u8)
537 return RF_EXIT_UNOBSERVABLE
538 }
539 rf_out(" wrote: " as *u8)
540 rf_out(outconf)
541 rf_out("\n" as *u8)
542 rf_out("NX-RIGFLOOR verdict=DERIVED\n" as *u8)
543 return RF_EXIT_OK
544}
545
546// An ABSENT bound is UNOBSERVABLE, never a pass. The caller passes an EMPTY band (lo=1, hi=0) as
547// the miss sentinel, so a key missing from the conf reports bound-absent instead of silently
548// scoring IN-BAND -- which would be a gate passing on the empty set.
549func rf_band(name: *u8, v: i64, lo: i64, hi: i64, scratch: *u8, line: *u8, bad: *i64) -> i64 {
550 var o: i64 = rf_bputs(line, 0, " " as *u8)
551 o = rf_bputs(line, o, name)
552 o = rf_bputs(line, o, " value=" as *u8)
553 o = rf_bputnum(line, o, v, scratch)
554 o = rf_bputs(line, o, " band=[" as *u8)
555 o = rf_bputnum(line, o, lo, scratch)
556 o = rf_bputs(line, o, "," as *u8)
557 o = rf_bputnum(line, o, hi, scratch)
558 o = rf_bputs(line, o, "] " as *u8)
559 if lo > hi {
560 o = rf_bputs(line, o, "UNOBSERVABLE-bound-absent\n" as *u8)
561 bad[1] = bad[1] + 1
562 } else {
563 var ok: i64 = 0
564 if v >= lo { if v <= hi { ok = 1 } }
565 if ok == 1 { o = rf_bputs(line, o, "IN-BAND\n" as *u8) }
566 if ok == 0 {
567 o = rf_bputs(line, o, "OUT-OF-BAND\n" as *u8)
568 bad[0] = bad[0] + 1
569 }
570 }
571 sys_write(RF_STDOUT, line, o)
572 return 0
573}
574
575func rf_score(asset: *u8, conf: *u8) -> i64 {
576 let line: *u8 = sys_mmap(RF_PATH_CAP)
577 let scratch: *u8 = sys_mmap(RF_SCRATCH_CAP)
578 let m: *i64 = sys_mmap(RF_WORD_BYTES*RF_AXES) as *i64
579 let av: *i64 = sys_mmap(RF_WORD_BYTES*RF_AXES) as *i64
580 let bad: *i64 = sys_mmap(RF_WORD_BYTES*RF_PAIR) as *i64
581 bad[0] = 0
582 bad[1] = 0
583
584 let cp: *i64 = sys_mmap(RF_WORD_BYTES*RF_PAIR) as *i64
585 let cbuf: *u8 = sys_read_file(conf, cp)
586 if (cbuf as i64) == 0 {
587 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=floor-conf-unreadable path=" as *u8)
588 rf_out(conf)
589 rf_out("\n" as *u8)
590 return RF_EXIT_UNOBSERVABLE
591 }
592 let cn: i64 = cp[0]
593 // A FLOOR DERIVED FROM A PARTIAL CORPUS MUST NOT BE USED AS ONE.
594 let complete: i64 = rf_conf_i64(cbuf, cn, "corpus_complete" as *u8, 0 - 1)
595 if complete != 1 {
596 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=floor-conf-not-corpus-complete\n" as *u8)
597 return RF_EXIT_UNOBSERVABLE
598 }
599 rf_out("NX-RIGFLOOR score\n asset: " as *u8)
600 rf_out(asset)
601 rf_out("\n floor: " as *u8)
602 rf_out(conf)
603 rf_out("\n" as *u8)
604 let n_read: i64 = rf_conf_i64(cbuf, cn, "n_read" as *u8, 0 - 1)
605 rf_say(" corpus_n=" as *u8, n_read, scratch, line)
606
607 let kind: i64 = rf_measure(asset, m, av)
608 if kind == RF_CONTAINER_UNKNOWN {
609 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=asset-unmeasurable\n" as *u8)
610 return RF_EXIT_UNOBSERVABLE
611 }
612 // SCORING ACROSS CONTAINERS IS A SUBJECT MISMATCH, NOT A STRICTER TEST. A floor derived from
613 // NXMSH2 donors has no joints band at all; scoring an NXANIM01 asset against it would silently
614 // grade a rigged character on three form axes and report the result as a pass.
615 let conf_container: i64 = rf_conf_i64(cbuf, cn, "container" as *u8, 0 - 1)
616 rf_say(" asset_container=" as *u8, kind, scratch, line)
617 rf_say(" floor_container=" as *u8, conf_container, scratch, line)
618 if conf_container != kind {
619 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=container-mismatch -- this asset and this floor are different container kinds and answer different axes. Derive a floor from a corpus of the same kind.\n" as *u8)
620 return RF_EXIT_UNOBSERVABLE
621 }
622 if av[RF_AX_VERTS] == 1 { rf_band("verts " as *u8, m[RF_AX_VERTS],
623 rf_conf_i64(cbuf, cn, "verts_min" as *u8, 1),
624 rf_conf_i64(cbuf, cn, "verts_max" as *u8, 0), scratch, line, bad) }
625 if av[RF_AX_TRIS] == 1 { rf_band("tris " as *u8, m[RF_AX_TRIS],
626 rf_conf_i64(cbuf, cn, "tris_min" as *u8, 1),
627 rf_conf_i64(cbuf, cn, "tris_max" as *u8, 0), scratch, line, bad) }
628 if av[RF_AX_JOINTS] == 1 { rf_band("joints " as *u8, m[RF_AX_JOINTS],
629 rf_conf_i64(cbuf, cn, "joints_min" as *u8, 1),
630 rf_conf_i64(cbuf, cn, "joints_max" as *u8, 0), scratch, line, bad) }
631 if av[RF_AX_TPV_PERMIL] == 1 { rf_band("tris_per_vert_permil" as *u8, m[RF_AX_TPV_PERMIL],
632 rf_conf_i64(cbuf, cn, "tris_per_vert_permil_min" as *u8, 1),
633 rf_conf_i64(cbuf, cn, "tris_per_vert_permil_max" as *u8, 0), scratch, line, bad) }
634 if av[RF_AX_VPJ] == 1 { rf_band("verts_per_joint " as *u8, m[RF_AX_VPJ],
635 rf_conf_i64(cbuf, cn, "verts_per_joint_min" as *u8, 1),
636 rf_conf_i64(cbuf, cn, "verts_per_joint_max" as *u8, 0), scratch, line, bad) }
637 if av[RF_AX_PARTS] == 1 { rf_band("parts " as *u8, m[RF_AX_PARTS],
638 rf_conf_i64(cbuf, cn, "parts_min" as *u8, 1),
639 rf_conf_i64(cbuf, cn, "parts_max" as *u8, 0), scratch, line, bad) }
640 if av[RF_AX_TPP] == 1 { rf_band("tris_per_part " as *u8, m[RF_AX_TPP],
641 rf_conf_i64(cbuf, cn, "tris_per_part_min" as *u8, 1),
642 rf_conf_i64(cbuf, cn, "tris_per_part_max" as *u8, 0), scratch, line, bad) }
643
644 rf_say(" axes_total=" as *u8, RF_AXES, scratch, line)
645 rf_say(" axes_out_of_band=" as *u8, bad[0], scratch, line)
646 rf_say(" axes_unobservable=" as *u8, bad[1], scratch, line)
647 if bad[1] > 0 {
648 rf_out("NX-RIGFLOOR verdict=UNOBSERVABLE reason=at-least-one-bound-absent-from-the-floor-conf\n" as *u8)
649 return RF_EXIT_UNOBSERVABLE
650 }
651 if bad[0] > 0 {
652 rf_out("NX-RIGFLOOR verdict=OUT-OF-BAND\n" as *u8)
653 return RF_EXIT_OUT_OF_BAND
654 }
655 rf_out("NX-RIGFLOOR verdict=IN-BAND\n" as *u8)
656 return RF_EXIT_OK
657}
658
659func main(argc: i64, argv: *i64) -> i64 {
660 if argc < 4 {
661 let u: i64 = rf_usage()
662 sys_exit(u)
663 return u
664 }
665 let verb: *u8 = argv[1] as *u8
666 let a2: *u8 = argv[2] as *u8
667 let a3: *u8 = argv[3] as *u8
668 var rc: i64 = RF_EXIT_USAGE
669 var known: i64 = 0
670 if verb[0] == RF_VERB_DERIVE { rc = rf_derive(a2, a3); known = 1 }
671 if verb[0] == RF_VERB_SCORE { rc = rf_score(a2, a3); known = 1 }
672 if known == 0 { rc = rf_usage() }
673 sys_exit(rc)
674 return rc
675}