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 }