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}