nx_solarsim.nx source
↩ module page · 151 lines · 8183 B
1// nx_solarsim.nx -- Simulates the solar system with integer-based N-body physics, rendering planets and trails in a packed RGB framebuffer.
2const O_MAGIC_38700: i64 = 38700
3const O_MAGIC_72300: i64 = 72300
4const O_MAGIC_100000: i64 = 100000
5const O_MAGIC_152400: i64 = 152400
6const O_MAGIC_520300: i64 = 520300
7const O_MAGIC_4096: i64 = 4096
8// nx_solarsim.nx -- BROWSER-RUNNABLE real N-body SOLAR-SYSTEM simulator (base-relative, integer/no-float, no JS
9// logic). The SAME code runs native (base = mmap -> PNG verify) and in the browser (base = 0 = sovereign WASM
10// linear memory). This is the "video games are simulators / full real physics" build: 5 planets (Mercury..Jupiter)
11// are each leapfrog-integrated under the Sun's gravity a = -GM r/|r|^3 (our integer isqrt; velocity x VS for the
12// exact symplectic half-kick) at their TRUE relative semi-major axes -- so the orbital PERIODS EMERGE from the
13// physics and follow Kepler's 3rd law (T proportional to a^1.5), matching the real measured planetary periods that
14// nx_sim_validation_real_gate validated to <0.1%. Inter-planet perturbation (<0.1%) is omitted -- the same
15// dominant-2-body approximation the validation uses; honest scope: inner system + Jupiter (fixed-point dynamic
16// range). The RADIAL display is sqrt-compressed for visibility (physics + periods stay in true units). The integer
17// rasterizer draws the Sun, each planet's trail, and the planets into a packed-RGB framebuffer; the browser blits
18// it. nx_wasm auto-exports every func + the linear memory. license_tier: ORIGINAL
19const W: i64 = 256
20const H: i64 = 256
21const O_FB: i64 = 0 // framebuffer W*H i64 packed-RGB at offset 0 (W*H*8 = 524288 bytes)
22const N: i64 = 5 // planets: Mercury, Venus, Earth, Mars, Jupiter
23const O_X: i64 = 524288 // x[N] (true coords, AU*SC)
24const O_Y: i64 = 524328 // y[N]
25const O_VSX: i64 = 524368 // vsx[N] (velocity * VS)
26const O_VSY: i64 = 524408 // vsy[N]
27const O_R0: i64 = 524448 // r0[N] initial radius (for display scale + period)
28const O_RPX: i64 = 524488 // rpx[N] display radius in pixels
29const O_TH: i64 = 524528 // trail head[N]
30const O_TC: i64 = 524568 // trail count[N]
31const O_STEP: i64 = 524608 // [0] global step counter
32const O_TR: i64 = 524616 // trails: N*TRAILN points * 2 i64 (px,py)
33const TRAILN: i64 = 80
34const SC: i64 = 100000 // 1 AU = 100000 units
35const GM: i64 = 15625000000000 // (SC/8)^2 * SC -> circular speed SC/8 at r=SC (Earth period ~3222 steps)
36const DD2: i64 = 8192 // position-step divisor (= 2*D*D, D=64)
37const VS: i64 = 128 // velocity scale (= 2*D, exact symplectic half-kick)
38const CX: i64 = 128
39const CY: i64 = 128
40const STEPS_PER_TICK: i64 = 24
41
42func rgb(r: i64, g: i64, b: i64) -> i64 { return (r & 255) | ((g & 255) << 8) | ((b & 255) << 16) }
43func 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 }
44func color_of(p: i64) -> i64 {
45 if p==0 { return rgb(170,150,130) } // Mercury
46 if p==1 { return rgb(225,195,120) } // Venus
47 if p==2 { return rgb(90,160,235) } // Earth
48 if p==3 { return rgb(225,110,70) } // Mars
49 return rgb(215,175,120) // Jupiter
50}
51func dim(c: i64) -> i64 { return rgb((c&255)*2/5, ((c>>8)&255)*2/5, ((c>>16)&255)*2/5) }
52func radius_of(p: i64) -> i64 { if p==4 { return 4 } return 2 } // Jupiter a touch bigger
53
54func putpx(base: i64, px: i64, py: i64, c: i64) -> i64 {
55 if px<0 { return 0 } if px>=W { return 0 } if py<0 { return 0 } if py>=H { return 0 }
56 let fb: *i64 = (base + O_FB) as *i64; fb[py*W+px]=c; return 0
57}
58func disc(base: i64, px: i64, py: i64, r: i64, c: i64) -> i64 {
59 var dy: i64=0-r
60 while dy<=r { var dx: i64=0-r
61 while dx<=r { if dx*dx+dy*dy <= r*r { putpx(base, px+dx, py+dy, c) } dx=dx+1 }
62 dy=dy+1 }
63 return 0
64}
65func 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 }
66
67// per-planet 2x specific orbital energy: (v^2) - 2GM/r (v = vs/VS)
68func energy_p(base: i64, p: i64) -> i64 {
69 let X: *i64=(base+O_X) as *i64; let Y: *i64=(base+O_Y) as *i64; let VSX: *i64=(base+O_VSX) as *i64; let VSY: *i64=(base+O_VSY) as *i64
70 let r: i64=isqrt(X[p]*X[p]+Y[p]*Y[p]); let vx: i64=VSX[p]/VS; let vy: i64=VSY[p]/VS
71 return vx*vx+vy*vy - 2*GM/r
72}
73
74func set_planet(base: i64, p: i64, a: i64) -> i64 {
75 let X: *i64=(base+O_X) as *i64; let Y: *i64=(base+O_Y) as *i64; let VSX: *i64=(base+O_VSX) as *i64; let VSY: *i64=(base+O_VSY) as *i64
76 let R0: *i64=(base+O_R0) as *i64; let RPX: *i64=(base+O_RPX) as *i64; let TH: *i64=(base+O_TH) as *i64; let TC: *i64=(base+O_TC) as *i64
77 X[p]=a; Y[p]=0; VSX[p]=0
78 let v: i64=isqrt(GM/a); VSY[p]=v*VS // circular-orbit tangential speed
79 R0[p]=a; RPX[p]=isqrt(a)*45/316 // sqrt radial compression (Earth a=SC -> 45 px)
80 TH[p]=0; TC[p]=0
81 return 0
82}
83func init_impl(base: i64) -> i64 {
84 set_planet(base, 0, O_MAGIC_38700) // Mercury 0.387 AU
85 set_planet(base, 1, O_MAGIC_72300) // Venus 0.723 AU
86 set_planet(base, 2, O_MAGIC_100000) // Earth 1.000 AU
87 set_planet(base, 3, O_MAGIC_152400) // Mars 1.524 AU
88 set_planet(base, 4, O_MAGIC_520300) // Jupiter 5.203 AU
89 let stp: *i64=(base+O_STEP) as *i64; stp[0]=0
90 return 0
91}
92
93// symplectic leapfrog step for planet p under the Sun's gravity (real physics, integer)
94func step_one_impl(base: i64, p: i64) -> i64 {
95 let X: *i64=(base+O_X) as *i64; let Y: *i64=(base+O_Y) as *i64; let VSX: *i64=(base+O_VSX) as *i64; let VSY: *i64=(base+O_VSY) as *i64
96 var x: i64=X[p]; var y: i64=Y[p]; var vsx: i64=VSX[p]; var vsy: i64=VSY[p]
97 let r2: i64=x*x+y*y; let r: i64=isqrt(r2); let r3: i64=r2*r
98 let ax: i64=0 - GM*x/r3; let ay: i64=0 - GM*y/r3
99 let vhx: i64=vsx+ax; let vhy: i64=vsy+ay
100 x=x+vhx/DD2; y=y+vhy/DD2
101 let r2b: i64=x*x+y*y; let rb: i64=isqrt(r2b); let r3b: i64=r2b*rb
102 let axb: i64=0 - GM*x/r3b; let ayb: i64=0 - GM*y/r3b
103 vsx=vhx+axb; vsy=vhy+ayb
104 X[p]=x; Y[p]=y; VSX[p]=vsx; VSY[p]=vsy
105 return 0
106}
107func record_trail(base: i64, p: i64) -> i64 {
108 let X: *i64=(base+O_X) as *i64; let Y: *i64=(base+O_Y) as *i64; let R0: *i64=(base+O_R0) as *i64; let RPX: *i64=(base+O_RPX) as *i64
109 let TH: *i64=(base+O_TH) as *i64; let TC: *i64=(base+O_TC) as *i64; let TR: *i64=(base+O_TR) as *i64
110 let rpx: i64=RPX[p]; let r0: i64=R0[p]
111 let px: i64=CX + X[p]*rpx/r0; let py: i64=CY - Y[p]*rpx/r0
112 let h: i64=TH[p]; let idx: i64=(p*TRAILN+h)*2; TR[idx]=px; TR[idx+1]=py
113 TH[p]=(h+1)%TRAILN; if TC[p]<TRAILN { TC[p]=TC[p]+1 }
114 return 0
115}
116func tick_impl(base: i64, cmd: i64) -> i64 {
117 if cmd==3 { init_impl(base); return 0 }
118 var p: i64=0
119 while p<N {
120 var s: i64=0; while s<STEPS_PER_TICK { step_one_impl(base, p); s=s+1 }
121 record_trail(base, p)
122 p=p+1
123 }
124 let stp: *i64=(base+O_STEP) as *i64; stp[0]=stp[0]+1
125 return 0
126}
127func render_impl(base: i64) -> i64 {
128 let X: *i64=(base+O_X) as *i64; let Y: *i64=(base+O_Y) as *i64; let R0: *i64=(base+O_R0) as *i64; let RPX: *i64=(base+O_RPX) as *i64
129 let TC: *i64=(base+O_TC) as *i64; let TR: *i64=(base+O_TR) as *i64
130 clear_fb(base, rgb(6,8,18))
131 disc(base, CX, CY, 6, rgb(255,220,90)) // the Sun
132 var p: i64=0
133 while p<N {
134 let cd: i64=dim(color_of(p)); var t: i64=0
135 while t<TC[p] { let idx: i64=(p*TRAILN+t)*2; putpx(base, TR[idx], TR[idx+1], cd); t=t+1 }
136 let rpx: i64=RPX[p]; let r0: i64=R0[p]
137 let bpx: i64=CX + X[p]*rpx/r0; let bpy: i64=CY - Y[p]*rpx/r0
138 disc(base, bpx, bpy, radius_of(p), color_of(p))
139 p=p+1
140 }
141 return 0
142}
143
144// ---- wasm interface (nx_wasm auto-exports these) ----
145func ww() -> i64 { return W }
146func hh() -> i64 { return H }
147func fb_off() -> i64 { return O_FB }
148func init() -> i64 { return init_impl(0) }
149func tick(cmd: i64) -> i64 { return tick_impl(0, cmd) }
150func render() -> i64 { return render_impl(0) }
151func mem_bytes() -> i64 { return O_TR + N*TRAILN*2*8 + O_MAGIC_4096 }