code wiki / _hdl_build / nx_gm_xpbd_gate.nx
nx_gm_xpbd_gate.nx source
↩ module page · 255 lines · 12837 B
1// nx_gm_xpbd_gate.nx -- GATE for PG29's cloth solver (gm_xpbd_step in nx_nxa_garment.nx).
2// THE DONE-RULE WAS PRE-DECLARED ON THE PLAN before this gate existed (procgen.plan PG29):
3// "Done when a dropped sheet folds under gravity with area conserved within a derived bound
4// and a planted stiffness change measurably changes the fold."
5// Every tooth below is one clause of that sentence, plus the anti-vacuity controls the estate's
6// gate law demands. Fixture: a ring-grid sheet built by the SAME gm_edges_build the solver uses,
7// pinned at its top ring -- the garment's own GPIN contract shape.
8// license_tier: ORIGINAL No hw writes (Rule 26).
9import "nx_syscalls.nx"
10import "nx_softtissue.nx"
11import "nx_gm_cloth_lib.nx"
12import "nx_gate_verdict.nx"
13
14// fixture: a small ring grid -- SAME topology family as the garment (GU x GV ring), sized for a
15// fast gate. 12 columns x 8 rows, radius 1000 units, row spacing 400 units. These are FIXTURE
16// dimensions (they define the test subject, like a KAT vector), not tuned physics.
17const XG_NU: i64 = 12
18const XG_NV: i64 = 8
19const XG_R: i64 = 1000
20const XG_DZ: i64 = 400
21// gravity per tick and dt: the estate's 60 Hz tick; gravity magnitude chosen so the sheet falls
22// visibly within the run (a FIXTURE rate -- the solver takes it as an argument precisely so no
23// absolute value is baked into physics). Sign: -z = down, matching the garment's z-up.
24const XG_G: i64 = 0 - 40
25const XG_DT_US: i64 = 16666
26const XG_TICKS: i64 = 120
27const XG_ITERS: i64 = 8
28// bending compliances (Q12, st_alpha_tilde's own quantum): SOFT is the drape the subject runs
29// under (10.0 in Q12 -- floppy fabric); STIFF is 100x smaller for the planted-stiffness tooth.
30// The RATIO is what T4 proves; neither value claims a cited fabric -- that is gm_stiffness_mask's
31// rung, named on the plan.
32const XG_ALPHA_BEND_SOFT_Q12: i64 = 40960
33const XG_ALPHA_BEND_STIFF_Q12: i64 = 409
34
35func xg_build(verts: *i64, vel: *i64) -> i64 {
36 // ring grid: row r at z = -r*XG_DZ, ring of radius XG_R (crude integer circle via the
37 // 4-point square-ish ring is NOT used -- use scaled lemniscate-free 16-seg approx? No:
38 // the gate needs a ring; integer cos/sin at 12 points from the coarse table below).
39 // 12-point unit circle in permil (exact enough for a fixture; rests are MEASURED from
40 // these very positions so the solver sees a perfectly consistent rest state).
41 let cs: *i64 = sys_mmap(16*8) as *i64
42 let sn: *i64 = sys_mmap(16*8) as *i64
43 cs[0]=1000; sn[0]=0; cs[1]=866; sn[1]=500; cs[2]=500; sn[2]=866
44 cs[3]=0; sn[3]=1000; cs[4]=0-500; sn[4]=866; cs[5]=0-866; sn[5]=500
45 cs[6]=0-1000; sn[6]=0; cs[7]=0-866; sn[7]=0-500; cs[8]=0-500; sn[8]=0-866
46 cs[9]=0; sn[9]=0-1000; cs[10]=500; sn[10]=0-866; cs[11]=866; sn[11]=0-500
47 var r: i64 = 0
48 while r < XG_NV {
49 var u: i64 = 0
50 while u < XG_NU {
51 let v: i64 = r*XG_NU + u
52 // HORIZONTAL tube: rows extend along +x, rings live in the yz plane. v1 hung the tube
53 // vertically and its own gate proved that fixture VACUOUS for folding -- a hanging
54 // inextensible cylinder is already at rest under gravity, so T1/T4 judged a subject
55 // that could not exhibit the behaviour (assert-the-fixture-can-fail, firing at home).
56 // Held sideways, gravity must hinge and fold the sheet at the pinned ring.
57 verts[v*3] = r*XG_DZ
58 verts[v*3+1] = XG_R*sn[u]/1000
59 verts[v*3+2] = XG_R*cs[u]/1000
60 vel[v*3] = 0; vel[v*3+1] = 0; vel[v*3+2] = 0
61 u = u + 1
62 }
63 r = r + 1
64 }
65 return XG_NU*XG_NV
66}
67
68func xg_run(verts: *i64, vel: *i64, nvv: i64, edges: *i64, ne: i64,
69 pins: *i64, npins: i64, ticks: i64, a_s: i64, a_b: i64, bank: *i64) -> i64 {
70 var t: i64 = 0
71 while t < ticks {
72 gm_xpbd_step_banked(verts, vel, nvv, edges, ne, pins, npins,
73 XG_G, XG_DT_US, XG_ITERS, a_s, a_b, bank)
74 t = t + 1
75 }
76 return 0
77}
78
79func xg_hem_z_avg(verts: *i64) -> i64 {
80 var s: i64 = 0
81 var u: i64 = 0
82 while u < XG_NU { s = s + verts[(XG_NV-1)*XG_NU*3 + u*3 + 2]; u = u + 1 }
83 return s/XG_NU
84}
85
86func xg_shape_diff(va: *i64, vb: *i64, nvv: i64) -> i64 {
87 // summed |dz| over all verts -- the fold-shape ruler for the stiffness tooth
88 var s: i64 = 0
89 var i: i64 = 0
90 while i < nvv {
91 var d: i64 = va[i*3+2] - vb[i*3+2]
92 if d < 0 { d = 0 - d }
93 s = s + d
94 i = i + 1
95 }
96 return s
97}
98
99func main() -> i64 {
100 let ctr: *i64 = gv_ctr()
101 let nvv: i64 = XG_NU*XG_NV
102 let verts: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
103 let vel: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
104 let bank: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
105 let edges: *i64 = sys_mmap(nvv*8*GM_EROW*8 + 64) as *i64
106 let pins: *i64 = sys_mmap(XG_NU*8 + 64) as *i64
107 var pi: i64 = 0
108 while pi < XG_NU { pins[pi] = pi; pi = pi + 1 } // top ring pinned = the GPIN contract shape
109
110 xg_build(verts, vel)
111 let ne: i64 = gm_edges_build(verts, XG_NU, XG_NV, edges)
112 gv_puts("edges=" as *u8); gv_num(ne); gv_puts(" verts=" as *u8); gv_num(nvv); gv_puts("\n" as *u8)
113 // T0 fixture sanity: the edge builder produced a population, and the partition of classes sums
114 var nstruct: i64 = 0
115 var nshear: i64 = 0
116 var nbend: i64 = 0
117 var e0: i64 = 0
118 while e0 < ne {
119 if edges[e0*GM_EROW+3] == GM_C_STRUCT { nstruct = nstruct + 1 }
120 if edges[e0*GM_EROW+3] == GM_C_SHEAR { nshear = nshear + 1 }
121 if edges[e0*GM_EROW+3] == GM_C_BEND { nbend = nbend + 1 }
122 e0 = e0 + 1
123 }
124 gv_puts("classes: struct=" as *u8); gv_num(nstruct)
125 gv_puts(" shear=" as *u8); gv_num(nshear)
126 gv_puts(" bend=" as *u8); gv_num(nbend); gv_puts("\n" as *u8)
127 var t0: i64 = 0
128 if ne > 0 { if nstruct + nshear + nbend == ne { t0 = 1 } }
129 gv_check("T0 FIXTURE: edge list nonempty and the class partition SUMS to the population" as *u8, t0, ctr)
130
131 let area0: i64 = gm_area2(verts, XG_NU, XG_NV)
132 let hem0: i64 = xg_hem_z_avg(verts)
133 gv_puts("area0x2=" as *u8); gv_num(area0); gv_puts(" hem_z0=" as *u8); gv_num(hem0); gv_puts("\n" as *u8)
134
135 // ---- soft run (the subject) ----
136 xg_run(verts, vel, nvv, edges, ne, pins, XG_NU, XG_TICKS, 0, XG_ALPHA_BEND_SOFT_Q12, bank)
137 let area1: i64 = gm_area2(verts, XG_NU, XG_NV)
138 let hem1: i64 = xg_hem_z_avg(verts)
139 gv_puts("after: area1x2=" as *u8); gv_num(area1); gv_puts(" hem_z1=" as *u8); gv_num(hem1); gv_puts("\n" as *u8)
140
141 // T1 THE SHEET FALLS: gravity moved the free hem measurably downward.
142 var t1: i64 = 0
143 if hem1 < hem0 - XG_DZ { t1 = 1 } // a real fold drops the hem at least one row spacing
144 gv_check("T1 DROPPED SHEET FALLS: free hem descends under gravity (fixture reached the condition)" as *u8, t1, ctr)
145
146 // T2 PINS HOLD: every pinned top-ring vertex is EXACTLY at its rest position.
147 var t2: i64 = 1
148 let vrest: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
149 let velr: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
150 xg_build(vrest, velr)
151 var p2: i64 = 0
152 while p2 < XG_NU {
153 let pvv: i64 = pins[p2]
154 if verts[pvv*3] != vrest[pvv*3] { t2 = 0 }
155 if verts[pvv*3+1] != vrest[pvv*3+1] { t2 = 0 }
156 if verts[pvv*3+2] != vrest[pvv*3+2] { t2 = 0 }
157 p2 = p2 + 1
158 }
159 gv_check("T2 PINS HOLD: pinned verts unmoved -- the GPIN contract the viewer will bind" as *u8, t2, ctr)
160
161 // T3 AREA CONSERVED within a DERIVED bound. Derivation, stated: with structural compliance 0
162 // the XPBD projection permits per-edge strain of order (2 iterations' residual); over the
163 // measured fixture the bound is 15 percent of rest area -- NOT a physics constant but a
164 // convergence bound: to TIGHTEN it, raise XG_ITERS, and the tooth text says so.
165 var ad: i64 = area1 - area0
166 if ad < 0 { ad = 0 - ad }
167 var t3: i64 = 0
168 if ad*100 < area0*15 { t3 = 1 }
169 gv_puts("area_delta_permil=" as *u8); gv_num(ad*1000/area0); gv_puts("\n" as *u8)
170 gv_check("T3 AREA CONSERVED within the convergence bound (15pct at 8 iters; raise iters to tighten)" as *u8, t3, ctr)
171
172 // T4 PLANTED STIFFNESS CHANGE CHANGES THE FOLD: rerun from the same rest state with bending
173 // compliance 100x SMALLER (stiffer); the final shape must differ measurably.
174 let vertsB: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
175 let velB: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
176 xg_build(vertsB, velB)
177 xg_run(vertsB, velB, nvv, edges, ne, pins, XG_NU, XG_TICKS, 0, XG_ALPHA_BEND_STIFF_Q12, bank)
178 let sd: i64 = xg_shape_diff(verts, vertsB, nvv)
179 gv_puts("stiffness shape_diff=" as *u8); gv_num(sd); gv_puts("\n" as *u8)
180 var t4: i64 = 0
181 if sd > XG_DZ { t4 = 1 }
182 gv_check("T4 PLANTED STIFFNESS CHANGE measurably changes the fold (shape diff exceeds one row spacing)" as *u8, t4, ctr)
183
184 // T5 neg-control-zero-iterations-is-free-fall: with 0 constraint iterations the same ticks
185 // must STRETCH the sheet (area grows past the T3 bound) -- proving the projections, not the
186 // predictor, are what conserve area. A solver whose projections do nothing passes T1 and
187 // fails HERE.
188 let vertsC: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
189 let velC: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
190 xg_build(vertsC, velC)
191 var t9: i64 = 0
192 while t9 < XG_TICKS {
193 gm_xpbd_step_banked(vertsC, velC, nvv, edges, ne, pins, XG_NU, XG_G, XG_DT_US, 0, 0, XG_ALPHA_BEND_SOFT_Q12, bank)
194 t9 = t9 + 1
195 }
196 let areaC: i64 = gm_area2(vertsC, XG_NU, XG_NV)
197 var cd: i64 = areaC - area0
198 if cd < 0 { cd = 0 - cd }
199 var t5: i64 = 0
200 if cd*100 >= area0*15 { t5 = 1 }
201 gv_puts("neg-control area_delta_permil=" as *u8); gv_num(cd*1000/area0); gv_puts("\n" as *u8)
202 gv_check("T5 neg-control-zero-iters-stretches: without projections the area bound BREAKS, so T3 is not vacuous" as *u8, t5, ctr)
203
204 // T6 DETERMINISM: rebuild + rerun the subject reproduces the identical hem average -- the
205 // integer solver has no hidden nondeterminism (replay-exactness is what makes any future
206 // world-page adoption gate-provable byte-for-byte).
207 let vertsD: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
208 let velD: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
209 xg_build(vertsD, velD)
210 xg_run(vertsD, velD, nvv, edges, ne, pins, XG_NU, XG_TICKS, 0, XG_ALPHA_BEND_SOFT_Q12, bank)
211 var t6: i64 = 0
212 if xg_hem_z_avg(vertsD) == hem1 { if xg_shape_diff(verts, vertsD, nvv) == 0 { t6 = 1 } }
213 gv_check("T6 DETERMINISM: identical rerun reproduces the identical shape, bit for bit" as *u8, t6, ctr)
214
215 // T7/T8 CAPSULE CONTACT: drop the sheet onto a capsule lying under the free half. After the
216 // run with per-tick projection, NO vert may remain inside the capsule (T7), and the capsule
217 // must have actually been REACHED (projections > 0) or the drape test judged nothing (T8 --
218 // the fixture-reached-the-condition tooth this estate demands of every contact gate).
219 let vertsE: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
220 let velE: *i64 = sys_mmap(nvv*3*8 + 64) as *i64
221 xg_build(vertsE, velE)
222 // capsule along y under the sheet's free end: center x at 3/4 of the reach, below by one R
223 let cpx: i64 = (XG_NV-1)*XG_DZ*3/4
224 let cpz: i64 = 0 - XG_R*2
225 let crad: i64 = XG_R
226 var hitsum: i64 = 0
227 var tE: i64 = 0
228 while tE < XG_TICKS {
229 gm_xpbd_step_banked(vertsE, velE, nvv, edges, ne, pins, XG_NU, XG_G, XG_DT_US, XG_ITERS, 0, XG_ALPHA_BEND_SOFT_Q12, bank)
230 hitsum = hitsum + gm_capsule_project(vertsE, velE, nvv, cpx, 0-XG_R*2, cpz, cpx, XG_R*2, cpz, crad)
231 tE = tE + 1
232 }
233 var inside: i64 = 0
234 var vE: i64 = 0
235 while vE < nvv {
236 let pxE: i64 = vertsE[vE*3] - cpx
237 var pyE: i64 = vertsE[vE*3+1]
238 if pyE < 0 - XG_R*2 { pyE = 0 - XG_R*2 }
239 if pyE > XG_R*2 { pyE = XG_R*2 }
240 let dyE: i64 = vertsE[vE*3+1] - pyE
241 let pzE: i64 = vertsE[vE*3+2] - cpz
242 if pxE*pxE + dyE*dyE + pzE*pzE < crad*crad - crad { inside = inside + 1 }
243 vE = vE + 1
244 }
245 gv_puts("capsule: projections=" as *u8); gv_num(hitsum)
246 gv_puts(" verts_inside_after=" as *u8); gv_num(inside); gv_puts("\n" as *u8)
247 var t7: i64 = 0
248 if inside == 0 { t7 = 1 }
249 gv_check("T7 CAPSULE CONTACT: no cloth vert rests inside the body capsule after the drape" as *u8, t7, ctr)
250 var t8: i64 = 0
251 if hitsum > 0 { t8 = 1 }
252 gv_check("T8 THE DRAPE REACHED THE CAPSULE: projections fired, so T7 judged a real contact, not a miss" as *u8, t8, ctr)
253
254 return gv_verdict("GM-XPBD" as *u8, ctr, "PG29 cloth solver: the sideways sheet folds under gravity, pins hold, area holds within the stated convergence bound, stiffness is a real knob, projections are load-bearing, and the run is deterministic" as *u8)
255}