Views
No views yet
policy/, bench/landing_bench.mjs).tgo, the minimum-energy cost of the ZEM/ZEV solution is itself closed-form:J(tgo) = 12|ZEM|²/tgo³ − 12(ZEM·ZEV)/tgo² + 4|ZEV|²/tgo[dx, dy, vx, vy, g] — position relative to the pad (m), velocity (m/s), the body's gravity (m/s²). One net spans Moon / Mars / Earth.tgo — the optimal time-to-go (s). Feed it to ZEM/ZEV, clamp to the engine limit, fly.5 → 32 → 32 → 1, ReLU, input/output normalization. 1,281 parameters.6/tgo², so the target is stiff exactly at touchdown, where being wrong matters most — and a smooth net cannot track it. DAgger (rolling out the learner, relabelling with the expert) helps a lot but plateaus well short:| Approach | soft landings (held-out, 120 starts) | miss | touchdown |
|---|---|---|---|
| Clone thrust — plain behavior cloning | 9/120 | — | — |
| Clone thrust — + DAgger, 6 rounds | 72/120 | 0.12 m | 1.05 m/s |
| Clone the time-to-go (this model) | 110/120 | 0.00 m | 0.03 m/s |
| Guidance | success | mean miss | touchdown | mean Δv |
|---|---|---|---|---|
| Scheduled ZEM/ZEV (search a flight time, fly the clock) | 95.0% | 0.03 m | 0.45 m/s | 139 m/s |
| Clock-free ZEM/ZEV (solve tgo every step) | 90.8% | 0.00 m | 0.02 m/s | 159 m/s |
| Learned · thrust (1,346p) | 60.0% | 0.12 m | 1.05 m/s | 142 m/s |
| Learned · tgo (this model) | 91.7% | 0.00 m | 0.03 m/s | 160 m/s |
node bench/landing_bench.mjs. Note the honest trade the benchmark exposes: the scheduled search still wins on success rate and fuel, because on thrust-marginal starts it can find some feasible flight time where the energy-optimal tgo demands more thrust than the engine has. The clock-free family buys an order of magnitude in precision, and needs no plan, for about 15% more Δv.land_policy.json (W1,b1,W2,b2,W3,b3 and the xm/xsd/ym/ysd normalization; mode: "tgo"). The net is one forward pass; ZEM/ZEV is four lines:1const P = await (await fetch('land_policy.json')).json();
2const mv = (W,a)=>W[0].map((_,j)=>a.reduce((s,ai,i)=>s+ai*W[i][j],0)), relu=z=>z.map(v=>v>0?v:0);
3function tgo(obs){ // obs = [dx, dy, vx, vy, g]
4 const x = obs.map((v,j)=>(v-P.xm[j])/P.xsd[j]);
5 const a1 = relu(mv(P.W1,x).map((v,j)=>v+P.b1[j]));
6 const a2 = relu(mv(P.W2,a1).map((v,j)=>v+P.b2[j]));
7 return Math.max(0.5, mv(P.W3,a2).map((v,j)=>v+P.b3[j])[0]*P.ysd[0]+P.ym[0]);
8}
9function thrust(r, v, rT, g, aMax){ // r,v,rT,g are 2-vectors; g = [0,-grav]
10 const t = tgo([r[0]-rT[0], r[1]-rT[1], v[0], v[1], -g[1]]);
11 const zx = rT[0]-(r[0]+v[0]*t+0.5*g[0]*t*t), zy = rT[1]-(r[1]+v[1]*t+0.5*g[1]*t*t);
12 const ex = -(v[0]+g[0]*t), ey = -(v[1]+g[1]*t);
13 let a = [6/(t*t)*zx - 2/t*ex, 6/(t*t)*zy - 2/t*ey];
14 const am = Math.hypot(...a);
15 return am > aMax ? a.map(x => x*aMax/am) : a;
16}