EngineDesign overnight sweep: solver window, config-driven stability, flight dedupe, lockout heartbeat, UI cleanup - #58
Closed
Carlsaurus wants to merge 5 commits into
Closed
Carlsaurus wants to merge 5 commits into
Carlsaurus wants to merge 5 commits into
Conversation
chamber_geometry.design_MR / design_pressure / design_thrust are supposed to
describe the design the config represents. Nothing ever wrote them.
config_schemas builds them with `getattr(chamber, 'design_MR', 2.55)`, so an
optimised config carried its template's values forward forever. On a real
emitted design they read MR 2.55 / 350 psi / 7000 N for an engine actually
solved at O/F 1.65 / 416 psi / 7200 N.
Not cosmetic: backend/routers/geometry.py reads design_MR and passes it
straight to solve_chamber_geometry_with_cea, so the Chamber Geometry tab drew
the contour at the stale mixture ratio -- and 2.55 sits OUTSIDE that config's
CEA cache range (MR_range [1.0, 2.5]), i.e. extrapolating past the table edge
in the one place the cache has no data.
_layer1_stamp_design_point() now writes the solved MR / Pc / F onto the config
at the point Layer 1 hands it back, mirrors them onto the legacy `chamber`
section for readers that still fall back to it, skips non-finite values so a
failed evaluate cannot overwrite a good design point with NaN, and warns when
the achieved MR falls outside combustion.cea.MR_range. Verified on a live run:
2.55 / 350 psi / 7000 N in, 1.6461 / 416.1 psi / 7200 N out, matching the
solver exactly.
Three regression tests cover it -- stamping, the range warning, and the
non-finite guard -- because the root problem was that nothing checked these
fields at all.
Also deletes engine/pipeline/comprehensive_geometry_sizing.py: 495 lines, zero
importers, unchanged since the initial commit, and carrying an IndentationError
that means it has NEVER been importable. It is recoverable from history if it
turns out to be wanted.
Audited for the same class of staleness and found two more, NOT fixed here
because they belong to other layers and are inert on the configs I can see:
- regen_cooling.chamber_inner_diameter / channel_length are synced only by
chamber_optimizer.py, not Layer 1, so they still read 0.08491 m / 0.18162 m
against an actual 0.127 m / 0.20337 m chamber. Harmless while
regen_cooling.enabled is false; wrong channel sizing the moment it is not.
- pressure_curves.initial_lox/fuel_pressure_pa (537.3 / 523.7 psi) disagree
with lox_tank/fuel_tank.initial_pressure_psi (548.6), because Layer 2 wrote
the curves before Layer 1 last moved the tanks. An ordering problem, not a
missing write.
Separately verified: all 379 config schema fields are referenced somewhere, so
the config is not carrying dead knobs; and comprehensive_geometry_sizing was
the only module in engine/ or backend/ that fails to import.
Verified: pytest 472 passed / 84 skipped with only the 4 pre-existing failures;
accelerator parity 16/16.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The initial tank pressure exists twice, owned by different layers and never reconciled: Layer 1 writes lox_tank/fuel_tank.initial_pressure_psi (layer1_static_optimization.py:2435), Layer 2 writes pressure_curves.initial_lox/fuel_pressure_pa (layer2_pressure.py:312). Re-running Layer 1 moves the tanks and silently leaves the curves describing the previous design. Measured on a real emitted config: LOX 548.6 psi vs a 537.3 psi curve start (11.3 psi), fuel 548.6 vs 523.7 (24.9 psi). This matters more than it looks. Tank pressure is the UPSTREAM BOUNDARY CONDITION for the feed-system twin, and per docs/adr/0001 EngineDesign's optimizer imports lib/feedtwin directly for Layer X rather than calling a service. Two disagreeing values for one boundary condition is precisely what silently poisons a twin, so Layer 1 now says so at the end of every run. Detection only. Which layer should own the value is a design call and guessing it here would be worse than the warning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two problems docs/adr/0001 names by name, both in the COPV -> regulator -> tank chain that lib/feedtwin absorbs. Fixed surgically: defaults are exactly the old literals, so nothing in EngineDesign changes behaviour unless someone overrides them. 1. Orifice geometry was hardcoded in the function body. dynamics.step() defined gamma_gas 1.4, Cd_regulator 0.7, Cd_valve 0.65, A_regulator 2e-5, A_valve_F/O 5e-5 as locals. These are real hardware numbers -- a different regulator or solenoid could not be modelled without editing code. They are now DynamicsParams fields, surfaced on ControllerConfig, and read through from_config with getattr so an older config still loads. 2. The polytropic reference state was PROCESS-GLOBAL. step() stored its reference temperatures, masses and volumes as attributes on the FUNCTION object, guarded by `hasattr(step, '_temp_initialized')`. It initialised on the first call ever made in a process and never reset, so the first trajectory stepped set the reference for every later one -- across different configs. An optimizer evaluating thousands of candidates, which is exactly the Layer X access pattern the ADR describes, would hand candidates 2..N the reference state of candidate 1. All 36 references now live on the `params` instance, which scopes them per-config. Verified: two DynamicsParams objects no longer share reference state, and nothing is left on the step function object. Four regression tests cover both fixes, including one that fails if process-global state ever returns. Also checked the other two feed fragments the ADR names, feed_loss.py and stability/chug.py: neither carries hardcoded physical constants, so they need nothing before Phase 04 collapses them into feedtwin. Verified: pytest 477 passed / 84 skipped with only the 4 pre-existing failures; robust_ddp suite 12 passed; accelerator parity 16/16. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The frontend had NO error boundary anywhere -- grep for ErrorBoundary / componentDidCatch / getDerivedStateFromError returned nothing. In React that means any exception thrown during render unmounts the ENTIRE tree, so every render bug presents identically: a white page, no message, no stack, no way to report what happened. That is how "press Optimize on the flight page and it goes blank" arrived with nothing to act on. Adds components/ErrorBoundary.tsx and wraps each of the nine tab panels in one. The tab buttons live in <header>, above the panels, so a crashing tab now shows a copyable error with its component stack and a Try again button while the tab bar stays alive and every other tab keeps working. Verified end to end, not just by inspection: injected a throw at the top of FlightSimulation, reloaded, and confirmed the boundary rendered "Flight Simulation hit an error" with the stack while the rest of the app -- header, all nine tabs, the other panels -- stayed mounted and usable. Throw reverted. Note this does not by itself remove the underlying throw on the flight optimize path. I could not reproduce that one: the handler is correctly guarded (checks result.error, checks result.data, wrapped in try/catch -- though try/catch does not cover render), and every field the flight UI renders is a REQUIRED float or List[float] in the backend models, with all Optional sub-objects already guarded (results?.truncation?., results.propellant &&, and the !results?.trajectory early return). With the boundary in place the next occurrence prints the real error instead of a blank page, which is what makes it fixable. The Try again button is declared VIEW_ONLY in the checkout gating audit: it clears local error state and touches no design state. Frontend gating audit 4 passed; npm run build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… flight dedupe, lockout heartbeat, UI cleanup
Physics / solver
- Chamber-pressure root-find window: choked-flow floor (2 atm) and tank-pressure ceiling
(min tank minus 2%) replace the 1 bar floor and hardcoded 15% "feed loss margin"; both
Python and the numba kernel now scan for the HIGHEST-Pc sign change before Brent. The old
window let Brent lock onto a spurious ~20 psi root: configs/default.yaml reported
F = -333 N at its own configured tank pressures, and the Python side said "no solution"
at 550/650/900 psi where the kernel solved. Both paths now agree at every pressure.
- Stability model reads config, not constants: StabilityConfig (n, chi, nozzle-entrance
Mach, damping fractions, regulator, acoustic gate allowance), feed_system.<side>.length
(the chug inertance length was a hardcoded 0.305 m with no schema field), cp_g from the
CEA state (was a fixed 2200 J/kg-K), nozzle-entrance Mach solved from the contraction
ratio (was 0.2), per-fluid handbook fallbacks recorded via assume() (fuel fallbacks were
methane's for every fuel). comprehensive_stability_analysis looked up feed_system["lox"]
.length, a key and attribute that never existed, so feed acoustics always used 1.0 m x
10 mm; the legacy and rich paths also used different chamber lengths. Chug gate centred
at gain margin 1.0 (0.80 called an unstable loop "stable"). Rigged heuristics removed:
stability_index floors, the water-hammer-to-margin map, and stability/enhanced.py.
- Ea mixture-ratio step in reaction_chemistry smoothed (20% jump at MR 1.5); the dead
reaction-progress computation on every chamber solve removed.
- Layer 1: refuses a target O/F outside the propellant's CEA table (a propellant switch
kept the old target and optimized against a table that could not evaluate it); names the
blocking hard constraint when a run ends with no feasible candidate; 1 m chamber-length
cap removed; ED_L1_WORKERS / ED_L1_TRACE_INFEAS debugging hooks.
- Canonical pintle design_MR 2.55 -> 1.4 (ethalox table is [1.0, 2.5]).
Flight
- Optimizer Layer 4 now renders the shared FlightSimulation component; the 1100-line copy
it replaced hardcoded RP-1 density and an 85% fill factor client-side, so every
non-kerolox design capped its fuel load against the wrong tank.
- flight router: explicit_capacity_kg carried a bool; rocket/environment fallbacks derived
from the request models instead of a drifting literal copy.
- tests/test_flight_propellant_iteration.py rebuilt on configs/default.yaml (it read a file
out of one developer's Downloads folder); the tank-cap test now constructs its premise.
Checkout lockout
- useCheckout re-takes the checkout every 60 s while held and no longer releases on tab
hide: the token lapsed after 5 min without a changed autosave and the next write came
back 423 ("Take Design 1 before saving").
UI
- Emoji removed everywhere; legend and axis-title overlap fixed on every chart with the
pattern (Layer 1 convergence, pressure curves, controller, custom plotter, heat flux,
flight); Design Requirements input caps that assumed one rocket removed; CEA O/F range
shown under the target; stale stability prose replaced; Time-Series simple profile seeds
from the design's tank pressures; Stability Model section in the config editor;
unreachable Coaxial label dropped.
Schema hygiene
- Removed fields nothing read (hot_gas_cp, yield_strength, youngs_modulus, mixing_model)
and the stale hot_gas_cp keys in shipped configs; pydantic ConfigDict; fuel tank
descriptions no longer say RP-1.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overnight sweep of EngineDesign: Layer 1, the flight/propellant optimizer, the stability model, and the frontend. Stacked on #56 and #57 (their commits are included here; merge those first or merge this one and close them).
Physics that was wrong
Pc_min = 1 bar, Brent converged to the spurious 20 psi root —configs/default.yamlevaluated to F = −333 N at its own configured tank pressures (815/677 psi). The hardcoded 15% "feed loss margin" ceiling also excluded real soft-injector operating points, and the Python solver's endpoint-only sign test reported "no solution" at 550/650/900 psi where the numba kernel found the root. Fix: choked-flow floor (2 atm), tank-pressure ceiling (min tank − 2%), and a highest-Pc bracket scan, mirrored verbatim in both paths. Both now agree at every pressure; canonical results unchanged (0 diffs in a 6-combo snapshot).cpwas a fixed 2200 J/kg·K (now CEAγR/(γ−1)); feed-line length for the chug inertance was a hardcoded 0.305 m with no schema field; nozzle-entrance Mach was 0.2 (now solved from the contraction ratio);comprehensive_stability_analysislooked upfeed_system["lox"].length— neither the key nor the attribute ever existed — so feed acoustics always used 1.0 m × 10 mm; the legacy and rich paths used different chamber lengths (1L mode 1772 Hz vs 1284 Hz for the same chamber). NewStabilityConfigblock (n, χ, Mach, damping fractions, regulator, gate allowance) andfeed_system.<side>.length, all editable in the Configuration tab. Chug gate centred at gain margin 1.0 (it was 0.80, which called an unstable loop "stable"). Removed:stability_indexfloors, the water-hammer→margin piecewise map (one branch was literally a constant), andstability/enhanced.py(pintle spatial model fed hardcoded 50/30 m/s velocities; every output was overwritten).reaction_chemistrysmoothed (20% jump at MR 1.5); the dead reaction-progress computation on every chamber solve removed.design_MR2.55 → 1.4 (ethalox table is [1.0, 2.5]).Flight
explicit_capacity_kgin the flight diagnostics carried a bool; rocket/environment fallbacks derived from the request models (the literal copy said propulsion dry mass 24 kg while its own component defaults summed to 16).tests/test_flight_propellant_iteration.pyread a config out of~/Downloads; rebuilt onconfigs/default.yaml, and the tank-cap test now constructs its premise. That test also caught the bool bug.The lockout ("Take Design 1 before saving — your checkout has lapsed")
Reproduced on an isolated server: hold the checkout, do nothing for five minutes, the next autosave returns 423. The server frees a checkout with no heartbeat inside
lock_ttl(300 s); the only heartbeat was an autosave, and autosave writes only when the design changed; the hook also released the token onvisibilitychange: hidden, so tabbing away mid-optimization made the run's result write 423.useCheckoutnow re-takes every 60 s while held and only releases onpagehide.UI
Schema hygiene
Removed fields nothing read (
hot_gas_cp,yield_strength,youngs_modulus,mixing_model) plus the stalehot_gas_cpkeys in shipped configs; pydanticConfigDict; fuel-tank descriptions no longer say RP-1.Verification
Not verified visually: the chart overlap fix could not be screenshotted while the browser pane was hidden; it is a deterministic recharts layout change (legend
verticalAlign="top", bottom margin 20).🤖 Generated with Claude Code