Pressure Relief Valve (#36708)

* initial system (this math is probably WRONG)

* General code cleanup and OnExamined support
(holy moly this code sucks)

* UICode and related events foundation
TODO:
- Actually write the XAML UI and the underlying system
- Un-shitcode the entire thing
- Actually test everything...

* Working UI code
TODO: Make predicted, as this certainly isn't predicted. Even though I said it was. It isn't.

* Remove one TODO for unshitcoding the examine code

* Add reminder
yea

* Make predicted (defenitely isn't)
(also defenitely isn't a copypaste from pressure pump code)

* It's predicted!
TODO:
- Give it snazzy predicted visuals!
- Have a different field for pressure entry, lest it gets bulldozed every UI update.

* Improve gas pressure relief valve UI
TODO: Reminder to reduce amount of dirties using deltafields

* Implement DirtyField prediction

* Entity<T> cleanup
A lot of Entity<T> conversions and lukewarm cleanup.

Also got caught copy pasting code in 4K UHD but it's not like you couldn't tell.

* More cleanup and comments

* Remove TODO comment on bulldozing window title

* """refactoring"""
- Move appearance out of shared and finally fix it. Pointless to predict appearance in this instance.
- More Entity<T> conversions because I like them.
- Move UI creation handling over entirely to the ActivatableUI system.
- Fix a hardcoded locale string (why????).

* Add visuals

* Revert debugging variable replacememt
yea

* Revert skissue

* Remove unused using directives and remove TODO

* Localize, cleanup, document

* Fix adminlogging discrepancy

* Add ability to construct, add guidebook entry

* Clear up comment

* Add guidebook tooltip to valve

* Convert GasPressureReliefValveBoundUserInterface declaration into primary constructor

* Adds more input handling and adds autofill on open

* Un-deepfry input validator shitcode
Genuinely what was I smoking

* improve visuals logic

* Refactor again
- Update math to the correct implementation
- Moved code that could be re-used in the future into a helper method under AtmosphereSystem.Gases.cs

* I'm sorry but I hate warnings

* Remove unused using directive in AtmosphereSystem.Gases.cs

* Review and cleanup

* Lukewarm UI glossup

* Maintainer for the upstream project btw

* Remove redundant state sets and messy logic

* Unduplicate valve updater code

* Redo UI (im sorry Slarti)

* run tests

* Test refactored UI messaging

* Second round of UI improvements
- God please find a way to improve this system. Feels bad.

* Update loop implementation

* Further predict UI

* Clear up SetToCurrentThreshold

* cleanup

* Update to master + pipe layers and bug fixes
want to run tests

* fixes

* Deploy rename pipebomb

* Documentation and requested changes

* Rename the method that wiggled away

* Undo rounding changes

* Fix comment

* Rename and cleanup

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
ArtisticRoomba
2025-07-03 09:00:34 -07:00
committed by GitHub
parent 1bc3d37d40
commit f874459092
32 changed files with 1173 additions and 80 deletions

View File

@@ -252,6 +252,128 @@ namespace Content.Server.Atmos.EntitySystems
Merge(destination, buffer);
}
/// <summary>
/// Calculates the dimensionless fraction of gas required to equalize pressure between two gas mixtures.
/// </summary>
/// <param name="gasMixture1">The first gas mixture involved in the pressure equalization.
/// This mixture should be the one you always expect to be the highest pressure.</param>
/// <param name="gasMixture2">The second gas mixture involved in the pressure equalization.</param>
/// <returns>A float (from 0 to 1) representing the dimensionless fraction of gas that needs to be transferred from the
/// mixture of higher pressure to the mixture of lower pressure.</returns>
/// <remarks>
/// <para>
/// This properly takes into account the effect
/// of gas merging from inlet to outlet affecting the temperature
/// (and possibly increasing the pressure) in the outlet.
/// </para>
/// <para>
/// The gas is assumed to expand freely,
/// so the temperature of the gas with the greater pressure is not changing.
/// </para>
/// </remarks>
/// <example>
/// If you want to calculate the moles required to equalize pressure between an inlet and an outlet,
/// multiply the fraction returned by the source moles.
/// </example>
public float FractionToEqualizePressure(GasMixture gasMixture1, GasMixture gasMixture2)
{
/*
Problem: the gas being merged from the inlet to the outlet could affect the
temp. of the gas and cause a pressure rise.
We want the pressure to be equalized, so we have to account for this.
For clarity, let's assume that gasMixture1 is the inlet and gasMixture2 is the outlet.
We require mechanical equilibrium, so \( P_1' = P_2' \)
Before the transfer, we have:
\( P_1 = \frac{n_1 R T_1}{V_1} \)
\( P_2 = \frac{n_2 R T_2}{V_2} \)
After removing fraction \( x \) moles from the inlet, we have:
\( P_1' = \frac{(1 - x) n_1 R T_1}{V_1} \)
The outlet will gain the same \( x n_1 \) moles of gas.
So \( n_2' = n_2 + x n_1 \)
After mixing, the outlet temperature will be changed.
Denote the new mixture temperature as \( T_2' \).
Volume is constant.
So we have:
\( P_2' = \frac{(n_2 + x n_1) R T_2}{V_2} \)
The total energy of the incoming inlet to outlet gas at \( T_1 \) plus the existing energy of the outlet gas at \( T_2 \)
will be equal to the energy of the new outlet gas at \( T_2' \).
This leads to the following derivation:
\( x n_1 C_1 T_1 + n_2 C_2 T_2 = (x n_1 C_1 + n_2 C_2) T_2' \)
Where \( C_1 \) and \( C_2 \) are the heat capacities of the inlet and outlet gases, respectively.
Solving for \( T_2' \) gives us:
\( T_2' = \frac{x n_1 C_1 T_1 + n_2 C_2 T_2}{x n_1 C_1 + n_2 C_2} \)
Once again, we require mechanical equilibrium (\( P_1' = P_2' \)),
so we can substitute \( T_2' \) into the pressure equation:
\( \frac{(1 - x) n_1 R T_1}{V_1} =
\frac{(n_2 + x n_1) R}{V_2} \cdot
\frac{x n_1 C_1 T_1 + n_2 C_2 T_2}
{x n_1 C_1 + n_2 C_2} \)
Now it's a matter of solving for \( x \).
Not going to show the full derivation here, just steps.
1. Cancel common factor \( R \).
2. Multiply both sides by \( x n_1 C_1 + n_2 C_2 \), so that everything
becomes a polynomial in terms of \( x \).
3. Expand both sides.
4. Collect like powers of \( x \).
5. After collecting, you should end up with a polynomial of the form:
\( (-n_1 C_1 T_1 (1 + \frac{V_2}{V_1})) x^2 +
(n_1 T_1 \frac{V_2}{V_1} (C_1 - C_2) - n_2 C_1 T_1 - n_1 C_2 T_2) x +
(n_1 T_1 \frac{V_2}{V_1} C_2 - n_2 C_2 T_2) = 0 \)
Divide through by \( n_1 C_1 T_1 \) and replace each ratio with a symbol for clarity:
\( k_V = \frac{V_2}{V_1} \)
\( k_n = \frac{n_2}{n_1} \)
\( k_T = \frac{T_2}{T_1} \)
\( k_C = \frac{C_2}{C_1} \)
*/
// Ensure that P_1 > P_2 so the quadratic works out.
if (gasMixture1.Pressure < gasMixture2.Pressure)
{
(gasMixture1, gasMixture2) = (gasMixture2, gasMixture1);
}
// Establish the dimensionless ratios.
var volumeRatio = gasMixture2.Volume / gasMixture1.Volume;
var molesRatio = gasMixture2.TotalMoles / gasMixture1.TotalMoles;
var temperatureRatio = gasMixture2.Temperature / gasMixture1.Temperature;
var heatCapacityRatio = GetHeatCapacity(gasMixture2) / GetHeatCapacity(gasMixture1);
// The quadratic equation is solved for the transfer fraction.
var quadraticA = 1 + volumeRatio;
var quadraticB = molesRatio - volumeRatio + heatCapacityRatio * (temperatureRatio + volumeRatio);
var quadraticC = heatCapacityRatio * (molesRatio * temperatureRatio - volumeRatio);
return (-quadraticB + MathF.Sqrt(quadraticB * quadraticB - 4 * quadraticA * quadraticC)) / (2 * quadraticA);
}
/// <summary>
/// Determines the number of moles that need to be removed from a <see cref="GasMixture"/> to reach a target pressure threshold.
/// </summary>
/// <param name="gasMixture">The gas mixture whose moles and properties will be used in the calculation.</param>
/// <param name="targetPressure">The target pressure threshold to calculate against.</param>
/// <returns>The difference in moles required to reach the target pressure threshold.</returns>
/// <remarks>The temperature of the gas is assumed to be not changing due to a free expansion.</remarks>
public static float MolesToPressureThreshold(GasMixture gasMixture, float targetPressure)
{
// Kid named PV = nRT.
return gasMixture.TotalMoles -
targetPressure * gasMixture.Volume / (Atmospherics.R * gasMixture.Temperature);
}
/// <summary>
/// Checks whether a gas mixture is probably safe.
/// This only checks temperature and pressure, not gas composition.