nx_ballistics.nx source
↩ module page · 143 lines · 7158 B
1// nx_ballistics.nx -- Simulates projectile motion with and without air drag for ballistic trajectory visualization.
2const O_MAGIC_3141593: i64 = 3141593
3const O_MAGIC_6283185: i64 = 6283185
4const O_MAGIC_1570796: i64 = 1570796
5const O_MAGIC_4096: i64 = 4096
6// nx_ballistics.nx -- BROWSER-RUNNABLE projectile ballistics (base-relative, integer/no-float, no JS logic). SAME
7// code runs native (base = mmap -> PNG) and in the browser (base = 0 = sovereign WASM memory). This is the military
8// "firing table" physics: two projectiles launched together -- one in VACUUM, one with quadratic AIR DRAG -- so you
9// watch drag fall short and steepen the descent. The real ballistic laws are validated by nx_ballistics_gate: range
10// R = v^2 sin(2theta)/g (max at 45deg, complementary angles give equal range), parabolic symmetry, and drag
11// reducing range. Acceleration: vacuum a=(0,-g); drag a=(-k|v|vx, -g-k|v|vy). nx_wasm auto-exports every func.
12// license_tier: ORIGINAL
13const W: i64 = 256
14const H: i64 = 256
15const O_FB: i64 = 0
16const N: i64 = 2 // 0 = vacuum, 1 = drag
17const O_X: i64 = 524288
18const O_Y: i64 = 524304
19const O_VX: i64 = 524320
20const O_VY: i64 = 524336
21const O_GND: i64 = 524352 // grounded flags
22const O_WX: i64 = 524368
23const O_WY: i64 = 524376
24const O_STEP: i64 = 524384
25const O_RST: i64 = 524392 // post-landing pause counter
26const O_TR: i64 = 524400 // trails: N*TRAILN*2 (display px)
27const TRAILN: i64 = 160
28const SC: i64 = 1000000
29const V0: i64 = 200000 // launch speed
30const GSTEP: i64 = 200 // gravity (velocity decrement / step)
31const K: i64 = 1 // drag coefficient
32const DSCALE: i64 = 566000000 // drag divisor (tunes drag ~25% of gravity at launch)
33const LAUNCH: i64 = 959931 // 55 deg in micro-rad
34const STEPS_PER_TICK: i64 = 8
35
36func rgb(r: i64, g: i64, b: i64) -> i64 { return (r & 255) | ((g & 255) << 8) | ((b & 255) << 16) }
37func isqrt(N0: i64) -> i64 { if N0<2 { return N0 } var x: i64=N0; var y: i64=(x+1)/2; while y<x { x=y; y=(x + N0/x)/2 } return x }
38func sin_fixed(Xin: i64) -> i64 {
39 let PI: i64=O_MAGIC_3141593; let TWO_PI: i64=O_MAGIC_6283185; let HALF: i64=O_MAGIC_1570796
40 var sign: i64=1; var X: i64=Xin
41 if X<0 { X=0-X; sign=0-1 }
42 X = X % TWO_PI
43 if X > PI { X = TWO_PI - X; sign = 0-sign }
44 if X > HALF { X = PI - X }
45 let x2: i64 = X*X/SC
46 var term: i64=X; var sum: i64=X; var k: i64=1
47 while k<=8 { term = (0-term)*x2/SC/((2*k)*(2*k+1)); sum=sum+term; k=k+1 }
48 return sum*sign
49}
50func cos_fixed(X: i64) -> i64 { return sin_fixed(X+O_MAGIC_1570796) }
51func vmag(vx: i64, vy: i64) -> i64 { return isqrt(vx*vx+vy*vy) }
52// the shared ballistic acceleration (used by both the animation and the validation gate -- DRY)
53func acc_x(vx: i64, vy: i64, drag: i64) -> i64 { if drag==0 { return 0 } return 0 - K*vmag(vx,vy)*vx/DSCALE }
54func acc_y(vx: i64, vy: i64, drag: i64) -> i64 { if drag==0 { return 0-GSTEP } return (0-GSTEP) - K*vmag(vx,vy)*vy/DSCALE }
55
56func putpx(base: i64, px: i64, py: i64, c: i64) -> i64 {
57 if px<0 { return 0 } if px>=W { return 0 } if py<0 { return 0 } if py>=H { return 0 }
58 let fb: *i64 = (base + O_FB) as *i64; fb[py*W+px]=c; return 0
59}
60func disc(base: i64, px: i64, py: i64, r: i64, c: i64) -> i64 {
61 var dy: i64=0-r
62 while dy<=r { var dx: i64=0-r
63 while dx<=r { if dx*dx+dy*dy <= r*r { putpx(base, px+dx, py+dy, c) } dx=dx+1 }
64 dy=dy+1 }
65 return 0
66}
67func clear_fb(base: i64, c: i64) -> i64 { let fb: *i64=(base+O_FB) as *i64; var i: i64=0; while i<W*H { fb[i]=c; i=i+1 } return 0 }
68
69func init_impl(base: i64) -> i64 {
70 let X: *i64=(base+O_X) as *i64; let Y: *i64=(base+O_Y) as *i64; let VX: *i64=(base+O_VX) as *i64; let VY: *i64=(base+O_VY) as *i64
71 let GND: *i64=(base+O_GND) as *i64
72 let vx0: i64 = V0*cos_fixed(LAUNCH)/SC; let vy0: i64 = V0*sin_fixed(LAUNCH)/SC
73 var i: i64=0
74 while i<N { X[i]=0; Y[i]=0; VX[i]=vx0; VY[i]=vy0; GND[i]=0; i=i+1 }
75 let WX: *i64=(base+O_WX) as *i64; let WY: *i64=(base+O_WY) as *i64
76 WX[0]=2*vx0*vy0/GSTEP*11/10; WY[0]=vy0*vy0/(2*GSTEP)*12/10 // vacuum range/apex bounds + margin
77 let stp: *i64=(base+O_STEP) as *i64; stp[0]=0
78 let rst: *i64=(base+O_RST) as *i64; rst[0]=0
79 let tc: *i64=(base+O_TR-8) as *i64 // (unused) keep layout simple: clear trail heads below
80 var t: i64=0; let TR: *i64=(base+O_TR) as *i64; while t<N*TRAILN*2 { TR[t]=0-1; t=t+1 }
81 return 0
82}
83func step_one_impl(base: i64) -> i64 {
84 let X: *i64=(base+O_X) as *i64; let Y: *i64=(base+O_Y) as *i64; let VX: *i64=(base+O_VX) as *i64; let VY: *i64=(base+O_VY) as *i64
85 let GND: *i64=(base+O_GND) as *i64
86 var i: i64=0
87 while i<N {
88 if GND[i]==0 {
89 VX[i]=VX[i]+acc_x(VX[i],VY[i],i); VY[i]=VY[i]+acc_y(VX[i],VY[i],i)
90 X[i]=X[i]+VX[i]; Y[i]=Y[i]+VY[i]
91 if Y[i]<=0 { Y[i]=0; GND[i]=1 }
92 }
93 i=i+1
94 }
95 return 0
96}
97func wx_to_px(base: i64, wx: i64) -> i64 { let WX: *i64=(base+O_WX) as *i64; return 4 + wx*248/WX[0] }
98func wy_to_py(base: i64, wy: i64) -> i64 { let WY: *i64=(base+O_WY) as *i64; return 250 - wy*242/WY[0] }
99func record_trail(base: i64) -> i64 {
100 let X: *i64=(base+O_X) as *i64; let Y: *i64=(base+O_Y) as *i64; let TR: *i64=(base+O_TR) as *i64
101 let stp: *i64=(base+O_STEP) as *i64; let h: i64 = stp[0] % TRAILN
102 var i: i64=0
103 while i<N { TR[(i*TRAILN+h)*2]=wx_to_px(base,X[i]); TR[(i*TRAILN+h)*2+1]=wy_to_py(base,Y[i]); i=i+1 }
104 return 0
105}
106func tick_impl(base: i64, cmd: i64) -> i64 {
107 if cmd==3 { init_impl(base); return 0 }
108 let GND: *i64=(base+O_GND) as *i64
109 if GND[0]==1 { if GND[1]==1 { // both landed -> pause then relaunch
110 let rst: *i64=(base+O_RST) as *i64; rst[0]=rst[0]+1
111 if rst[0]>40 { init_impl(base) }
112 return 0
113 } }
114 var s: i64=0
115 while s<STEPS_PER_TICK { step_one_impl(base); let stp: *i64=(base+O_STEP) as *i64; stp[0]=stp[0]+1; record_trail(base); s=s+1 }
116 return 0
117}
118func render_impl(base: i64) -> i64 {
119 let X: *i64=(base+O_X) as *i64; let Y: *i64=(base+O_Y) as *i64; let TR: *i64=(base+O_TR) as *i64
120 clear_fb(base, rgb(12,16,26))
121 var gx: i64=0; while gx<W { putpx(base, gx, 250, rgb(60,70,55)); gx=gx+1 } // ground line
122 // trails: vacuum green, drag orange
123 var i: i64=0
124 while i<N {
125 var col: i64=rgb(90,210,140); if i==1 { col=rgb(235,150,60) }
126 var t: i64=0
127 while t<TRAILN { let px: i64=TR[(i*TRAILN+t)*2]; if px>=0 { putpx(base, px, TR[(i*TRAILN+t)*2+1], col) } t=t+1 }
128 i=i+1
129 }
130 // current projectiles
131 disc(base, wx_to_px(base,X[0]), wy_to_py(base,Y[0]), 3, rgb(140,255,180))
132 disc(base, wx_to_px(base,X[1]), wy_to_py(base,Y[1]), 3, rgb(255,190,110))
133 return 0
134}
135
136// ---- wasm interface ----
137func ww() -> i64 { return W }
138func hh() -> i64 { return H }
139func fb_off() -> i64 { return O_FB }
140func init() -> i64 { return init_impl(0) }
141func tick(cmd: i64) -> i64 { return tick_impl(0, cmd) }
142func render() -> i64 { return render_impl(0) }
143func mem_bytes() -> i64 { return O_TR + N*TRAILN*2*8 + O_MAGIC_4096 }