Re-run on 2026-10-16 with OpenCode (DeepSeek V4 Pro via OpenRouter) against the updated starter (scoped picture bundle, answer scrub, moveButton, key_input clocks, check_env.py). No hardcoded demo code. Cursor did not edit workspace/.
2026-10-16 · task L5i_U7CEdI2w · opencode · openrouter/deepseek/deepseek-v4-pro-0813
Build a Starship landing you can watch in 3D. There are two vehicles. The booster, after separation, flips and burns back toward the launch site, burns again as it hits the thick air, then either settles into the tower's mechanical arms or splashes down. The ship, still in orbit, burns to fall out of orbit, hits the air belly-first behind its heat tiles, flips upright from about a kilometer up, then catches on the tower or splashes. The tower, the arms, the pad, the water, and the ship are solid. Crashing into the tower should break things. Landing too fast should break the ship into pieces that still fall and hit the ground. A gentle upright touch should not. Wind should push the vehicle, and the push should depend on which way the wind is blowing, without a slow fluid simulation. Show the Earth, the air getting thick, the ship, the flames, the tower, and the pieces if it breaks. There are too many controls for a keyboard. An Xbox controller should fly it: sticks for aiming and flaps, triggers for throttle, bumpers for how many engines, and buttons for catch versus splash.
Running the prompt-created workspace on this machine — not a mock. Save an episode, then open Replay below to scrub the timeline.
opening · 0 ms
Exact files the agent wrote — not rewritten by hand.
import math
from helpers import base_observation, key_input, rng_for
GAME_CLASS = "Starship"
G = 9.81
RHO0 = 1.225
H_SCALE = 8500.0
RE = 6371000.0
TOWER_H = 145.0
TOWER_R = 8.0
CATCH_Y = 118.0
CATCH_X = 18.0
CATCH_Z = 0.0
SPLASH_X = 430.0
WATER_X = 300.0
SAFE_VDOWN = 4.0
SAFE_H = 2.5
W = 960
H = 560
FOCAL = 520.0
CX = W / 2.0
CY = H / 2.0
NEAR = 3.0
SUB = 0.05
LAUNCH_T = 78.0
X_SEP = 16000.0
Y_SEP = 65000.0
RETURN_T = 92.0
ORBIT_T = 64.0
ENTRY_Y = 30000.0
ENTRY_X = 12000.0
REENTRY_T = 40.0
def clamp(x, a, b):
return a if x < a else (b if x > b else x)
def vadd(a, b):
return (a[0] + b[0], a[1] + b[1], a[2] + b[2])
def vsub(a, b):
return (a[0] - b[0], a[1] - b[1], a[2] - b[2])
def vmul(a, s):
return (a[0] * s, a[1] * s, a[2] * s)
def vdot(a, b):
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
def vcross(a, b):
return (
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
)
def vlen(a):
return math.sqrt(vdot(a, a))
def vnorm(a):
l = vlen(a)
if l < 1e-9:
return (0.0, 0.0, 1.0)
return (a[0] / l, a[1] / l, a[2] / l)
def density(y):
return RHO0 * math.exp(-max(0.0, y) / H_SCALE)
def smoothstep(t):
t = clamp(t, 0.0, 1.0)
return t * t * (3.0 - 2.0 * t)
class Vehicle:
def __init__(self, kind, name, mass, f_engine, max_engines, length, color):
self.kind = kind
self.name = name
self.mass = mass
self.f_engine = f_engine
self.max_engines = max_engines
self.length = length
self.color = color
self.pos = (0.0, 0.0, 0.0)
self.vel = (0.0, 0.0, 0.0)
self.pitch = 0.0
self.yaw = 0.0
self.throttle = 0.0
self.engines = max_engines
self.flap = 0.0
self.target = "catch"
self.resolved = False
self.caught = False
self.splashed = False
self.broken = False
self.outcome = ""
self.phase = "idle"
self.burn = 0.0
self.man = {}
self.kb = {"pitch": 0.0, "yaw": 0.0, "throttle": 0.0}
self.g_pitch = 0.0
self.g_yaw = 0.0
self.g_throttle = 0.0
self.g_flap = 0.0
self.pt = 0.0
self.heat = 0.0
def nose(self):
p = math.radians(self.pitch)
y = math.radians(self.yaw)
return (
math.sin(p),
math.cos(p) * math.cos(y),
math.cos(p) * math.sin(y),
)
def belly(self):
p = math.radians(self.pitch)
y = math.radians(self.yaw)
return (
math.cos(p),
-math.sin(p) * math.cos(y),
-math.sin(p) * math.sin(y),
)
def max_accel(self):
return self.engines * self.f_engine / self.mass
def cdrag(self):
if self.kind == "ship":
low, high = 0.00020, 0.00120
else:
low, high = 0.00028, 0.00090
b = self.belly()
n = self.nose()
nrel = vnorm(self.vel)
align = clamp(vdot(b, vmul(nrel, -1.0)), 0.0, 1.0)
cd = low + (high - low) * align
cd += abs(self.flap) * 0.00025
return cd
class Starship:
def __init__(self):
self.t = 0.0
self.stage = "launch"
self.stage_t = 0.0
self.warp = 0.12
self.rng = rng_for("")
self.wind_base = 8.0
self.wind_dir = 40.0
self.wind_z = 0.0
self.booster = Vehicle("booster", "Booster", 200000.0, 1.0e6, 3, 71.0, "#d6d3d1")
self.ship = Vehicle("ship", "Ship", 120000.0, 0.75e6, 3, 50.0, "#b8bdc2")
self.pieces = []
self.tower_damaged = False
self.tower_lean = 0.0
self.reward = 0.0
self.outcomes = []
self.last = None
self.stack = (0.0, 0.0, 0.0)
self.stack_vel = (0.0, 0.0, 0.0)
self.splash_z = 0.0
def reset(self, task_id=""):
rng = rng_for(task_id)
self.__init__()
self.rng = rng
self.wind_base = rng.uniform(5.0, 14.0)
self.wind_dir = rng.uniform(0.0, 360.0)
wd = math.radians(self.wind_dir)
self.wind_z = 0.0
self.splash_z = rng.uniform(-120.0, 120.0)
self.booster.target = "catch"
self.ship.target = "catch"
return self._observe(None)
def _wind_vec(self, y):
wd = math.radians(self.wind_dir)
mag = self.wind_base * (0.35 + 0.65 * clamp(y / 14000.0, 0.0, 1.0))
return (mag * math.cos(wd), 0.0, mag * math.sin(wd))
def _apply_controls(self, move):
m = (move or "").strip()
if not m or m == "stay":
return
if m.startswith("ctrl:"):
self._apply_ctrl(m[5:])
return
if m == "throttle up":
self.booster.kb["throttle"] += 0.12
self.ship.kb["throttle"] += 0.12
elif m == "throttle down":
self.booster.kb["throttle"] -= 0.12
self.ship.kb["throttle"] -= 0.12
elif m == "engines up":
self._bump_engines(1)
elif m == "engines down":
self._bump_engines(-1)
elif m == "target catch":
self.booster.target = "catch"
self.ship.target = "catch"
elif m == "target splash":
self.booster.target = "splash"
self.ship.target = "splash"
else:
for token in m.split():
v = self._active()
if token == "up":
v.kb["pitch"] += 0.10
elif token == "down":
v.kb["pitch"] -= 0.10
elif token == "left":
v.kb["yaw"] -= 0.10
elif token == "right":
v.kb["yaw"] += 0.10
def _bump_engines(self, d):
for v in (self.booster, self.ship):
v.engines = clamp(v.engines + d, 1, v.max_engines)
def _active(self):
if self.booster.resolved:
return self.ship
return self.booster
def _apply_ctrl(self, body):
v = self._active()
if body == "clear":
for veh in (self.booster, self.ship):
for key in ("pitch", "yaw", "flap", "throttle"):
veh.man.pop(key, None)
return
for p in body.split(","):
if not p or "=" not in p:
continue
k, val = p.split("=", 1)
if k == "p":
try:
v.man["pitch"] = clamp(float(val), -1.0, 1.0)
except ValueError:
pass
elif k == "y":
try:
v.man["yaw"] = clamp(float(val), -1.0, 1.0)
except ValueError:
pass
elif k == "f":
try:
v.man["flap"] = clamp(float(val), -1.0, 1.0)
except ValueError:
pass
elif k == "t":
try:
v.man["throttle"] = clamp(float(val), 0.0, 1.0)
except ValueError:
pass
elif k == "e":
try:
n = clamp(int(round(float(val))), 1, 3)
self.booster.man["engines"] = n
self.ship.man["engines"] = n
except ValueError:
pass
elif k == "g":
if val in ("catch", "splash"):
self.booster.man["target"] = val
self.ship.man["target"] = val
def step(self, action):
move = action.move
self._apply_controls(move)
dt = 0.0 if move.startswith("ctrl:") else self._current_warp()
if dt > 0:
self._advance(dt)
self.last = move
return self._observe(move), True, None
def _advance(self, dt):
n = max(1, int(round(dt / SUB)))
h = dt / n
for _ in range(n):
self.t += h
self.stage_t += h
self._world(h)
def _world(self, h):
if self.stage == "launch":
self._launch(h)
else:
self._advance_booster(h)
self._advance_ship(h)
for p in self.pieces:
self._piece(p, h)
if self.booster.resolved and self.ship.resolved:
self.stage = "done"
def _launch(self, h):
p = clamp(self.t / LAUNCH_T, 0.0, 1.0)
x = X_SEP * p * p
y = Y_SEP * p * p
self.stack = (x, y, 0.0)
self.stack_vel = (2.0 * X_SEP * p / LAUNCH_T, 2.0 * Y_SEP * p / LAUNCH_T, 0.0)
for v in (self.booster, self.ship):
v.pos = self.stack
v.vel = self.stack_vel
v.pitch = 0.0
v.yaw = 0.0
v.throttle = 1.0
v.burn = 1.0
if p >= 1.0:
self.booster.phase = "return"
self.booster.pt = 0.0
self.ship.phase = "ascend"
self.ship.pt = 0.0
self.stage = "flight"
def _advance_booster(self, h):
v = self.booster
if v.resolved:
return
if v.phase == "return":
v.pt += h
self._booster_return(v)
elif v.phase == "landing":
self._guide_landing(v, h)
self._integrate(v, h)
self._collide(v)
self._resolve_landing(v)
def _advance_ship(self, h):
v = self.ship
if v.resolved:
return
if v.phase == "ascend":
v.pt += h
self._ship_ascend(v)
elif v.phase == "loiter":
if self.booster.resolved:
v.phase = "deorbit"
v.pt = 0.0
else:
self._ship_loiter(v)
elif v.phase == "deorbit":
v.pt += h
self._ship_deorbit(v)
elif v.phase == "reentry":
v.pt += h
self._ship_reentry(v)
elif v.phase == "flip":
v.g_pitch = 0.0
v.g_yaw = 0.0
v.g_throttle = 0.0
v.g_flap = 0.0
self._apply_manual(v)
self._integrate(v, h)
if abs(v.pitch) < 12.0:
v.phase = "landing"
v.pt = 0.0
elif v.phase == "landing":
self._guide_landing(v, h)
self._integrate(v, h)
self._collide(v)
self._resolve_landing(v)
def _ship_reentry(self, v):
tau = clamp(v.pt / REENTRY_T, 0.0, 1.0)
s = smoothstep(tau)
x = ENTRY_X * (1.0 - s)
y = ENTRY_Y + (1000.0 - ENTRY_Y) * s
v.pos = (x, y, 0.0)
v.vel = (0.0, -40.0 - 90.0 * s, 0.0)
v.pitch = 90.0
v.yaw = 0.0
v.throttle = 0.0
v.burn = 0.0
v.heat = clamp(math.sin(math.pi * clamp(tau, 0.0, 1.0)) * 1.2, 0.0, 1.0)
if tau >= 1.0:
v.pos = (0.0, 1000.0, 0.0)
v.vel = (0.0, -40.0, 0.0)
v.pitch = 90.0
v.phase = "flip"
v.pt = 0.0
def _booster_return(self, v):
tau = clamp(v.pt / RETURN_T, 0.0, 1.0)
if tau < 0.35:
v.pitch = 0.0 + 180.0 * smoothstep(tau / 0.35)
v.burn = 0.9 * smoothstep(tau / 0.15)
v.throttle = 0.85
elif tau < 0.6:
v.pitch = 180.0
v.burn = 0.0
v.throttle = 0.0
elif tau < 0.82:
v.pitch = 180.0 * (1.0 - smoothstep((tau - 0.6) / 0.22))
v.burn = 0.8 * smoothstep((tau - 0.6) / 0.08)
v.throttle = 0.75
else:
v.pitch = 0.0
v.burn = 0.6
v.throttle = 0.5
s = smoothstep(tau)
x = X_SEP * (1.0 - s)
y = Y_SEP + (0.0 - Y_SEP) * s
arc = 26000.0 * math.sin(math.pi * clamp(tau, 0.0, 1.0))
y = y + arc * (1.0 if tau < 0.55 else max(0.0, 1.0 - (tau - 0.55) / 0.45))
v.pos = (x, y, 0.0)
v.vel = (-10.0 * s * 3.0 - 8.0, -70.0 * s, 0.0)
if tau >= 1.0:
v.pos = (0.0, 2200.0, 0.0)
v.vel = (-6.0, -70.0, 0.0)
v.pitch = 0.0
v.burn = 0.0
v.throttle = 0.0
v.phase = "landing"
v.pt = 0.0
def _ship_ascend(self, v):
p = clamp(v.pt / 30.0, 0.0, 1.0)
x = X_SEP + (ENTRY_X - X_SEP) * p
y = Y_SEP + (200000.0 - Y_SEP) * p
v.burn = 1.0
v.throttle = 1.0
v.pitch = 0.0
v.pos = (x, y, 0.0)
if p >= 1.0:
v.phase = "loiter"
v.pt = 0.0
def _ship_loiter(self, v):
v.pos = (ENTRY_X + math.sin(self.t * 0.02) * 8000.0, 200000.0, 0.0)
v.burn = 0.0
v.throttle = 0.0
v.pitch = 0.0
def _ship_deorbit(self, v):
p = clamp(v.pt / 18.0, 0.0, 1.0)
x = ENTRY_X
y = 200000.0 + (ENTRY_Y - 200000.0) * p
v.burn = 0.9
v.throttle = 0.9
v.pitch = 180.0
v.pos = (x, y, 0.0)
if p >= 1.0:
v.pos = (ENTRY_X, ENTRY_Y, 0.0)
v.vel = (0.0, -60.0, 0.0)
v.pitch = 90.0
v.yaw = 0.0
v.throttle = 0.0
v.burn = 0.0
v.phase = "reentry"
v.pt = 0.0
def _current_warp(self):
if self.stage == "launch":
return 0.12
if self.stage == "done":
return 0.0
v = self._active()
return {
"return": 0.6,
"landing": 0.05,
"ascend": 1.2,
"loiter": 2.5,
"deorbit": 0.8,
"reentry": 0.14,
"flip": 0.05,
}.get(v.phase, 0.05)
def _integrate(self, v, h):
rho = density(v.pos[1])
wind = self._wind_vec(v.pos[1])
vrel = (v.vel[0] - wind[0], v.vel[1] - wind[1], v.vel[2] - wind[2])
sp = vlen(vrel)
n = v.nose()
b = v.belly()
thrust = v.throttle * v.engines * v.f_engine / v.mass
ax = n[0] * thrust
ay = n[1] * thrust - G * (RE / (RE + v.pos[1])) ** 2
az = n[2] * thrust
cd = v.cdrag()
drag = rho * sp * cd
ax -= drag * vrel[0]
ay -= drag * vrel[1]
az -= drag * vrel[2]
lift = rho * sp * v.flap * 0.0004
ax += b[0] * lift
ay += b[1] * lift
az += b[2] * lift
rate = 15.0 + 70.0 * (rho / RHO0) + 9.0 * v.throttle * v.engines
dth = clamp(v.g_throttle - v.throttle, -1.6 * h, 1.6 * h)
v.throttle = clamp(v.throttle + dth, 0.0, 1.0)
dp = clamp(v.g_pitch - v.pitch, -rate * h, rate * h)
v.pitch += dp
dy = clamp(v.g_yaw - v.yaw, -rate * 0.8 * h, rate * 0.8 * h)
v.yaw += dy
df = clamp(v.g_flap - v.flap, -2.0 * h, 2.0 * h)
v.flap = clamp(v.flap + df, -1.0, 1.0)
v.vel = (v.vel[0] + ax * h, v.vel[1] + ay * h, v.vel[2] + az * h)
v.pos = (v.pos[0] + v.vel[0] * h, v.pos[1] + v.vel[1] * h, v.pos[2] + v.vel[2] * h)
decay = math.exp(-h / 0.4)
v.kb["pitch"] *= decay
v.kb["yaw"] *= decay
v.kb["throttle"] *= decay
def _apply_manual(self, v):
if "pitch" in v.man:
v.g_pitch = clamp(v.g_pitch - v.man["pitch"] * 40.0, -180.0, 180.0)
if "yaw" in v.man:
v.g_yaw = clamp(v.g_yaw + v.man["yaw"] * 25.0, -90.0, 90.0)
if "flap" in v.man:
v.g_flap = clamp(v.man["flap"], -1.0, 1.0)
if "throttle" in v.man:
v.g_throttle = clamp(v.man["throttle"], 0.0, 1.0)
if "engines" in v.man:
v.engines = clamp(int(v.man["engines"]), 1, v.max_engines)
if "target" in v.man:
v.target = v.man["target"]
def _guide_landing(self, v, h):
ty = CATCH_Y if v.target == "catch" else 0.0
tx, tz = self._target_point(v)
x, y, z = v.pos
vx, vy, vz = v.vel
agl = max(0.5, y - ty)
need = G + max(0.0, vy * vy) / (2.0 * agl)
v.g_throttle = clamp(need / max(v.max_accel(), 1e-6), 0.0, 1.0)
kp, kd = 0.05, 1.2
ax = kp * (tx - x) - kd * vx
az = kp * (tz - z) - kd * vz
v.g_pitch = clamp(math.degrees(clamp(ax / G, -0.3, 0.3)) - clamp(v.kb["pitch"], -1.0, 1.0) * 30.0, -25.0, 25.0)
v.g_yaw = clamp(math.degrees(clamp(az / G, -0.3, 0.3)) - clamp(v.kb["yaw"], -1.0, 1.0) * 30.0, -25.0, 25.0)
v.g_throttle = clamp(v.g_throttle + v.kb["throttle"], 0.0, 1.0)
v.g_flap = 0.0
self._apply_manual(v)
def _collide(self, v):
if v.resolved:
return
x, y, z = v.pos
sp = vlen(v.vel)
dxy = math.hypot(x, z)
if dxy < TOWER_R + 4.0 and 0.0 < y < TOWER_H:
self._crash(v, "flew into the tower")
self.tower_damaged = True
self.tower_lean = 0.14
return
if 0.0 <= x <= 32.0 and abs(z) <= 14.0 and 108.0 <= y <= 124.0 and sp > 18.0:
self._crash(v, "slammed into the arms")
self.tower_damaged = True
self.tower_lean = 0.1
def _target_point(self, v):
if v.target == "catch":
return (CATCH_X, CATCH_Z)
return (SPLASH_X, self.splash_z)
def _resolve_landing(self, v):
if v.resolved:
return
ty = CATCH_Y if v.target == "catch" else 0.0
tx, tz = self._target_point(v)
x, y, z = v.pos
vx, vy, vz = v.vel
dx = x - tx
dz = z - tz
dxy = math.sqrt(dx * dx + dz * dz)
if v.target == "catch":
if y <= ty + 1.5 and vy > -SAFE_VDOWN:
if dxy < 8.0 and abs(vz) < 3.0 and abs(vx) < 3.0 and abs(v.pitch) < 15.0:
self._catch(v)
else:
self._crash(v, "missed the arms")
elif y <= 0.0:
self._crash(v, "hit the ground")
else:
if y <= 0.5 and vy > -SAFE_VDOWN and x > WATER_X:
self._splash(v)
elif y <= 0.5 and vy > -SAFE_VDOWN and x <= WATER_X:
if abs(v.pitch) < 20.0:
v.resolved = True
v.splashed = True
v.outcome = v.name + " touched down on the pad"
self.outcomes.append(v.outcome)
else:
self._crash(v, "toppled on the pad")
elif y <= 0.0:
self._crash(v, "hit too fast")
if self.booster.resolved and self.ship.resolved:
self.stage = "done"
self.warp = 0.0
def _catch(self, v):
v.resolved = True
v.caught = True
v.outcome = v.name + " caught by the tower arms"
self.outcomes.append(v.outcome)
self.reward += 0.5
v.pos = (CATCH_X, CATCH_Y, CATCH_Z)
v.vel = (0.0, 0.0, 0.0)
def _splash(self, v):
v.resolved = True
v.splashed = True
v.outcome = v.name + " splashed down gently in the water"
self.outcomes.append(v.outcome)
self.reward += 0.3
v.pos = (v.pos[0], 0.0, v.pos[2])
v.vel = (0.0, 0.0, 0.0)
def _crash(self, v, why):
v.resolved = True
v.broken = True
v.outcome = v.name + " broke apart (" + why + ")"
self.outcomes.append(v.outcome)
self.reward -= 0.5
self._spawn_pieces(v)
def _spawn_pieces(self, v):
n = int(self.rng.uniform(7, 13))
for _ in range(n):
ang = self.rng.uniform(0.0, math.tau)
up = self.rng.uniform(0.3, 1.0)
spd = self.rng.uniform(6.0, 30.0)
self.pieces.append(
{
"pos": (v.pos[0], v.pos[1], v.pos[2]),
"vel": (
math.cos(ang) * spd,
up * spd,
math.sin(ang) * spd,
),
"size": self.rng.uniform(2.5, 7.5),
"color": v.color,
}
)
def _piece(self, p, h):
x, y, z = p["pos"]
vx, vy, vz = p["vel"]
rho = density(y)
sp = math.sqrt(vx * vx + vy * vy + vz * vz)
drag = rho * sp * 0.004
vx -= drag * vx * h
vy -= (G + drag * vy) * h
vz -= drag * vz * h
x += vx * h
y += vy * h
z += vz * h
if y <= 0.0:
y = 0.0
vy = 0.0
vx *= 0.4
vz *= 0.4
p["pos"] = (x, y, z)
p["vel"] = (vx, vy, vz)
def _observe(self, move):
active = self._active()
rho = density(active.pos[1])
wind = self._wind_vec(active.pos[1])
wind_spd = math.hypot(wind[0], wind[2])
scene = self._scene()
horizon = self._horizon(active.pos[1])
altitude = max(0.0, active.pos[1])
speed = vlen(active.vel)
vs = active.vel[1]
phase_label = self._phase_label()
wind_label = self._wind_label(wind_spd)
obs = base_observation(
reward=clamp(self.reward, -1.0, 1.0),
done=(self.stage == "done"),
legal_moves=[],
last_move=move,
scene=scene,
horizon=horizon,
atmo=clamp(rho / RHO0, 0.0, 1.0),
altitude=int(altitude),
speed=int(speed),
vertical=int(vs),
throttle=round(active.throttle, 2),
engines=active.engines,
flap=round(active.flap, 2),
target=active.target,
air=round(clamp(rho / RHO0, 0.0, 1.0), 2),
wind=wind_label,
wind_deg=int(self.wind_dir),
phase=phase_label,
active=active.name,
booster=self._status(self.booster),
ship=self._status(self.ship),
outcomes=list(self.outcomes),
tower_damaged=self.tower_damaged,
inputs=self._inputs(),
note=self._note(),
)
return obs
def _status(self, v):
if v.resolved:
return v.outcome
if v.phase == "ascend":
return "to orbit"
if v.phase == "loiter":
return "in orbit"
if v.phase == "deorbit":
return "deorbiting"
if v.phase == "reentry":
return "reentering"
if v.phase == "flip":
return "flipping"
if v.phase == "landing":
return "landing"
if v.phase == "return":
return "returning"
return "on the pad"
def _phase_label(self):
if self.stage == "launch":
return "Liftoff"
if self.stage == "done":
return "Mission complete"
v = self._active()
return {
"return": "Booster boostback",
"landing": ("Booster landing" if v.kind == "booster" else "Ship landing"),
"ascend": "Ship to orbit",
"loiter": "Ship in orbit",
"deorbit": "Deorbit burn",
"reentry": "Ship reentry",
"flip": "Ship flip",
}.get(v.phase, v.phase)
def _wind_label(self, spd):
wd = self.wind_dir % 360.0
dirs = ["north", "north-east", "east", "south-east", "south", "south-west", "west", "north-west"]
idx = int(round(wd / 45.0)) % 8
return f"{int(spd)} m/s from the {dirs[idx]}"
def _horizon(self, y):
h = max(0.0, y)
dip = math.acos(clamp(RE / (RE + h), -1.0, 1.0))
return clamp(0.46 + dip * 1.6, 0.46, 0.9)
def _note(self):
if self.stage == "done":
return "Mission over. Press R to reset."
if self.stage == "launch":
return "Starship lifting off. Watch the boostback."
v = self._active()
if v.kind == "booster":
return "Booster returns. Steer it to the tower arms or a splash."
return "Ship reenters belly-first, then flips to land."
def _inputs(self):
return [
key_input(
"Aim",
"WASD or arrows pitch and yaw",
{
"w": "up", "ArrowUp": "up",
"s": "down", "ArrowDown": "down",
"a": "left", "ArrowLeft": "left",
"d": "right", "ArrowRight": "right",
},
hold=True,
every_ms=50,
idle="stay",
),
key_input("Throttle", "E more thrust, Q less", {"e": "throttle up", "q": "throttle down"}, hold=True),
key_input("Engines", "X more engines, Z fewer", {"x": "engines up", "z": "engines down"}),
key_input("Landing", "C catch on tower, V splash down", {"c": "target catch", "v": "target splash"}),
]
def _scene(self):
cam_target = self._active().pos
cam = vadd(cam_target, (-320.0, 210.0, 520.0))
shapes = []
self._water(shapes, cam)
self._pad(shapes, cam)
self._tower(shapes, cam)
for p in self.pieces:
self._shape_piece(shapes, cam, p)
if not self.booster.resolved:
self._shape_vehicle(shapes, cam, self.booster)
if not self.ship.resolved and self.stage != "launch":
self._shape_vehicle(shapes, cam, self.ship)
shapes.sort(key=lambda s: s.get("d", 0.0), reverse=True)
return shapes
def _cam_space(self, cam, p):
forward = vnorm(vsub(self._active().pos, cam))
world_up = (0.0, 1.0, 0.0)
right = vnorm(vcross(forward, world_up))
up = vcross(right, forward)
d = vsub(p, cam)
return (vdot(d, right), vdot(d, up), vdot(d, forward))
def _project_cs(self, cs):
xc, yc, zc = cs
z = zc if zc > NEAR else NEAR
sx = CX + FOCAL * xc / z
sy = CY - FOCAL * yc / z
return (clamp(sx, -4000.0, 4000.0), clamp(sy, -4000.0, 4000.0))
def _clip_poly(self, pts_cs):
out = []
n = len(pts_cs)
for i in range(n):
a = pts_cs[i]
b = pts_cs[(i + 1) % n]
ain = a[2] >= NEAR
bin_ = b[2] >= NEAR
if ain:
out.append(a)
if ain != bin_:
t = (NEAR - a[2]) / (b[2] - a[2] - 1e-9)
out.append((a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, NEAR))
return out
def _clip_line(self, a, b):
ain = a[2] >= NEAR
bin_ = b[2] >= NEAR
if ain and bin_:
return [a, b]
if ain != bin_:
t = (NEAR - a[2]) / (b[2] - a[2] - 1e-9)
mid = (a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, NEAR)
return [a, mid] if ain else [mid, b]
return []
def _push(self, shapes, cam, kind, pts, fill, stroke, width, op=1.0):
if not pts:
return
cs = [self._cam_space(cam, p) for p in pts]
if kind == "circle":
if cs[0][2] < NEAR:
return
sx, sy = self._project_cs(cs[0])
px = [[sx, sy]]
depth = cs[0][2]
elif kind == "line":
clipped = self._clip_line(cs[0], cs[1])
if not clipped:
return
px = [[self._project_cs(c)[0], self._project_cs(c)[1]] for c in clipped]
depth = (clipped[0][2] + clipped[-1][2]) / 2.0
else:
clipped = self._clip_poly(cs)
if len(clipped) < 3:
return
px = [[self._project_cs(c)[0], self._project_cs(c)[1]] for c in clipped]
depth = sum(c[2] for c in clipped) / len(clipped)
shapes.append(
{
"t": kind,
"p": px,
"f": fill,
"s": stroke,
"w": width,
"o": op,
"d": depth,
}
)
def _water(self, shapes, cam):
y = 0.0
pts = [
(-1200.0, y, -1500.0),
(3000.0, y, -1500.0),
(3000.0, y, 1500.0),
(-1200.0, y, 1500.0),
]
self._push(shapes, cam, "poly", pts, "#0b3a63", None, 0, 0.75)
def _pad(self, shapes, cam):
y = 0.02
pts = [
(-70.0, y, -70.0),
(140.0, y, -70.0),
(140.0, y, 70.0),
(-70.0, y, 70.0),
]
self._push(shapes, cam, "poly", pts, "#3f3f46", None, 0, 1.0)
def _tower(self, shapes, cam):
lean = self.tower_lean
top = (TOWER_H * math.sin(lean), TOWER_H * math.cos(lean), 0.0)
base = (0.0, 0.0, 0.0)
self._push(shapes, cam, "line", [base, top], None, "#78716c", 7.0, 1.0)
self._push(shapes, cam, "line", [base, top], None, "#57534e", 3.0, 1.0)
arm_y = CATCH_Y
a1 = [
(-6.0, arm_y, -8.0),
(30.0, arm_y, -8.0),
]
a2 = [
(-6.0, arm_y, 8.0),
(30.0, arm_y, 8.0),
]
self._push(shapes, cam, "line", a1, None, "#292524", 4.0, 1.0)
self._push(shapes, cam, "line", a2, None, "#292524", 4.0, 1.0)
self._push(shapes, cam, "circle", [(CATCH_X, CATCH_Y, CATCH_Z)], "#84cc16", None, 3.0, 0.9)
def _shape_vehicle(self, shapes, cam, v):
n = v.nose()
half = v.length / 2.0
tail = vsub(v.pos, vmul(n, half))
nose = vadd(v.pos, vmul(n, half))
self._push(shapes, cam, "line", [tail, nose], v.color, "#1c1917", 9.0, 1.0)
self._push(shapes, cam, "line", [tail, nose], None, "#f4f4f5", 4.5, 1.0)
if v.kind == "ship" and v.heat > 0.05:
b = v.belly()
heat = v.heat
bp = vadd(v.pos, vmul(b, 6.0))
self._push(shapes, cam, "circle", [bp], "#f97316", "#fbbf24", 4.0, clamp(heat, 0.2, 1.0))
bp2 = vadd(v.pos, vmul(b, 10.0))
self._push(shapes, cam, "circle", [bp2], "#fbbf24", None, 2.0, clamp(heat * 0.8, 0.1, 1.0))
if v.burn > 0.01:
flen = v.length * (0.5 + 0.9 * v.burn)
fdir = vmul(n, -1.0)
nozzle = tail
tip = vadd(nozzle, vmul(fdir, flen))
right = vnorm(vcross(fdir, (0.0, 0.0, 1.0)))
w = 4.0 + 4.0 * v.burn
p1 = vadd(nozzle, vmul(right, w))
p2 = vadd(nozzle, vmul(right, -w))
self._push(shapes, cam, "poly", [p1, p2, tip], "#fbbf24", None, 0, 0.95)
tip2 = vadd(nozzle, vmul(fdir, flen * 0.5))
self._push(shapes, cam, "poly", [p1, p2, tip2], "#ffffff", None, 0, 0.8)
def _shape_piece(self, shapes, cam, p):
self._push(shapes, cam, "circle", [p["pos"]], p["color"], None, 1.2, 1.0)