code wiki / (root) / nx_targetfit.nx

nx_targetfit.nx source

↩ module page · 178 lines · 9883 B

1// nx_targetfit.nx -- DID THE GENERATOR HIT ITS TARGET, AND BY HOW MUCH DID IT MISS. 2// 3// THE QUESTION THIS ANSWERS, AND WHY IT IS NOT ONE WE ALREADY ASK. The estate owns two rulers that 4// look adjacent and answer different things. nx_mmdev_lib md_measure gives exact per-vertex deviation 5// against a reference -- how far apart two SHAPES are. nx_proportion_guard pg_check gives absolute 6// plausibility -- whether a body is a POSSIBLE body at all. Neither answers the operator's question, 7// which is: we ingested a reference, we derived a target from it, the generator produced something -- 8// DID IT HIT THE TARGET, while still being allowed to vary. 9// 10// THE WHOLE DIFFICULTY IS THE WORD 'ALLOWED'. A conformance ruler that demands exactness destroys the 11// thing it is measuring: procedural generation exists to produce variety, and a checker that passes 12// only near-identical output would drive the generator toward a single figure. Rule 25 is explicit -- 13// build intelligence, never strip features; over-efficiency destroys variety and richness. So the 14// verdict here is a BAND, never a point, and the band's width is published beside every verdict. 15// 16// THE BAND IS NORMALISED BY THE AXIS'S OWN PLAUSIBLE ENVELOPE, WHICH IS THE PART THAT MAKES IT MEAN 17// ANYTHING. A naive ruler expresses tolerance as a percentage of the target: five percent. But five 18// percent of total height is about 87 mm and five percent of breast projection is about 4 mm, and 19// those are not comparable quantities -- one is a shrug, the other is a different body. So a miss here 20// is expressed as PERMIL OF THE AXIS'S PLAUSIBLE RANGE, taken from pg_bound. A tenth of the human 21// range means the same thing on every axis, which is the only way a multi-axis verdict can be summed 22// without secretly weighting whichever axis happens to have the biggest numbers. 23// 24// WHAT IS DERIVED AND WHAT IS DECLARED, stated rather than blurred: the NORMALISATION is derived -- 25// it comes from bounds nx_proportion_guard already holds and defends. The BAND EDGES (tight, slight, 26// moderate) are DECLARED DEFAULTS, not measured, and they are parameters so a caller can drive them 27// from conf. Anyone who tightens them by feel rather than by measurement is doing the thing that 28// file's own provenance note forbids. 29// 30// AND THE ENVELOPE'S REFUSAL PROPAGATES. pg_bound REFUSES an axis with no bound row, on the principle 31// that missing is not permission. An axis we cannot normalise is UNMEASURABLE here, never a pass: 32// silently treating an unbounded axis as conforming would let the axes nobody has characterised be 33// exactly the ones that always agree. 34// license_tier: ORIGINAL No hardware writes (Rule 26). Pure integer, pure buffer. 35import "nx_syscalls.nx" 36import "nx_canon_proportions.nx" 37import "nx_proportion_guard.nx" 38 39// ---- grades --------------------------------------------------------------------------------------- 40// Four, because 'passed' and 'failed' cannot express a generator that is drifting. A run that moves 41// from mostly-TIGHT to mostly-MODERATE has not failed and IS getting worse, and that is the signal 42// worth having before the failure arrives. 43const TF_TIGHT: i64 = 0 44const TF_SLIGHT: i64 = 1 45const TF_MODERATE: i64 = 2 46const TF_OUTSIDE: i64 = 3 47const TF_UNBOUNDED: i64 = 4 // the axis has no envelope: we cannot say, and we do not pretend to 48 49// DECLARED DEFAULTS, in permil of the axis's plausible envelope width. Not measured -- named so they 50// can be overridden from conf, and named so nobody mistakes them for findings. 51const TF_TIGHT_PERMIL_DEFAULT: i64 = 50 // within 5 percent of the plausible range 52const TF_SLIGHT_PERMIL_DEFAULT: i64 = 150 53const TF_MODERATE_PERMIL_DEFAULT: i64 = 300 54 55// ---- whole-subject verdicts ----------------------------------------------------------------------- 56const TF_WITHIN: i64 = 0 // every measurable axis landed inside the moderate band 57const TF_OUT: i64 = 1 // at least one axis missed beyond moderate 58const TF_UNMEASURABLE: i64 = 3 // nothing could be measured at all 59 60// ---- out slots ------------------------------------------------------------------------------------- 61const TF_O_AXES: i64 = 0 62const TF_O_TIGHT: i64 = 1 63const TF_O_SLIGHT: i64 = 2 64const TF_O_MODERATE: i64 = 3 65const TF_O_OUTSIDE: i64 = 4 66const TF_O_UNBOUNDED: i64 = 5 67const TF_O_WORST: i64 = 6 // worst variance seen, permil of envelope 68const TF_O_WORSTAXIS: i64 = 7 // which axis that was -- the count alone is not a worklist 69const TF_O_MEANVAR: i64 = 8 // mean variance over MEASURABLE axes only 70const TF_O_BANDT: i64 = 9 // the bands actually used, echoed so a verdict carries its own bar 71const TF_O_BANDS: i64 = 10 72const TF_O_BANDM: i64 = 11 73const TF_O_SLOTS: i64 = 12 74 75const TF_PERMIL: i64 = 1000 76 77func tf_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 78 79// The envelope width for an axis under a regime, or -1 when the axis has no bound row. 80// COMPOSED FROM pg_bound: there is exactly one table of plausible ranges in this estate and this is 81// not a second one. If those bounds move, this ruler moves with them, which is the point. 82func tf_envelope(axis: i64, regime: i64) -> i64 { 83 let lo: i64 = pg_bound(axis, regime, PG_MIN) 84 let hi: i64 = pg_bound(axis, regime, PG_MAX) 85 if lo <= 0 { return 0 - 1 } 86 if hi <= lo { return 0 - 1 } 87 return hi - lo 88} 89 90// Miss on one axis, in permil of that axis's plausible envelope. -1 when unbounded. 91func tf_variance_permil(axis: i64, regime: i64, target: i64, measured: i64) -> i64 { 92 let w: i64 = tf_envelope(axis, regime) 93 if w < 0 { return 0 - 1 } 94 return (tf_abs(measured - target) * TF_PERMIL) / w 95} 96 97func tf_grade(variance_permil: i64, tight: i64, slight: i64, moderate: i64) -> i64 { 98 if variance_permil < 0 { return TF_UNBOUNDED } 99 if variance_permil <= tight { return TF_TIGHT } 100 if variance_permil <= slight { return TF_SLIGHT } 101 if variance_permil <= moderate { return TF_MODERATE } 102 return TF_OUTSIDE 103} 104 105// One axis, graded. out[0] = variance permil (-1 unbounded), returns the grade. 106func tf_axis_fit(axis: i64, regime: i64, target: i64, measured: i64, 107 tight: i64, slight: i64, moderate: i64, out: *i64) -> i64 { 108 let v: i64 = tf_variance_permil(axis, regime, target, measured) 109 out[0] = v 110 return tf_grade(v, tight, slight, moderate) 111} 112 113// ---- THE SUBJECT VERDICT --------------------------------------------------------------------------- 114// axes/targets/measured are parallel arrays of length n. grades is written per axis so the caller has 115// a WORKLIST and not merely a count -- a verdict that says 'two axes missed' without naming them costs 116// the reader the whole investigation again. 117// 118// THE PARTITION SUMS BY CONSTRUCTION and the sum is published: tight + slight + moderate + outside + 119// unbounded == axes. An unreconciled partition is a leak, and this one is cheap to check. 120func tf_fit(axes: *i64, targets: *i64, measured: *i64, n: i64, regime: i64, 121 tight: i64, slight: i64, moderate: i64, out: *i64, grades: *i64) -> i64 { 122 var k: i64 = 0 123 while k < TF_O_SLOTS { out[k] = 0; k = k + 1 } 124 out[TF_O_AXES] = n 125 out[TF_O_WORST] = 0 - 1 126 out[TF_O_WORSTAXIS] = 0 - 1 127 out[TF_O_BANDT] = tight 128 out[TF_O_BANDS] = slight 129 out[TF_O_BANDM] = moderate 130 // AN EMPTY AXIS SET IS NOT A CONFORMING SUBJECT. Zero axes measured means the comparison did not 131 // happen; returning WITHIN here would let a harness that measured nothing report a clean fit 132 // forever, which is the empty-set-passes defect wearing a conformance costume. 133 if n <= 0 { return TF_UNMEASURABLE } 134 let scratch: *i64 = sys_mmap(8) as *i64 135 var sumvar: i64 = 0 136 var nmeas: i64 = 0 137 var i: i64 = 0 138 while i < n { 139 let g: i64 = tf_axis_fit(axes[i], regime, targets[i], measured[i], tight, slight, moderate, scratch) 140 grades[i] = g 141 if g == TF_TIGHT { out[TF_O_TIGHT] = out[TF_O_TIGHT] + 1 } 142 if g == TF_SLIGHT { out[TF_O_SLIGHT] = out[TF_O_SLIGHT] + 1 } 143 if g == TF_MODERATE { out[TF_O_MODERATE] = out[TF_O_MODERATE] + 1 } 144 if g == TF_OUTSIDE { out[TF_O_OUTSIDE] = out[TF_O_OUTSIDE] + 1 } 145 if g == TF_UNBOUNDED { out[TF_O_UNBOUNDED] = out[TF_O_UNBOUNDED] + 1 } 146 if g != TF_UNBOUNDED { 147 let v: i64 = scratch[0] 148 sumvar = sumvar + v 149 nmeas = nmeas + 1 150 if v > out[TF_O_WORST] { out[TF_O_WORST] = v; out[TF_O_WORSTAXIS] = axes[i] } 151 } 152 i = i + 1 153 } 154 sys_munmap(scratch as *u8, 8) 155 // THE MEAN IS OVER MEASURABLE AXES ONLY, and the unbounded count sits beside it so the denominator 156 // is visible. A mean that quietly divides by the full axis count would improve every time an axis 157 // became unmeasurable, which is a metric that rewards losing the ability to measure. 158 if nmeas > 0 { out[TF_O_MEANVAR] = sumvar / nmeas } 159 if nmeas == 0 { return TF_UNMEASURABLE } 160 if out[TF_O_OUTSIDE] > 0 { return TF_OUT } 161 return TF_WITHIN 162} 163 164// The partition check, offered as a function so a caller can assert it rather than eyeball it. 165func tf_reconciles(out: *i64) -> i64 { 166 let s: i64 = out[TF_O_TIGHT] + out[TF_O_SLIGHT] + out[TF_O_MODERATE] 167 + out[TF_O_OUTSIDE] + out[TF_O_UNBOUNDED] 168 if s == out[TF_O_AXES] { return 1 } 169 return 0 170} 171 172// DRIFT, which is the signal worth having BEFORE a failure. Two runs of the same generator against the 173// same target: is the second one further from it than the first? Returns permil difference in mean 174// variance, positive meaning WORSE. A pass/fail ruler cannot see this at all, and by the time it can, 175// the generator has already been shipping degraded output for however long the drift took. 176func tf_drift(prev: *i64, now: *i64) -> i64 { 177 return now[TF_O_MEANVAR] - prev[TF_O_MEANVAR] 178}