From 1b135271a3997b66c5bc5050317f9b9368932335 Mon Sep 17 00:00:00 2001 From: AMPW-german Date: Mon, 15 Jun 2026 22:22:50 +0200 Subject: [PATCH 1/4] Adds a basic fatigue mechanic --- GEffectsLogic/GEffectsLogic.csproj | 4 +- GEffectsLogic/LogicSettings.cs | 24 ++++++++- GEffectsLogic/PhysiologicalModel.cs | 82 ++++++++++++++++++++++++----- 3 files changed, 93 insertions(+), 17 deletions(-) diff --git a/GEffectsLogic/GEffectsLogic.csproj b/GEffectsLogic/GEffectsLogic.csproj index 0a2111e..436a87a 100644 --- a/GEffectsLogic/GEffectsLogic.csproj +++ b/GEffectsLogic/GEffectsLogic.csproj @@ -7,9 +7,9 @@ Debug;Release;PerfDebug False - 0.2.0 + 0.3.0 $(VersionPrefix) - 0.2.0.0 + 0.3.0.0 $(VersionPrefix).0 nogit diff --git a/GEffectsLogic/LogicSettings.cs b/GEffectsLogic/LogicSettings.cs index 3dc2d8f..c38562e 100644 --- a/GEffectsLogic/LogicSettings.cs +++ b/GEffectsLogic/LogicSettings.cs @@ -2,7 +2,7 @@ { public static class LogicSettings { - public static double StabilizationTimeThreshold { get; set; } = 30.0; + public static double StabilizationTimeThreshold { get; set; } = 600.0; // --- Physiological model parameters --- @@ -16,7 +16,6 @@ public static class LogicSettings public static double HydrostaticShiftExponent { get; set; } = 2.2; // Keep these summing to ~1.0 - public static double HeadCoreShiftFraction { get; set; } = 0.45; public static double CoreLowerShiftFraction { get; set; } = 0.55; // Passive return / compensation @@ -98,5 +97,26 @@ public static class LogicSettings public static double GSuitGlobalShiftReductionMax { get; set; } = 0.20; // optional mild global scaling public static double GSuitCoreLowerReductionMax { get; set; } = 0.60; // reduce core->lower pooling public static double GSuitLowerReturnBoostMax { get; set; } = 0.80; // increase lower return + + // --- Fatigue / resistance reduction --- + + // Fraction of GSuitEffectiveness that the suit retains passively (hardware inflation) regardless of fatigue + public static double GSuitPassiveFraction { get; set; } = 0.25; + + // Straining (AGSM) fatigue: rate at which the human straining component degrades + // Build rate is per-second at strainingLevel=1 (quadratic: actual rate = BuildRate * strainingLevel²) + // ~60-90s of max straining to saturate (1/BuildRate ≈ saturation time) + public static double StrainingFatigueBuildRate { get; set; } = 0.015; // saturates ~67s at full strain + public static double StrainingFatigueRecoveryTau { get; set; } = 150.0; // ~2.5 min to recover + + // G-suit mechanical fatigue: slower than straining fatigue (suit outlasts the pilot's AGSM) + // Build rate is per-second at strainingLevel=1 (linear: actual rate = BuildRate * strainingLevel) + public static double GSuitFatigueBuildRate { get; set; } = 0.004; // saturates ~250s at full strain + public static double GSuitFatigueRecoveryTau { get; set; } = 300.0; // ~5 min to recover + + // Cardiovascular fatigue: hrFatigue (0..1) accumulates at CardioFatigueBuildRate × hrElevation per second + public static double CardioFatigueBuildRate { get; set; } = 0.008; // ~125s at max HR elevation to fully fatigue + public static double CardioFatigueRecoveryTau { get; set; } = 240.0; // ~4 min to fully recover + public static double CardioFatigueMaxHRFloor { get; set; } = 1.25; // HR floor when fully fatigued } } \ No newline at end of file diff --git a/GEffectsLogic/PhysiologicalModel.cs b/GEffectsLogic/PhysiologicalModel.cs index 6df7579..7b94dba 100644 --- a/GEffectsLogic/PhysiologicalModel.cs +++ b/GEffectsLogic/PhysiologicalModel.cs @@ -24,15 +24,17 @@ namespace GEffectsLogic /// public class PhysiologicalModel { - // TODO - fix straining level being a fixed input - // --- Compartment blood volumes (fraction of total, sum = 1.0) --- protected double bloodHead = LogicSettings.RestingBloodHead; protected double bloodCore = LogicSettings.RestingBloodCore; protected double bloodLower = LogicSettings.RestingBloodLower; protected double brainO2 = 1.0; protected double heartRateMultiplier = 1.0; // Baroreceptor reflex: heart rate multiplier (1.0 = resting) - protected double strainingLevel = 0.0; // Straining effort (0..1): pilot anti-G straining maneuver including g suit inflation + protected double strainingLevel = 0.0; // Straining effort (0..1): pilot anti-G straining maneuver including g-suit inflation + protected double strainingFatigue = 0.0; // Accumulated AGSM fatigue (0..1): degrades the human straining component + protected double gSuitFatigue = 0.0; // Accumulated g-suit fatigue (0..1): degrades mechanical suit compression + protected double hrFatigue = 0.0; // Cardiovascular fatigue (0..1): suppresses baroreceptor HR toward fatigueHeartRateFloor + protected double fatigueHeartRateFloor = LogicSettings.CardioFatigueMaxHRFloor; protected double perfusionLevel = 0.0; protected double consciousnessLevel = 1.0; //protected double confusionLevel = 0.0; @@ -42,7 +44,7 @@ public class PhysiologicalModel #region Public read-only state public readonly int UniqueID; - + /// Fraction of total blood in the head compartment. public double BloodHead => bloodHead; @@ -58,6 +60,18 @@ public class PhysiologicalModel /// Current heart rate multiplier from baroreceptor reflex. public double HeartRateMultiplier => heartRateMultiplier; + /// Accumulated AGSM fatigue (0 = fresh, 1 = fully exhausted). + public double StrainingFatigue => strainingFatigue; + + /// Accumulated g-suit mechanical fatigue (0 = full effectiveness, 1 = degraded to passive fraction). + public double GSuitFatigue => gSuitFatigue; + + /// Cardiovascular fatigue accumulator (0 = fresh, 1 = fully fatigued — HR pinned to floor). + public double HrFatigue => hrFatigue; + + /// Fixed HR floor the baroreceptor is suppressed toward when fully fatigued. + public double FatigueHeartRateFloor => fatigueHeartRateFloor; + /// Current straining level (0 = none, 1 = max). //public double StrainingLevel => strainingLevel; @@ -88,6 +102,10 @@ public virtual void Reset() brainO2 = 1.0; heartRateMultiplier = 1.0; strainingLevel = 0.0; + strainingFatigue = 0.0; + gSuitFatigue = 0.0; + hrFatigue = 0.0; + fatigueHeartRateFloor = LogicSettings.CardioFatigueMaxHRFloor; tunnelVisionLevel = 0.0; greyScaleLevel = 0.0; primaryColor = true; @@ -148,8 +166,32 @@ public virtual void Update(double dt, double gz, double gx = 0.0, double gy = 0. targetStraining = Math.Clamp(targetStraining, 0.0, 1.0); strainingLevel = StepTowardsLinear(strainingLevel, targetStraining, LogicSettings.StrainingTau, dt); - // Suit effect only for +Gz loading - double suitActivation = Math.Clamp(LogicSettings.GSuitEffectiveness * strainingLevel, 0.0, 1.0); + // Fatigue: straining fatigue fills while strainingLevel is high, drains slowly at rest + double strainingFatigueBuildRate = LogicSettings.StrainingFatigueBuildRate * strainingLevel * strainingLevel; + double strainingFatigueDecayRate = 1.0 / LogicSettings.StrainingFatigueRecoveryTau; + strainingFatigue += strainingLevel > 0.01 + ? strainingFatigueBuildRate * dt + : -strainingFatigueDecayRate * strainingFatigue * dt; + strainingFatigue = Math.Clamp(strainingFatigue, 0.0, 1.0); + + // G-suit fatigue builds more slowly (mechanical, outlasts the pilot's AGSM), also drains slowly + double gSuitFatigueBuildRate = LogicSettings.GSuitFatigueBuildRate * strainingLevel; + double gSuitFatigueDecayRate = 1.0 / LogicSettings.GSuitFatigueRecoveryTau; + gSuitFatigue += strainingLevel > 0.01 + ? gSuitFatigueBuildRate * dt + : -gSuitFatigueDecayRate * gSuitFatigue * dt; + gSuitFatigue = Math.Clamp(gSuitFatigue, 0.0, 1.0); + + // Effective straining: human AGSM component fully degrades with strainingFatigue + double effectiveStraining = strainingLevel * (1.0 - strainingFatigue); + + // Effective g-suit: mechanical suit retains a passive fraction, only the active compression degrades + double gSuitActiveFraction = 1.0 - LogicSettings.GSuitPassiveFraction; + double effectiveGSuit = LogicSettings.GSuitEffectiveness * + (LogicSettings.GSuitPassiveFraction + (gSuitActiveFraction * (1.0 - gSuitFatigue))); + + // Suit effect only for +Gz loading, coupled to effective straining + double suitActivation = Math.Clamp(effectiveGSuit * effectiveStraining, 0.0, 1.0); double suit = gzNet > 0.0 ? suitActivation : 0.0; // Mild global scaling + targeted redistribution @@ -171,9 +213,9 @@ public virtual void Update(double dt, double gz, double gx = 0.0, double gy = 0. double returnRate = LogicSettings.PassiveReturnRate * heartRateMultiplier; double lowerReturnRate = returnRate * (1.0 + (LogicSettings.GSuitLowerReturnBoostMax * suit)); - bloodHead = (bloodHead + (shiftHeadRate + (returnRate * LogicSettings.RestingBloodHead)) * dt) / (1.0 + (returnRate * dt)); - bloodCore = (bloodCore + (shiftCoreRate + (returnRate * LogicSettings.RestingBloodCore)) * dt) / (1.0 + (returnRate * dt)); - bloodLower = (bloodLower + (shiftLowerRate + (lowerReturnRate * LogicSettings.RestingBloodLower)) * dt) / (1.0 + (lowerReturnRate * dt)); + bloodHead = (bloodHead + ((shiftHeadRate + (returnRate * LogicSettings.RestingBloodHead)) * dt)) / (1.0 + (returnRate * dt)); + bloodCore = (bloodCore + ((shiftCoreRate + (returnRate * LogicSettings.RestingBloodCore)) * dt)) / (1.0 + (returnRate * dt)); + bloodLower = (bloodLower + ((shiftLowerRate + (lowerReturnRate * LogicSettings.RestingBloodLower)) * dt)) / (1.0 + (lowerReturnRate * dt)); // Enforce conservation (redistribute any numerical drift) double total = bloodHead + bloodCore + bloodLower; @@ -247,9 +289,23 @@ public virtual void Update(double dt, double gz, double gx = 0.0, double gy = 0. brainO2 = StepTowardsLinear(brainO2, targetBrainO2, o2Tau, dt); brainO2 = Math.Clamp(brainO2, LogicSettings.BrainO2Floor, 1.0); - // When head perfusion drops, heart rate increases (delayed response) - double targetHR = 1.0 + (LogicSettings.BaroreceptorGain * Math.Max(0, 1.0 - perfusionRatio)); - targetHR = Math.Min(targetHR, LogicSettings.MaxHeartRateMultiplier); + // Cardiovascular fatigue: hrFatigue (0..1) accumulates with time × HR elevation, + // decays slowly at rest. It is independent of current HR so it always wins eventually. + double hrElevation = Math.Max(0.0, heartRateMultiplier - 1.0); + double hrFatigueBuildRate = LogicSettings.CardioFatigueBuildRate * hrElevation; + hrFatigue += hrElevation > 0.05 + ? hrFatigueBuildRate * dt + : -(hrFatigue / LogicSettings.CardioFatigueRecoveryTau) * dt; + hrFatigue = Math.Clamp(hrFatigue, 0.0, 1.0); + + // fatigueHeartRateFloor is a fixed setting: the resting HR offset the cardiovascular + // system is stuck at once fully fatigued (independent of feedback). + // Baroreceptor target from current perfusion deficit. + double baroTarget = 1.0 + (LogicSettings.BaroreceptorGain * Math.Max(0, 1.0 - perfusionRatio)); + baroTarget = Math.Min(baroTarget, LogicSettings.MaxHeartRateMultiplier); + + // hrFatigue suppresses baroreceptor response: at hrFatigue=1 the target is pinned to the floor. + double targetHR = baroTarget + (hrFatigue * (fatigueHeartRateFloor - baroTarget)); double hrTau = LogicSettings.BaroreceptorTimeConstant; heartRateMultiplier = StepTowardsLinear(heartRateMultiplier, targetHR, hrTau, dt); @@ -322,7 +378,7 @@ public virtual void Update(double dt, double gz, double gx = 0.0, double gy = 0. double visualReserve = (0.7 * visualPerf) + (0.3 * visualO2); double visualDeficit = 1.0 - visualReserve; - // Early/mid visual impairment path. + // Early/mid-visual impairment path. // Keeps onset before LOC, but avoids saturating too early. double physiologicalTunnelTarget = Math.Clamp((visualDeficit - 0.18) / 0.82, 0.0, 1.0); physiologicalTunnelTarget = Math.Pow(physiologicalTunnelTarget, 2.2); From 2c4433d5269d5828d96536db4d5f71c76240daef Mon Sep 17 00:00:00 2001 From: AMPW-german Date: Mon, 15 Jun 2026 22:23:43 +0200 Subject: [PATCH 2/4] Add multi tab support to the GraphicLogicTest --- GraphicLogicTest/MainWindow.axaml | 216 ++---------------- GraphicLogicTest/MainWindow.xaml.cs | 168 +++++--------- GraphicLogicTest/SimulationViewModel.cs | 162 +++++++++++++ GraphicLogicTest/Views/SecondaryView.axaml | 15 ++ GraphicLogicTest/Views/SecondaryView.axaml.cs | 13 ++ GraphicLogicTest/Views/SimulationView.axaml | 183 +++++++++++++++ .../Views/SimulationView.axaml.cs | 26 +++ 7 files changed, 473 insertions(+), 310 deletions(-) create mode 100644 GraphicLogicTest/SimulationViewModel.cs create mode 100644 GraphicLogicTest/Views/SecondaryView.axaml create mode 100644 GraphicLogicTest/Views/SecondaryView.axaml.cs create mode 100644 GraphicLogicTest/Views/SimulationView.axaml create mode 100644 GraphicLogicTest/Views/SimulationView.axaml.cs diff --git a/GraphicLogicTest/MainWindow.axaml b/GraphicLogicTest/MainWindow.axaml index 942814b..2e9d8b0 100644 --- a/GraphicLogicTest/MainWindow.axaml +++ b/GraphicLogicTest/MainWindow.axaml @@ -1,210 +1,40 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -