code wiki / (root) / nx_solarsim.nx

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 }