code wiki / _hdl_build / nx_assetvariant_gate.nx
nx_assetvariant_gate.nx source
↩ module page · 219 lines · 10413 B
1// nx_assetvariant_gate.nx -- prove SWAP-AS-DATA v0: ONE part-recipe system emits MANY distinct creature/person
2// variants by swapping slots (species x head x ears x tail x build), all rendered on the SAME photoreal-path
3// shader into a 4x3 CONTACT SHEET (the DnD bestiary card). Asserts:
4// T1 every variant visibly renders (foreground coverage per tile)
5// T2 all 12 variant tiles are pairwise DISTINCT (diversity is real, not one blob)
6// T3 a HEAD-SLOT-ONLY swap changes the render (plain vs horns on the same base = the swap works)
7// T4 species swap changes the render (human vs wolf)
8// Writes knowledge/synth_variants.png (384x216 sheet). license_tier: ORIGINAL expect_exit: 0
9import "nx_syscalls.nx"
10import "nx_sdfrender.nx"
11import "nx_assetvariant.nx"
12import "nx_itrig.nx"
13import "nx_gate_verdict.nx" // D001: inherit the verdict contract rather than re-emit its strings
14
15// Row count named, not inline: T1..T5. The verdict maps fails onto the shared contract as
16// passed = AV_GATE_ROWS - fails, so adding a row means moving ONE constant, not two call sites.
17const AV_GATE_ROWS: i64 = 5
18import "nx_png.nx"
19import "nx_critvis_lib.nx"
20
21func hw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
22func pn(v: i64) -> i64 {
23 let b: *u8 = sys_mmap(32) as *u8
24 var x: i64 = v; var neg: i64 = 0
25 if x < 0 { neg = 1; x = 0 - x }
26 var i: i64 = 31
27 if x == 0 { b[i] = 48 as u8; i = i - 1 }
28 while x > 0 { b[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 }
29 if neg == 1 { b[i] = 45 as u8; i = i - 1 }
30 sys_write(1, (b as i64 + i + 1) as *u8, 31 - i)
31 return 0
32}
33
34const SW: i64 = 384
35const SH: i64 = 216
36const TW: i64 = 96
37const TH: i64 = 72
38const TFOCAL: i64 = 109 // 586 * 96 / 512 (same FOV as production)
39
40// render one variant tile into the sheet fb at (tx0,ty0), single ray/px through the production shader
41func tile_render(base: i64, fb: *i64, tx0: i64, ty0: i64) -> i64 {
42 let yaw: i64 = 470 // ~15deg orbit
43 let cy4: i64 = it_cos4096(yaw)
44 let sy4: i64 = it_sin4096(yaw)
45 let R: i64 = 5 * 1024
46 let rox: i64 = 0 - sy4 * R / 4096
47 let roz: i64 = 0 - cy4 * R / 4096
48 var y: i64 = 0
49 while y < TH {
50 let mc1: i64 = 24 + y * 30 / TH
51 let mc2: i64 = 26 + y * 28 / TH
52 let mc3: i64 = 40 + y * 26 / TH
53 let misscol: i64 = mc1 + mc2 * 256 + mc3 * 65536
54 var x: i64 = 0
55 while x < TW {
56 let ndcx: i64 = (2 * x + 1 - TW) * 1024 / (2 * TFOCAL)
57 let ndcy: i64 = (TH - (2 * y + 1)) * 1024 / (2 * TFOCAL)
58 let rl: i64 = sdf_isqrt(ndcx * ndcx + ndcy * ndcy + 1024 * 1024)
59 let vdx: i64 = ndcx * 1024 / rl
60 let rdy: i64 = ndcy * 1024 / rl
61 let vdz: i64 = 1024 * 1024 / rl
62 let rdx: i64 = (cy4 * vdx + sy4 * vdz) / 4096
63 let rdz: i64 = (0 - sy4 * vdx + cy4 * vdz) / 4096
64 let col: i64 = sdf_shade_ray(base, rox, 0, roz, rdx, rdy, rdz, 240, 184, 160, misscol)
65 let idx: i64 = (ty0 + y) * SW + tx0 + x
66 fb[idx] = col
67 x = x + 1
68 }
69 y = y + 1
70 }
71 return 0
72}
73func tile_cksum(fb: *i64, tx0: i64, ty0: i64) -> i64 {
74 var s: i64 = 1469598103
75 var y: i64 = 0
76 while y < TH {
77 var x: i64 = 0
78 while x < TW {
79 let px: i64 = fb[(ty0 + y) * SW + tx0 + x]
80 s = (s * 31 + (px & 0xffffff)) & 0x0fffffffffffffff
81 x = x + 1
82 }
83 y = y + 1
84 }
85 return s
86}
87func tile_fg(fb: *i64, tx0: i64, ty0: i64) -> i64 {
88 var c: i64 = 0
89 var y: i64 = 0
90 while y < TH {
91 var x: i64 = 0
92 while x < TW {
93 let px: i64 = fb[(ty0 + y) * SW + tx0 + x]
94 let w: i64 = cv_pl(px)
95 c = c + w
96 x = x + 1
97 }
98 y = y + 1
99 }
100 return c
101}
102
103func main() -> i64 {
104 hw("=== nx_assetvariant_gate -- one recipe system, 12 creature/person variants via SWAP SLOTS ===\n" as *u8)
105 let base: i64 = sys_mmap(sdf_bytes()) as i64
106 let fb: *i64 = sys_mmap(SW * SH * 8) as *i64
107 // variant tuples {sp, hv, ev, tv, build}
108 let vt: *i64 = sys_mmap(12 * 5 * 8) as *i64
109 var q: i64 = 0
110 vt[q]=0; vt[q+1]=0; vt[q+2]=0; vt[q+3]=0; vt[q+4]=100; q=q+5 // 0 human plain
111 vt[q]=0; vt[q+1]=1; vt[q+2]=0; vt[q+3]=0; vt[q+4]=100; q=q+5 // 1 human HORNS (head-swap-only vs 0)
112 vt[q]=0; vt[q+1]=2; vt[q+2]=1; vt[q+3]=1; vt[q+4]=100; q=q+5 // 2 beastman (snout+ears+tail)
113 vt[q]=0; vt[q+1]=0; vt[q+2]=0; vt[q+3]=0; vt[q+4]=125; q=q+5 // 3 human BULK
114 vt[q]=0; vt[q+1]=0; vt[q+2]=1; vt[q+3]=0; vt[q+4]=80; q=q+5 // 4 elf (pointy ears, slim)
115 vt[q]=2; vt[q+1]=0; vt[q+2]=1; vt[q+3]=0; vt[q+4]=100; q=q+5 // 5 goblin
116 vt[q]=2; vt[q+1]=1; vt[q+2]=1; vt[q+3]=0; vt[q+4]=80; q=q+5 // 6 goblin horned slim
117 vt[q]=2; vt[q+1]=2; vt[q+2]=1; vt[q+3]=1; vt[q+4]=100; q=q+5 // 7 goblin snouted+tail
118 vt[q]=2; vt[q+1]=0; vt[q+2]=1; vt[q+3]=0; vt[q+4]=125; q=q+5 // 8 goblin BULK (hobgoblin)
119 vt[q]=1; vt[q+1]=0; vt[q+2]=1; vt[q+3]=1; vt[q+4]=100; q=q+5 // 9 wolf
120 vt[q]=1; vt[q+1]=1; vt[q+2]=1; vt[q+3]=1; vt[q+4]=125; q=q+5 // 10 DIRE wolf (horned bulk)
121 vt[q]=1; vt[q+1]=0; vt[q+2]=1; vt[q+3]=0; vt[q+4]=80; q=q+5 // 11 coyote (slim, no tail)
122 let cks: *i64 = sys_mmap(12 * 8) as *i64
123 let fgs: *i64 = sys_mmap(12 * 8) as *i64
124 var v: i64 = 0
125 while v < 12 {
126 let sp: i64 = vt[v*5]
127 let hv: i64 = vt[v*5+1]
128 let ev: i64 = vt[v*5+2]
129 let tv: i64 = vt[v*5+3]
130 let bl: i64 = vt[v*5+4]
131 let np: i64 = av_build(base, sp, hv, ev, tv, bl)
132 let tx0: i64 = (v % 4) * TW
133 let ty0: i64 = (v / 4) * TH
134 tile_render(base, fb, tx0, ty0)
135 let ck: i64 = tile_cksum(fb, tx0, ty0)
136 let fg: i64 = tile_fg(fb, tx0, ty0)
137 cks[v] = ck
138 fgs[v] = fg
139 hw(" v" as *u8); pn(v); hw(" sp=" as *u8); pn(sp); hw(" parts=" as *u8); pn(np); hw(" fg=" as *u8); pn(fg); hw("\n" as *u8)
140 v = v + 1
141 }
142 write_png(fb, SW, SH, "knowledge/synth_variants.png" as *u8)
143 hw(" wrote knowledge/synth_variants.png (4x3 contact sheet, 384x216)\n" as *u8)
144
145 var fails: i64 = 0
146 // T1 all visible
147 var t1: i64 = 1
148 let fgmin: i64 = TW * TH * 2 / 100
149 var a: i64 = 0
150 while a < 12 { if fgs[a] < fgmin { t1 = 0 } a = a + 1 }
151 if t1 == 1 { hw("T1 PASS all 12 variants visibly render (fg >= 2 percent each)\n" as *u8) }
152 else { fails = fails + 1; hw("T1 FAIL a variant is invisible\n" as *u8) }
153 // T2 pairwise distinct
154 var distinct: i64 = 0
155 a = 0
156 while a < 12 {
157 var seen: i64 = 0
158 var b: i64 = 0
159 while b < a { if cks[b] == cks[a] { seen = 1 } b = b + 1 }
160 if seen == 0 { distinct = distinct + 1 }
161 a = a + 1
162 }
163 if distinct == 12 { hw("T2 PASS 12/12 tiles pairwise DISTINCT -- swap slots produce real diversity\n" as *u8) }
164 else { fails = fails + 1; hw("T2 FAIL only " as *u8); pn(distinct); hw(" distinct\n" as *u8) }
165 // T3 head-slot-only swap visible
166 if cks[0] != cks[1] { hw("T3 PASS head-slot-ONLY swap (plain vs horns, same base) changes the render\n" as *u8) }
167 else { fails = fails + 1; hw("T3 FAIL head swap invisible\n" as *u8) }
168 // T4 species swap
169 if cks[0] != cks[9] { hw("T4 PASS species swap (human vs wolf) changes the render\n" as *u8) }
170 else { fails = fails + 1; hw("T4 FAIL species identical\n" as *u8) }
171
172 // ---- MUTATION-DERIVED TOOTH: BILATERAL SYMMETRY OF PAIRED FEATURES (2026-08-01, debt 1785604588) ----
173 // nx_gate_mutation_probe scored this pair 2/4 with TWO survivors, both the same shape:
174 // nx_assetvariant.nx:75 the HORN pair (hx-130 / hx+130, heady+300)
175 // nx_assetvariant.nx:91 the EAR pair (hx-210 / hx+210, heady+210)
176 // Flipping a ` + ` there either drops a feature below the head or puts BOTH of a pair on the same
177 // side. Every tooth above compares render CHECKSUMS for distinctness -- T2 pairwise distinct, T3
178 // head-swap visible, T4 species-swap visible -- and a misplaced horn still yields a DIFFERENT
179 // checksum, so distinctness stays satisfied. The gate could not tell a correct model from a
180 // deformed one; it only knew the twelve were not identical.
181 // LAW: A DISTINCTNESS TEST IS NOT A CORRECTNESS TEST. Proving twelve outputs differ says nothing
182 // about whether any one of them is right, and mutation finds exactly that gap.
183 // The invariant is bilateral symmetry: a human is built about hx=0, so every part must have a
184 // mirror at -cx with the SAME height. Centre-line parts (cx==0) mirror onto themselves. This needs
185 // no hard-coded coordinate, so it cannot rot as the model changes.
186 let sym_np: i64 = av_build(base, AV_HUMAN, 1, 2, 0, 100) // horns + ears, the two mutated slots
187 let sp6: *i64 = (base + O_PARTS) as *i64
188 var asym: i64 = 0
189 var si: i64 = 0
190 while si < sym_np {
191 let scx: i64 = sp6[si * 6]
192 let scy: i64 = sp6[si * 6 + 1]
193 var found: i64 = 0
194 var sj: i64 = 0
195 while sj < sym_np {
196 if sp6[sj * 6] == (0 - scx) {
197 if sp6[sj * 6 + 1] == scy { found = 1 }
198 }
199 sj = sj + 1
200 }
201 if found == 0 { asym = asym + 1 }
202 si = si + 1
203 }
204 if asym == 0 { hw("T5 PASS bilateral symmetry: every part has a mirror at -cx, same height\n" as *u8) }
205 else { fails = fails + 1; hw("T5 FAIL " as *u8); pn(asym); hw(" part(s) have NO mirror -- a paired feature is misplaced\n" as *u8) }
206
207 // D001 MIGRATE-ON-TOUCH, BY HAND. nx_gate_dry_apply SKIPPED this gate as anchor-rung: it prints
208 // "5/5 GREEN" without the literal `verdict=GREEN` the applier keys on. Rather than take the
209 // documented escape hatch a second time, the fails==0 idiom maps cleanly onto the shared contract:
210 // passed = TOTAL - fails. That is the mapping the applier's own FAILSZERO family refuses to do
211 // automatically -- and correctly so, since ctr[1]=0 would score RED -- but it is unambiguous when a
212 // human reads the gate and knows the row count.
213 let ctr: *i64 = gv_ctr()
214 ctr[0] = AV_GATE_ROWS - fails
215 ctr[1] = AV_GATE_ROWS
216 let rc: i64 = gv_verdict("ASSETVARIANT-GATE" as *u8, ctr, "one base system, 12 swap-slot variants; tiles distinct AND bilaterally symmetric" as *u8)
217 sys_exit(rc)
218 return rc
219}