STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_layerx_robustness.py
Go to the documentation of this file.
1"""Layer X's loops do not report "settled" on missing data, and a burn's step fits its clock.
2
3Each case is a failure the 2026-10-02 audit found reachable: a throat history with a gap, a time
4step that straddles Fire, and the flight's inputs reaching it from a run.
5"""
6
7from __future__ import annotations
8
9import math
10import os
11import sys
12
13import numpy as np
14import pytest
15
16sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
17
18from engine.layerx import replay as rpl # noqa: E402
19
20
22 old = (np.array([0.0, 1.0]), np.array([1.0, 1.04]))
23 assert rpl.schedule_change(old, None) == math.inf
24 assert rpl.schedule_change(None, None) == 0.0
25
26
28 rp = {"available": True, "t": [0.0, 1.0, 2.0, 3.0], "A_throat_m2": [1.0, float("nan"), 1.04, 1.06]}
29 t, a = rpl.throat_schedule(rp)
30 assert np.all(np.isfinite(a)) and a[1] == pytest.approx(1.02)
31 assert rpl.throat_schedule({"available": True, "t": [0.0, 1.0], "A_throat_m2": [float("nan"), 1.0]}) is None
32
33
34feedtwin = pytest.importorskip("feedtwin", reason="lib/feedtwin is not installed")
35
36from engine.layerx import DrawingStore, LayerXSettings, prepare, run_prepared # noqa: E402
37from engine.layerx.sources import shipped_drawings_dir # noqa: E402
38from engine.pipeline.io import load_config # noqa: E402
39
40ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
41CFG = os.path.join(ROOT, "configs", "ethalox_6800N.yaml")
42# The GN2 drawing is a nitrogen-over-LOX hot fire, refused since 2026-10-03 unless acknowledged
43# (engine/layerx/prepare.py gn2_on_lox); these tests study its burn, so their settings acknowledge it.
44needs_drawings = pytest.mark.skipif(not (shipped_drawings_dir() / "copv_study_gn2.json").is_file(),
45 reason="feed-twin's shipped drawings are not next to this checkout")
46
47
48@pytest.fixture(scope="module")
49def setup():
50 drawing = {d.name: d for d in DrawingStore(None).list()}["copv_study_gn2"]
51 return load_config(CFG), drawing
52
53
54@needs_drawings
56 cfg, drawing = setup
57 bad = prepare(cfg, None, drawing, LayerXSettings(drawing_id=drawing.id, ack_gn2_condensation=True, dt=0.2), [])
58 assert not bad.ok and any(c.key == "time_step" and c.status == "fail" for c in bad.checks)
59 good = prepare(cfg, None, drawing, LayerXSettings(drawing_id=drawing.id, ack_gn2_condensation=True, dt=0.05), [])
60 assert good.ok
61
62
63@needs_drawings
65 from engine.layerx import flight as flt
66
67 cfg, drawing = setup
68 seen = {}
69
70 def fake_fly(config, timeseries, loads, ambient_pa, **kw):
71 seen.update(kw)
72 return {"ok": False, "error": "no vehicle today", "notes": []}
73
74 monkeypatch.setattr(flt, "fly", fake_fly)
75 st = LayerXSettings(drawing_id=drawing.id, ack_gn2_condensation=True, replay=False, flight=True, liftoff_mass_kg=86.18)
76 res = run_prepared(prepare(cfg, None, drawing, st, []), replay=False, config=cfg)
77 assert seen["liftoff_mass_kg"] == pytest.approx(86.18)
78 # The drawing's 15.1 L + 8.67 L tanks at lockup hold about half a kilogram of nitrogen.
79 assert 0.3 < seen["ullage_gas_kg"] < 0.8
80 assert res["converged"] is True
81 assert any(e["label"] == "Flight failed" for e in res["events"])
test_the_run_hands_the_flight_its_mass_and_a_first_failure_leaves_the_pad_burn(setup, monkeypatch)