code wiki / _hdl_build / nx_collide2d.nx

nx_collide2d.nx source

↩ module page · 128 lines · 5723 B

1// nx_collide2d.nx -- certified composable part: CONTINUOUS (swept) 2D collision with slide response 2// and impulse integration. The gamebench board's own bar for a physics-collision HAVE names exactly 3// three absences (nx_gamebench.nx bit-5 comment): swept/continuous collision, slide-along-surface 4// response, velocity/impulse integration -- this part is those three, gated and mutation-proven. 5// LIB, no main (ecosystem convention, cf. nx_gamesave/nx_swgpu). Integer-only: positions/velocities in 6// plain i64 world units, time-of-impact as a Q8 fraction of one step. Deterministic by construction. 7// Restitution is a Q8 PARAMETER (rule 11: coefficients are data, never buried constants); rest=0 gives 8// pure slide (normal component killed, tangent PRESERVED), rest=C2_Q gives a perfect bounce. 9// license_tier: ORIGINAL expect_exit: 0 10import "nx_syscalls.nx" 11const C2_Q: i64 = 256 // Q8 fixed-point denominator for step fractions + restitution 12const C2_MAXIT: i64 = 4 // contact resolutions per step (corner = 2 walls in one step) 13const C2_CKMOD: i64 = 1000000007 // rolling-checksum modulus (bounded, overflow-free) 14 15// ---- swept point-vs-inflated-AABB (Minkowski: circle of radius r vs box == point vs box grown by r). 16// walls = flat rows [x0,y0,x1,y1]; (vx,vy) = the full displacement THIS substep. 17// Returns Q8 time of FIRST impact in [0..C2_Q), or C2_Q if the path is free. 18// Writes the contact normal (axis-aligned, unit) to nrm[0..1]. 19func c2_sweep(px: i64, py: i64, vx: i64, vy: i64, r: i64, walls: *i64, nw: i64, nrm: *i64) -> i64 { 20 var best: i64 = C2_Q 21 nrm[0] = 0 22 nrm[1] = 0 23 var w: i64 = 0 24 while w < nw { 25 let bx0: i64 = walls[w*4] - r 26 let by0: i64 = walls[w*4+1] - r 27 let bx1: i64 = walls[w*4+2] + r 28 let by1: i64 = walls[w*4+3] + r 29 var ok: i64 = 1 30 var t0: i64 = 0 31 var t1: i64 = C2_Q 32 var n0x: i64 = 0 33 var n0y: i64 = 0 34 // X slab 35 if vx == 0 { 36 if px < bx0 { ok = 0 } 37 if px > bx1 { ok = 0 } 38 } else { 39 var ta: i64 = (bx0 - px)*C2_Q/vx 40 var tb: i64 = (bx1 - px)*C2_Q/vx 41 var nsx: i64 = 0 - 1 42 if ta > tb { let tt: i64 = ta; ta = tb; tb = tt; nsx = 1 } 43 // >= not >: a contact at entry time EXACTLY 0 (touching after a slide) must still record 44 // its normal, or the second wall of a corner is silently dropped and the mover tunnels 45 // (found by T8/T5 on first run -- the gate biting its own part) 46 if ta >= t0 { if ta >= 0 { t0 = ta; n0x = nsx; n0y = 0 } } 47 if tb < t1 { t1 = tb } 48 } 49 // Y slab 50 if vy == 0 { 51 if py < by0 { ok = 0 } 52 if py > by1 { ok = 0 } 53 } else { 54 var tc: i64 = (by0 - py)*C2_Q/vy 55 var td: i64 = (by1 - py)*C2_Q/vy 56 var nsy: i64 = 0 - 1 57 if tc > td { let tu: i64 = tc; tc = td; td = tu; nsy = 1 } 58 if tc >= t0 { if tc >= 0 { t0 = tc; n0x = 0; n0y = nsy } } 59 if td < t1 { t1 = td } 60 } 61 if ok == 1 { if t0 <= t1 { if t0 < best { if t0 >= 0 { 62 // a zero normal means we started inside both slabs with no entry crossing -- not a hit 63 var hasn: i64 = 0 64 if n0x != 0 { hasn = 1 } 65 if n0y != 0 { hasn = 1 } 66 if hasn == 1 { 67 best = t0 68 nrm[0] = n0x 69 nrm[1] = n0y 70 } 71 }}}} 72 w = w + 1 73 } 74 return best 75} 76 77// ---- one simulation step: gravity impulse -> iterative swept resolve (slide + restitution). 78// st: [0]=px [1]=py [2]=vx [3]=vy [4]=r [5]=contact-count [6]=spare (pure i64 vector => gs_save-able) 79// gy = gravity impulse per step; rest = restitution Q8 (0 = pure slide, 256 = perfect bounce). 80func c2_step(st: *i64, walls: *i64, nw: i64, gy: i64, rest: i64, nrm: *i64) -> i64 { 81 st[3] = st[3] + gy 82 var rem: i64 = C2_Q 83 var it: i64 = 0 84 while it < C2_MAXIT { 85 var doit: i64 = 1 86 if rem < 1 { doit = 0 } 87 if doit == 1 { 88 let sx: i64 = st[2]*rem/C2_Q 89 let sy: i64 = st[3]*rem/C2_Q 90 var still: i64 = 1 91 if sx != 0 { still = 0 } 92 if sy != 0 { still = 0 } 93 if still == 1 { 94 rem = 0 95 } else { 96 let t: i64 = c2_sweep(st[0], st[1], sx, sy, st[4], walls, nw, nrm) 97 if t >= C2_Q { 98 st[0] = st[0] + sx 99 st[1] = st[1] + sy 100 rem = 0 101 } else { 102 st[0] = st[0] + sx*t/C2_Q 103 st[1] = st[1] + sy*t/C2_Q 104 st[5] = st[5] + 1 105 // impulse on the CONTACT AXIS only; the tangent component is PRESERVED = slide 106 if nrm[0] != 0 { st[2] = 0 - st[2]*rest/C2_Q } 107 if nrm[1] != 0 { st[3] = 0 - st[3]*rest/C2_Q } 108 // 1-unit nudge off the surface so integer truncation can never leave us embedded 109 st[0] = st[0] + nrm[0] 110 st[1] = st[1] + nrm[1] 111 rem = rem*(C2_Q - t)/C2_Q 112 } 113 } 114 } 115 it = it + 1 116 } 117 return 0 118} 119 120// bounded positive rolling checksum over the state vector (the harness's independent walker uses the 121// same shape over positions only -- transparency law: the verifier must not flow through the save path) 122func c2_ck(ck0: i64, v: i64) -> i64 { 123 var vv: i64 = v % C2_CKMOD 124 if vv < 0 { vv = vv + C2_CKMOD } 125 var ck: i64 = ck0 % C2_CKMOD 126 if ck < 0 { ck = ck + C2_CKMOD } 127 return (ck*31 + vv) % C2_CKMOD 128}