30 t, F, mO, mF = header_curve()
31 body = dict(time_array=t.tolist(), thrust_array=F.tolist(), mdot_O_array=mO.tolist(), mdot_F_array=mF.tolist(),
32 lox_mass_kg=cfg.lox_tank.mass, fuel_mass_kg=cfg.fuel_tank.mass)
34 return FlightSimRequest(**body)
72 cfg.design_requirements.max_apogee_m = 15000 * 0.3048
73 cfg.design_requirements.max_apogee_datum =
"MSL"
76 assert r.status ==
"success"
77 assert c[
"ceiling_agl_m"] == pytest.approx(3945.33, abs=0.01)
78 assert c[
"nominal_apogee_agl_m"] == pytest.approx(r.apogee_m)
79 assert "surface_roughness_m 0" in c[
"corner"]
80 assert c[
"corner_apogee_agl_m"] > c[
"nominal_apogee_agl_m"] + 100.0,
"a smooth skin flies higher"
81 assert c[
"corner_margin_m"] == pytest.approx(c[
"ceiling_agl_m"] - c[
"corner_apogee_agl_m"])
82 assert c[
"violated"]
is False
83 assert r.apogee_msl_m == pytest.approx(r.apogee_m + 626.67)
99 cfg.design_requirements.max_apogee_m = 3000.0
100 cfg.design_requirements.max_apogee_datum =
"AGL"
101 t, F, mO, mF = header_curve()
102 req = FlightOptimizeRequest(time_array=t.tolist(), thrust_array=F.tolist(), mdot_O_array=mO.tolist(),
103 mdot_F_array=mF.tolist(), target_apogee_m=3500.0, apogee_tolerance_m=15.0)
104 session = types.SimpleNamespace(app_state=types.SimpleNamespace(has_config=
lambda:
True, config=cfg,
105 snapshot=
lambda: (cfg,
None)))
106 out = asyncio.run(optimize_flight_altitude(req, session))
107 assert out.success
is False and "ceiling" in out.infeasible_reason
and out.simulations_run == 0
118 rk = RocketConfig(airframe_mass=43.43, engine_mass=24.54, lox_tank_structure_mass=4.1, fuel_tank_structure_mass=5.0,
119 copv_dry_mass=3.2, radius=0.078359, inertia=[8.0, 8.0, 0.5])
120 d = build_flight_config(cfg,
_request(cfg, rocket=rk))
121 PintleEngineConfig(**d)
123 assert (r[
"engine_mass"], r[
"lox_tank_structure_mass"], r[
"fuel_tank_structure_mass"], r[
"copv_dry_mass"]) == (24.54, 4.1, 5.0, 3.2)
124 assert r[
"propulsion_dry_mass"] == pytest.approx(24.54 + 4.1 + 5.0 + 3.2)
125 assert r[
"nose_kind"] == cfg.rocket.nose_kind,
"a field the request omits keeps the config's value"
129 """+10 kg of engine: the 1-DOF (engine/pipeline/flight_1dof, checked against Sutton) predicts
130 the loss with the same drag; the flight must move by about as much, not by zero."""
136 rk = dict(airframe_mass=cfg.rocket.airframe_mass, lox_tank_structure_mass=cfg.rocket.lox_tank_structure_mass,
137 fuel_tank_structure_mass=cfg.rocket.fuel_tank_structure_mass, copv_dry_mass=cfg.rocket.copv_dry_mass,
138 radius=cfg.rocket.radius, inertia=list(cfg.rocket.inertia), fins=cfg.rocket.fins.model_dump())
139 light =
_simulate(cfg,
_request(cfg, rocket=RocketConfig(engine_mass=cfg.rocket.engine_mass, **rk)))
140 heavy =
_simulate(cfg,
_request(cfg, rocket=RocketConfig(engine_mass=cfg.rocket.engine_mass + 10.0, **rk)))
141 d = light.report[
"drag"]
142 drag = DragCurves(tuple(d[
"mach"]), tuple(d[
"cd_power_off"]), tuple(d[
"cd_power_on"]), d[
"model"], d[
"source"])
143 t, F, mO, mF = header_curve()
144 m0 = light.report[
"wet_mass_kg"]
145 kw = dict(area_m2=math.pi * cfg.rocket.radius**2, drag=drag, elevation_m=626.67)
146 predicted = vertical_apogee_agl(t, F, mO + mF, m0, **kw) - vertical_apogee_agl(t, F, mO + mF, m0 + 10.0, **kw)
147 moved = light.apogee_m - heavy.apogee_m
148 assert heavy.report[
"wet_mass_kg"] == pytest.approx(m0 + 10.0, abs=1e-6)
149 assert moved == pytest.approx(predicted, rel=0.1), (moved, predicted)
159 env = EnvironmentConfig(latitude=35.35, longitude=-117.81, elevation=1500.0, date=[2026, 1, 30, 18])
164 p_pad = compute_ambient_pressure_from_elevation(cfg.environment.elevation)
165 assert r.report[
"reference_pressure_pa"] == pytest.approx(p_pad, rel=1e-9)
167 assert stated.report[
"reference_pressure_pa"] == 90000.0