Skip to content

Commit 3cbb52e

Browse files
committed
Add cryocooler support
1 parent f729f92 commit 3cbb52e

9 files changed

Lines changed: 463 additions & 114 deletions

File tree

RealFuels/CryoCooler.cfg

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Cryocooler defaults for RealFuels tanks.
2+
// The slider ceiling, mass/cost scaling, and the fraction-of-Carnot efficiency curve are
3+
// all KSPFields so they can be overridden per part config or via PARTUPGRADE for tech tiers.
4+
//
5+
// COP = (T_cold / (T_hot - T_cold)) * cryoCoolerEfficiency.Evaluate(T_cold)
6+
//
7+
// The default curve is calibrated to published flight cryocooler data:
8+
// - 4 K class (He / lab): ~1% of Carnot
9+
// - 20 K class (LH2): ~5-15% of Carnot (NICMOS ≈ 6%, modern RBC ≈ 10-15%)
10+
// - 77-90 K class (LN2/LOX): ~20-30% of Carnot (Sunpower CryoTel ≈ 24%, NASA RBC ≈ 30%)
11+
// - 150+ K (mild refrigeration): ~40-50% of Carnot
12+
//
13+
// Mass: coolerBaseMass = 0.050 t (50 kg) fixed overhead: electronics, housing, mounting
14+
// coolerMassPerKWInput = curve (t/kW), keyed to T_cold (K)
15+
// Captures the hardware-complexity penalty of multi-stage coolers:
16+
// 250 K → 0.010 t/kW (warm refrigeration, trivial single-stage)
17+
// 150 K → 0.015 t/kW (easy single-stage)
18+
// 90 K → 0.020 t/kW (reference: modern Stirling/PT flight hardware)
19+
// 65 K → 0.025 t/kW (pushing single-stage limits)
20+
// 40 K → 0.030 t/kW (two-stage often required)
21+
// 20 K → 0.060 t/kW (two-stage, LH2 class)
22+
// 4 K → 0.120 t/kW (three-stage He JT+PT, JWST MIRI class)
23+
// Cost: coolerBaseCost = 150 funds ($150k 1965) fixed per-unit overhead
24+
// coolerCostPerKWInput = curve (funds/kW), same temperature shape as mass
25+
// Reference at 90 K: 1500 funds/kW ($1.5M/kW 1965)
26+
// 1 fund = $1,000 (1965 USD)
27+
//
28+
// kW input | kg | funds | reference
29+
// ---------+---------+------------+-------------------------------------------
30+
// 0.055 | 9.5 | ? | Ball SB230 (mid 1990s, ~65K)
31+
// 0.08 | 1.3 | ~5 | Sunpower CryoTel GT (commercial, 2020s, 40K)
32+
// 0.15 | 10.5 | ~250 | Ball SB235 (space-qualified, mid 2000s, ~65K)
33+
// 0.24 | ~55 | ~1,800 | NICMOS NCS (mission-qualified, late 1990s, ~77K)
34+
// 0.255 | 14.4 | ? | Ball SB235E (mid 2000s, ~65K)
35+
// 0.475 | ~400 | ? | JWST MIRI Cryocooler (NGAS, 4K-class He JT + PT, ~2011)
36+
37+
// TODO: figure out something better than zzzRealFuels
38+
@PART[*]:HAS[@MODULE[ModuleFuelTanks]]:FOR[zzzRealFuels]
39+
{
40+
@MODULE[ModuleFuelTanks]
41+
{
42+
maxCoolerInputKW = 20
43+
coolerBaseMass = 0.050
44+
coolerMassPerKWInput
45+
{
46+
key = 4 0.120
47+
key = 20 0.060
48+
key = 40 0.030
49+
key = 65 0.025
50+
key = 90 0.020
51+
key = 150 0.015
52+
key = 250 0.010
53+
}
54+
coolerBaseCost = 150
55+
coolerCostPerKWInput
56+
{
57+
key = 4 9000
58+
key = 20 4500
59+
key = 40 2250
60+
key = 65 1875
61+
key = 90 1500
62+
key = 150 1125
63+
key = 250 750
64+
}
65+
cryoCoolerEfficiency
66+
{
67+
key = 0 0
68+
key = 4 0.01
69+
key = 20 0.08
70+
key = 65 0.20
71+
key = 90 0.25
72+
key = 150 0.40
73+
key = 250 0.50
74+
}
75+
}
76+
}

RealFuels/Localization/en-us.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ Localization
181181
#RF_FuelTankRF_NoMLI = No MLI
182182
#RF_FuelTankRF_Boiloffunit = kg/hr
183183
#RF_FuelTankRF_kerbalismtips = boiloff product
184+
#RF_FuelTankRF_CryoCoolerInputPower = Cooler Input Power
185+
#RF_FuelTankRF_CryoCoolerLift = Cooling Lift
186+
#RF_FuelTankRF_CryoCoolerDraw = Power Draw
187+
#RF_FuelTankRF_CryoCoolerCOP = Avg COP
184188

