Calculate turbulance.

Turbulance is based off time of day, and day of year.
Each theatre may adjust their turbulance parameters.
This commit is contained in:
SnappyComebacks 2022-11-18 16:15:47 -07:00
parent bc6f953f76
commit 1eccedb74d
14 changed files with 168 additions and 0 deletions

View File

@ -10,6 +10,7 @@ Saves from 5.x are not compatible with 6.0.
* **[Mission Generation]** Reworked the ground object generation which now uses a new layout system * **[Mission Generation]** Reworked the ground object generation which now uses a new layout system
* **[Mission Generation]** Added information about the modulation (AM/FM) of the assigned frequencies to the kneeboard and assign AM modulation instead of FM for JTAC. * **[Mission Generation]** Added information about the modulation (AM/FM) of the assigned frequencies to the kneeboard and assign AM modulation instead of FM for JTAC.
* **[Mission Generation]** Adjusted wind speeds. Wind speeds at high altitude are generally higher now. * **[Mission Generation]** Adjusted wind speeds. Wind speeds at high altitude are generally higher now.
* **[Mission Generation]** Added turbulance. Higher in Summer and Winter, also higher at day time than at night time.
* **[Factions]** Updated the Faction file structure. Older custom faction files will not work correctly and have to be updated to the new structure. * **[Factions]** Updated the Faction file structure. Older custom faction files will not work correctly and have to be updated to the new structure.
* **[Flight Planning]** Added preset formations for different flight types at hold, join, ingress, and split waypoints. Air to Air flights will tend toward line-abreast and spread-four formations. Air to ground flights will tend towards trail formation. * **[Flight Planning]** Added preset formations for different flight types at hold, join, ingress, and split waypoints. Air to Air flights will tend toward line-abreast and spread-four formations. Air to ground flights will tend towards trail formation.
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. AI does not plan. * **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. AI does not plan.

View File

@ -17,6 +17,7 @@ class EnvironmentGenerator:
def set_atmospheric(self, atmospheric: AtmosphericConditions) -> None: def set_atmospheric(self, atmospheric: AtmosphericConditions) -> None:
self.mission.weather.qnh = atmospheric.qnh.mm_hg self.mission.weather.qnh = atmospheric.qnh.mm_hg
self.mission.weather.season_temperature = atmospheric.temperature_celsius self.mission.weather.season_temperature = atmospheric.temperature_celsius
self.mission.weather.turbulence_at_ground = int(atmospheric.turbulance_per_10cm)
def set_clouds(self, clouds: Optional[Clouds]) -> None: def set_clouds(self, clouds: Optional[Clouds]) -> None:
if clouds is None: if clouds is None:

View File

@ -400,6 +400,9 @@ class BriefingPage(KneeboardPage):
f"Temperature: {round(self.weather.atmospheric.temperature_celsius)} °C at sea level" f"Temperature: {round(self.weather.atmospheric.temperature_celsius)} °C at sea level"
) )
writer.text(f"QNH: {qnh_in_hg} inHg / {qnh_mm_hg} mmHg / {qnh_hpa} hPa") writer.text(f"QNH: {qnh_in_hg} inHg / {qnh_mm_hg} mmHg / {qnh_hpa} hPa")
writer.text(
f"Turbulance: {round(self.weather.atmospheric.turbulance_per_10cm)} per 10cm at ground level."
)
fl = self.flight fl = self.flight

View File

@ -45,4 +45,9 @@ class SeasonalConditions:
winter_avg_temperature: float winter_avg_temperature: float
temperature_day_night_difference: float temperature_day_night_difference: float
high_avg_yearly_turbulance_per_10cm: float
low_avg_yearly_turbulance_per_10cm: float
solar_noon_turbulance_per_10cm: float
midnight_turbulance_per_10cm: float
weather_type_chances: dict[Season, WeatherTypeChances] weather_type_chances: dict[Season, WeatherTypeChances]

View File

