From 26ddf8ea8e459bd67718faa80f81382332c2c51b Mon Sep 17 00:00:00 2001 From: nathan Date: Sat, 16 May 2026 00:16:32 -0700 Subject: [PATCH] Persist applied weather debug state --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 2 +- Docs/PersistenceDesignDocument.md | 7 ++ Docs/TechnicalDesignDocument.md | 11 ++- Scripts/verify_weather_debug_persistence.py | 87 +++++++++++++++++++ Source/AgrarianGame/AgrarianGameState.cpp | 30 +++++++ Source/AgrarianGame/AgrarianGameState.h | 6 ++ .../AgrarianPersistenceSubsystem.cpp | 40 +++++++++ .../AgrarianPersistenceSubsystem.h | 6 ++ Source/AgrarianGame/AgrarianSaveGame.h | 6 ++ Source/AgrarianGame/AgrarianTypes.h | 60 +++++++++++++ .../AgrarianWeatherProviderSubsystem.cpp | 3 + 11 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 Scripts/verify_weather_debug_persistence.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 8eb2fad..45e5c20 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -431,7 +431,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Cache real-weather snapshots server-side so clients never call public weather APIs directly. Added provider/tile-keyed server cache entries with TTL, cache reuse before Open-Meteo or NOAA/NWS requests, cache clearing/debug helpers, and documentation that clients consume only replicated game-state weather and temperature. - [x] Map real weather inputs into Agrarian weather states: temperature, precipitation, wind, cloud cover, humidity, pressure, visibility, and weather code. Added replicated `FAgrarianMappedWeatherInputs`, provider snapshot mapping, Open-Meteo visibility derivation, NOAA/NWS grid enrichment for humidity/sky cover/pressure/visibility, and game-state application that preserves raw mapped inputs alongside the collapsed Agrarian weather state. - [x] Add deterministic fallback weather simulation when external weather data is unavailable. Added tile/day-seeded fallback snapshots for temperature, daily low/high, cloud cover, humidity, wind, pressure, precipitation, visibility, provider code, and mapped Agrarian state; fallback snapshots use the normal server cache and apply through the same mapped-weather path as live providers. -- [ ] Store weather source, provider timestamp, tile coordinate, and applied in-game weather state for debugging and persistence. +- [x] Store weather source, provider timestamp, tile coordinate, and applied in-game weather state for debugging and persistence. Added tile ID/coordinate fields to mapped weather inputs, a replicated `FAgrarianWeatherDebugSnapshot` on game state, provider snapshot mapping into the debug path, and save fields for mapped inputs plus applied weather debug state. - [ ] Add weather save/load support. - [x] Connect weather to body temperature. - [~] Connect shelter to weather protection. diff --git a/Docs/PersistenceDesignDocument.md b/Docs/PersistenceDesignDocument.md index 38a9e64..95312ff 100644 --- a/Docs/PersistenceDesignDocument.md +++ b/Docs/PersistenceDesignDocument.md @@ -122,6 +122,13 @@ transform, survival snapshot, care history snapshot, and inventory stacks. `RestorePlayers` reapplies those records to matching live characters before or alongside world actor restore. +The same save path captures authoritative world weather state from +`AAgrarianGameState`: world hour, collapsed weather enum, mapped provider inputs, +and `FAgrarianWeatherDebugSnapshot`. The debug snapshot includes weather source, +provider timestamp, tile ID, tile center coordinate, provider weather code, and +the final applied in-game weather state so weather-related save/load issues can +be inspected per tile. + ## Care History Snapshot Reserve a long-term care history snapshot beside the immediate survival diff --git a/Docs/TechnicalDesignDocument.md b/Docs/TechnicalDesignDocument.md index 0f39c1b..3e41263 100644 --- a/Docs/TechnicalDesignDocument.md +++ b/Docs/TechnicalDesignDocument.md @@ -183,13 +183,22 @@ They receive weather, temperature, source, and state through replicated game state fields. Real-weather provider values are mapped into `FAgrarianMappedWeatherInputs` -before they affect gameplay. The mapped snapshot keeps temperature, +before they affect gameplay. The mapped snapshot keeps tile ID, tile center +coordinate, temperature, precipitation, wind, cloud cover, humidity, pressure, visibility, and provider weather code available alongside the collapsed Agrarian weather state. Open-Meteo fills those fields directly where available; NOAA/NWS fills them from grid data where available and derives provisional visibility/weather-state values until a deeper provider-specific mapping pass is added. +The applied weather state also has a replicated debug snapshot: +`FAgrarianWeatherDebugSnapshot`. It records the weather source, provider +timestamp, tile ID, tile center coordinate, provider weather code, input values, +and final in-game `EAgrarianWeatherType` after mapping. Save files persist both +the mapped inputs and the applied debug snapshot so weather issues can be traced +back to a specific provider response and tile without inferring those values from +separate systems. + Deterministic fallback weather keeps the game playable when external providers are disabled, unreachable, or return unusable data. The fallback snapshot is derived from tile ID and Agrarian day, then mapped through the same diff --git a/Scripts/verify_weather_debug_persistence.py b/Scripts/verify_weather_debug_persistence.py new file mode 100644 index 0000000..e607782 --- /dev/null +++ b/Scripts/verify_weather_debug_persistence.py @@ -0,0 +1,87 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +TYPES_H = ROOT / "Source" / "AgrarianGame" / "AgrarianTypes.h" +GAME_STATE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianGameState.h" +GAME_STATE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianGameState.cpp" +SAVE_GAME_H = ROOT / "Source" / "AgrarianGame" / "AgrarianSaveGame.h" +PERSISTENCE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianPersistenceSubsystem.h" +PERSISTENCE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianPersistenceSubsystem.cpp" +PROVIDER_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianWeatherProviderSubsystem.cpp" +TDD = ROOT / "Docs" / "TechnicalDesignDocument.md" +PERSISTENCE_DOC = ROOT / "Docs" / "PersistenceDesignDocument.md" +ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md" + + +EXPECTED = { + TYPES_H: [ + "FName TileId = NAME_None;", + "float Latitude = 0.0f;", + "float Longitude = 0.0f;", + "struct FAgrarianWeatherDebugSnapshot", + "FString ProviderTimestamp;", + "EAgrarianWeatherType AppliedWeather", + ], + PROVIDER_CPP: [ + "MappedInputs.TileId = Snapshot.TileId;", + "MappedInputs.Latitude = Snapshot.Latitude;", + "MappedInputs.Longitude = Snapshot.Longitude;", + ], + GAME_STATE_H: [ + "FAgrarianWeatherDebugSnapshot ActiveWeatherDebug", + "FAgrarianWeatherDebugSnapshot GetWeatherDebugSnapshot() const;", + ], + GAME_STATE_CPP: [ + "DOREPLIFETIME(AAgrarianGameState, ActiveWeatherDebug);", + "ActiveWeatherDebug.Provider = ActiveWeatherInputs.Provider;", + "ActiveWeatherDebug.ProviderTimestamp = ActiveWeatherInputs.ProviderTimestamp;", + "ActiveWeatherDebug.AppliedWeather = ActiveWeatherInputs.MappedWeather;", + "FAgrarianWeatherDebugSnapshot AAgrarianGameState::GetWeatherDebugSnapshot() const", + ], + SAVE_GAME_H: [ + "FAgrarianMappedWeatherInputs WeatherInputs;", + "FAgrarianWeatherDebugSnapshot WeatherDebug;", + ], + PERSISTENCE_H: [ + "bool CaptureWorldState(UAgrarianSaveGame* SaveGame) const;", + "bool RestoreWorldState(const UAgrarianSaveGame* SaveGame) const;", + ], + PERSISTENCE_CPP: [ + "SaveGame->WeatherInputs = GameState->ActiveWeatherInputs;", + "SaveGame->WeatherDebug = GameState->GetWeatherDebugSnapshot();", + "GameState->ApplyMappedWeatherInputs(SaveGame->WeatherInputs);", + "CaptureWorldState(SaveGame);", + ], + TDD: [ + "`FAgrarianWeatherDebugSnapshot`", + "weather source", + "provider timestamp", + "tile center coordinate", + ], + PERSISTENCE_DOC: [ + "`FAgrarianWeatherDebugSnapshot`", + "weather source", + "provider timestamp", + "tile center coordinate", + ], + ROADMAP: [ + "[x] Store weather source, provider timestamp, tile coordinate, and applied in-game weather state for debugging and persistence.", + ], +} + + +def main() -> None: + missing = [] + for path, snippets in EXPECTED.items(): + text = path.read_text(encoding="utf-8") + for snippet in snippets: + if snippet not in text: + missing.append(f"{path.relative_to(ROOT)}: {snippet}") + if missing: + raise RuntimeError("Weather debug persistence verification failed: " + "; ".join(missing)) + print("Agrarian weather debug persistence verification complete.") + + +if __name__ == "__main__": + main() diff --git a/Source/AgrarianGame/AgrarianGameState.cpp b/Source/AgrarianGame/AgrarianGameState.cpp index 589bb6f..49506c0 100644 --- a/Source/AgrarianGame/AgrarianGameState.cpp +++ b/Source/AgrarianGame/AgrarianGameState.cpp @@ -49,6 +49,13 @@ AAgrarianGameState::AAgrarianGameState() ActiveGrowingSeason.MinAverageGrowingTempC = 7.0f; ActiveGrowingSeason.CropSafetyBufferDays = 14; ActiveGrowingSeason.ClimateProfile = TEXT("coastal_mediterranean_mild"); + ActiveWeatherInputs.TileId = ActiveSolarTileId; + ActiveWeatherInputs.Latitude = ActiveTileLatitude; + ActiveWeatherInputs.Longitude = ActiveTileLongitude; + ActiveWeatherDebug.TileId = ActiveSolarTileId; + ActiveWeatherDebug.Latitude = ActiveTileLatitude; + ActiveWeatherDebug.Longitude = ActiveTileLongitude; + ActiveWeatherDebug.AppliedWeather = Weather; } void AAgrarianGameState::BeginPlay() @@ -99,6 +106,7 @@ void AAgrarianGameState::GetLifetimeReplicatedProps(TArray& O DOREPLIFETIME(AAgrarianGameState, bHasRegionalObservedTemperature); DOREPLIFETIME(AAgrarianGameState, RegionalWeatherSource); DOREPLIFETIME(AAgrarianGameState, ActiveWeatherInputs); + DOREPLIFETIME(AAgrarianGameState, ActiveWeatherDebug); DOREPLIFETIME(AAgrarianGameState, DaysPerAgrarianYear); DOREPLIFETIME(AAgrarianGameState, ActiveSolarTileId); DOREPLIFETIME(AAgrarianGameState, ActiveTileLatitude); @@ -130,6 +138,7 @@ void AAgrarianGameState::SetWeather(EAgrarianWeatherType NewWeather) if (HasAuthority()) { Weather = NewWeather; + ActiveWeatherDebug.AppliedWeather = NewWeather; UpdateAmbientTemperature(); OnRep_Weather(); } @@ -180,6 +189,22 @@ void AAgrarianGameState::ApplyMappedWeatherInputs(const FAgrarianMappedWeatherIn ActiveWeatherInputs.VisibilityMeters = FMath::Max(0.0f, ActiveWeatherInputs.VisibilityMeters); ActiveWeatherInputs.bHasProviderData = true; + ActiveWeatherDebug.TileId = ActiveWeatherInputs.TileId != NAME_None ? ActiveWeatherInputs.TileId : ActiveSolarTileId; + ActiveWeatherDebug.Latitude = ActiveWeatherInputs.TileId != NAME_None ? ActiveWeatherInputs.Latitude : ActiveTileLatitude; + ActiveWeatherDebug.Longitude = ActiveWeatherInputs.TileId != NAME_None ? ActiveWeatherInputs.Longitude : ActiveTileLongitude; + ActiveWeatherDebug.Provider = ActiveWeatherInputs.Provider; + ActiveWeatherDebug.ProviderTimestamp = ActiveWeatherInputs.ProviderTimestamp; + ActiveWeatherDebug.AppliedWeather = ActiveWeatherInputs.MappedWeather; + ActiveWeatherDebug.ProviderWeatherCode = ActiveWeatherInputs.ProviderWeatherCode; + ActiveWeatherDebug.TemperatureC = ActiveWeatherInputs.TemperatureC; + ActiveWeatherDebug.PrecipitationMm = ActiveWeatherInputs.PrecipitationMm; + ActiveWeatherDebug.WindSpeedKmh = ActiveWeatherInputs.WindSpeedKmh; + ActiveWeatherDebug.CloudCoverPercent = ActiveWeatherInputs.CloudCoverPercent; + ActiveWeatherDebug.RelativeHumidityPercent = ActiveWeatherInputs.RelativeHumidityPercent; + ActiveWeatherDebug.PressureMslHpa = ActiveWeatherInputs.PressureMslHpa; + ActiveWeatherDebug.VisibilityMeters = ActiveWeatherInputs.VisibilityMeters; + ActiveWeatherDebug.bHasProviderData = ActiveWeatherInputs.bHasProviderData; + SetRegionalTemperatureProfile(ActiveWeatherInputs.DailyLowTemperatureC, ActiveWeatherInputs.DailyHighTemperatureC); SetRegionalObservedTemperature( ActiveWeatherInputs.TemperatureC, @@ -188,6 +213,11 @@ void AAgrarianGameState::ApplyMappedWeatherInputs(const FAgrarianMappedWeatherIn SetWeather(ActiveWeatherInputs.MappedWeather); } +FAgrarianWeatherDebugSnapshot AAgrarianGameState::GetWeatherDebugSnapshot() const +{ + return ActiveWeatherDebug; +} + float AAgrarianGameState::GetClearSkyTemperatureForHour(float HourOfDay) const { const float LowTemperature = FMath::Min(RegionalDailyLowTemperatureC, RegionalDailyHighTemperatureC); diff --git a/Source/AgrarianGame/AgrarianGameState.h b/Source/AgrarianGame/AgrarianGameState.h index 28f3fa5..2adb59d 100644 --- a/Source/AgrarianGame/AgrarianGameState.h +++ b/Source/AgrarianGame/AgrarianGameState.h @@ -55,6 +55,9 @@ public: UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Weather") FAgrarianMappedWeatherInputs ActiveWeatherInputs; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Weather") + FAgrarianWeatherDebugSnapshot ActiveWeatherDebug; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar") FName ActiveSolarTileId = TEXT("gz_us_ca_pacifica_utm10n_e544_n4160"); @@ -109,6 +112,9 @@ public: UFUNCTION(BlueprintCallable, Category = "Agrarian|World|Weather") void ApplyMappedWeatherInputs(const FAgrarianMappedWeatherInputs& MappedInputs); + UFUNCTION(BlueprintPure, Category = "Agrarian|World|Weather") + FAgrarianWeatherDebugSnapshot GetWeatherDebugSnapshot() const; + UFUNCTION(BlueprintPure, Category = "Agrarian|World|Temperature") float GetClearSkyTemperatureForHour(float HourOfDay) const; diff --git a/Source/AgrarianGame/AgrarianPersistenceSubsystem.cpp b/Source/AgrarianGame/AgrarianPersistenceSubsystem.cpp index 5b541f3..c75b313 100644 --- a/Source/AgrarianGame/AgrarianPersistenceSubsystem.cpp +++ b/Source/AgrarianGame/AgrarianPersistenceSubsystem.cpp @@ -2,6 +2,7 @@ #include "AgrarianPersistenceSubsystem.h" #include "AgrarianGameCharacter.h" +#include "AgrarianGameState.h" #include "AgrarianInventoryComponent.h" #include "AgrarianPersistentActorComponent.h" #include "AgrarianSaveGame.h" @@ -120,6 +121,44 @@ int32 UAgrarianPersistenceSubsystem::RestoreWorldActors(const UAgrarianSaveGame* return RestoredCount; } +bool UAgrarianPersistenceSubsystem::CaptureWorldState(UAgrarianSaveGame* SaveGame) const +{ + UWorld* World = GetWorld(); + AAgrarianGameState* GameState = World ? World->GetGameState() : nullptr; + if (!SaveGame || !GameState) + { + return false; + } + + SaveGame->WorldHours = GameState->WorldHours; + SaveGame->Weather = GameState->Weather; + SaveGame->WeatherInputs = GameState->ActiveWeatherInputs; + SaveGame->WeatherDebug = GameState->GetWeatherDebugSnapshot(); + return true; +} + +bool UAgrarianPersistenceSubsystem::RestoreWorldState(const UAgrarianSaveGame* SaveGame) const +{ + UWorld* World = GetWorld(); + AAgrarianGameState* GameState = World ? World->GetGameState() : nullptr; + if (!SaveGame || !GameState || !GameState->HasAuthority()) + { + return false; + } + + GameState->WorldHours = SaveGame->WorldHours; + if (SaveGame->WeatherInputs.bHasProviderData) + { + GameState->ApplyMappedWeatherInputs(SaveGame->WeatherInputs); + } + else + { + GameState->SetWeather(SaveGame->Weather); + } + + return true; +} + int32 UAgrarianPersistenceSubsystem::CapturePlayers(UAgrarianSaveGame* SaveGame) const { if (!SaveGame) @@ -209,6 +248,7 @@ bool UAgrarianPersistenceSubsystem::SaveCurrentWorld() const return false; } + CaptureWorldState(SaveGame); CapturePlayers(SaveGame); CaptureWorldActors(SaveGame); return WriteSave(SaveGame); diff --git a/Source/AgrarianGame/AgrarianPersistenceSubsystem.h b/Source/AgrarianGame/AgrarianPersistenceSubsystem.h index 9a66576..ee58c4f 100644 --- a/Source/AgrarianGame/AgrarianPersistenceSubsystem.h +++ b/Source/AgrarianGame/AgrarianPersistenceSubsystem.h @@ -46,6 +46,12 @@ public: UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence") int32 RestoreWorldActors(const UAgrarianSaveGame* SaveGame, bool bClearExistingActors = true) const; + UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence") + bool CaptureWorldState(UAgrarianSaveGame* SaveGame) const; + + UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence") + bool RestoreWorldState(const UAgrarianSaveGame* SaveGame) const; + UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence") int32 CapturePlayers(UAgrarianSaveGame* SaveGame) const; diff --git a/Source/AgrarianGame/AgrarianSaveGame.h b/Source/AgrarianGame/AgrarianSaveGame.h index a4860d0..755a481 100644 --- a/Source/AgrarianGame/AgrarianSaveGame.h +++ b/Source/AgrarianGame/AgrarianSaveGame.h @@ -61,6 +61,12 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save") EAgrarianWeatherType Weather = EAgrarianWeatherType::Clear; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save") + FAgrarianMappedWeatherInputs WeatherInputs; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save") + FAgrarianWeatherDebugSnapshot WeatherDebug; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save") TArray Players; diff --git a/Source/AgrarianGame/AgrarianTypes.h b/Source/AgrarianGame/AgrarianTypes.h index 95d6ed4..0f791ca 100644 --- a/Source/AgrarianGame/AgrarianTypes.h +++ b/Source/AgrarianGame/AgrarianTypes.h @@ -19,6 +19,15 @@ struct FAgrarianMappedWeatherInputs { GENERATED_BODY() + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + FName TileId = NAME_None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float Latitude = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float Longitude = 0.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float TemperatureC = 12.0f; @@ -62,6 +71,57 @@ struct FAgrarianMappedWeatherInputs bool bHasProviderData = false; }; +USTRUCT(BlueprintType) +struct FAgrarianWeatherDebugSnapshot +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + FName TileId = NAME_None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float Latitude = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float Longitude = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + FString Provider = TEXT("deterministic"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + FString ProviderTimestamp; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + EAgrarianWeatherType AppliedWeather = EAgrarianWeatherType::Clear; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + int32 ProviderWeatherCode = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float TemperatureC = 12.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float PrecipitationMm = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float WindSpeedKmh = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float CloudCoverPercent = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float RelativeHumidityPercent = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float PressureMslHpa = 1013.25f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + float VisibilityMeters = 10000.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") + bool bHasProviderData = false; +}; + UENUM(BlueprintType) enum class EAgrarianSeason : uint8 { diff --git a/Source/AgrarianGame/AgrarianWeatherProviderSubsystem.cpp b/Source/AgrarianGame/AgrarianWeatherProviderSubsystem.cpp index b717852..4f18a98 100644 --- a/Source/AgrarianGame/AgrarianWeatherProviderSubsystem.cpp +++ b/Source/AgrarianGame/AgrarianWeatherProviderSubsystem.cpp @@ -122,6 +122,9 @@ bool UAgrarianWeatherProviderSubsystem::ApplySnapshotToGameState(const FAgrarian FAgrarianMappedWeatherInputs UAgrarianWeatherProviderSubsystem::MapSnapshotToAgrarianWeatherInputs(const FAgrarianWeatherProviderSnapshot& Snapshot) const { FAgrarianMappedWeatherInputs MappedInputs; + MappedInputs.TileId = Snapshot.TileId; + MappedInputs.Latitude = Snapshot.Latitude; + MappedInputs.Longitude = Snapshot.Longitude; MappedInputs.TemperatureC = Snapshot.CurrentTemperatureC; MappedInputs.DailyLowTemperatureC = Snapshot.DailyLowTemperatureC; MappedInputs.DailyHighTemperatureC = Snapshot.DailyHighTemperatureC;