code wiki / (root) / nx_wasm_physlab.nx

nx_wasm_physlab.nx source

↩ module page · 220 lines · 8242 B

1// nx_wasm_physlab.nx -- PHYSLAB: the VISIBLE consumer of the shared 3D core (reuse census W2: "a game surface 2// consumes nx_phys3d + nx_softjiggle"). Crates STACK and fall ASLEEP (dimmed = sleeping, visibly), spheres 3// BOUNCE with real restitution, and a 5-node JIGGLE CHAIN hangs from the mouse and swings AROUND a capsule 4// pillar (collision visible). Click spawns crates. ALL game logic lives here (the no-JS-logic law): JS may 5// only pass raw mouse state and blit the framebuffer. ALL INTEGER (phys3d/softjiggle fixed-point law) -- the 6// ENTIRE surface is VM-vettable byte-for-byte (no f32 anywhere). Side view: x right, y up, orthographic. 7// license_tier: ORIGINAL 8import "nx_syscalls.nx" 9import "nx_phys3d.nx" 10import "nx_softjiggle.nx" 11const O_MAGIC_100000: i64 = 100000 12const O_MAGIC_1500: i64 = 1500 13const O_MAGIC_3500: i64 = 3500 14const O_MAGIC_3000: i64 = 3000 15const O_MAGIC_2800: i64 = 2800 16const O_MAGIC_3600: i64 = 3600 17const O_MAGIC_1800: i64 = 1800 18const O_MAGIC_2600: i64 = 2600 19const O_MAGIC_65536: i64 = 65536 20 21const W: i64 = 320 22const H: i64 = 240 23const HW: i64 = 160 24const GROUNDY: i64 = 210 // screen y of the ground line 25const SCALE: i64 = 16 // world fx256 -> screen px divisor 26const O_FB: i64 = 0 27const O_PH: i64 = 614400 // W*H*8; ph_bytes now 106560 (warm+target tables, 2026-07-03) 28const O_SJ: i64 = 721024 29const O_ST: i64 = 739584 // [0]=tick [1]=prev buttons [2]=spawned [3]=chain0 id [4]=lastmx [5]=lastmy 30const NCRATE: i64 = 5 31 32func st_ptr(base: i64) -> *i64 { return (base + O_ST) as *i64 } 33func mem_bytes() -> i64 { return O_ST + 128 } 34 35func world_init(base: i64) -> i64 { 36 ph_init(base + O_PH) 37 sj_init(base + O_SJ) 38 // ground slab (static) 39 ph_add(base + O_PH, 1, 0, 0 - 512, 0, O_MAGIC_100000, 512, O_MAGIC_100000, 0, 0, 200) 40 // crate stack at x=-1500 41 var k: i64 = 0 42 while k < NCRATE { 43 ph_add(base + O_PH, 1, 0 - O_MAGIC_1500, 200 + k * 400, 0, 200, 200, 200, 256, 30, 200) 44 k = k + 1 45 } 46 // two bouncy spheres dropped from height 47 let s1: i64 = ph_add(base + O_PH, 0, 0 - O_MAGIC_3500, O_MAGIC_3000, 0, 200, 200, 200, 256, 200, 60) 48 let s2: i64 = ph_add(base + O_PH, 0, 0 - O_MAGIC_2800, O_MAGIC_3600, 0, 160, 160, 160, 256, 170, 60) 49 // capsule pillar (jiggle-world obstacle), vertical at x=+1800 50 sj_add_capsule(base + O_SJ, O_MAGIC_1800, 0, 0, O_MAGIC_1800, O_MAGIC_1500, 0, 300) 51 // 5-node jiggle chain; node 0 anchored to the mouse, the rest chained 52 var c: i64 = 0 53 var prev: i64 = 0 - 1 54 while c < 5 { 55 let id: i64 = sj_add(base + O_SJ, O_MAGIC_3000, O_MAGIC_2600 - c * 260, 0, 16 + c * 3, 26, 380, 46) 56 if c > 0 { sj_chain(base + O_SJ, id, prev) } 57 prev = id 58 c = c + 1 59 } 60 let st: *i64 = st_ptr(base) 61 st[0] = 0 62 st[1] = 0 63 st[2] = 0 64 st[3] = 0 65 return 0 66} 67 68func start_at(base: i64) -> i64 { return world_init(base) } 69 70// mx,my = raw canvas pixels (JS passes them untranslated); buttons bit0=left-click bit1=reset 71func tick_at(base: i64, mx: i64, my: i64, buttons: i64) -> i64 { 72 let st: *i64 = st_ptr(base) 73 if (buttons & 2) != 0 { world_init(base); return 0 } 74 // screen -> world (the inverse of the projection below) 75 var wx: i64 = (mx - HW) * SCALE 76 var wy: i64 = (GROUNDY - my) * SCALE 77 if wy < 100 { wy = 100 } 78 st[4] = wx 79 st[5] = wy 80 sj_anchor(base + O_SJ, 0, wx, wy, 0) 81 // click edge -> spawn a crate at the cursor (cap total bodies) 82 let prevb: i64 = st[1] 83 if (buttons & 1) != 0 { if (prevb & 1) == 0 { 84 let hdr: *i64 = (base + O_PH) as *i64 85 if hdr[0] < 22 { 86 ph_add(base + O_PH, 1, wx, wy, 0, 180, 180, 180, 256, 40, 200) 87 st[2] = st[2] + 1 88 } 89 } } 90 st[1] = buttons 91 ph_step(base + O_PH) 92 sj_step(base + O_SJ) 93 st[0] = st[0] + 1 94 return 0 95} 96 97func px(base: i64, x: i64, y: i64, col: i64) -> i64 { 98 if x < 0 { return 0 } 99 if x >= W { return 0 } 100 if y < 0 { return 0 } 101 if y >= H { return 0 } 102 let fb: *i64 = (base + O_FB) as *i64 103 fb[y * W + x] = col 104 return 0 105} 106func rect(base: i64, x0: i64, y0: i64, x1: i64, y1: i64, col: i64) -> i64 { 107 var y: i64 = y0 108 while y <= y1 { 109 var x: i64 = x0 110 while x <= x1 { px(base, x, y, col); x = x + 1 } 111 y = y + 1 112 } 113 return 0 114} 115func circle(base: i64, cx: i64, cy: i64, r: i64, col: i64) -> i64 { 116 var dy: i64 = 0 - r 117 while dy <= r { 118 var dx: i64 = 0 - r 119 while dx <= r { 120 if dx * dx + dy * dy <= r * r { px(base, cx + dx, cy + dy, col) } 121 dx = dx + 1 122 } 123 dy = dy + 1 124 } 125 return 0 126} 127func sxof(wx: i64) -> i64 { return HW + wx / SCALE } 128func syof(wy: i64) -> i64 { return GROUNDY - wy / SCALE } 129func rgbp(r: i64, g: i64, b: i64) -> i64 { return r + g * 256 + b * O_MAGIC_65536 } 130 131func render_at(base: i64) -> i64 { 132 // sky gradient bands + ground 133 var y: i64 = 0 134 while y < H { 135 var col: i64 = rgbp(30 + y / 4, 34 + y / 4, 48 + y / 5) 136 if y >= GROUNDY { col = rgbp(46, 40, 34) } 137 rect(base, 0, y, W - 1, y, col) 138 y = y + 1 139 } 140 rect(base, 0, GROUNDY, W - 1, GROUNDY + 1, rgbp(90, 82, 66)) 141 // capsule pillar 142 let cs: *i64 = sj_capsule(base + O_SJ, 0) 143 let px0: i64 = sxof(cs[0] - cs[6]) 144 let px1: i64 = sxof(cs[0] + cs[6]) 145 let py0: i64 = syof(cs[4]) 146 let py1: i64 = syof(cs[1]) 147 rect(base, px0, py0, px1, py1, rgbp(84, 96, 120)) 148 circle(base, sxof(cs[0]), py0, cs[6] / SCALE, rgbp(84, 96, 120)) 149 // bodies: crates as rects (dim when ASLEEP -- sleep made visible), spheres as circles 150 let hdr: *i64 = (base + O_PH) as *i64 151 var i: i64 = 1 152 while i < hdr[0] { 153 let b: *i64 = ph_body(base + O_PH, i) 154 let bx: i64 = sxof(b[1]) 155 let by: i64 = syof(b[2]) 156 var shade: i64 = 255 157 if b[14] == 0 { shade = 150 } 158 let hue: i64 = (i * 53) % 3 159 var col: i64 = rgbp(200 * shade / 255, 140 * shade / 255, 70 * shade / 255) 160 if hue == 1 { col = rgbp(150 * shade / 255, 180 * shade / 255, 90 * shade / 255) } 161 if hue == 2 { col = rgbp(120 * shade / 255, 150 * shade / 255, 210 * shade / 255) } 162 if b[0] == 1 { 163 let hx: i64 = b[7] / SCALE 164 let hy: i64 = b[8] / SCALE 165 rect(base, bx - hx, by - hy, bx + hx, by + hy, col) 166 rect(base, bx - hx, by - hy, bx + hx, by - hy + 1, rgbp(20, 20, 24)) 167 } else { 168 circle(base, bx, by, b[7] / SCALE, col) 169 px(base, bx - 2, by - 3, rgbp(255, 255, 255)) 170 } 171 i = i + 1 172 } 173 // jiggle chain: links then nodes (anchor cursor drawn last) 174 var c: i64 = 0 175 while c < 5 { 176 let n: *i64 = sj_node(base + O_SJ, c) 177 let nx2: i64 = sxof(n[0]) 178 let ny2: i64 = syof(n[1]) 179 if c > 0 { 180 let p: *i64 = sj_node(base + O_SJ, c - 1) 181 let px2: i64 = sxof(p[0]) 182 let py2: i64 = syof(p[1]) 183 var s: i64 = 0 184 while s <= 12 { 185 px(base, px2 + (nx2 - px2) * s / 12, py2 + (ny2 - py2) * s / 12, rgbp(230, 210, 120)) 186 s = s + 1 187 } 188 } 189 circle(base, nx2, ny2, 3, rgbp(250, 190, 60)) 190 c = c + 1 191 } 192 let st: *i64 = st_ptr(base) 193 circle(base, sxof(st[4]), syof(st[5]), 2, rgbp(255, 255, 255)) 194 return 0 195} 196 197// counts for the HUD (JS displays; logic stays here) 198func body_count_at(base: i64) -> i64 { let h: *i64 = (base + O_PH) as *i64; return h[0] } 199func asleep_count_at(base: i64) -> i64 { 200 let h: *i64 = (base + O_PH) as *i64 201 var n: i64 = 0 202 var i: i64 = 0 203 while i < h[0] { 204 let b: *i64 = ph_body(base + O_PH, i) 205 if b[10] > 0 { if b[14] == 0 { n = n + 1 } } 206 i = i + 1 207 } 208 return n 209} 210 211// ---- base-0 wasm exports ---- 212func start() -> i64 { return start_at(0) } 213func tick(mx: i64, my: i64, buttons: i64) -> i64 { return tick_at(0, mx, my, buttons) } 214func render() -> i64 { return render_at(0) } 215func body_count() -> i64 { return body_count_at(0) } 216func asleep_count() -> i64 { return asleep_count_at(0) } 217func fb_off() -> i64 { return O_FB } 218func ww() -> i64 { return W } 219func hh() -> i64 { return H } 220func main() -> i64 { start(); return 0 }