@ -57,6 +57,23 @@ class SeasonData:
) )
@dataclass(frozen=True)
class TurbulanceData:
high_avg_yearly_turbulance_per_10cm: float | None
low_avg_yearly_turbulance_per_10cm: float | None
solar_noon_turbulance_per_10cm: float | None
midnight_turbulance_per_10cm: float | None
@staticmethod
def from_yaml(data: dict[str, Any]) -> TurbulanceData:
return TurbulanceData(
data.get("high_avg_yearly_turbulance_per_10cm"),
data.get("low_avg_yearly_turbulance_per_10cm"),
data.get("solar_noon_turbulance_per_10cm"),
data.get("midnight_turbulance_per_10cm"),
)
class TheaterLoader: class TheaterLoader:
THEATER_RESOURCE_DIR = Path("resources/theaters") THEATER_RESOURCE_DIR = Path("resources/theaters")
@ -113,6 +130,7 @@ class TheaterLoader:
spring = SeasonData.from_yaml(climate_data["seasons"]["spring"]) spring = SeasonData.from_yaml(climate_data["seasons"]["spring"])
summer = SeasonData.from_yaml(climate_data["seasons"]["summer"]) summer = SeasonData.from_yaml(climate_data["seasons"]["summer"])
fall = SeasonData.from_yaml(climate_data["seasons"]["fall"]) fall = SeasonData.from_yaml(climate_data["seasons"]["fall"])
turbulance = TurbulanceData.from_yaml(climate_data["turbulance"])
if summer.average_pressure is None: if summer.average_pressure is None:
raise RuntimeError( raise RuntimeError(
f"{self.descriptor_path} does not define a summer average pressure" f"{self.descriptor_path} does not define a summer average pressure"
@ -129,12 +147,32 @@ class TheaterLoader:
raise RuntimeError( raise RuntimeError(
f"{self.descriptor_path} does not define a winter average temperature" f"{self.descriptor_path} does not define a winter average temperature"
) )
if turbulance.high_avg_yearly_turbulance_per_10cm is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a yearly average high turbulance"
)
if turbulance.low_avg_yearly_turbulance_per_10cm is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a yearly average low turbulance"
)
if turbulance.solar_noon_turbulance_per_10cm is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a solar noon turbulance"
)
if turbulance.midnight_turbulance_per_10cm is None:
raise RuntimeError(
f"{self.descriptor_path} does not define a midnight turbulance"
)
return SeasonalConditions( return SeasonalConditions(
summer.average_pressure, summer.average_pressure,
winter.average_pressure, winter.average_pressure,
summer.average_temperature, summer.average_temperature,
winter.average_temperature, winter.average_temperature,
climate_data["day_night_temperature_difference"], climate_data["day_night_temperature_difference"],
turbulance.high_avg_yearly_turbulance_per_10cm,
turbulance.low_avg_yearly_turbulance_per_10cm,
turbulance.solar_noon_turbulance_per_10cm,
turbulance.midnight_turbulance_per_10cm,
{ {
Season.Winter: winter.weather, Season.Winter: winter.weather,
Season.Spring: spring.weather, Season.Spring: spring.weather,

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import datetime import datetime
import logging import logging
import math
import random import random
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
@ -36,6 +37,9 @@ class AtmosphericConditions:
#: Temperature at sea level in Celcius. #: Temperature at sea level in Celcius.
temperature_celsius: float temperature_celsius: float
#: Turbulance per 10 cm.
turbulance_per_10cm: float
@dataclass(frozen=True) @dataclass(frozen=True)
class WindConditions: class WindConditions:
@ -108,18 +112,38 @@ class Weather:
day, day,
) )
seasonal_turbulance = self.interpolate_seasonal_turbulance(
seasonal_conditions.high_avg_yearly_turbulance_per_10cm,
seasonal_conditions.low_avg_yearly_turbulance_per_10cm,
day,
)
day_turbulance = seasonal_conditions.solar_noon_turbulance_per_10cm
night_turbulance = seasonal_conditions.midnight_turbulance_per_10cm
time_of_day_turbulance = self.interpolate_solar_activity(
time_of_day, day_turbulance, night_turbulance
)
random_turbulance = random.normalvariate(mu=0, sigma=0.5)
turbulance = abs(
seasonal_turbulance + time_of_day_turbulance + random_turbulance
)
if time_of_day == TimeOfDay.Day: if time_of_day == TimeOfDay.Day:
temperature += seasonal_conditions.temperature_day_night_difference / 2 temperature += seasonal_conditions.temperature_day_night_difference / 2
if time_of_day == TimeOfDay.Night: if time_of_day == TimeOfDay.Night:
temperature -= seasonal_conditions.temperature_day_night_difference / 2 temperature -= seasonal_conditions.temperature_day_night_difference / 2
pressure += self.pressure_adjustment pressure += self.pressure_adjustment
temperature += self.temperature_adjustment temperature += self.temperature_adjustment
turbulance += self.turbulance_adjustment
logging.debug( logging.debug(
"Weather: Before random: temp {} press {}".format(temperature, pressure) "Weather: Before random: temp {} press {}".format(temperature, pressure)
) )
conditions = AtmosphericConditions( conditions = AtmosphericConditions(
qnh=self.random_pressure(pressure), qnh=self.random_pressure(pressure),
temperature_celsius=self.random_temperature(temperature), temperature_celsius=self.random_temperature(temperature),
turbulance_per_10cm=turbulance,
) )
logging.debug( logging.debug(
"Weather: After random: temp {} press {}".format( "Weather: After random: temp {} press {}".format(
@ -136,6 +160,10 @@ class Weather:
def temperature_adjustment(self) -> float: def temperature_adjustment(self) -> float:
raise NotImplementedError raise NotImplementedError
@property
def turbulance_adjustment(self) -> float:
raise NotImplementedError
def generate_clouds(self) -> Optional[Clouds]: def generate_clouds(self) -> Optional[Clouds]:
raise NotImplementedError raise NotImplementedError
@ -243,6 +271,42 @@ class Weather:
winter_factor = distance_from_peak_summer / day_of_year_peak_summer winter_factor = distance_from_peak_summer / day_of_year_peak_summer
return interpolate(summer_value, winter_value, winter_factor, clamp=True) return interpolate(summer_value, winter_value, winter_factor, clamp=True)
@staticmethod
def interpolate_seasonal_turbulance(
high_value: float, low_value: float, day: datetime.date
) -> float:
day_of_year = day.timetuple().tm_yday
day_of_year_peak_summer = 183
distance_from_peak_summer = -day_of_year_peak_summer + day_of_year
amplitude = 0.5 * (high_value - low_value)
offset = amplitude + low_value
# A high peak in summer and winter, between high_value and low_value.
return (
amplitude * math.cos(4 * math.pi * distance_from_peak_summer / 365.25)
+ offset
)
@staticmethod
def interpolate_solar_activity(
time_of_day: TimeOfDay, high: float, low: float
) -> float:
scale: float = 0
match time_of_day:
case TimeOfDay.Dawn:
scale = 0.4
case TimeOfDay.Day:
scale = 1
case TimeOfDay.Dusk:
scale = 0.6
case TimeOfDay.Night:
scale = 0
return interpolate(value1=low, value2=high, factor=scale, clamp=True)
class ClearSkies(Weather): class ClearSkies(Weather):
@property @property
@ -253,6 +317,10 @@ class ClearSkies(Weather):
def temperature_adjustment(self) -> float: def temperature_adjustment(self) -> float:
return 3.0 return 3.0
@property
def turbulance_adjustment(self) -> float:
return 0.3
def generate_clouds(self) -> Optional[Clouds]: def generate_clouds(self) -> Optional[Clouds]:
return None return None
@ -272,6 +340,10 @@ class Cloudy(Weather):
def temperature_adjustment(self) -> float: def temperature_adjustment(self) -> float:
return 0.0 return 0.0
@property
def turbulance_adjustment(self) -> float:
return 0.6
def generate_clouds(self) -> Optional[Clouds]: def generate_clouds(self) -> Optional[Clouds]:
return Clouds.random_preset(rain=False) return Clouds.random_preset(rain=False)
@ -292,6 +364,10 @@ class Raining(Weather):
def temperature_adjustment(self) -> float: def temperature_adjustment(self) -> float:
return -3.0 return -3.0
@property
def turbulance_adjustment(self) -> float:
return 0.9
def generate_clouds(self) -> Optional[Clouds]: def generate_clouds(self) -> Optional[Clouds]:
return Clouds.random_preset(rain=True) return Clouds.random_preset(rain=True)
@ -312,6 +388,10 @@ class Thunderstorm(Weather):
def temperature_adjustment(self) -> float: def temperature_adjustment(self) -> float:
return -3.0 return -3.0
@property
def turbulance_adjustment(self) -> float:
return 1.2
def generate_clouds(self) -> Optional[Clouds]: def generate_clouds(self) -> Optional[Clouds]:
return Clouds( return Clouds(
base=self.random_cloud_base(), base=self.random_cloud_base(),

View File

@ -37,3 +37,8 @@ climate:
raining: 30 raining: 30
cloudy: 50 cloudy: 50
clear: 20 clear: 20
turbulance:
high_avg_yearly_turbulance_per_10cm: 4
low_avg_yearly_turbulance_per_10cm: 1
solar_noon_turbulance_per_10cm: 1
midnight_turbulance_per_10cm: 0

View File

@ -46,3 +46,8 @@ climate:
raining: 30 raining: 30
cloudy: 45 cloudy: 45
clear: 25 clear: 25
turbulance:
high_avg_yearly_turbulance_per_10cm: 9
low_avg_yearly_turbulance_per_10cm: 3
solar_noon_turbulance_per_10cm: 2
midnight_turbulance_per_10cm: 0

View File

@ -38,3 +38,8 @@ climate:
raining: 45 raining: 45
cloudy: 30 cloudy: 30
clear: 20 clear: 20
turbulance:
high_avg_yearly_turbulance_per_10cm: 3
low_avg_yearly_turbulance_per_10cm: 1
solar_noon_turbulance_per_10cm: 2
midnight_turbulance_per_10cm: 1

View File

@ -37,3 +37,8 @@ climate:
raining: 10 raining: 10
cloudy: 45 cloudy: 45
clear: 45 clear: 45
turbulance:
high_avg_yearly_turbulance_per_10cm: 9
low_avg_yearly_turbulance_per_10cm: 1
solar_noon_turbulance_per_10cm: 6
midnight_turbulance_per_10cm: 0

View File

@ -37,3 +37,8 @@ climate:
raining: 30 raining: 30
cloudy: 50 cloudy: 50
clear: 20 clear: 20
turbulance:
high_avg_yearly_turbulance_per_10cm: 5
low_avg_yearly_turbulance_per_10cm: 2
solar_noon_turbulance_per_10cm: 3
midnight_turbulance_per_10cm: 1

View File

@ -38,3 +38,8 @@ climate:
raining: 2 raining: 2
cloudy: 28 cloudy: 28
clear: 70 clear: 70
turbulance:
high_avg_yearly_turbulance_per_10cm: 8
low_avg_yearly_turbulance_per_10cm: 1
solar_noon_turbulance_per_10cm: 5
midnight_turbulance_per_10cm: 0

View File

@ -37,3 +37,8 @@ climate:
raining: 15 raining: 15
cloudy: 35 cloudy: 35
clear: 50 clear: 50
turbulance:
high_avg_yearly_turbulance_per_10cm: 7
low_avg_yearly_turbulance_per_10cm: 2
solar_noon_turbulance_per_10cm: 4
midnight_turbulance_per_10cm: 1

View File

@ -38,3 +38,8 @@ climate:
raining: 30 raining: 30
cloudy: 50 cloudy: 50
clear: 20 clear: 20
turbulance:
high_avg_yearly_turbulance_per_10cm: 5
low_avg_yearly_turbulance_per_10cm: 2
solar_noon_turbulance_per_10cm: 3
midnight_turbulance_per_10cm: 1