code wiki / _hdl_build / nx_hairdyn_gate.nx
nx_hairdyn_gate.nx source
↩ module page · 258 lines · 12803 B
1// nx_hairdyn_gate.nx -- GATE: prove the hair chain is a CHAIN, that its plant is DERIVED rather
2// than tuned, and that the measurement which says so cannot pass on hair that does not move.
3//
4// WHAT THIS GATE IS FOR. knowledge/gamefeel_oracle.conf's hair_k_q10 row has never had a plant
5// behind it; the shipping hair was two hand-picked integers driving one point. The teeth below do
6// not assert that hair is right, they MEASURE it: ask the derivation for a frequency, run a free
7// decay, and read the frequency back out with the estate's one ringdown analyser.
8//
9// NO TOLERANCE IS CHOSEN ANYWHERE IN THIS FILE. The frequency teeth compare against the DAMPED
10// prediction (a ringdown cannot measure an undamped frequency) inside the ANALYSER'S OWN
11// resolution, both derived below. The damping teeth assert MONOTONICITY, which needs no tolerance
12// at all -- a stronger property than an equality with a number picked to make it hold.
13//
14// license_tier: ORIGINAL No hw writes (Rule 26).
15import "nx_syscalls.nx"
16import "nx_gate_verdict.nx"
17import "nx_hairdyn_lib.nx"
18
19const HG_NSEG: i64 = 6
20const HG_NSEG_SHORT: i64 = 2
21const HG_NSEG_LONG: i64 = 12
22// segment length is pure GEOMETRY -- T4 exists precisely to prove the frequency does not depend on
23// it, so these two differ by a factor of two and nothing else.
24const HG_SEGLEN_A: i64 = 256
25const HG_SEGLEN_B: i64 = 512
26// ZETA TEST POINTS ARE BANKED VALUES, NOT INVENTED. 100 and 300 permil are the exact endpoints of
27// knowledge/gamefeel_oracle.conf's chest_zeta_permil row -- the estate's own banked band for
28// "underdamped envelope, VISIBLE oscillation". Using its endpoints means both points are, by the
29// estate's own definition, in the regime where a decrement is measurable, rather than two numbers
30// chosen by me because they happened to pass. A gate that invents its own constants to test a lane
31// about invented constants would be its own counter-example.
32const HG_ZETA_LO: i64 = 100
33const HG_ZETA_HI: i64 = 300
34
35// A ringdown measures the DAMPED natural frequency fn*sqrt(1-zeta^2), never the undamped one.
36// Comparing a measurement against the UNDAMPED target is an error that GROWS WITH DAMPING -- at
37// 480 permil it is a 12% offset, easily large enough to read as a broken derivation and send the
38// next reader into a solver that is working correctly. Integer form: sqrt(1-z^2) for z in permil
39// is isqrt(10^6 - z^2)/1000, exact enough that the instrument resolution below dominates it.
40func hg_fn_damped(fn_mhz: i64, zeta_permil: i64) -> i64 {
41 return fn_mhz * rd_isqrt(1000000 - zeta_permil*zeta_permil) / 1000
42}
43
44// THE INSTRUMENT'S OWN RESOLUTION, DERIVED -- never a tolerance chosen to make a test pass.
45// rd_analyze times one full period by counting SAMPLES between zero crossings, so the period is
46// known to +-1 sample. With P samples per period a one-sample error moves the frequency by about
47// fn/P, and P = ticks_per_second*1000/fn_mhz. The +1 absorbs the integer division's truncation.
48// This is why the bar TIGHTENS at low frequency (more samples per period) rather than being one
49// slack number across the range.
50func hg_res_mhz(fn_mhz: i64) -> i64 {
51 let per_samples: i64 = hd_ticks_per_s()*1000/fn_mhz
52 if per_samples < 1 { return fn_mhz }
53 return fn_mhz/per_samples + 1
54}
55
56func hg_report(tag: *u8, rc: i64, out: *i64) -> i64 {
57 gv_puts(" " as *u8)
58 gv_puts(tag)
59 gv_puts(": fn=" as *u8)
60 gv_num(out[RD_R_FN_MHZ])
61 gv_puts(" mhz zeta=" as *u8)
62 gv_num(out[RD_R_ZETA])
63 gv_puts(" permil peaks=" as *u8)
64 gv_num(out[RD_R_PEAKS])
65 gv_puts(" a1=" as *u8)
66 gv_num(out[RD_R_A1])
67 gv_puts(" a2=" as *u8)
68 gv_num(out[RD_R_A2])
69 gv_puts(" resid=" as *u8)
70 gv_num(out[RD_R_RESID])
71 gv_puts(" rc=" as *u8)
72 gv_num(rc)
73 gv_puts(" " as *u8)
74 gv_puts(rd_reason(rc))
75 gv_puts("\n" as *u8)
76 return 0
77}
78
79func main() -> i64 {
80 let ctr: *i64 = gv_ctr()
81 gv_head("nx_hairdyn gate -- hair as a CHAIN on the one solver, plant MEASURED in band units" as *u8)
82 let out: *i64 = sys_mmap(RD_R_N*8 + 64) as *i64
83 let tipS: *i64 = sys_mmap(HD_RD_SAMPLES*8 + 64) as *i64
84 let rootS: *i64 = sys_mmap(HD_RD_SAMPLES*8 + 64) as *i64
85 let lag: *i64 = sys_mmap(64) as *i64
86
87 gv_puts(" tick rate DERIVED from nx_softbind SB_MHZ_TICK_SQ = " as *u8)
88 gv_num(hd_ticks_per_s())
89 gv_puts(" hz, sample period " as *u8)
90 gv_num(hd_dt_us())
91 gv_puts(" us, excitation " as *u8)
92 gv_num(hd_amp_q8())
93 gv_puts(" q8 (half the banked SB_MAXD_HAIR clamp)\n" as *u8)
94
95 // ---- T1: the frequency derivation round-trips, at BOTH ends of the proven range. Two points,
96 // because a single frequency that happens to read back is a coincidence, not a derivation.
97 var rt: i64 = 1
98 var reached: i64 = 0
99 var fi: i64 = 0
100 while fi < 2 {
101 var fn: i64 = HD_FN_LO_MHZ
102 if fi == 1 { fn = HD_FN_HI_MHZ }
103 let rc: i64 = hd_ringdown(HG_NSEG, HG_SEGLEN_A, fn, HG_ZETA_LO, out)
104 let want: i64 = hg_fn_damped(fn, HG_ZETA_LO)
105 let got: i64 = out[RD_R_FN_MHZ]
106 let res: i64 = hg_res_mhz(fn)
107 gv_puts(" ask fn=" as *u8)
108 gv_num(fn)
109 gv_puts(" zeta=" as *u8)
110 gv_num(HG_ZETA_LO)
111 gv_puts(" -> damped-predicted " as *u8)
112 gv_num(want)
113 gv_puts(" +-" as *u8)
114 gv_num(res)
115 gv_puts("\n" as *u8)
116 hg_report("measured" as *u8, rc, out)
117 if rc == RD_OK { reached = reached + 1 }
118 if rc != RD_OK { rt = 0 }
119 if got < want - res { rt = 0 }
120 if got > want + res { rt = 0 }
121 fi = fi + 1
122 }
123 // BIND THE ASSERTION TO ITS DENOMINATOR: a tooth that passes because the loop never ran is not
124 // a tooth. Both frequencies must actually have produced a measurable decay.
125 if reached != 2 { rt = 0 }
126 gv_check("fixture-reached-the-condition: BOTH endpoint ringdowns produced a measurable plant" as *u8, reached == 2, ctr)
127 gv_check("frequency derivation ROUND-TRIPS at both ends (damped-corrected, within analyser resolution)" as *u8, rt, ctr)
128
129 // ---- T2: damping is actually driven by the derivation: more requested zeta, more measured zeta.
130 // MONOTONICITY, so no tolerance is needed and none is invented.
131 let rzl: i64 = hd_ringdown(HG_NSEG, HG_SEGLEN_A, HD_FN_HI_MHZ, HG_ZETA_LO, out)
132 let zlo: i64 = out[RD_R_ZETA]
133 hg_report("zeta-lo" as *u8, rzl, out)
134 let rzh: i64 = hd_ringdown(HG_NSEG, HG_SEGLEN_A, HD_FN_HI_MHZ, HG_ZETA_HI, out)
135 let zhi: i64 = out[RD_R_ZETA]
136 hg_report("zeta-hi" as *u8, rzh, out)
137 var zmono: i64 = 0
138 if rzl == RD_OK {
139 if rzh == RD_OK {
140 if zhi > zlo { zmono = 1 }
141 }
142 }
143 gv_check("measured damping RISES with requested damping (both observable, monotone)" as *u8, zmono, ctr)
144
145 // ---- T3: frequency is set by the SPRING, not by the geometry. Same plant, segment length
146 // doubled: the measured frequency must not move. This is what makes the derivation meaningful --
147 // if geometry moved the frequency, sb_k_sym would not be the ruler that sets it.
148 let rg1: i64 = hd_ringdown(HG_NSEG, HG_SEGLEN_A, HD_FN_HI_MHZ, HG_ZETA_LO, out)
149 let fg1: i64 = out[RD_R_FN_MHZ]
150 let rg2: i64 = hd_ringdown(HG_NSEG, HG_SEGLEN_B, HD_FN_HI_MHZ, HG_ZETA_LO, out)
151 let fg2: i64 = out[RD_R_FN_MHZ]
152 var ginv: i64 = 0
153 if rg1 == RD_OK {
154 if rg2 == RD_OK {
155 if fg1 == fg2 { ginv = 1 }
156 }
157 }
158 gv_puts(" seglen " as *u8)
159 gv_num(HG_SEGLEN_A)
160 gv_puts(" -> " as *u8)
161 gv_num(fg1)
162 gv_puts(" mhz ; seglen " as *u8)
163 gv_num(HG_SEGLEN_B)
164 gv_puts(" -> " as *u8)
165 gv_num(fg2)
166 gv_puts(" mhz\n" as *u8)
167 gv_check("frequency is INVARIANT to segment length (the spring sets it, not the geometry)" as *u8, ginv, ctr)
168
169 // ---- T4: it is a CHAIN, not N independent copies of one spring. The tip must LAG the root, and
170 // that lag must GROW with chain length. N uncoupled springs would give the same lag at any
171 // length; only one-hop coupling makes the delay accumulate down the strand.
172 let rl1: i64 = hd_ringdown_t(HG_NSEG_SHORT, HG_SEGLEN_A, HD_FN_HI_MHZ, HG_ZETA_LO, out, tipS, rootS)
173 var lag_short: i64 = 0 - 1
174 if rl1 == RD_OK {
175 let ps1: i64 = out[RD_R_PER_US]/hd_dt_us()
176 if rd_phase_lag_permil(rootS, tipS, HD_RD_SAMPLES, ps1, lag) == RD_OK { lag_short = lag[0] }
177 }
178 let rl2: i64 = hd_ringdown_t(HG_NSEG_LONG, HG_SEGLEN_A, HD_FN_HI_MHZ, HG_ZETA_LO, out, tipS, rootS)
179 var lag_long: i64 = 0 - 1
180 if rl2 == RD_OK {
181 let ps2: i64 = out[RD_R_PER_US]/hd_dt_us()
182 if rd_phase_lag_permil(rootS, tipS, HD_RD_SAMPLES, ps2, lag) == RD_OK { lag_long = lag[0] }
183 }
184 gv_puts(" tip-vs-root phase lag: " as *u8)
185 gv_num(HG_NSEG_SHORT)
186 gv_puts(" segments -> " as *u8)
187 gv_num(lag_short)
188 gv_puts(" permil of a period ; " as *u8)
189 gv_num(HG_NSEG_LONG)
190 gv_puts(" segments -> " as *u8)
191 gv_num(lag_long)
192 gv_puts(" permil\n" as *u8)
193 var chain: i64 = 0
194 if lag_short >= 0 {
195 if lag_long > lag_short { chain = 1 }
196 }
197 gv_check("IT IS A CHAIN: tip-vs-root phase lag GROWS with strand length" as *u8, chain, ctr)
198
199 // ---- T5: THE ANTI-VACUITY TOOTH. A RIGID hair chain -- joints that exist and follow the head
200 // but carry no dynamics, i.e. the trivial wrong implementation that reads on screen as a helmet
201 // -- MUST NOT yield a plant. If this passes, every measurement above is decoration, because the
202 // instrument would be reporting a frequency for hair that does not move.
203 let rr: i64 = hd_ringdown_rigid(HG_NSEG, HG_SEGLEN_A, out)
204 hg_report("RIGID control" as *u8, rr, out)
205 gv_check("neg-control-rigid-chain-cannot-produce-a-plant (third state, not a frequency)" as *u8, rr != RD_OK, ctr)
206 // and it must not fake the CHAIN property either: no dynamics, no lag.
207 var rigid_lag: i64 = 0 - 1
208 if rd_phase_lag_permil(rootS, rootS, HD_RD_SAMPLES, 8, lag) == RD_OK { rigid_lag = lag[0] }
209 gv_check("neg-control-a-signal-against-itself-has-zero-lag (the lag probe is not fabricating)" as *u8, rigid_lag == 0, ctr)
210
211 // ---- T6: THE SHIPPED PLANT, MEASURED. nx_softbind's SB_K_HAIR / SB_C_HAIR are two hand-picked
212 // integers that were never derived from a frequency. This does not assert them against a band --
213 // there IS no citable hair band (see the lib header: the source slug resolves to nothing) -- it
214 // REPORTS what the estate has actually been shipping, in the units a band would use.
215 let rs: i64 = hd_ringdown_kc(HG_NSEG, HG_SEGLEN_A, SB_K_HAIR, SB_C_HAIR, out)
216 gv_puts(" SHIPPED nx_softbind hair plant K=" as *u8)
217 gv_num(SB_K_HAIR)
218 gv_puts(" C=" as *u8)
219 gv_num(SB_C_HAIR)
220 gv_puts(":\n" as *u8)
221 hg_report("shipped" as *u8, rs, out)
222 gv_check("the SHIPPED hair plant is observable at all (a plant nobody can measure is not a plant)" as *u8, rs == RD_OK, ctr)
223
224 // ---- T7: ANTI-DRIFT. nx_softbind now states the shipped hair plant twice -- once as the two
225 // per-1024 integers the solver consumes, and once as the frequency and damping ratio a reader
226 // (or a future citation) can understand. Two spellings of one fact is exactly how a citation
227 // quietly stops being load-bearing, so the equality is asserted here rather than trusted. If
228 // anyone edits either side, this goes RED instead of the two drifting apart in silence.
229 let kfrom: i64 = sb_k_sym(SB_HAIR_FN_MHZ)
230 let cfrom: i64 = sb_c_for_zeta(SB_K_HAIR, SB_HAIR_ZETA_PERMIL)
231 gv_puts(" shipped K " as *u8)
232 gv_num(SB_K_HAIR)
233 gv_puts(" vs sb_k_sym(" as *u8)
234 gv_num(SB_HAIR_FN_MHZ)
235 gv_puts(" mhz) = " as *u8)
236 gv_num(kfrom)
237 gv_puts(" ; shipped C " as *u8)
238 gv_num(SB_C_HAIR)
239 gv_puts(" vs sb_c_for_zeta(K," as *u8)
240 gv_num(SB_HAIR_ZETA_PERMIL)
241 gv_puts(" permil) = " as *u8)
242 gv_num(cfrom)
243 gv_puts("\n" as *u8)
244 gv_check("the physical spelling of the shipped hair plant is EXACT, not approximate (K)" as *u8, kfrom == SB_K_HAIR, ctr)
245 gv_check("the physical spelling of the shipped hair plant is EXACT, not approximate (C)" as *u8, cfrom == SB_C_HAIR, ctr)
246 // NEG-CONTROL for that pair: the pinning must be TIGHT. One mhz either side of the declared
247 // frequency must NOT still produce the shipped stiffness, or the assertion above would pass for
248 // a whole range of frequencies and would be pinning nothing.
249 var tight: i64 = 0
250 if sb_k_sym(SB_HAIR_FN_MHZ + 30) != SB_K_HAIR {
251 if sb_k_sym(SB_HAIR_FN_MHZ - 30) != SB_K_HAIR { tight = 1 }
252 }
253 gv_check("neg-control-the-frequency-pin-is-tight (a nearby frequency does NOT reproduce it)" as *u8, tight, ctr)
254
255 let rc9: i64 = gv_verdict("HAIRDYN" as *u8, ctr, "hair chain plant" as *u8)
256 sys_exit(rc9)
257 return rc9
258}