code wiki / (root) / nx_part_solver_lib.nx

nx_part_solver_lib.nx source

↩ module page · 1095 lines · 49528 B

1// nx_part_solver_lib.nx -- ★CONSTRAINT-BASED PART PLACEMENT (operator method pointer 2026-08-12: 2// arXiv 2406.11824 Infinigen Indoors organizes ROOMS with a constraint DSL + maximal-satisfaction solver; 3// operator transfer: "use similar logic ... to organizing the generated elements of a human"). 4// This is rung 1 of that transfer: parts are POINTS with radii; constraints are DECLARATIVE ROWS in a 5// data file (rule 11 -- the anatomy lives in data, never typed coordinates in an emitter); the solver is 6// deterministic integer constraint PROJECTION (fixed pass count, fixed order). The SAME engine is meant 7// to assemble face/organ parts AND, later, furnish rooms and city blocks (one engine, bodies AND worlds). 8// 9// ★RUNG 2 (2026-08-14): EXTENT + ORIENTATION + ENVELOPE AS FIRST-CLASS PARTS -- the rung named from two 10// directions on /world/procgen ("orientation + extent (parts are points today)" and W4 R1 "orientation + 11// extents + envelope as first-class parts"). A part may now carry a BOX and a FACING, and three new 12// constraints consume them, so orientation is SOLVED geometry, not decoration: turning a part swaps its 13// footprint, which changes what it collides with and whether it still fits its envelope. 14// 15// Constraint rows (whitespace-separated, # comments): 16// part <name> <x> <y> <z> <r> declare a part with an INITIAL (possibly garbage) placement 17// extent <a> <ex> <ey> <ez> give a declared part HALF-EXTENTS (a box). Default 0 = a point. 18// facing <a> <fx> <fy> orientation as an integer direction vector in the ground plane, 19// snapped to the nearest cardinal. Default (1,0) = +x. 20// attach <a> <b> <dx> <dy> <dz> hard: pos[a] = pos[b] + (dx,dy,dz) 21// mirror <a> <b> bilateral symmetry about the x=0 plane: ax=-bx, ay=by, az=bz 22// station <a> <lo> <hi> a.z clamped into [lo,hi] (the anatomical station band) 23// clear <a> <b> <mind> centre distance >= mind (no-intersection of POINTS) 24// inside <a> <b> <maxd> centre distance <= maxd (containment; a moves toward b) 25// band <a> <b> <lo> <hi> centre distance inside [lo,hi] 26// ratio <a> <b> <c> <d> <num> <den> [tol_permil] dist(a,b) : dist(c,d) = num:den 27// noclip <a> <b> [gap] BOX no-intersection: the two oriented footprints must be 28// separated by >= gap on some ground axis. Parts whose z-intervals 29// do not overlap are exempt (different storeys never collide). 30// envelope <a> <b> box a lies ENTIRELY INSIDE box b -- the room shell / city block / 31// skull cavity. Only a moves: an envelope is GIVEN, not solved. 32// face <a> <b> a's facing is the cardinal direction toward b (chairs face the 33// table, buildings face the street). Orientation solved from 34// geometry, never typed. 35// 36// ORIENTATION IS EXACT BY CONSTRUCTION, AND THE BOUND IS DECLARED: facing snaps to the four cardinal 37// directions, so a rotated footprint is an exact swap of ex/ey -- no trig, no fixed-point error, and the 38// measured violation is the true violation. Arbitrary-angle boxes need an exact-enough integer rotation 39// contract (the fleet's rc_sin_q14 is a Bhaskara approximation, ~2 permil -- fine for a camera, NOT for a 40// predicate whose whole contract is an exact refusal). That is the NEXT rung, and it is named, not claimed. 41// 42// VERDICT CONTRACT (fail-closed, never fabricate): after PS_PASSES projection passes every constraint's 43// violation is MEASURED; SOLVED only if all violations <= PS_TOL, else INCONSISTENT with the WORST 44// constraint NAMED -- an unsatisfiable set degrades loudly, it never reports a fabricated success. 45// license_tier: ORIGINAL No hw writes (Rule 26). 46import "nx_syscalls.nx" 47 48import "nx_vecmath.nx" 49const PS_MAXP: i64 = 256 50const PS_MAXC: i64 = 512 51const PS_NAMEB: i64 = 32 52const PS_PASSES: i64 = 96 53const PS_TOL: i64 = 2 54const PS_ERRFD: i64 = 2 55const PS_K_ATTACH: i64 = 1 56const PS_K_MIRROR: i64 = 2 57const PS_K_STATION: i64 = 3 58const PS_K_CLEAR: i64 = 4 59const PS_K_INSIDE: i64 = 5 60// ★BEAUTY-AS-PROPORTIONS kinds (operator 2026-08-12): aesthetic canons are RELATIONS BETWEEN DISTANCES 61// (φ-band ratios, feature-spacing bands), so beauty lives in the constraint DATA, never in code. 62const PS_K_RATIO: i64 = 6 63const PS_K_BAND: i64 = 7 64// ★EXTENT/ORIENTATION/ENVELOPE kinds (rung 2): a part with a box and a facing. 65const PS_K_NOCLIP: i64 = 8 66const PS_K_ENVELOPE: i64 = 9 67const PS_K_FACE: i64 = 10 68const PS_RATIO_DEFTOL: i64 = 60 69const PS_E_PARSE: i64 = 3 70const PS_E_INCONSISTENT: i64 = 4 71 72static PS_NP: i64 73static PS_NC: i64 74static PS_PN: i64 75static PS_PX: i64 76static PS_PY: i64 77static PS_PZ: i64 78static PS_PR: i64 79static PS_EX: i64 80static PS_EY: i64 81static PS_EZ: i64 82static PS_FX: i64 83static PS_FY: i64 84static PS_CK: i64 85static PS_CA: i64 86static PS_CB: i64 87static PS_C1: i64 88static PS_C2: i64 89static PS_C3: i64 90static PS_CC: i64 91static PS_CD: i64 92static PS_WORST: i64 93static PS_WORSTV: i64 94static PS_RESID: i64 95 96// ---- PG10 (2026-09-06): SCENE COMPOSITION OVER THE LIVE SOLVER -- one constraint set, no second solver ------- 97// ps_scene_compose(b, n) lowers a SCENE grammar onto the rows above and then calls ps_load + ps_solve: the scene 98// is composed BY the part solver, so rooms, trees, water and beings are placed against each other in ONE constraint 99// set and an unsatisfiable scene is refused by the solver's own worst-constraint name. Scene rows (whitespace, #): 100// seed <n> the placement seed (initial positions only; the solver decides the rest) 101// site <ex> <ey> <ez> the world box, centred at the origin -- REQUIRED, exactly one, named `site` 102// water <name> <ex> <ey> <dx> <dy> a water body: a ground slab at a GIVEN offset from the site centre (attach) 103// room <name> <ex> <ey> <ez> a building footprint and height; inside the site; clips nothing; faces water 104// tree <name> <r> a crown; inside the site; clips nothing 105// being <name> <r> inside the site; clips nothing; lives within SC_NEAR of the first room 106// near <a> <b> <lo> <hi> an explicit distance band between two scene parts 107// Lowering: every solid gets `envelope <p> site`; every solid pair gets `noclip a b SC_GAP` (0 against water, a 108// shoreline is walked); rooms get `face <room> <water>`; beings get `band <being> <room> lo hi` with lo derived 109// from the room's footprint and the being's radius. Returns 1 SOLVED, 0 INCONSISTENT (ps_worst names it), negative 110// = a NAMED refusal (-SC_E_PARSE grammar, -SC_E_CAP the pair count would exceed PS_MAXC -- a bound, announced). 111// The composed rows stay readable through ps_scene_rows/ps_scene_len so a gate can prove the composer WROTE rows 112// and the solver SOLVED them -- nothing else moved a part. 113const SC_MAXP: i64 = 64 114const SC_NAMEB: i64 = 24 115const SC_BUFB: i64 = 65536 116const SC_K_SITE: i64 = 1 117const SC_K_WATER: i64 = 2 118const SC_K_ROOM: i64 = 3 119const SC_K_TREE: i64 = 4 120const SC_K_BEING: i64 = 5 121const SC_GAP: i64 = 2 // the pavement between solids 122const SC_NEAR: i64 = 40 // how far from its house a being may wander (the band width) 123const SC_WATER_EZ: i64 = 1 // a water body is a ground slab one unit tall, so ground solids collide with it 124const SC_E_PARSE: i64 = 3 125const SC_E_CAP: i64 = 5 126const SC_LCG_A: i64 = 48271 127const SC_LCG_M: i64 = 2147483647 128const SC_MAXNEAR: i64 = 64 129const SC_ROWB: i64 = 96 // the longest row the composer can write; the buffer guard keeps this headroom 130static SC_NP: i64 131static SC_NAMES: i64 132static SC_KIND: i64 133static SC_A: i64 134static SC_B: i64 135static SC_C: i64 136static SC_D: i64 137static SC_E: i64 138static SC_BUF: i64 139static SC_LEN: i64 140static SC_SEED: i64 141static SC_NEARN: i64 142static SC_NEAR_A: i64 143static SC_NEAR_B: i64 144static SC_NEAR_LO: i64 145static SC_NEAR_HI: i64 146static SC_OVER: i64 147func sc_reset() -> i64 { 148 if SC_NAMES == 0 { 149 SC_NAMES = sys_mmap(SC_MAXP*SC_NAMEB) as i64 150 SC_KIND = sys_mmap(SC_MAXP*8) as i64 151 SC_A = sys_mmap(SC_MAXP*8) as i64 152 SC_B = sys_mmap(SC_MAXP*8) as i64 153 SC_C = sys_mmap(SC_MAXP*8) as i64 154 SC_D = sys_mmap(SC_MAXP*8) as i64 155 SC_E = sys_mmap(SC_MAXP*8) as i64 156 SC_BUF = sys_mmap(SC_BUFB) as i64 157 SC_NEAR_A = sys_mmap(SC_MAXNEAR*8) as i64 158 SC_NEAR_B = sys_mmap(SC_MAXNEAR*8) as i64 159 SC_NEAR_LO = sys_mmap(SC_MAXNEAR*8) as i64 160 SC_NEAR_HI = sys_mmap(SC_MAXNEAR*8) as i64 161 } 162 let nm: *u8 = SC_NAMES as *u8 163 var i: i64 = 0 164 while i < SC_MAXP*SC_NAMEB { nm[i] = 0 as u8; i = i + 1 } 165 SC_NP = 0 166 SC_LEN = 0 167 SC_SEED = 1 168 SC_NEARN = 0 169 SC_OVER = 0 170 return 0 171} 172func sc_name(i: i64) -> *u8 { return ((SC_NAMES as i64) + i*SC_NAMEB) as *u8 } 173func sc_find(b: *u8, ts: i64, tl: i64) -> i64 { 174 var i: i64 = 0 175 while i < SC_NP { 176 let nm: *u8 = sc_name(i) 177 if ps_slen(nm) == tl { 178 var j: i64 = 0 179 var ok: i64 = 1 180 while j < tl { if (nm[j] as i64) != (b[ts+j] as i64) { ok = 0; j = tl } else { j = j + 1 } } 181 if ok == 1 { return i } 182 } 183 i = i + 1 184 } 185 return 0 - 1 186} 187// add a scene part; refuses a duplicate name (two parts wearing one name would silently merge in the solver) 188func sc_add(b: *u8, ts: i64, tl: i64, kind: i64, a: i64, bb: i64, c: i64, d: i64, e: i64) -> i64 { 189 if SC_NP >= SC_MAXP { ps_err("SCENE-REFUSE part capacity\n" as *u8); return 0 - SC_E_CAP } 190 if sc_find(b, ts, tl) >= 0 { ps_err("SCENE-REFUSE duplicate part name\n" as *u8); return 0 - SC_E_PARSE } 191 let dst: *u8 = sc_name(SC_NP) 192 var cl: i64 = tl 193 if cl > SC_NAMEB - 1 { cl = SC_NAMEB - 1 } 194 var q: i64 = 0 195 while q < cl { dst[q] = b[ts+q]; q = q + 1 } 196 dst[cl] = 0 as u8 197 let k: *i64 = SC_KIND as *i64 198 let pa: *i64 = SC_A as *i64 199 let pb: *i64 = SC_B as *i64 200 let pc: *i64 = SC_C as *i64 201 let pd: *i64 = SC_D as *i64 202 let pe: *i64 = SC_E as *i64 203 k[SC_NP] = kind; pa[SC_NP] = a; pb[SC_NP] = bb; pc[SC_NP] = c; pd[SC_NP] = d; pe[SC_NP] = e 204 SC_NP = SC_NP + 1 205 return SC_NP - 1 206} 207func sc_rand() -> i64 { 208 if SC_SEED <= 0 { SC_SEED = 1 } 209 SC_SEED = (SC_SEED * SC_LCG_A) % SC_LCG_M 210 return SC_SEED 211} 212// the row writer: strings and integers into the composed buffer; an overflow is REMEMBERED, never silently cut 213func sc_w(s: *u8) -> i64 { 214 let n: i64 = ps_slen(s) 215 if SC_LEN + n + SC_ROWB > SC_BUFB { SC_OVER = 1; return 0 } 216 let dst: *u8 = SC_BUF as *u8 217 var i: i64 = 0 218 while i < n { dst[SC_LEN + i] = s[i]; i = i + 1 } 219 SC_LEN = SC_LEN + n 220 return 0 221} 222func sc_wn(v: i64) -> i64 { 223 if SC_LEN + 24 + SC_ROWB > SC_BUFB { SC_OVER = 1; return 0 } 224 let dst: *u8 = SC_BUF as *u8 225 var m: i64 = v 226 if m < 0 { dst[SC_LEN] = 45 as u8; SC_LEN = SC_LEN + 1; m = 0 - m } 227 if m == 0 { dst[SC_LEN] = 48 as u8; SC_LEN = SC_LEN + 1; return 0 } 228 let t: *u8 = sys_mmap(32) 229 var k: i64 = 0 230 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 231 var i: i64 = 0 232 while i < k { dst[SC_LEN + i] = t[k - 1 - i]; i = i + 1 } 233 SC_LEN = SC_LEN + k 234 return 0 235} 236func sc_sp() -> i64 { return sc_w(" " as *u8) } 237func sc_nl() -> i64 { return sc_w("\n" as *u8) } 238func sc_kind(i: i64) -> i64 { let k: *i64 = SC_KIND as *i64; return k[i] } 239func sc_a(i: i64) -> i64 { let a: *i64 = SC_A as *i64; return a[i] } 240func sc_b(i: i64) -> i64 { let a: *i64 = SC_B as *i64; return a[i] } 241func sc_c(i: i64) -> i64 { let a: *i64 = SC_C as *i64; return a[i] } 242func sc_d(i: i64) -> i64 { let a: *i64 = SC_D as *i64; return a[i] } 243func sc_e(i: i64) -> i64 { let a: *i64 = SC_E as *i64; return a[i] } 244func sc_first_of(kind: i64) -> i64 { 245 var i: i64 = 0 246 while i < SC_NP { if sc_kind(i) == kind { return i } i = i + 1 } 247 return 0 - 1 248} 249func ps_scene_rows() -> *u8 { return SC_BUF as *u8 } 250func ps_scene_len() -> i64 { return SC_LEN } 251func ps_scene_np() -> i64 { return SC_NP } 252func ps_scene_nc() -> i64 { return PS_NC } 253func ps_scene_compose(b: *u8, n: i64) -> i64 { 254 sc_reset() 255 let tk: *i64 = sys_mmap(16*8) as *i64 256 var p: i64 = 0 257 var site: i64 = 0 - 1 258 while p < n { 259 var e: i64 = p 260 var go: i64 = 1 261 while go == 1 { 262 if e >= n { go = 0 } else { if (b[e] as i64) == 10 { go = 0 } else { e = e + 1 } } 263 } 264 var nt: i64 = ps_ntok(b, p, e, tk) 265 if nt > 0 { if (b[tk[0]] as i64) == 35 { nt = 0 } } 266 if nt > 0 { 267 var hd: i64 = 0 268 if ps_tokeq(b, tk[0], tk[1], "seed" as *u8) == 1 { 269 if nt < 2 { ps_err("SCENE-REFUSE seed row needs a number\n" as *u8); return 0 - SC_E_PARSE } 270 SC_SEED = ps_toknum(b, tk[2], tk[3]) 271 hd = 1 272 } 273 if hd == 0 { if ps_tokeq(b, tk[0], tk[1], "site" as *u8) == 1 { 274 if nt < 4 { ps_err("SCENE-REFUSE site row needs ex ey ez\n" as *u8); return 0 - SC_E_PARSE } 275 if site >= 0 { ps_err("SCENE-REFUSE a scene has exactly one site\n" as *u8); return 0 - SC_E_PARSE } 276 let sn: *u8 = "site" as *u8 277 site = sc_add(sn, 0, 4, SC_K_SITE, ps_toknum(b, tk[2], tk[3]), ps_toknum(b, tk[4], tk[5]), ps_toknum(b, tk[6], tk[7]), 0, 0) 278 if site < 0 { return site } 279 hd = 1 280 } } 281 if hd == 0 { if ps_tokeq(b, tk[0], tk[1], "water" as *u8) == 1 { 282 if nt < 6 { ps_err("SCENE-REFUSE water row needs name ex ey dx dy\n" as *u8); return 0 - SC_E_PARSE } 283 let r: i64 = sc_add(b, tk[2], tk[3], SC_K_WATER, ps_toknum(b, tk[4], tk[5]), ps_toknum(b, tk[6], tk[7]), SC_WATER_EZ, ps_toknum(b, tk[8], tk[9]), ps_toknum(b, tk[10], tk[11])) 284 if r < 0 { return r } 285 hd = 1 286 } } 287 if hd == 0 { if ps_tokeq(b, tk[0], tk[1], "room" as *u8) == 1 { 288 if nt < 5 { ps_err("SCENE-REFUSE room row needs name ex ey ez\n" as *u8); return 0 - SC_E_PARSE } 289 let r: i64 = sc_add(b, tk[2], tk[3], SC_K_ROOM, ps_toknum(b, tk[4], tk[5]), ps_toknum(b, tk[6], tk[7]), ps_toknum(b, tk[8], tk[9]), 0, 0) 290 if r < 0 { return r } 291 hd = 1 292 } } 293 if hd == 0 { if ps_tokeq(b, tk[0], tk[1], "tree" as *u8) == 1 { 294 if nt < 3 { ps_err("SCENE-REFUSE tree row needs name r\n" as *u8); return 0 - SC_E_PARSE } 295 let rr: i64 = ps_toknum(b, tk[4], tk[5]) 296 let r: i64 = sc_add(b, tk[2], tk[3], SC_K_TREE, rr, rr, rr, 0, 0) 297 if r < 0 { return r } 298 hd = 1 299 } } 300 if hd == 0 { if ps_tokeq(b, tk[0], tk[1], "being" as *u8) == 1 { 301 if nt < 3 { ps_err("SCENE-REFUSE being row needs name r\n" as *u8); return 0 - SC_E_PARSE } 302 let rr: i64 = ps_toknum(b, tk[4], tk[5]) 303 let r: i64 = sc_add(b, tk[2], tk[3], SC_K_BEING, rr, rr, rr, 0, 0) 304 if r < 0 { return r } 305 hd = 1 306 } } 307 if hd == 0 { if ps_tokeq(b, tk[0], tk[1], "near" as *u8) == 1 { 308 if nt < 5 { ps_err("SCENE-REFUSE near row needs a b lo hi\n" as *u8); return 0 - SC_E_PARSE } 309 let ia: i64 = sc_find(b, tk[2], tk[3]) 310 let ib: i64 = sc_find(b, tk[4], tk[5]) 311 if ia < 0 { ps_err("SCENE-REFUSE near names an undeclared part\n" as *u8); return 0 - SC_E_PARSE } 312 if ib < 0 { ps_err("SCENE-REFUSE near names an undeclared part\n" as *u8); return 0 - SC_E_PARSE } 313 if SC_NEARN >= SC_MAXNEAR { ps_err("SCENE-REFUSE near capacity\n" as *u8); return 0 - SC_E_CAP } 314 let na: *i64 = SC_NEAR_A as *i64 315 let nb: *i64 = SC_NEAR_B as *i64 316 let nlo: *i64 = SC_NEAR_LO as *i64 317 let nhi: *i64 = SC_NEAR_HI as *i64 318 na[SC_NEARN] = ia; nb[SC_NEARN] = ib; nlo[SC_NEARN] = ps_toknum(b, tk[6], tk[7]); nhi[SC_NEARN] = ps_toknum(b, tk[8], tk[9]) 319 SC_NEARN = SC_NEARN + 1 320 hd = 1 321 } } 322 if hd == 0 { ps_err("SCENE-REFUSE unknown scene row keyword\n" as *u8); return 0 - SC_E_PARSE } 323 } 324 p = e + 1 325 } 326 if site < 0 { ps_err("SCENE-REFUSE a scene needs a site row\n" as *u8); return 0 - SC_E_PARSE } 327 // the bound, announced: rows = parts*3 + pairs + rooms + beings + near, against the solver's PS_MAXC 328 let ns: i64 = SC_NP - 1 329 let pairs: i64 = ns*(ns-1)/2 330 var rooms: i64 = 0 331 var beings: i64 = 0 332 var i: i64 = 0 333 while i < SC_NP { if sc_kind(i) == SC_K_ROOM { rooms = rooms + 1 } if sc_kind(i) == SC_K_BEING { beings = beings + 1 } i = i + 1 } 334 let rows: i64 = SC_NP*3 + pairs + rooms + beings + SC_NEARN 335 if rows > PS_MAXC { ps_err("SCENE-REFUSE constraint capacity: the pairwise no-clip set exceeds PS_MAXC -- fewer solids, or a bigger solver\n" as *u8); return 0 - SC_E_CAP } 336 // emit: the site, then every part with its envelope, then the pairwise no-clip set, faces, bands 337 let sx: i64 = sc_a(site) 338 let sy: i64 = sc_b(site) 339 sc_w("part site 0 0 0 0\n" as *u8) 340 sc_w("extent site " as *u8); sc_wn(sx); sc_sp(); sc_wn(sy); sc_sp(); sc_wn(sc_c(site)); sc_nl() 341 i = 0 342 while i < SC_NP { 343 if i != site { 344 let k: i64 = sc_kind(i) 345 if k == SC_K_WATER { 346 sc_w("part " as *u8); sc_w(sc_name(i)); sc_sp(); sc_wn(sc_d(i)); sc_sp(); sc_wn(sc_e(i)); sc_w(" 0 0\n" as *u8) 347 sc_w("extent " as *u8); sc_w(sc_name(i)); sc_sp(); sc_wn(sc_a(i)); sc_sp(); sc_wn(sc_b(i)); sc_sp(); sc_wn(SC_WATER_EZ); sc_nl() 348 sc_w("attach " as *u8); sc_w(sc_name(i)); sc_w(" site " as *u8); sc_wn(sc_d(i)); sc_sp(); sc_wn(sc_e(i)); sc_w(" 0\n" as *u8) 349 } else { 350 let x0: i64 = sc_rand() % (2*sx + 1) - sx 351 let y0: i64 = sc_rand() % (2*sy + 1) - sy 352 var pr: i64 = 0 353 if k != SC_K_ROOM { pr = sc_a(i) } 354 sc_w("part " as *u8); sc_w(sc_name(i)); sc_sp(); sc_wn(x0); sc_sp(); sc_wn(y0); sc_w(" 0 " as *u8); sc_wn(pr); sc_nl() 355 sc_w("extent " as *u8); sc_w(sc_name(i)); sc_sp(); sc_wn(sc_a(i)); sc_sp(); sc_wn(sc_b(i)); sc_sp(); sc_wn(sc_c(i)); sc_nl() 356 sc_w("envelope " as *u8); sc_w(sc_name(i)); sc_w(" site\n" as *u8) 357 } 358 } 359 i = i + 1 360 } 361 i = 0 362 while i < SC_NP { 363 var j: i64 = i + 1 364 while j < SC_NP { 365 if i != site { if j != site { 366 var gap: i64 = SC_GAP 367 if sc_kind(i) == SC_K_WATER { gap = 0 } 368 if sc_kind(j) == SC_K_WATER { gap = 0 } 369 sc_w("noclip " as *u8); sc_w(sc_name(i)); sc_sp(); sc_w(sc_name(j)); sc_sp(); sc_wn(gap); sc_nl() 370 } } 371 j = j + 1 372 } 373 i = i + 1 374 } 375 let water: i64 = sc_first_of(SC_K_WATER) 376 let home: i64 = sc_first_of(SC_K_ROOM) 377 i = 0 378 while i < SC_NP { 379 if sc_kind(i) == SC_K_ROOM { if water >= 0 { 380 sc_w("face " as *u8); sc_w(sc_name(i)); sc_sp(); sc_w(sc_name(water)); sc_nl() 381 } } 382 if sc_kind(i) == SC_K_BEING { if home >= 0 { 383 var lo: i64 = sc_a(home) 384 if sc_b(home) > lo { lo = sc_b(home) } 385 lo = lo + sc_a(i) + SC_GAP 386 sc_w("band " as *u8); sc_w(sc_name(i)); sc_sp(); sc_w(sc_name(home)); sc_sp(); sc_wn(lo); sc_sp(); sc_wn(lo + SC_NEAR); sc_nl() 387 } } 388 i = i + 1 389 } 390 let na: *i64 = SC_NEAR_A as *i64 391 let nb: *i64 = SC_NEAR_B as *i64 392 let nlo: *i64 = SC_NEAR_LO as *i64 393 let nhi: *i64 = SC_NEAR_HI as *i64 394 i = 0 395 while i < SC_NEARN { 396 sc_w("band " as *u8); sc_w(sc_name(na[i])); sc_sp(); sc_w(sc_name(nb[i])); sc_sp(); sc_wn(nlo[i]); sc_sp(); sc_wn(nhi[i]); sc_nl() 397 i = i + 1 398 } 399 if SC_OVER == 1 { ps_err("SCENE-REFUSE composed rows exceed the buffer\n" as *u8); return 0 - SC_E_CAP } 400 let rc: i64 = ps_load(SC_BUF as *u8, SC_LEN) 401 if rc != 0 { return rc } 402 return ps_solve() 403} 404 405func ps_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 406func ps_err(s: *u8) -> i64 { sys_write(PS_ERRFD, s, ps_slen(s)); return 0 } 407func ps_out(s: *u8) -> i64 { sys_write(1, s, ps_slen(s)); return 0 } 408func ps_outn(v: i64) -> i64 { 409 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 410 var m: i64 = v 411 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 412 let t: *u8 = sys_mmap(32) 413 var k: i64 = 0 414 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 415 let o: *u8 = sys_mmap(32) 416 var i: i64 = 0 417 while i < k { o[i] = t[k - 1 - i]; i = i + 1 } 418 sys_write(1, o, k) 419 return 0 420} 421// RETIRED ONTO THE SHARED OWNER 2026-08-24: the eighth private copy of the same Newton floor-sqrt found in one 422// session; vm_isqrt is gate-proven exact over 20,000 inputs and the alias keeps every call site untouched. 423func ps_isqrt(v: i64) -> i64 { return vm_isqrt(v) } 424func ps_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 425 426// ---- cardinal snap: an integer direction vector -> one of (1,0) (0,1) (-1,0) (0,-1). EXACT, no trig. ---- 427func ps_cardx(rx: i64, ry: i64) -> i64 { 428 if ps_abs(rx) >= ps_abs(ry) { if rx < 0 { return 0 - 1 } return 1 } 429 return 0 430} 431func ps_cardy(rx: i64, ry: i64) -> i64 { 432 if ps_abs(rx) >= ps_abs(ry) { return 0 } 433 if ry < 0 { return 0 - 1 } 434 return 1 435} 436 437func ps_reset() -> i64 { 438 if PS_PN == 0 { 439 PS_PN = sys_mmap(PS_MAXP*PS_NAMEB) as i64 440 PS_PX = sys_mmap(PS_MAXP*8) as i64 441 PS_PY = sys_mmap(PS_MAXP*8) as i64 442 PS_PZ = sys_mmap(PS_MAXP*8) as i64 443 PS_PR = sys_mmap(PS_MAXP*8) as i64 444 PS_EX = sys_mmap(PS_MAXP*8) as i64 445 PS_EY = sys_mmap(PS_MAXP*8) as i64 446 PS_EZ = sys_mmap(PS_MAXP*8) as i64 447 PS_FX = sys_mmap(PS_MAXP*8) as i64 448 PS_FY = sys_mmap(PS_MAXP*8) as i64 449 PS_CK = sys_mmap(PS_MAXC*8) as i64 450 PS_CA = sys_mmap(PS_MAXC*8) as i64 451 PS_CB = sys_mmap(PS_MAXC*8) as i64 452 PS_C1 = sys_mmap(PS_MAXC*8) as i64 453 PS_C2 = sys_mmap(PS_MAXC*8) as i64 454 PS_C3 = sys_mmap(PS_MAXC*8) as i64 455 PS_CC = sys_mmap(PS_MAXC*8) as i64 456 PS_CD = sys_mmap(PS_MAXC*8) as i64 457 } 458 let pn: *u8 = PS_PN as *u8 459 var i: i64 = 0 460 while i < PS_MAXP*PS_NAMEB { pn[i] = 0 as u8; i = i + 1 } 461 PS_NP = 0 462 PS_NC = 0 463 PS_WORST = 0 - 1 464 PS_WORSTV = 0 465 PS_RESID = 0 466 return 0 467} 468func ps_np() -> i64 { return PS_NP } 469func ps_x(i: i64) -> i64 { let a: *i64 = PS_PX as *i64; return a[i] } 470func ps_y(i: i64) -> i64 { let a: *i64 = PS_PY as *i64; return a[i] } 471func ps_z(i: i64) -> i64 { let a: *i64 = PS_PZ as *i64; return a[i] } 472func ps_r(i: i64) -> i64 { let a: *i64 = PS_PR as *i64; return a[i] } 473func ps_ex(i: i64) -> i64 { let a: *i64 = PS_EX as *i64; return a[i] } 474func ps_ey(i: i64) -> i64 { let a: *i64 = PS_EY as *i64; return a[i] } 475func ps_ez(i: i64) -> i64 { let a: *i64 = PS_EZ as *i64; return a[i] } 476func ps_fx(i: i64) -> i64 { let a: *i64 = PS_FX as *i64; return a[i] } 477func ps_fy(i: i64) -> i64 { let a: *i64 = PS_FY as *i64; return a[i] } 478func ps_name(i: i64) -> *u8 { return ((PS_PN as i64) + i*PS_NAMEB) as *u8 } 479// EFFECTIVE half-extents: turning a part 90 degrees swaps its ground footprint. This is the whole point 480// of orientation being first-class -- a facing that did not change the footprint would be decoration. 481func ps_exx(i: i64) -> i64 { let f: *i64 = PS_FY as *i64; if f[i] != 0 { return ps_ey(i) } return ps_ex(i) } 482func ps_eyy(i: i64) -> i64 { let f: *i64 = PS_FY as *i64; if f[i] != 0 { return ps_ex(i) } return ps_ey(i) } 483func ps_worst() -> i64 { return PS_WORST } 484func ps_worstv() -> i64 { return PS_WORSTV } 485func ps_resid() -> i64 { return PS_RESID } 486func ps_kindname(k: i64) -> *u8 { 487 if k == PS_K_ATTACH { return "attach" as *u8 } 488 if k == PS_K_MIRROR { return "mirror" as *u8 } 489 if k == PS_K_STATION { return "station" as *u8 } 490 if k == PS_K_CLEAR { return "clear" as *u8 } 491 if k == PS_K_INSIDE { return "inside" as *u8 } 492 if k == PS_K_RATIO { return "ratio" as *u8 } 493 if k == PS_K_BAND { return "band" as *u8 } 494 if k == PS_K_NOCLIP { return "noclip" as *u8 } 495 if k == PS_K_ENVELOPE { return "envelope" as *u8 } 496 if k == PS_K_FACE { return "face" as *u8 } 497 return "unknown" as *u8 498} 499func ps_find(name: *u8) -> i64 { 500 var i: i64 = 0 501 while i < PS_NP { 502 let pn: *u8 = ps_name(i) 503 var j: i64 = 0 504 var eq: i64 = 0 - 1 505 while eq < 0 { 506 let ca: i64 = pn[j] as i64 507 let cb: i64 = name[j] as i64 508 if ca != cb { eq = 0 } else { if ca == 0 { eq = 1 } else { j = j + 1 } } 509 } 510 if eq == 1 { return i } 511 i = i + 1 512 } 513 return 0 - 1 514} 515 516// ---- tokenizer: tokens in [s,e), out pairs (start,len) up to 8; returns count ---- 517func ps_ntok(b: *u8, s: i64, e: i64, out: *i64) -> i64 { 518 var n: i64 = 0 519 var p: i64 = s 520 while p < e { 521 var c: i64 = b[p] as i64 522 var issp: i64 = 0 523 if c == 32 { issp = 1 } 524 if c == 9 { issp = 1 } 525 if c == 13 { issp = 1 } 526 if issp == 1 { p = p + 1 } else { 527 let ts: i64 = p 528 var go: i64 = 1 529 while go == 1 { 530 if p >= e { go = 0 } else { 531 let c2: i64 = b[p] as i64 532 var sp2: i64 = 0 533 if c2 == 32 { sp2 = 1 } 534 if c2 == 9 { sp2 = 1 } 535 if c2 == 13 { sp2 = 1 } 536 if sp2 == 1 { go = 0 } else { p = p + 1 } 537 } 538 } 539 if n < 8 { out[n*2] = ts; out[n*2+1] = p - ts; n = n + 1 } 540 } 541 } 542 return n 543} 544func ps_tokeq(b: *u8, ts: i64, tl: i64, kw: *u8) -> i64 { 545 if ps_slen(kw) != tl { return 0 } 546 var i: i64 = 0 547 while i < tl { if (b[ts+i] as i64) != (kw[i] as i64) { return 0 } i = i + 1 } 548 return 1 549} 550func ps_toknum(b: *u8, ts: i64, tl: i64) -> i64 { 551 var v: i64 = 0 552 var i: i64 = 0 553 var ng: i64 = 0 554 if tl > 0 { if (b[ts] as i64) == 45 { ng = 1; i = 1 } } 555 while i < tl { 556 let c: i64 = b[ts+i] as i64 557 if c >= 48 { if c <= 57 { v = v*10 + (c - 48) } } 558 i = i + 1 559 } 560 if ng == 1 { return 0 - v } 561 return v 562} 563func ps_tokpart(b: *u8, ts: i64, tl: i64) -> i64 { 564 var i: i64 = 0 565 while i < PS_NP { 566 let pn: *u8 = ps_name(i) 567 if ps_slen(pn) == tl { 568 var j: i64 = 0 569 var ok: i64 = 1 570 while j < tl { if (pn[j] as i64) != (b[ts+j] as i64) { ok = 0; j = tl } else { j = j + 1 } } 571 if ok == 1 { return i } 572 } 573 i = i + 1 574 } 575 return 0 - 1 576} 577 578// parse the constraint file. 0 ok, negative = named parse refusal (printed with the line number). 579func ps_load(b: *u8, n: i64) -> i64 { 580 ps_reset() 581 let tk: *i64 = sys_mmap(16*8) as *i64 582 let px: *i64 = PS_PX as *i64 583 let py: *i64 = PS_PY as *i64 584 let pz: *i64 = PS_PZ as *i64 585 let prr: *i64 = PS_PR as *i64 586 let pex: *i64 = PS_EX as *i64 587 let pey: *i64 = PS_EY as *i64 588 let pez: *i64 = PS_EZ as *i64 589 let pfx: *i64 = PS_FX as *i64 590 let pfy: *i64 = PS_FY as *i64 591 let ck: *i64 = PS_CK as *i64 592 let ca: *i64 = PS_CA as *i64 593 let cb: *i64 = PS_CB as *i64 594 let c1: *i64 = PS_C1 as *i64 595 let c2: *i64 = PS_C2 as *i64 596 let c3: *i64 = PS_C3 as *i64 597 let ccv: *i64 = PS_CC as *i64 598 let cdv: *i64 = PS_CD as *i64 599 var p: i64 = 0 600 var ln: i64 = 0 601 while p < n { 602 ln = ln + 1 603 var e: i64 = p 604 var go: i64 = 1 605 while go == 1 { 606 if e >= n { go = 0 } else { if (b[e] as i64) == 10 { go = 0 } else { e = e + 1 } } 607 } 608 var nt: i64 = ps_ntok(b, p, e, tk) 609 if nt > 0 { if (b[tk[0]] as i64) == 35 { nt = 0 } } 610 if nt > 0 { 611 var hd: i64 = 0 612 if ps_tokeq(b, tk[0], tk[1], "part" as *u8) == 1 { 613 if nt < 6 { ps_err("PART-SOLVER-REFUSE part row needs name x y z r\n" as *u8); return 0 - PS_E_PARSE } 614 if PS_NP >= PS_MAXP { ps_err("PART-SOLVER-REFUSE part capacity\n" as *u8); return 0 - PS_E_PARSE } 615 let dst: *u8 = ps_name(PS_NP) 616 var cl: i64 = tk[3] 617 if cl > PS_NAMEB - 1 { cl = PS_NAMEB - 1 } 618 var q: i64 = 0 619 while q < cl { dst[q] = b[tk[2]+q]; q = q + 1 } 620 dst[cl] = 0 as u8 621 px[PS_NP] = ps_toknum(b, tk[4], tk[5]) 622 py[PS_NP] = ps_toknum(b, tk[6], tk[7]) 623 pz[PS_NP] = ps_toknum(b, tk[8], tk[9]) 624 prr[PS_NP] = ps_toknum(b, tk[10], tk[11]) 625 // a part is a POINT facing +x until an extent/facing row says otherwise -- so every 626 // constraint file written before rung 2 loads and solves byte-identically. 627 pex[PS_NP] = 0 628 pey[PS_NP] = 0 629 pez[PS_NP] = 0 630 pfx[PS_NP] = 1 631 pfy[PS_NP] = 0 632 PS_NP = PS_NP + 1 633 hd = 1 634 } 635 if hd == 0 { if ps_tokeq(b, tk[0], tk[1], "extent" as *u8) == 1 { 636 if nt < 5 { ps_err("PART-SOLVER-REFUSE extent row needs part ex ey ez\n" as *u8); return 0 - PS_E_PARSE } 637 let ie: i64 = ps_tokpart(b, tk[2], tk[3]) 638 if ie < 0 { ps_err("PART-SOLVER-REFUSE extent names an undeclared part\n" as *u8); return 0 - PS_E_PARSE } 639 let vex: i64 = ps_toknum(b, tk[4], tk[5]) 640 let vey: i64 = ps_toknum(b, tk[6], tk[7]) 641 let vez: i64 = ps_toknum(b, tk[8], tk[9]) 642 if vex < 0 { ps_err("PART-SOLVER-REFUSE extent half-sizes must not be negative\n" as *u8); return 0 - PS_E_PARSE } 643 if vey < 0 { ps_err("PART-SOLVER-REFUSE extent half-sizes must not be negative\n" as *u8); return 0 - PS_E_PARSE } 644 if vez < 0 { ps_err("PART-SOLVER-REFUSE extent half-sizes must not be negative\n" as *u8); return 0 - PS_E_PARSE } 645 pex[ie] = vex 646 pey[ie] = vey 647 pez[ie] = vez 648 hd = 1 649 } } 650 if hd == 0 { if ps_tokeq(b, tk[0], tk[1], "facing" as *u8) == 1 { 651 if nt < 4 { ps_err("PART-SOLVER-REFUSE facing row needs part fx fy\n" as *u8); return 0 - PS_E_PARSE } 652 let ifc: i64 = ps_tokpart(b, tk[2], tk[3]) 653 if ifc < 0 { ps_err("PART-SOLVER-REFUSE facing names an undeclared part\n" as *u8); return 0 - PS_E_PARSE } 654 let rfx: i64 = ps_toknum(b, tk[4], tk[5]) 655 let rfy: i64 = ps_toknum(b, tk[6], tk[7]) 656 if rfx == 0 { if rfy == 0 { ps_err("PART-SOLVER-REFUSE facing vector is zero -- a direction of nothing is not a direction\n" as *u8); return 0 - PS_E_PARSE } } 657 pfx[ifc] = ps_cardx(rfx, rfy) 658 pfy[ifc] = ps_cardy(rfx, rfy) 659 hd = 1 660 } } 661 if hd == 0 { 662 var kind: i64 = 0 663 if ps_tokeq(b, tk[0], tk[1], "attach" as *u8) == 1 { kind = PS_K_ATTACH } 664 if ps_tokeq(b, tk[0], tk[1], "mirror" as *u8) == 1 { kind = PS_K_MIRROR } 665 if ps_tokeq(b, tk[0], tk[1], "station" as *u8) == 1 { kind = PS_K_STATION } 666 if ps_tokeq(b, tk[0], tk[1], "clear" as *u8) == 1 { kind = PS_K_CLEAR } 667 if ps_tokeq(b, tk[0], tk[1], "inside" as *u8) == 1 { kind = PS_K_INSIDE } 668 if ps_tokeq(b, tk[0], tk[1], "ratio" as *u8) == 1 { kind = PS_K_RATIO } 669 if ps_tokeq(b, tk[0], tk[1], "band" as *u8) == 1 { kind = PS_K_BAND } 670 if ps_tokeq(b, tk[0], tk[1], "noclip" as *u8) == 1 { kind = PS_K_NOCLIP } 671 if ps_tokeq(b, tk[0], tk[1], "envelope" as *u8) == 1 { kind = PS_K_ENVELOPE } 672 if ps_tokeq(b, tk[0], tk[1], "face" as *u8) == 1 { kind = PS_K_FACE } 673 if kind == 0 { ps_err("PART-SOLVER-REFUSE unknown row keyword\n" as *u8); return 0 - PS_E_PARSE } 674 if PS_NC >= PS_MAXC { ps_err("PART-SOLVER-REFUSE constraint capacity\n" as *u8); return 0 - PS_E_PARSE } 675 let ia: i64 = ps_tokpart(b, tk[2], tk[3]) 676 if ia < 0 { ps_err("PART-SOLVER-REFUSE constraint names an undeclared part\n" as *u8); return 0 - PS_E_PARSE } 677 var ib: i64 = 0 - 1 678 var ic: i64 = 0 - 1 679 var id2: i64 = 0 - 1 680 var v1: i64 = 0 681 var v2: i64 = 0 682 var v3: i64 = 0 683 if kind == PS_K_STATION { 684 if nt < 4 { ps_err("PART-SOLVER-REFUSE station row needs part lo hi\n" as *u8); return 0 - PS_E_PARSE } 685 v1 = ps_toknum(b, tk[4], tk[5]) 686 v2 = ps_toknum(b, tk[6], tk[7]) 687 } else { 688 if nt < 3 { ps_err("PART-SOLVER-REFUSE row needs two parts\n" as *u8); return 0 - PS_E_PARSE } 689 ib = ps_tokpart(b, tk[4], tk[5]) 690 if ib < 0 { ps_err("PART-SOLVER-REFUSE constraint names an undeclared part\n" as *u8); return 0 - PS_E_PARSE } 691 if kind == PS_K_ATTACH { 692 if nt < 6 { ps_err("PART-SOLVER-REFUSE attach row needs a b dx dy dz\n" as *u8); return 0 - PS_E_PARSE } 693 v1 = ps_toknum(b, tk[6], tk[7]) 694 v2 = ps_toknum(b, tk[8], tk[9]) 695 v3 = ps_toknum(b, tk[10], tk[11]) 696 } 697 if kind == PS_K_CLEAR { if nt < 4 { ps_err("PART-SOLVER-REFUSE clear row needs a b mind\n" as *u8); return 0 - PS_E_PARSE } } 698 if kind == PS_K_CLEAR { v1 = ps_toknum(b, tk[6], tk[7]) } 699 if kind == PS_K_INSIDE { if nt < 4 { ps_err("PART-SOLVER-REFUSE inside row needs a b maxd\n" as *u8); return 0 - PS_E_PARSE } } 700 if kind == PS_K_INSIDE { v1 = ps_toknum(b, tk[6], tk[7]) } 701 // noclip's gap is OPTIONAL and defaults to 0 (touching boxes are separated). A gap is 702 // the door-swing / fire-lane / pavement -- clearance is DATA, never a hidden constant. 703 if kind == PS_K_NOCLIP { if nt >= 4 { v1 = ps_toknum(b, tk[6], tk[7]) } } 704 if kind == PS_K_NOCLIP { if v1 < 0 { ps_err("PART-SOLVER-REFUSE noclip gap must not be negative\n" as *u8); return 0 - PS_E_PARSE } } 705 if kind == PS_K_BAND { 706 if nt < 5 { ps_err("PART-SOLVER-REFUSE band row needs a b lo hi\n" as *u8); return 0 - PS_E_PARSE } 707 v1 = ps_toknum(b, tk[6], tk[7]) 708 v2 = ps_toknum(b, tk[8], tk[9]) 709 } 710 if kind == PS_K_RATIO { 711 // ratio a b c d num den [tol_permil]: dist(a,b) : dist(c,d) = num:den within tol 712 // (c,d) is the REFERENCE pair -- only (a,b) is adjusted. Deterministic by construction. 713 if nt < 7 { ps_err("PART-SOLVER-REFUSE ratio row needs a b c d num den [tol]\n" as *u8); return 0 - PS_E_PARSE } 714 ic = ps_tokpart(b, tk[6], tk[7]) 715 id2 = ps_tokpart(b, tk[8], tk[9]) 716 if ic < 0 { ps_err("PART-SOLVER-REFUSE constraint names an undeclared part\n" as *u8); return 0 - PS_E_PARSE } 717 if id2 < 0 { ps_err("PART-SOLVER-REFUSE constraint names an undeclared part\n" as *u8); return 0 - PS_E_PARSE } 718 v1 = ps_toknum(b, tk[10], tk[11]) 719 v2 = ps_toknum(b, tk[12], tk[13]) 720 v3 = PS_RATIO_DEFTOL 721 if nt >= 8 { v3 = ps_toknum(b, tk[14], tk[15]) } 722 if v1 < 1 { ps_err("PART-SOLVER-REFUSE ratio num must be positive\n" as *u8); return 0 - PS_E_PARSE } 723 if v2 < 1 { ps_err("PART-SOLVER-REFUSE ratio den must be positive\n" as *u8); return 0 - PS_E_PARSE } 724 } 725 } 726 ck[PS_NC] = kind 727 ca[PS_NC] = ia 728 cb[PS_NC] = ib 729 ccv[PS_NC] = ic 730 cdv[PS_NC] = id2 731 c1[PS_NC] = v1 732 c2[PS_NC] = v2 733 c3[PS_NC] = v3 734 PS_NC = PS_NC + 1 735 } 736 } 737 p = e + 1 738 } 739 if PS_NP < 1 { ps_err("PART-SOLVER-REFUSE no parts declared\n" as *u8); return 0 - PS_E_PARSE } 740 return 0 741} 742 743// do the z intervals of two parts overlap? Parts on different storeys never collide in the ground plane. 744// Non-strict on purpose: two zero-height parts on the same floor DO share their plane. 745func ps_zoverlap(a: i64, b2: i64) -> i64 { 746 let pz: *i64 = PS_PZ as *i64 747 if ps_abs(pz[a] - pz[b2]) > ps_ez(a) + ps_ez(b2) { return 0 } 748 return 1 749} 750 751func ps_apply(ci: i64) -> i64 { 752 let px: *i64 = PS_PX as *i64 753 let py: *i64 = PS_PY as *i64 754 let pz: *i64 = PS_PZ as *i64 755 let pfx: *i64 = PS_FX as *i64 756 let pfy: *i64 = PS_FY as *i64 757 let ck: *i64 = PS_CK as *i64 758 let cav: *i64 = PS_CA as *i64 759 let cbv: *i64 = PS_CB as *i64 760 let c1: *i64 = PS_C1 as *i64 761 let c2: *i64 = PS_C2 as *i64 762 let c3: *i64 = PS_C3 as *i64 763 let k: i64 = ck[ci] 764 let a: i64 = cav[ci] 765 let b2: i64 = cbv[ci] 766 if k == PS_K_ATTACH { 767 px[a] = px[b2] + c1[ci] 768 py[a] = py[b2] + c2[ci] 769 pz[a] = pz[b2] + c3[ci] 770 } 771 if k == PS_K_MIRROR { 772 let sx: i64 = px[a] + px[b2] 773 px[a] = px[a] - sx/2 774 px[b2] = px[b2] - (sx - sx/2) 775 let my: i64 = (py[a] + py[b2])/2 776 py[a] = my 777 py[b2] = my 778 let mz: i64 = (pz[a] + pz[b2])/2 779 pz[a] = mz 780 pz[b2] = mz 781 } 782 if k == PS_K_STATION { 783 if pz[a] < c1[ci] { pz[a] = c1[ci] } 784 if pz[a] > c2[ci] { pz[a] = c2[ci] } 785 } 786 if k == PS_K_CLEAR { 787 let dx: i64 = px[a] - px[b2] 788 let dy: i64 = py[a] - py[b2] 789 let dz: i64 = pz[a] - pz[b2] 790 let d: i64 = ps_isqrt(dx*dx + dy*dy + dz*dz) 791 if d == 0 { 792 px[a] = px[a] + c1[ci]/2 + 1 793 px[b2] = px[b2] - c1[ci]/2 - 1 794 } else { if d < c1[ci] { 795 let push: i64 = c1[ci] - d 796 var mx: i64 = dx*push/(2*d) 797 var my2: i64 = dy*push/(2*d) 798 var mz2: i64 = dz*push/(2*d) 799 if mx == 0 { if my2 == 0 { if mz2 == 0 { mx = 1 } } } 800 px[a] = px[a] + mx 801 py[a] = py[a] + my2 802 pz[a] = pz[a] + mz2 803 px[b2] = px[b2] - mx 804 py[b2] = py[b2] - my2 805 pz[b2] = pz[b2] - mz2 806 } } 807 } 808 if k == PS_K_INSIDE { 809 let dx2: i64 = px[a] - px[b2] 810 let dy3: i64 = py[a] - py[b2] 811 let dz3: i64 = pz[a] - pz[b2] 812 let d2: i64 = ps_isqrt(dx2*dx2 + dy3*dy3 + dz3*dz3) 813 if d2 > c1[ci] { if d2 > 0 { 814 px[a] = px[b2] + dx2*c1[ci]/d2 815 py[a] = py[b2] + dy3*c1[ci]/d2 816 pz[a] = pz[b2] + dz3*c1[ci]/d2 817 } } 818 } 819 if k == PS_K_BAND { 820 let bdx: i64 = px[a] - px[b2] 821 let bdy: i64 = py[a] - py[b2] 822 let bdz: i64 = pz[a] - pz[b2] 823 let bd: i64 = ps_isqrt(bdx*bdx + bdy*bdy + bdz*bdz) 824 var tgt: i64 = 0 - 1 825 if bd < c1[ci] { tgt = c1[ci] } 826 if bd > c2[ci] { tgt = c2[ci] } 827 if tgt >= 0 { 828 if bd == 0 { 829 px[a] = px[a] + tgt/2 + 1 830 px[b2] = px[b2] - tgt/2 - 1 831 } else { 832 let dif: i64 = tgt - bd 833 var mx: i64 = bdx*dif/(2*bd) 834 var my3: i64 = bdy*dif/(2*bd) 835 var mz3: i64 = bdz*dif/(2*bd) 836 if mx == 0 { if my3 == 0 { if mz3 == 0 { if dif > 0 { mx = 1 } else { mx = 0 - 1 } } } } 837 px[a] = px[a] + mx 838 py[a] = py[a] + my3 839 pz[a] = pz[a] + mz3 840 px[b2] = px[b2] - mx 841 py[b2] = py[b2] - my3 842 pz[b2] = pz[b2] - mz3 843 } 844 } 845 } 846 if k == PS_K_RATIO { 847 let ccv2: *i64 = PS_CC as *i64 848 let cdv2: *i64 = PS_CD as *i64 849 let c: i64 = ccv2[ci] 850 let d4: i64 = cdv2[ci] 851 let rdx: i64 = px[c] - px[d4] 852 let rdy: i64 = py[c] - py[d4] 853 let rdz: i64 = pz[c] - pz[d4] 854 let dref: i64 = ps_isqrt(rdx*rdx + rdy*rdy + rdz*rdz) 855 let tgt2: i64 = dref*c1[ci]/c2[ci] 856 let adx: i64 = px[a] - px[b2] 857 let ady: i64 = py[a] - py[b2] 858 let adz: i64 = pz[a] - pz[b2] 859 let dab: i64 = ps_isqrt(adx*adx + ady*ady + adz*adz) 860 let slack: i64 = tgt2*c3[ci]/1000 + PS_TOL 861 var dif2: i64 = tgt2 - dab 862 var adif: i64 = dif2 863 if adif < 0 { adif = 0 - adif } 864 if adif > slack { 865 if dab == 0 { 866 px[a] = px[a] + tgt2/2 + 1 867 px[b2] = px[b2] - tgt2/2 - 1 868 } else { 869 var mx2: i64 = adx*dif2/(2*dab) 870 var my4: i64 = ady*dif2/(2*dab) 871 var mz4: i64 = adz*dif2/(2*dab) 872 if mx2 == 0 { if my4 == 0 { if mz4 == 0 { if dif2 > 0 { mx2 = 1 } else { mx2 = 0 - 1 } } } } 873 px[a] = px[a] + mx2 874 py[a] = py[a] + my4 875 pz[a] = pz[a] + mz4 876 px[b2] = px[b2] - mx2 877 py[b2] = py[b2] - my4 878 pz[b2] = pz[b2] - mz4 879 } 880 } 881 } 882 // ★NOCLIP: separate two oriented FOOTPRINTS along the cheapest escape axis. Exempt when the z 883 // intervals miss, so a first-floor flat never pushes a ground-floor shop sideways. 884 if k == PS_K_NOCLIP { 885 if ps_zoverlap(a, b2) == 1 { 886 let ndx: i64 = px[a] - px[b2] 887 let ndy: i64 = py[a] - py[b2] 888 let ox: i64 = (ps_exx(a) + ps_exx(b2) + c1[ci]) - ps_abs(ndx) 889 let oy: i64 = (ps_eyy(a) + ps_eyy(b2) + c1[ci]) - ps_abs(ndy) 890 if ox > 0 { if oy > 0 { 891 if ox <= oy { 892 let mvx: i64 = (ox + 1)/2 893 var sgx: i64 = 1 894 if ndx < 0 { sgx = 0 - 1 } 895 px[a] = px[a] + sgx*mvx 896 px[b2] = px[b2] - sgx*mvx 897 } else { 898 let mvy: i64 = (oy + 1)/2 899 var sgy: i64 = 1 900 if ndy < 0 { sgy = 0 - 1 } 901 py[a] = py[a] + sgy*mvy 902 py[b2] = py[b2] - sgy*mvy 903 } 904 } } 905 } 906 } 907 // ★ENVELOPE: slide a until its box is wholly inside b's box. ONLY a moves -- the room shell / block 908 // boundary is an INPUT (the same lesson the cottage taught: the envelope is given, the contents solved). 909 if k == PS_K_ENVELOPE { 910 let rax: i64 = ps_exx(a) 911 let ray: i64 = ps_eyy(a) 912 let raz: i64 = ps_ez(a) 913 let rbx: i64 = ps_exx(b2) 914 let rby: i64 = ps_eyy(b2) 915 let rbz: i64 = ps_ez(b2) 916 let slx: i64 = rbx - rax 917 if slx < 0 { px[a] = px[b2] } else { 918 if px[a] < px[b2] - slx { px[a] = px[b2] - slx } 919 if px[a] > px[b2] + slx { px[a] = px[b2] + slx } 920 } 921 let sly: i64 = rby - ray 922 if sly < 0 { py[a] = py[b2] } else { 923 if py[a] < py[b2] - sly { py[a] = py[b2] - sly } 924 if py[a] > py[b2] + sly { py[a] = py[b2] + sly } 925 } 926 let slz: i64 = rbz - raz 927 if slz < 0 { pz[a] = pz[b2] } else { 928 if pz[a] < pz[b2] - slz { pz[a] = pz[b2] - slz } 929 if pz[a] > pz[b2] + slz { pz[a] = pz[b2] + slz } 930 } 931 } 932 // ★FACE: orientation SOLVED from geometry. Turning a part swaps its footprint, so this row is 933 // load-bearing on noclip and envelope -- not a label. 934 if k == PS_K_FACE { 935 let fdx: i64 = px[b2] - px[a] 936 let fdy: i64 = py[b2] - py[a] 937 var any: i64 = 0 938 if fdx != 0 { any = 1 } 939 if fdy != 0 { any = 1 } 940 if any == 1 { 941 pfx[a] = ps_cardx(fdx, fdy) 942 pfy[a] = ps_cardy(fdx, fdy) 943 } 944 } 945 return 0 946} 947 948// one constraint's measured violation (0 = satisfied) 949func ps_violation(ci: i64) -> i64 { 950 let px: *i64 = PS_PX as *i64 951 let py: *i64 = PS_PY as *i64 952 let pz: *i64 = PS_PZ as *i64 953 let ck: *i64 = PS_CK as *i64 954 let cav: *i64 = PS_CA as *i64 955 let cbv: *i64 = PS_CB as *i64 956 let c1: *i64 = PS_C1 as *i64 957 let c2: *i64 = PS_C2 as *i64 958 let c3: *i64 = PS_C3 as *i64 959 let k: i64 = ck[ci] 960 let a: i64 = cav[ci] 961 let b2: i64 = cbv[ci] 962 if k == PS_K_ATTACH { 963 return ps_abs(px[a] - (px[b2] + c1[ci])) + ps_abs(py[a] - (py[b2] + c2[ci])) + ps_abs(pz[a] - (pz[b2] + c3[ci])) 964 } 965 if k == PS_K_MIRROR { 966 return ps_abs(px[a] + px[b2]) + ps_abs(py[a] - py[b2]) + ps_abs(pz[a] - pz[b2]) 967 } 968 if k == PS_K_STATION { 969 if pz[a] < c1[ci] { return c1[ci] - pz[a] } 970 if pz[a] > c2[ci] { return pz[a] - c2[ci] } 971 return 0 972 } 973 // penetration depth of two footprints = the SHALLOWEST escape. 0 when separated on any ground axis 974 // or when the z intervals miss. 975 if k == PS_K_NOCLIP { 976 if ps_zoverlap(a, b2) == 0 { return 0 } 977 let vox: i64 = (ps_exx(a) + ps_exx(b2) + c1[ci]) - ps_abs(px[a] - px[b2]) 978 let voy: i64 = (ps_eyy(a) + ps_eyy(b2) + c1[ci]) - ps_abs(py[a] - py[b2]) 979 if vox <= 0 { return 0 } 980 if voy <= 0 { return 0 } 981 if vox <= voy { return vox } 982 return voy 983 } 984 // total overflow of a's box beyond b's faces. 0 when wholly contained. 985 if k == PS_K_ENVELOPE { 986 var ov: i64 = 0 987 let qx: i64 = ps_abs(px[a] - px[b2]) + ps_exx(a) - ps_exx(b2) 988 if qx > 0 { ov = ov + qx } 989 let qy: i64 = ps_abs(py[a] - py[b2]) + ps_eyy(a) - ps_eyy(b2) 990 if qy > 0 { ov = ov + qy } 991 let qz: i64 = ps_abs(pz[a] - pz[b2]) + ps_ez(a) - ps_ez(b2) 992 if qz > 0 { ov = ov + qz } 993 return ov 994 } 995 // orientation error, in milli-cosine, against the BEST cardinal facing toward b: exactly 0 when the 996 // part already points the right way, strictly positive otherwise. Never a fabricated near-miss. 997 if k == PS_K_FACE { 998 let gdx: i64 = px[b2] - px[a] 999 let gdy: i64 = py[b2] - py[a] 1000 let gd: i64 = ps_isqrt(gdx*gdx + gdy*gdy) 1001 if gd == 0 { return 0 } 1002 let bfx: i64 = ps_cardx(gdx, gdy) 1003 let bfy: i64 = ps_cardy(gdx, gdy) 1004 let best: i64 = (bfx*gdx + bfy*gdy)*1000/gd 1005 let have: i64 = (ps_fx(a)*gdx + ps_fy(a)*gdy)*1000/gd 1006 if have >= best { return 0 } 1007 return best - have 1008 } 1009 let dx: i64 = px[a] - px[b2] 1010 let dy: i64 = py[a] - py[b2] 1011 let dz: i64 = pz[a] - pz[b2] 1012 let d: i64 = ps_isqrt(dx*dx + dy*dy + dz*dz) 1013 if k == PS_K_CLEAR { if d < c1[ci] { return c1[ci] - d } return 0 } 1014 if k == PS_K_INSIDE { if d > c1[ci] { return d - c1[ci] } return 0 } 1015 if k == PS_K_BAND { 1016 if d < c1[ci] { return c1[ci] - d } 1017 if d > c2[ci] { return d - c2[ci] } 1018 return 0 1019 } 1020 if k == PS_K_RATIO { 1021 let ccv3: *i64 = PS_CC as *i64 1022 let cdv3: *i64 = PS_CD as *i64 1023 let rdx2: i64 = px[ccv3[ci]] - px[cdv3[ci]] 1024 let rdy2: i64 = py[ccv3[ci]] - py[cdv3[ci]] 1025 let rdz2: i64 = pz[ccv3[ci]] - pz[cdv3[ci]] 1026 let dref2: i64 = ps_isqrt(rdx2*rdx2 + rdy2*rdy2 + rdz2*rdz2) 1027 let tgt3: i64 = dref2*c1[ci]/c2[ci] 1028 var dev: i64 = d - tgt3 1029 if dev < 0 { dev = 0 - dev } 1030 let slack2: i64 = tgt3*c3[ci]/1000 1031 if dev > slack2 { return dev - slack2 } 1032 return 0 1033 } 1034 return 0 1035} 1036 1037// fixed-order fixed-count projection passes, then MEASURE. 1 = SOLVED, 0 = INCONSISTENT (worst named). 1038func ps_solve() -> i64 { 1039 var pass: i64 = 0 1040 while pass < PS_PASSES { 1041 var ci: i64 = 0 1042 while ci < PS_NC { ps_apply(ci); ci = ci + 1 } 1043 pass = pass + 1 1044 } 1045 PS_RESID = 0 1046 PS_WORST = 0 - 1 1047 PS_WORSTV = 0 1048 var ok: i64 = 1 1049 var c: i64 = 0 1050 while c < PS_NC { 1051 let v: i64 = ps_violation(c) 1052 PS_RESID = PS_RESID + v 1053 if v > PS_WORSTV { PS_WORSTV = v; PS_WORST = c } 1054 if v > PS_TOL { ok = 0 } 1055 c = c + 1 1056 } 1057 return ok 1058} 1059func ps_ckind(ci: i64) -> i64 { let ck: *i64 = PS_CK as *i64; return ck[ci] } 1060 1061func ps_dump() -> i64 { 1062 var i: i64 = 0 1063 while i < PS_NP { 1064 ps_out(" part " as *u8) 1065 ps_out(ps_name(i)) 1066 ps_out(" " as *u8) 1067 ps_outn(ps_x(i)) 1068 ps_out(" " as *u8) 1069 ps_outn(ps_y(i)) 1070 ps_out(" " as *u8) 1071 ps_outn(ps_z(i)) 1072 ps_out("\n" as *u8) 1073 // the box line appears ONLY for a part that declared one, so every pre-rung-2 constraint file 1074 // still prints byte-identically. A point has nothing to say about its footprint. 1075 if ps_ex(i) + ps_ey(i) + ps_ez(i) > 0 { 1076 ps_out(" box " as *u8) 1077 ps_outn(ps_ex(i)) 1078 ps_out(" " as *u8) 1079 ps_outn(ps_ey(i)) 1080 ps_out(" " as *u8) 1081 ps_outn(ps_ez(i)) 1082 ps_out(" facing " as *u8) 1083 ps_outn(ps_fx(i)) 1084 ps_out(" " as *u8) 1085 ps_outn(ps_fy(i)) 1086 ps_out(" footprint " as *u8) 1087 ps_outn(ps_exx(i)) 1088 ps_out(" " as *u8) 1089 ps_outn(ps_eyy(i)) 1090 ps_out("\n" as *u8) 1091 } 1092 i = i + 1 1093 } 1094 return 0 1095}