This commit is contained in:
2026-03-03 16:43:30 +00:00
commit 03452517b5
58 changed files with 13181 additions and 0 deletions

View File

@@ -0,0 +1,234 @@
// Pure calculation functions for rocket vehicle design (no React)
const G0 = 9.80665 // m/s² standard gravity
/**
* Calculate full rocket geometry and performance from engine data and rocket inputs.
*
* @param {object} engineData — results section from engine JSON (allThermo, nozzleGeometry, etc.)
* @param {object} rocketInputs — {
* outerRadius, // m — vehicle outer radius
* burnTime, // s
* arrangement, // 'coaxial' | 'tandem'
* innerPropellant, // 'fuel' | 'ox' (coaxial only — which propellant goes in inner tank)
* rhoFuel, // kg/m³
* rhoOx, // kg/m³
* payloadMass, // kg
* payloadBayLength, // m
* structMassFraction, // 01 (dry mass fraction of propellant mass)
* }
* @returns {object|null}
*/
export function calcRocketGeometry(engineData, rocketInputs) {
if (!engineData || !rocketInputs) return null
const {
outerRadius,
burnTime,
arrangement,
innerPropellant,
rhoFuel,
rhoOx,
payloadMass,
payloadBayLength,
structMassFraction,
} = rocketInputs
const Isp = engineData?.allThermo?.Isp
const F = engineData?.allThermo?.F
const mdot = engineData?.allThermo?.mdot
const OF = engineData?.allThermo?.OF
// Flow rates: use direct values if solved, otherwise derive from mdot + OF
let mdot_f = engineData?.allThermo?.mdot_f
let mdot_ox = engineData?.allThermo?.mdot_ox
if (mdot && OF && isFinite(mdot) && isFinite(OF) && OF > 0) {
if (!mdot_f || !isFinite(mdot_f)) mdot_f = mdot / (1 + OF)
if (!mdot_ox || !isFinite(mdot_ox)) mdot_ox = mdot * OF / (1 + OF)
}
// Require at minimum: radius, propellant densities, and flow rates from an engine
if (
!outerRadius || outerRadius <= 0 ||
!rhoFuel || !rhoOx ||
!mdot_f || !mdot_ox ||
!isFinite(mdot_f) || !isFinite(mdot_ox)
) return null
const R = outerRadius
const tb = (burnTime && burnTime > 0) ? burnTime : 30
// ── Propellant volumes ──────────────────────────────────────────────
const m_fuel = mdot_f * tb
const m_ox = mdot_ox * tb
const V_fuel = m_fuel / rhoFuel
const V_ox = m_ox / rhoOx
const V_prop = V_fuel + V_ox
// ── Nose cone (ogive approximation: height = 2R) ────────────────────
const L_nose = 2 * R
// ── Payload bay ─────────────────────────────────────────────────────
const L_payload = payloadBayLength ?? 0
// ── Engine section (from nozzle geometry if available) ──────────────
const ng = engineData?.nozzleGeometry
const cg = engineData?.chamberGeometry
const L_engine = (ng?.Ln ?? 0) + (cg?.Lc ?? 0)
// ── Tank geometry ───────────────────────────────────────────────────
let L_tank_fuel, L_tank_ox, L_tank, r_inner
if (arrangement === 'coaxial') {
// Both tanks share the same axial section; total volume in annulus + inner cylinder
// Inner tank radius from the smaller propellant volume
// Outer tank occupies the remaining annular area
const V_inner = innerPropellant === 'fuel' ? V_fuel : V_ox
const V_outer = innerPropellant === 'fuel' ? V_ox : V_fuel
// Solve for tank length using outer volume first (conservative — outer is usually larger)
// V_outer = π (R² - r_inner²) L_tank and V_inner = π r_inner² L_tank
// From V_inner / (V_inner + V_outer) = r_inner² / R²
// → r_inner = R √(V_inner / V_prop_total)
r_inner = R * Math.sqrt(V_inner / V_prop)
// Guard: inner radius must be smaller than outer
if (r_inner >= R) r_inner = R * 0.7
L_tank = V_prop / (Math.PI * R * R)
L_tank_fuel = arrangement === 'coaxial' ? L_tank : null
L_tank_ox = arrangement === 'coaxial' ? L_tank : null
} else {
// Tandem: stacked cylinders
L_tank_fuel = V_fuel / (Math.PI * R * R)
L_tank_ox = V_ox / (Math.PI * R * R)
L_tank = L_tank_fuel + L_tank_ox
r_inner = null
}
// ── Total vehicle length ────────────────────────────────────────────
const totalLength = L_nose + L_payload + L_tank + L_engine
// ── Mass budget ─────────────────────────────────────────────────────
const m_prop = m_fuel + m_ox
const m_struct = m_prop * (structMassFraction ?? 0.10)
const m_payload = payloadMass ?? 0
const m_dry = m_struct + m_payload
const m_wet = m_prop + m_dry
const massRatio = m_wet / m_dry
// ── Tsiolkovsky delta-v ─────────────────────────────────────────────
const deltaV = Isp && isFinite(Isp) && massRatio > 1
? Isp * G0 * Math.log(massRatio)
: null
// ── TWR at liftoff ──────────────────────────────────────────────────
const TWR = F && isFinite(F) && m_wet > 0
? F / (m_wet * G0)
: null
return {
// Dimensions
outerRadius: R,
L_nose,
L_payload,
L_tank,
L_tank_fuel,
L_tank_ox,
L_engine,
totalLength,
r_inner,
// Mass
m_fuel,
m_ox,
V_fuel,
V_ox,
m_prop,
m_struct,
m_payload,
m_dry,
m_wet,
// Performance
massRatio,
deltaV,
TWR,
// Config
arrangement,
innerPropellant: arrangement === 'coaxial' ? innerPropellant : null,
// Nozzle geometry for 3D model (null → fall back to a generic flare)
nozzleExitRadius: ng?.re ?? null,
nozzleThroatRadius: ng?.rt ?? null,
chamberLength: cg?.Lc ?? 0,
nozzleLength: ng?.Ln ?? 0,
}
}
/**
* Returns a list of { key, label, ok, value } requirement objects so the UI
* can show exactly what is missing before the calc can run.
*/
export function diagnoseRocketInputs(engineCalcData, rocketInputs) {
const t = engineCalcData?.allThermo ?? {}
const mdot = t.mdot
const OF = t.OF
const mdot_f_raw = t.mdot_f
const mdot_ox_raw = t.mdot_ox
// Derived flow rates (same logic as calcRocketGeometry)
let mdot_f = mdot_f_raw
let mdot_ox = mdot_ox_raw
if (mdot && OF && isFinite(mdot) && isFinite(OF) && OF > 0) {
if (!mdot_f || !isFinite(mdot_f)) mdot_f = mdot / (1 + OF)
if (!mdot_ox || !isFinite(mdot_ox)) mdot_ox = mdot * OF / (1 + OF)
}
const R = rocketInputs?.outerRadius
const bt = rocketInputs?.burnTime
const fmt = v => (v != null && isFinite(v)) ? v.toPrecision(4) : null
return [
{
key: 'mdot',
label: 'Total mass flow (ṁ)',
ok: !!(mdot && isFinite(mdot)),
value: fmt(mdot),
hint: 'Enter ṁ or Thrust + Isp on the Engine page',
},
{
key: 'OF',
label: 'O/F ratio',
ok: !!(OF && isFinite(OF) && OF > 0),
value: fmt(OF),
hint: 'Enter O/F ratio on the Engine page',
},
{
key: 'mdot_f',
label: 'Fuel flow rate (ṁ_f)',
ok: !!(mdot_f && isFinite(mdot_f)),
value: fmt(mdot_f),
hint: mdot && OF ? 'Derived from ṁ + O/F' : 'Needs ṁ and O/F both solved',
},
{
key: 'mdot_ox',
label: 'Oxidizer flow rate (ṁ_ox)',
ok: !!(mdot_ox && isFinite(mdot_ox)),
value: fmt(mdot_ox),
hint: mdot && OF ? 'Derived from ṁ + O/F' : 'Needs ṁ and O/F both solved',
},
{
key: 'outerRadius',
label: 'Outer diameter',
ok: !!(R && R > 0),
value: R ? `${(R * 2000).toFixed(0)} mm` : null,
hint: 'Enter outer diameter in Vehicle Geometry section',
},
{
key: 'burnTime',
label: 'Burn time',
ok: true, // always ok — falls back to 30 s
value: bt ? `${bt} s` : '30 s (default)',
hint: null,
},
]
}