185189
#RF_TankDefineSelection_HighlyPressurized = Highly Pressurized
186190
#RF_TankDefineSelection_NotHighlyPressurized = Not Highly Pressurized

RealFuels/Localization/pt-br.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ Localization
177177
#RF_FuelTankRF_NoMLI = Sem MLI
178178
#RF_FuelTankRF_Boiloffunit = kg/h
179179
#RF_FuelTankRF_kerbalismtips = produto de evaporação
180+
#RF_FuelTankRF_CryoCoolerInputPower = Cooler Input Power
181+
#RF_FuelTankRF_CryoCoolerLift = Cooling Lift
182+
#RF_FuelTankRF_CryoCoolerDraw = Power Draw
183+
#RF_FuelTankRF_CryoCoolerCOP = Avg COP
180184

181185
#RF_TankDefineSelection_HighlyPressurized = Altamente Pressurizado
182186
#RF_TankDefineSelection_NotHighlyPressurized = Não Altamente Pressurizado

RealFuels/Localization/ru.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ Localization
177177
#RF_FuelTankRF_NoMLI = Без ЭВТИ
178178
#RF_FuelTankRF_Boiloffunit = кг/ч
179179
#RF_FuelTankRF_kerbalismtips = резальтат испарения
180+
#RF_FuelTankRF_CryoCoolerInputPower = Cooler Input Power
181+
#RF_FuelTankRF_CryoCoolerLift = Cooling Lift
182+
#RF_FuelTankRF_CryoCoolerDraw = Power Draw
183+
#RF_FuelTankRF_CryoCoolerCOP = Avg COP
180184

181185
#RF_TankDefineSelection_HighlyPressurized = Бак высокого давления
182186
#RF_TankDefineSelection_NotHighlyPressurized = Бак нормальн. давления

RealFuels/Localization/zh-cn.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ Localization
177177
#RF_FuelTankRF_NoMLI = 无隔热层
178178
#RF_FuelTankRF_Boiloffunit = 千克/时
179179
#RF_FuelTankRF_kerbalismtips = 蒸发产出
180+
#RF_FuelTankRF_CryoCoolerInputPower = Cooler Input Power
181+
#RF_FuelTankRF_CryoCoolerLift = Cooling Lift
182+
#RF_FuelTankRF_CryoCoolerDraw = Power Draw
183+
#RF_FuelTankRF_CryoCoolerCOP = Avg COP
180184

181185
#RF_TankDefineSelection_HighlyPressurized = 高压
182186
#RF_TankDefineSelection_NotHighlyPressurized = 非高压

RealFuels/MFSSettings.cfg

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ MFSSETTINGS
55
BatteryMultiplier = 1
66

