nx_gpe_hair_geometry_gate.nx source
↩ module page · 223 lines · 9957 B
1// nx_gpe_hair_geometry_gate.nx -- GATE: prove /world/beach renders hair GEOMETRY that MOVES with
2// the N-segment chain, and prove the same measurement CANNOT pass on rigid hair.
3//
4// WHAT THIS GATE IS FOR. Two capabilities shipped and never met. ref9d.nxa carries real groom
5// geometry (HSTR, 2200 strands x 8 points -- measured by nx_asset_floor_gate, sections=16) and the
6// page drew it RIGID: every hair vertex skinned haw=1 to the head joint, a helmet. nx_hairdyn_lib
7// shipped a spring-bone chain on nx_softdyn's one solver and was imported by NOTHING but its own
8// gate. gpe_hair_geometry is the join. These teeth measure the join rather than asserting it.
9//
10// THE ANTI-VACUITY SUBJECT IS THE LIB'S OWN RIGID CONTROL. hd_tick_rigid is not a mutant I wrote
11// for this gate -- it ships in nx_hairdyn_lib precisely as "the ABSENCE of the solver", the
12// trivial wrong implementation that still has hair in the sense that the joints exist and follow
13// the head. Baking with it yields a table that cannot vary and cannot differ between segments, so
14// every motion tooth below is proven against the exact thing this rung replaces.
15//
16// NO TOLERANCE IS CHOSEN ANYWHERE. Every tooth is a COUNT or an INEQUALITY against zero or one:
17// does the table vary at all, does it supply more than one distinct offset, does the swing change
18// sign. A threshold picked to make a swing "big enough" would be the magic number this estate
19// refuses, and none is needed to separate a chain from a helmet.
20//
21// license_tier: ORIGINAL No hw writes (Rule 26).
22import "nx_syscalls.nx"
23import "nx_gate_verdict.nx"
24import "nx_game_page_emit.nx"
25
26const HGG_BUF: i64 = 1048576
27const HGG_TBL_SLOTS: i64 = 8192
28
29// literal substring search over the emitted page bytes. Local to this gate by the same precedent
30// every sibling follows (ce_find in nx_craft_emit, af_find in nx_asset_floor_gate): the emitted
31// buffer is not NUL-terminated, so a C-string finder cannot be composed here.
32func hgg_find(b: *u8, n: i64, s: *u8) -> i64 {
33 var sl: i64 = 0
34 while s[sl] != (0 as u8) { sl = sl + 1 }
35 if sl == 0 { return 0 - 1 }
36 if n < sl { return 0 - 1 }
37 var i: i64 = 0
38 while i <= n - sl {
39 var j: i64 = 0
40 var ok: i64 = 1
41 while j < sl {
42 if b[i+j] != s[j] { ok = 0 }
43 j = j + 1
44 }
45 if ok == 1 { return i }
46 i = i + 1
47 }
48 return 0 - 1
49}
50
51// how many DIFFERENT values sit in t[off .. off+n-1]. This is the "N offsets, not one" ruler:
52// the single-point driver this rung replaces can only ever produce 1.
53func hgg_distinct(t: *i64, off: i64, n: i64) -> i64 {
54 var d: i64 = 0
55 var i: i64 = 0
56 while i < n {
57 var seen: i64 = 0
58 var k: i64 = 0
59 while k < i {
60 if t[off+k] == t[off+i] { seen = 1 }
61 k = k + 1
62 }
63 if seen == 0 { d = d + 1 }
64 i = i + 1
65 }
66 return d
67}
68
69// 1 if ANY entry differs from its frame-0 value: the table MOVES.
70func hgg_varies(t: *i64, n: i64, f: i64) -> i64 {
71 var fr: i64 = 1
72 while fr < f {
73 var i: i64 = 0
74 while i < n {
75 if t[fr*n+i] != t[i] { return 1 }
76 i = i + 1
77 }
78 fr = fr + 1
79 }
80 return 0
81}
82
83// sign changes of one segment's offset across the table. A table spanning about one natural period
84// must cross zero twice; this MEASURES the span from the data instead of trusting the constant the
85// frame count was derived from.
86func hgg_sign_changes(t: *i64, n: i64, f: i64, seg: i64) -> i64 {
87 var c: i64 = 0
88 var prev: i64 = 0
89 var fr: i64 = 0
90 while fr < f {
91 var s: i64 = 0
92 if t[fr*n+seg] > 0 { s = 1 }
93 if t[fr*n+seg] < 0 { s = 0 - 1 }
94 if s != 0 {
95 if prev != 0 { if s != prev { c = c + 1 } }
96 prev = s
97 }
98 fr = fr + 1
99 }
100 return c
101}
102
103// largest |offset| reached by one segment over the whole table
104func hgg_seg_maxabs(t: *i64, n: i64, f: i64, seg: i64) -> i64 {
105 var mx: i64 = 0
106 var fr: i64 = 0
107 while fr < f {
108 var v: i64 = t[fr*n+seg]
109 if v < 0 { v = 0 - v }
110 if v > mx { mx = v }
111 fr = fr + 1
112 }
113 return mx
114}
115
116func main() -> i64 {
117 let ctr: *i64 = gv_ctr()
118 gv_head("nx_gpe_hair_geometry gate -- hair GEOMETRY driven by the N-segment chain" as *u8)
119
120 let n: i64 = gpe_hair_nseg()
121 let f: i64 = gpe_hair_frames()
122 let m: i64 = f*n
123 gv_puts(" derived: nseg=" as *u8); gv_num(n)
124 gv_puts(" frames=" as *u8); gv_num(f)
125 gv_puts(" entries=" as *u8); gv_num(m)
126 gv_puts(" ticks_per_s=" as *u8); gv_num(hd_ticks_per_s())
127 gv_puts(" fn_mhz=" as *u8); gv_num(SB_HAIR_FN_MHZ)
128 gv_puts("\n" as *u8)
129
130 // ---- FIXTURE REACHED THE CONDITION (asserted BEFORE any outcome) ----
131 var dimok: i64 = 0
132 if n > 1 { if f > 1 { if m <= HGG_TBL_SLOTS { dimok = 1 } } }
133 gv_check("fixture-chain-dimensions-usable (nseg>1, frames>1, table fits) -- a zero-entry or single-segment table cannot express either property below" as *u8, dimok, ctr)
134
135 let tbl: *i64 = sys_mmap(HGG_TBL_SLOTS*8) as *i64
136 let rtb: *i64 = sys_mmap(HGG_TBL_SLOTS*8) as *i64
137 let wrote: i64 = gpe_hair_chain_bake(tbl, 0)
138 let rwrote: i64 = gpe_hair_chain_bake(rtb, 1)
139 var wok: i64 = 0
140 if wrote == m { if rwrote == m { wok = 1 } }
141 gv_check("fixture-both-bakes-wrote-every-declared-entry (declared == written, so a short table cannot read as a still one)" as *u8, wok, ctr)
142
143 // ---- THE CHAIN IS A CHAIN ----
144 let d0: i64 = hgg_distinct(tbl, 0, n)
145 let rd0: i64 = hgg_distinct(rtb, 0, n)
146 gv_puts(" distinct offsets at frame 0: chain=" as *u8); gv_num(d0)
147 gv_puts(" rigid=" as *u8); gv_num(rd0); gv_puts("\n" as *u8)
148 var multi: i64 = 0
149 if d0 > 1 { multi = 1 }
150 gv_check("chain-supplies-N-DISTINCT-offsets-at-one-instant (the single-point driver this rung replaces can only ever supply 1)" as *u8, multi, ctr)
151
152 let vr: i64 = hgg_varies(tbl, n, f)
153 let rvr: i64 = hgg_varies(rtb, n, f)
154 gv_puts(" table varies across frames: chain=" as *u8); gv_num(vr)
155 gv_puts(" rigid=" as *u8); gv_num(rvr); gv_puts("\n" as *u8)
156 gv_check("chain-table-MOVES (some segment offset differs from its frame-0 value)" as *u8, vr, ctr)
157
158 // per-segment swing magnitudes: a chain's segments do NOT all swing alike
159 var s0: i64 = hgg_seg_maxabs(tbl, n, f, 0)
160 var sl: i64 = hgg_seg_maxabs(tbl, n, f, n - 1)
161 gv_puts(" max|offset| q8: seg0=" as *u8); gv_num(s0)
162 gv_puts(" seg" as *u8); gv_num(n - 1); gv_puts("=" as *u8); gv_num(sl); gv_puts("\n" as *u8)
163 var differ: i64 = 0
164 if s0 != sl { differ = 1 }
165 gv_check("chain-segments-swing-DIFFERENTLY (root-most and tip-most magnitudes differ -- N copies of one point cannot)" as *u8, differ, ctr)
166
167 let sc: i64 = hgg_sign_changes(tbl, n, f, n - 1)
168 gv_puts(" tip sign changes across the table: " as *u8); gv_num(sc); gv_puts("\n" as *u8)
169 var osc: i64 = 0
170 if sc >= 2 { osc = 1 }
171 gv_check("table-spans-about-one-oscillation (tip crosses zero at least twice) -- MEASURED from the data, not trusted from the frame-count constant" as *u8, osc, ctr)
172
173 // ---- NEGATIVE CONTROLS: the rigid chain, which is what shipped ----
174 var rstill: i64 = 0
175 if rvr == 0 { rstill = 1 }
176 var lstill: i64 = 0
177 if vr == 0 { lstill = 1 }
178 gv_bite("neg-control-rigid-hair-CANNOT-move (dynamics removed via the lib's own hd_tick_rigid)" as *u8, rstill, lstill, ctr)
179
180 var rone: i64 = 0
181 if rd0 == 1 { rone = 1 }
182 var lone: i64 = 0
183 if d0 == 1 { lone = 1 }
184 gv_bite("neg-control-rigid-hair-supplies-ONE-offset-not-N" as *u8, rone, lone, ctr)
185
186 // ---- THE EMITTED RENDERER ----
187 let out: *u8 = sys_mmap(HGG_BUF) as *u8
188 let plen: i64 = gpe_hair_geometry(out, 0)
189 gv_puts(" emitted bytes: " as *u8); gv_num(plen); gv_puts("\n" as *u8)
190 var eok: i64 = 0
191 if plen > 0 { if plen < HGG_BUF { eok = 1 } }
192 gv_check("fixture-emitter-produced-bytes-within-the-buffer (an empty emit would pass every absence tooth below)" as *u8, eok, ctr)
193
194 var hv: i64 = 0
195 if hgg_find(out, plen, "S.HVRT&&S.HTRI" as *u8) >= 0 { hv = 1 }
196 gv_check("emitted-consumes-HVRT/HTRI-groom-MESH (the section nx_nxa_hair writes and NOTHING read)" as *u8, hv, ctr)
197
198 var hs: i64 = 0
199 if hgg_find(out, plen, "S.HSTR" as *u8) >= 0 { hs = 1 }
200 gv_check("emitted-consumes-HSTR-guide-strand-geometry (what ref9d.nxa actually carries today)" as *u8, hs, ctr)
201
202 var nof: i64 = 0
203 if hgg_find(out, plen, "NXHCH[fr*NXHCH_N+hs[v]]" as *u8) >= 0 { nof = 1 }
204 gv_check("emitted-indexes-the-chain-table-BY-VERTEX-SEGMENT (this is the line that consumes N offsets rather than one)" as *u8, nof, ctr)
205
206 var upl: i64 = 0
207 if hgg_find(out, plen, "bufferSubData" as *u8) >= 0 { upl = 1 }
208 gv_check("emitted-UPLOADS-the-displaced-geometry-each-frame (a table nothing uploads moves nothing on screen)" as *u8, upl, ctr)
209
210 var ann: i64 = 0
211 if hgg_find(out, plen, "no-hair-geometry" as *u8) >= 0 { ann = 1 }
212 gv_check("emitted-ANNOUNCES-absent-hair-geometry-rather-than-reporting-success (an asset with no groom must not read as working hair)" as *u8, ann, ctr)
213
214 var wir: i64 = 0
215 if hgg_find(out, plen, "requestAnimationFrame(hpTick)" as *u8) >= 0 { wir = 1 }
216 gv_check("emitted-WIRES-the-hair-probe-into-the-DOM-not-merely-DEFINES-it (measured: __nx_hair_probe once occurred exactly once -- its own definition -- so the rung's only measurement was invisible to every capture)" as *u8, wir, ctr)
217
218 var rigidclaim: i64 = 0
219 if hgg_find(out, plen, "rigid to the head joint" as *u8) < 0 { rigidclaim = 1 }
220 gv_check("neg-control-emitted-page-no-longer-claims-hair-is-RIGID-to-the-head-joint (the superseded block is gone, not merely shadowed)" as *u8, rigidclaim, ctr)
221
222 return gv_verdict("NX-GPE-HAIR-GEOMETRY" as *u8, ctr, "hair geometry consumed from the asset and displaced by the chain" as *u8)
223}