77
basemassUseTotalVolume = True
8-
9-
radiatorMinTempMult = 0.99
10-
8+
119
IgnoreFuelsForFill
1210
{
1311
IntakeAir = True

Source/Tanks/BgBoiloffCache.cs

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,105 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
15
namespace RealFuels.Tanks
26
{
37
internal sealed class BgBoiloffCache
48
{
5-
internal readonly string DataVersion;
69
internal readonly int MliLayers;
710
internal readonly BgTankEntry[] Tanks;
8-
internal readonly double[] InternalTemps; // mutable, parallel to Tanks; -1 = uninitialized
11+
internal readonly double[] InternalTemps; // mutable, parallel to Tanks
12+
internal readonly double[] FluxScratch; // mutable per-tick scratch for Q_kW pre-pass
13+
14+
internal readonly double CoolerInputKW; // (0 if no cooler installed)
15+
internal readonly double CoolerFrac; // fraction-of-Carnot at CoolerLowestTempK
16+
internal readonly double CoolerLowestTempK; // cold-side T of the cooler
917

10-
internal BgBoiloffCache(string dataVersion, int mliLayers, BgTankEntry[] tanks)
18+
internal BgBoiloffCache(int mliLayers, BgTankEntry[] tanks,
19+
double coolerInputKW, double coolerFrac, double coolerLowestTempK)
1120
{
12-
DataVersion = dataVersion;
1321
MliLayers = mliLayers;
1422
Tanks = tanks;
1523
InternalTemps = new double[tanks.Length];
16-
for (int i = 0; i < tanks.Length; i++) InternalTemps[i] = -1d;
24+
FluxScratch = new double[tanks.Length];
25+
CoolerInputKW = coolerInputKW;
26+
CoolerFrac = coolerFrac;
27+
CoolerLowestTempK = coolerLowestTempK;
28+
}
29+
30+
internal static BgBoiloffCache Build(string data, string coolerData, int mliLayers)
31+
{
32+
var tanks = new List<BgTankEntry>();
33+
34+
foreach (string entry in data.Split(';'))
35+
{
36+
if (string.IsNullOrEmpty(entry)) continue;
37+
string[] split = entry.Split(',');
38+
if (split.Length != 7) continue;
39+
40+
string resourceName = split[0];
41+
if (!double.TryParse(split[1], NumberStyles.Float, CultureInfo.InvariantCulture, out double boilingPointK)) continue;
42+
if (!double.TryParse(split[2], NumberStyles.Float, CultureInfo.InvariantCulture, out double tankAreaM2)) continue;
43+
if (!double.TryParse(split[3], NumberStyles.Float, CultureInfo.InvariantCulture, out double conductWPerK)) continue;
44+
if (!int.TryParse(split[4], out int isDewarInt)) continue;
45+
if (!double.TryParse(split[5], NumberStyles.Float, CultureInfo.InvariantCulture, out double hsp)) continue;
46+
if (!double.TryParse(split[6], NumberStyles.Float, CultureInfo.InvariantCulture, out double structThermalMassKJ)) continue;
47+
48+
PartResourceDefinition resDef = PartResourceLibrary.Instance.GetDefinition(resourceName);
49+
if (resDef == null || resDef.density <= 0d) continue;
50+
if (!MFSSettings.resourceVsps.TryGetValue(resourceName, out double vsp) || vsp <= 0) continue;
51+
52+
tanks.Add(new BgTankEntry
53+
{
54+
Name = resourceName,
55+
Vsp = vsp,
56+
Density = resDef.density,
57+
BoilingPointK = boilingPointK,
58+
TankAreaM2 = tankAreaM2,
59+
ConductWPerK = conductWPerK,
60+
IsDewar = isDewarInt != 0,
61+
Hsp = hsp,
62+
StructThermalMassKJ = structThermalMassKJ,
63+
});
64+
}
65+
66+
double coolerInputKW = 0d, coolerFrac = 0d, coolerLowestTempK = 0d;
67+
if (!string.IsNullOrEmpty(coolerData))
68+
{
69+
string[] cSplit = coolerData.Split(',');
70+
if (cSplit.Length == 3
71+
&& double.TryParse(cSplit[0], NumberStyles.Float, CultureInfo.InvariantCulture, out coolerInputKW)
72+
&& double.TryParse(cSplit[1], NumberStyles.Float, CultureInfo.InvariantCulture, out coolerFrac)
73+
&& double.TryParse(cSplit[2], NumberStyles.Float, CultureInfo.InvariantCulture, out coolerLowestTempK))
74+
{
75+
// ok
76+
}
77+
else
78+
{
79+
coolerInputKW = 0d; coolerFrac = 0d; coolerLowestTempK = 0d;
80+
}
81+
}
82+
83+
return new BgBoiloffCache(mliLayers, tanks.ToArray(), coolerInputKW, coolerFrac, coolerLowestTempK);
84+
}
85+
86+
internal void InitTemps(ProtoPartModuleSnapshot proto_module)
87+
{
88+
// Seed InternalTemps from the persisted TANK nodes
89+
var tempLookup = new Dictionary<string, double>(StringComparer.Ordinal);
90+
foreach (ConfigNode tankNode in proto_module.moduleValues.GetNodes("TANK"))
91+
{
92+
string tName = tankNode.GetValue("name");
93+
string sVal = tankNode.GetValue("internalTemp");
94+
if (tName != null && double.TryParse(sVal, NumberStyles.Float, CultureInfo.InvariantCulture, out double t))
95+
tempLookup[tName] = t;
96+
}
97+
98+
for (int i = 0; i < Tanks.Length; i++)
99+
{
100+
InternalTemps[i] = tempLookup.TryGetValue(Tanks[i].Name, out double v) && v > 0
101+
? v : Tanks[i].BoilingPointK;
102+
}
17103
}
18104
}
19105

Source/Tanks/MFSSettings.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ public class MFSSettings
1414
public static bool partUtilizationTweakable = false;
1515
public static string unitLabel = "u";
1616
public static bool basemassUseTotalVolume = false;
17-
public static double radiatorMinTempMult = 0.99d;
1817

1918
// Move all possible tank upgrades into the preview list in OnStart
2019
// It requires an external mod to be responsible for calling the Validate() method.
@@ -99,7 +98,6 @@ public static void ModuleManagerPostLoad()
9998
node.TryGetValue("partUtilizationTweakable", ref partUtilizationTweakable);
10099
node.TryGetValue("unitLabel", ref unitLabel);
101100
node.TryGetValue("basemassUseTotalVolume", ref basemassUseTotalVolume);
102-
node.TryGetValue("radiatorMinTempMult", ref radiatorMinTempMult);
103101
node.TryGetValue("previewAllLockedTypes", ref previewAllLockedTypes);
104102

105103
ignoreFuelsForFill.Clear();

0 commit comments

Comments
 (0)