Skip to content

Instantly share code, notes, and snippets.

@ImaginaryDevelopment
Last active September 4, 2025 21:24
Show Gist options
  • Select an option

  • Save ImaginaryDevelopment/5c8646d229c3acd0ab968349f2c18d1c to your computer and use it in GitHub Desktop.

Select an option

Save ImaginaryDevelopment/5c8646d229c3acd0ab968349f2c18d1c to your computer and use it in GitHub Desktop.

Revisions

  1. ImaginaryDevelopment revised this gist Sep 4, 2025. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions Planets.fs
    Original file line number Diff line number Diff line change
    @@ -345,7 +345,7 @@ module TestData =
    newTestCase :: testCases

    // Current formula version - increment this when formulas change
    let currentFormulaVersion = "v1.1.0"
    let currentFormulaVersion = "v1.2.0"

    // Function to update formula version (call this when you change formulas)
    let updateFormulaVersion (newVersion: string) =
    @@ -1173,12 +1173,12 @@ module Formulas =
    decimal (level * level) * 0.0215m

    let calculateBaseShipSpeed (level: int) : decimal =
    // Formula: level^2 * 0.029 (improved coefficient for better accuracy)
    decimal (level * level) * 0.029m
    // Official formula: 1 + 0.2 * (level - 1) + (1/75) * (level - 1)²
    1.0m + 0.2m * decimal (level - 1) + (1.0m / 75.0m) * decimal ((level - 1) * (level - 1))

    let calculateBaseCargoSpace (level: int) : int =
    // Formula: level^2 * 0.24 (improved coefficient for better accuracy)
    int (decimal (level * level) * 0.24m)
    // Official formula: 5 + 2 * (level - 1) + 0.1 * (level - 1)²
    int (5.0m + 2.0m * decimal (level - 1) + 0.1m * decimal ((level - 1) * (level - 1)))

    // Alternative formulas for testing
    let calculateBaseMiningRateV2 (level: int) : decimal =
  2. ImaginaryDevelopment created this gist Sep 4, 2025.
    1,719 changes: 1,719 additions & 0 deletions Planets.fs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1719 @@
    module IdlePlanetMiner

    // =============================================================================
    // CORE DATA TYPES
    // =============================================================================

    // Planet information from the wiki
    type Planet = {
    Name: string
    BasePrice: int
    Distance: int
    Tele: int option
    Resources: Map<string, decimal>
    }

    // Planet level information
    type PlanetLevels = {
    Mining: int
    ShipSpeed: int
    Cargo: int
    }

    // Planet stats (calculated results)
    type PlanetStats = {
    OrePerSecond: decimal
    Speed: decimal
    Cargo: int
    }

    // Modifier source for debugging
    type ModifierSource = {
    Name: string
    Value: decimal
    Description: string
    }

    // Planet calculation result with modifiers
    type PlanetCalculationResult = {
    FinalStats: PlanetStats
    MiningModifiers: ModifierSource list
    SpeedModifiers: ModifierSource list
    CargoModifiers: ModifierSource list
    }

    // Planet multiplier for beacons
    type PlanetMultiplier = {
    Mining: decimal
    Speed: decimal
    Cargo: decimal
    }

    // Research information
    type Research = {
    Name: string
    Description: string
    Multipliers: Map<string, decimal> // Stat name -> multiplier (e.g., "Mining" -> 1.25m, "Speed" -> 1.15m)
    Cost: int
    }

    // Beacon information
    type Beacon = {
    Name: string
    PlanetRange: int * int // (start, end) inclusive
    Multipliers: PlanetMultiplier
    }

    // User multipliers and settings
    type Multipliers = {
    MiningRate: decimal
    ShipSpeed: decimal
    Cargo: decimal
    ColonizationBonuses: Map<int, PlanetMultiplier>
    Daughtership: bool
    RoomLevels: Map<string, int> // Room name -> level
    CompletedResearches: string list // List of completed research names
    GlobalManagerBonuses: Map<string, string * decimal> // Manager name -> (bonus type, value)
    }

    // Room information
    type Room = {
    Name: string
    Boost: string
    MinCost: int
    BaseEffect: decimal
    PerLevel: decimal
    MaxLevel: int
    MaxBonus: decimal
    }

    // Test data types
    type PlanetTestData = {
    PlanetNumber: int
    InputLevels: PlanetLevels
    ActualResults: PlanetStats
    }

    type TestCase = {
    TestDate: string
    Description: string
    PlanetData: PlanetTestData list
    CapturedMultipliers: Multipliers
    CapturedPurchasedBonuses: {| MineBoost: decimal |}
    CapturedDaughtershipMultipliers: {| MiningRate: decimal; ShipSpeed: decimal; Cargo: decimal |}
    CapturedBeacons: Map<string, Beacon>
    LastCalculatedAccuracy: decimal option
    FormulaVersion: string option
    }

    // Validation result types
    type PlanetValidationResult = {
    PlanetNumber: int
    CalculatedStats: PlanetStats
    ActualStats: PlanetStats
    MiningAccuracy: decimal
    SpeedAccuracy: decimal
    CargoAccuracy: decimal
    OverallAccuracy: decimal
    }

    type TestCaseValidationResult = {
    TestCaseDescription: string
    TestDate: string
    PlanetResults: PlanetValidationResult list
    AverageAccuracy: decimal
    }

    type AccuracyComparison = {
    TestCaseDescription: string
    PreviousAccuracy: decimal option
    CurrentAccuracy: decimal
    AccuracyChange: decimal option
    FormulaVersion: string option
    }

    // =============================================================================
    // TEST DATA MODULE - Input levels and actual results for validation
    // =============================================================================
    module TestData =

    // Test cases with multiple planets, actual results, and captured multipliers
    let testCases = [
    {
    TestDate = "2025-09-04"
    Description = "Initial test case - Planets 1-13 data recorded before Advanced Mining research"
    PlanetData = [
    {
    PlanetNumber = 1
    InputLevels = { Mining = 34; ShipSpeed = 16; Cargo = 16 }
    ActualResults = { OrePerSecond = 76.62m; Speed = 15.93m; Cargo = 75 }
    }
    {
    PlanetNumber = 2
    InputLevels = { Mining = 25; ShipSpeed = 14; Cargo = 14 }
    ActualResults = { OrePerSecond = 43.21m; Speed = 13.32m; Cargo = 62 }
    }
    {
    PlanetNumber = 3
    InputLevels = { Mining = 21; ShipSpeed = 11; Cargo = 11 }
    ActualResults = { OrePerSecond = 24.17m; Speed = 9.86m; Cargo = 45 }
    }
    ]
    CapturedMultipliers = {
    MiningRate = 1.0m
    ShipSpeed = 1.0m
    Cargo = 1.0m
    ColonizationBonuses = Map [
    1, { Mining = 1.3m; Speed = 1.0m; Cargo = 1.0m } // Planet 1 colonized
    2, { Mining = 1.3m; Speed = 1.0m; Cargo = 1.0m } // Planet 2 colonized
    ]
    Daughtership = true
    RoomLevels = Map [
    "Engineering", 1 // Level 1 Engineering room
    "Aeronautical", 2 // Level 2 Aeronautical room
    "Packaging", 0 // No Packaging room yet
    ]
    CompletedResearches = [] // No research completed when data was recorded
    GlobalManagerBonuses = Map [] // No global manager bonuses when data was recorded
    }
    CapturedPurchasedBonuses = { MineBoost = 1.2m }
    CapturedDaughtershipMultipliers = {
    MiningRate = 1.5m
    ShipSpeed = 1.25m
    Cargo = 1.25m
    }
    CapturedBeacons = Map [
    "Beacon 1-4", {
    Name = "Beacon 1-4"
    PlanetRange = (1, 4)
    Multipliers = {
    Mining = 1.06m
    Speed = 1.04m
    Cargo = 1.04m
    }
    }
    "Beacon 5-7", {
    Name = "Beacon 5-7"
    PlanetRange = (5, 7)
    Multipliers = {
    Mining = 1.06m
    Speed = 1.04m
    Cargo = 1.04m
    }
    }
    "Beacon 8-10", {
    Name = "Beacon 8-10"
    PlanetRange = (8, 10)
    Multipliers = {
    Mining = 1.06m
    Speed = 1.0m
    Cargo = 1.04m
    }
    }
    "Beacon 11-13", {
    Name = "Beacon 11-13"
    PlanetRange = (11, 13)
    Multipliers = {
    Mining = 1.06m
    Speed = 1.0m
    Cargo = 1.04m
    }
    }
    "Beacon 14-16", {
    Name = "Beacon 14-16"
    PlanetRange = (14, 16)
    Multipliers = {
    Mining = 1.06m
    Speed = 1.0m
    Cargo = 1.04m
    }
    }
    ]
    LastCalculatedAccuracy = None // Will be calculated during validation
    FormulaVersion = None // Will be set during validation
    }
    {
    TestDate = "2025-01-27"
    Description = "Current test case - Planets 1-13 with updated levels and current bonuses"
    PlanetData = [
    {
    PlanetNumber = 1
    InputLevels = { Mining = 34; ShipSpeed = 20; Cargo = 20 }
    ActualResults = { OrePerSecond = 95.77m; Speed = 24.06m; Cargo = 103 }
    }
    {
    PlanetNumber = 2
    InputLevels = { Mining = 25; ShipSpeed = 15; Cargo = 15 }
    ActualResults = { OrePerSecond = 54.01m; Speed = 16.05m; Cargo = 68 }
    }
    {
    PlanetNumber = 3
    InputLevels = { Mining = 23; ShipSpeed = 13; Cargo = 13 }
    ActualResults = { OrePerSecond = 35.65m; Speed = 13.31m; Cargo = 56 }
    }
    {
    PlanetNumber = 4
    InputLevels = { Mining = 20; ShipSpeed = 10; Cargo = 10 }
    ActualResults = { OrePerSecond = 27.67m; Speed = 9.71m; Cargo = 40 }
    }
    {
    PlanetNumber = 5
    InputLevels = { Mining = 13; ShipSpeed = 7; Cargo = 9 }
    ActualResults = { OrePerSecond = 13.02m; Speed = 6.71m; Cargo = 36 }
    }
    {
    PlanetNumber = 6
    InputLevels = { Mining = 16; ShipSpeed = 11; Cargo = 11 }
    ActualResults = { OrePerSecond = 18.61m; Speed = 10.84m; Cargo = 45 }
    }
    {
    PlanetNumber = 7
    InputLevels = { Mining = 14; ShipSpeed = 8; Cargo = 8 }
    ActualResults = { OrePerSecond = 14.77m; Speed = 7.64m; Cargo = 31 }
    }
    {
    PlanetNumber = 8
    InputLevels = { Mining = 15; ShipSpeed = 10; Cargo = 10 }
    ActualResults = { OrePerSecond = 16.63m; Speed = 9.71m; Cargo = 40 }
    }
    {
    PlanetNumber = 9
    InputLevels = { Mining = 12; ShipSpeed = 8; Cargo = 8 }
    ActualResults = { OrePerSecond = 11.38m; Speed = 7.64m; Cargo = 31 }
    }
    {
    PlanetNumber = 10
    InputLevels = { Mining = 12; ShipSpeed = 7; Cargo = 7 }
    ActualResults = { OrePerSecond = 11.38m; Speed = 6.71m; Cargo = 27 }
    }
    {
    PlanetNumber = 11
    InputLevels = { Mining = 12; ShipSpeed = 5; Cargo = 6 }
    ActualResults = { OrePerSecond = 11.38m; Speed = 9.69m; Cargo = 23 }
    }
    {
    PlanetNumber = 12
    InputLevels = { Mining = 12; ShipSpeed = 7; Cargo = 7 }
    ActualResults = { OrePerSecond = 11.38m; Speed = 6.45m; Cargo = 27 }
    }
    {
    PlanetNumber = 13
    InputLevels = { Mining = 13; ShipSpeed = 7; Cargo = 7 }
    ActualResults = { OrePerSecond = 13.02m; Speed = 6.45m; Cargo = 27 }
    }
    ]
    CapturedMultipliers = UserInput.multipliers
    CapturedPurchasedBonuses = UserInput.purchasedBonuses
    CapturedDaughtershipMultipliers = UserInput.daughtershipMultipliers
    CapturedBeacons = UserInput.beacons
    LastCalculatedAccuracy = None // Will be calculated during validation
    FormulaVersion = None // Will be set during validation
    }
    ]

    // Function to capture current state as a new test case with multiple planets
    let captureCurrentState
    (planetData: PlanetTestData list)
    (description: string)
    : TestCase =
    {
    TestDate = System.DateTime.Now.ToString("yyyy-MM-dd")
    Description = description
    PlanetData = planetData
    CapturedMultipliers = UserInput.multipliers
    CapturedPurchasedBonuses = UserInput.purchasedBonuses
    CapturedDaughtershipMultipliers = UserInput.daughtershipMultipliers
    CapturedBeacons = UserInput.beacons
    LastCalculatedAccuracy = None // Will be calculated during validation
    FormulaVersion = None // Will be set during validation
    }

    // Helper function to create a single planet test data
    let createPlanetTestData
    (planetNumber: int)
    (inputLevels: PlanetLevels)
    (actualResults: PlanetStats)
    : PlanetTestData =
    {
    PlanetNumber = planetNumber
    InputLevels = inputLevels
    ActualResults = actualResults
    }

    // Helper function to add a new test case to the list
    let addTestCase (newTestCase: TestCase) : TestCase list =
    newTestCase :: testCases

    // Current formula version - increment this when formulas change
    let currentFormulaVersion = "v1.1.0"

    // Function to update formula version (call this when you change formulas)
    let updateFormulaVersion (newVersion: string) =
    printfn "Formula version updated to: %s" newVersion
    printfn "Previous version was: %s" currentFormulaVersion
    printfn "Run validation to see accuracy changes!"

    // =============================================================================
    // USER INPUT MODULE - Current user game settings
    // =============================================================================
    module UserInput =
    // Your current game multipliers and settings
    let multipliers = {
    MiningRate = 1.0m // Base mining rate multiplier
    ShipSpeed = 1.0m
    Cargo = 1.0m
    ColonizationBonuses = Map [
    1, { Mining = 1.3m; Speed = 1.0m; Cargo = 1.0m } // Planet 1 colonized
    2, { Mining = 1.3m; Speed = 1.0m; Cargo = 1.0m } // Planet 2 colonized
    ]
    Daughtership = true // You have daughtership
    RoomLevels =
    Map [
    "Engineering", 1 // Level 1 Engineering room
    "Aeronautical", 2 // Level 2 Aeronautical room
    "Packaging", 0 // No Packaging room yet
    ]
    CompletedResearches = [
    "Advanced Mining" // You have completed this research
    // "Advanced Furnace" // You have completed this research
    // Add more completed researches here
    ]
    GlobalManagerBonuses = Map [
    // Example: "Mining Manager", ("Mining", 1.15m) // 15% mining bonus
    // Example: "Speed Manager", ("Speed", 1.10m) // 10% speed bonus
    // Example: "Cargo Manager", ("Cargo", 1.20m) // 20% cargo bonus
    "Dominique", ("Speed", 1.10m)
    ]
    }

    let purchasedBonuses = {
    MineBoost = 1.2m // Purchased mine boost (separate from base rate)
    }

    let daughtershipMultipliers = {
    MiningRate = 1.5m
    ShipSpeed = 1.25m
    Cargo = 1.25m
    }

    let beacons = Map [
    "Beacon 1-4", {
    Name = "Beacon 1-4"
    PlanetRange = (1, 4)
    Multipliers = {
    Mining = 1.06m // Your current mining bonus for planets 1-4
    Speed = 1.04m // Your current speed bonus for planets 1-4
    Cargo = 1.04m // Your current cargo bonus for planets 1-4
    }
    }
    "Beacon 5-7", {
    Name = "Beacon 5-7"
    PlanetRange = (5, 7)
    Multipliers = {
    Mining = 1.06m // Your current mining bonus for planets 5-7
    Speed = 1.04m // Your current speed bonus for planets 5-7
    Cargo = 1.04m // Your current cargo bonus for planets 5-7
    }
    }
    "Beacon 8-10", {
    Name = "Beacon 8-10"
    PlanetRange = (8, 10)
    Multipliers = {
    Mining = 1.06m // Your current mining bonus for planets 8-10
    Speed = 1.04m // Your current speed bonus for planets 8-10 (no bonus)
    Cargo = 1.04m // Your current cargo bonus for planets 8-10
    }
    }
    "Beacon 11-13", {
    Name = "Beacon 11-13"
    PlanetRange = (11, 13)
    Multipliers = {
    Mining = 1.06m // Your current mining bonus for planets 11-13
    Speed = 1.0m // Your current speed bonus for planets 11-13 (no bonus)
    Cargo = 1.04m // Your current cargo bonus for planets 11-13
    }
    }
    "Beacon 14-16", {
    Name = "Beacon 14-16"
    PlanetRange = (14, 16)
    Multipliers = {
    Mining = 1.06m // Your current mining bonus for planets 14-16
    Speed = 1.0m // Your current speed bonus for planets 14-16 (no bonus)
    Cargo = 1.04m // Your current cargo bonus for planets 14-16
    }
    }
    ]

    // =============================================================================
    // CORE DATA TYPES
    // =============================================================================

    // idle planet miner
    type Planet = {
    Name: string
    BasePrice: int
    Tele: int option
    Distance: int
    Resources: Map<string, decimal>
    }

    type PlanetLevels = {
    Mining: int
    ShipSpeed: int
    Cargo: int
    }

    type PlanetStats = {
    OrePerSecond: decimal
    Speed: decimal
    Cargo: int
    }

    type ModifierSource = {
    Name: string
    Value: decimal
    Description: string
    }

    type PlanetCalculationResult = {
    FinalStats: PlanetStats
    MiningModifiers: ModifierSource list
    SpeedModifiers: ModifierSource list
    CargoModifiers: ModifierSource list
    }

    type PlanetMultiplier = {
    Mining: decimal
    Speed: decimal
    Cargo: decimal
    }

    type Research = {
    Name: string
    Description: string
    Multipliers: Map<string, decimal> // Stat name -> multiplier (e.g., "Mining" -> 1.25m, "Speed" -> 1.15m)
    Cost: int
    }

    type Beacon = {
    Name: string
    PlanetRange: int * int // (start, end) inclusive
    Multipliers: PlanetMultiplier
    }


    type Room = {
    Name: string
    Boost: string
    MinCost: int
    BaseEffect: decimal
    PerLevel: decimal
    MaxLevel: int
    MaxBonus: decimal
    }

    let planets =
    Map[1, // https://idle-planet-miner.fandom.com/wiki/Planets
    {
    Name = "Balor"
    BasePrice = 100
    Distance = 10
    Tele = None
    Resources = Map["Copper", 1.0m]
    }

    2,
    {
    Name = "Drasta"
    BasePrice = 200
    Distance = 12
    Tele = None
    Resources = Map [ "Copper", 0.8m; "Iron", 0.2m ]
    }

    3,
    {
    Name = "Anadius"
    BasePrice = 500
    Distance = 14
    Tele = None
    Resources = Map [ "Copper", 0.5m; "Iron", 0.5m ]
    }

    4,
    {
    Name = "Dholen"
    BasePrice = 1_250
    Distance = 15
    Tele = None
    Resources = Map [ "Iron", 0.8m; "Lead", 0.2m ]
    }

    5,
    {
    Name = "Verr"
    BasePrice = 5_000
    Distance = 16
    Tele = Some 1
    Resources = Map [ "Lead", 0.5m; "Iron", 0.3m; "Copper", 0.2m ]
    }

    6,
    {
    Name = "Newton"
    BasePrice = 9_000
    Distance = 18
    Tele = Some 1
    Resources = Map["Lead", 1.0m]
    }

    7,
    {
    Name = "Widow"
    BasePrice = 15_000
    Distance = 20
    Tele = Some 1
    Resources = Map [ "Iron", 0.4m; "Copper", 0.4m; "Silica", 0.2m ]
    }

    8,
    {
    Name = "Acheron"
    BasePrice = 25_000
    Distance = 22
    Tele = Some 2
    Resources = Map [ "Silica", 0.6m; "Copper", 0.4m ]
    }

    9,
    {
    Name = "Yangtze"
    BasePrice = 40_000
    Distance = 23
    Tele = Some 2
    Resources = Map [ "Silica", 0.8m; "Aluminium", 0.2m ]
    }

    10,
    {
    Name = "Solveig"
    BasePrice = 75_000
    Distance = 25
    Tele = Some 2
    Resources = Map [ "Aluminium", 0.5m; "Silica", 0.3m; "Lead", 0.2m ]
    }

    11,
    {
    Name = "Imir"
    BasePrice = 150_000
    Distance = 26
    Tele = Some 3
    Resources = Map [ "Aluminium", 1.0m ]
    }

    12,
    {
    Name = "Relic"
    BasePrice = 250_000
    Distance = 28
    Tele = Some 3
    Resources = Map [ "Lead", 0.45m; "Silica", 0.35m; "Silver", 0.2m ]
    }

    13,
    {
    Name = "Nith"
    BasePrice = 400_000
    Distance = 30
    Tele = Some 3
    Resources = Map [ "Silver", 0.8m; "Aluminium", 0.2m ]
    }

    14,
    {
    Name = "Batalla"
    BasePrice = 800_000
    Distance = 33
    Tele = Some 4
    Resources = Map [ "Copper", 0.4m; "Iron", 0.4m; "Gold", 0.2m ]
    }

    15,
    {
    Name = "Micah"
    BasePrice = 1_500_000
    Distance = 35
    Tele = Some 4
    Resources = Map [ "Gold", 0.5m; "Silver", 0.5m ]
    }

    16,
    {
    Name = "Pranas"
    BasePrice = 3_000_000
    Distance = 37
    Tele = Some 4
    Resources = Map [ "Gold", 1.0m ]
    }

    17,
    {
    Name = "Castellus"
    BasePrice = 6_000_000
    Distance = 40
    Tele = Some 5
    Resources = Map [ "Aluminium", 0.4m; "Silica", 0.35m; "Diamond", 0.25m ]
    }]

    let levels =
    Map[
    // Planet, (Levels, Stats)
    1,
    ({
    Mining = 34
    ShipSpeed = 20
    Cargo = 20
    },
    {
    OrePerSecond = 95.77m
    Speed = 24.06m
    Cargo = 103
    })

    2,
    ({
    Mining = 25
    ShipSpeed = 15
    Cargo = 15
    },
    {
    OrePerSecond = 54.01m
    Speed = 16.05m
    Cargo = 68
    })

    3,
    ({
    Mining = 23
    ShipSpeed = 13
    Cargo = 13
    },
    {
    OrePerSecond = 35.65m
    Speed = 13.31m
    Cargo = 56
    })

    4,
    ({
    Mining = 20
    ShipSpeed = 10
    Cargo = 10
    },
    {
    OrePerSecond = 27.67m
    Speed = 9.71m
    Cargo = 40
    })
    5,
    ({
    Mining = 13
    ShipSpeed = 7
    Cargo = 9
    },
    {
    OrePerSecond = 13.02m
    Speed = 6.71m
    Cargo = 36
    })
    6,
    ({
    Mining = 16
    ShipSpeed = 11
    Cargo = 11
    },
    {
    OrePerSecond = 18.61m
    Speed = 10.84m
    Cargo = 45
    })
    7,
    ({
    Mining = 14
    ShipSpeed = 8
    Cargo = 8
    },
    {
    OrePerSecond = 14.77m
    Speed = 7.64m
    Cargo = 31
    })
    8,
    ({
    Mining = 15
    ShipSpeed = 10
    Cargo = 10
    },
    {
    OrePerSecond = 16.63m
    Speed = 9.71m
    Cargo = 40
    })
    9,
    ({
    Mining = 12
    ShipSpeed = 8
    Cargo = 8
    },
    {
    OrePerSecond = 11.38m
    Speed = 7.64m
    Cargo = 31
    })
    10,
    ({
    Mining = 12
    ShipSpeed = 7
    Cargo = 7
    },
    {
    OrePerSecond = 11.38m
    Speed = 6.71m
    Cargo = 27
    })
    11,
    ({
    Mining = 12
    ShipSpeed = 5
    Cargo = 6
    },
    {
    OrePerSecond = 11.38m
    Speed = 9.69m
    Cargo = 23
    })
    12,
    ({
    Mining = 12
    ShipSpeed = 7
    Cargo = 7
    },
    {
    OrePerSecond = 11.38m
    Speed = 6.45m
    Cargo = 27
    })
    13,
    ({
    Mining = 13
    ShipSpeed = 7
    Cargo = 7
    },
    {
    OrePerSecond = 13.02m
    Speed = 6.45m
    Cargo = 27
    })
    ]

    let rooms =
    Map [
    // From wiki: https://idle-planet-miner.fandom.com/wiki/Rooms
    "Engineering",
    {
    Name = "Engineering"
    Boost = "Increase mine speed"
    MinCost = 3
    BaseEffect = 1.25m
    PerLevel = 0.15m
    MaxLevel = 60
    MaxBonus = 10.1m
    }
    "Forge",
    {
    Name = "Forge"
    Boost = "Increase smelt speed"
    MinCost = 3
    BaseEffect = 1.20m
    PerLevel = 0.10m
    MaxLevel = 60
    MaxBonus = 7.10m
    }
    "Aeronautical",
    {
    Name = "Aeronautical"
    Boost = "Increase ship speed"
    MinCost = 6
    BaseEffect = 1.50m
    PerLevel = 0.25m
    MaxLevel = 60
    MaxBonus = 16.25m
    }
    "Astronomy",
    {
    Name = "Astronomy"
    Boost = "Reduce planet upgrade prices"
    MinCost = 12
    BaseEffect = 0.90m
    PerLevel = -0.04m
    MaxLevel = 11
    MaxBonus = 0.50m
    }
    "Packaging",
    {
    Name = "Packaging"
    Boost = "Increase cargo"
    MinCost = 21
    BaseEffect = 1.50m
    PerLevel = 0.25m
    MaxLevel = 60
    MaxBonus = 16.25m
    }
    "Workshop",
    {
    Name = "Workshop"
    Boost = "Increase craft speed"
    MinCost = 35
    BaseEffect = 1.20m
    PerLevel = 0.10m
    MaxLevel = 60
    MaxBonus = 7.1m
    }
    "Laboratory",
    {
    Name = "Laboratory"
    Boost = "Decrease project cost"
    MinCost = 56
    BaseEffect = 0.90m
    PerLevel = -0.04m
    MaxLevel = 11
    MaxBonus = 0.50m
    }
    "Robotics",
    {
    Name = "Robotics"
    Boost = "Decrease rover time"
    MinCost = 87
    BaseEffect = 0.90m
    PerLevel = -0.04m
    MaxLevel = 11
    MaxBonus = 0.50m
    }
    "Lounge",
    {
    Name = "Lounge"
    Boost = "Increase credits earned"
    MinCost = 133
    BaseEffect = 1.15m
    PerLevel = 0.05m
    MaxLevel = 60
    MaxBonus = 4.1m
    }
    "Backup Generator",
    {
    Name = "Backup Generator"
    Boost = "Increase max idle time"
    MinCost = 200
    BaseEffect = 0.5m // +0:30
    PerLevel = 0.5m // +0:30
    MaxLevel = 44
    MaxBonus = 24.0m // 24 hours
    }
    "Terrarium",
    {
    Name = "Terrarium"
    Boost = "Decrease colonization cost"
    MinCost = 298
    BaseEffect = 0.90m
    PerLevel = -0.04m
    MaxLevel = 11
    MaxBonus = 0.50m
    }
    "Underforge",
    {
    Name = "Underforge"
    Boost = "Decrease smelter ingredients"
    MinCost = 439
    BaseEffect = 0.90m
    PerLevel = -0.04m
    MaxLevel = 11
    MaxBonus = 0.50m
    }
    "Dorm",
    {
    Name = "Dorm"
    Boost = "Decrease crafter ingredients"
    MinCost = 642
    BaseEffect = 0.90m
    PerLevel = -0.04m
    MaxLevel = 11
    MaxBonus = 0.50m
    }
    "Probability Drive",
    {
    Name = "Probability Drive"
    Boost = "Enables Surges (50% roll)"
    MinCost = 934
    BaseEffect = 0.0m // T0
    PerLevel = 1.0m // +1
    MaxLevel = 23
    MaxBonus = 23.0m // T23
    }
    "Sales",
    {
    Name = "Sales"
    Boost = "Increase alloy and item value"
    MinCost = 1351
    BaseEffect = 1.15m
    PerLevel = 0.05m
    MaxLevel = 60
    MaxBonus = 4.1m
    }
    "Classroom",
    {
    Name = "Classroom"
    Boost = "All manager bonuses"
    MinCost = 1946
    BaseEffect = 1.15m
    PerLevel = 0.05m
    MaxLevel = 60
    MaxBonus = 4.1m
    }
    "Marketing",
    {
    Name = "Marketing"
    Boost = "Increase market bonuses"
    MinCost = 2792
    BaseEffect = 1.30m
    PerLevel = 0.10m
    MaxLevel = 60
    MaxBonus = 7.2m
    }
    "Planet Relations",
    {
    Name = "Planet Relations"
    Boost = "Colonizing Bonuses"
    MinCost = 4402
    BaseEffect = 1.25m
    PerLevel = 0.10m
    MaxLevel = 60
    MaxBonus = 7.15m
    }
    "Belt Studies",
    {
    Name = "Belt Studies"
    Boost = "Asteroid and debris Value"
    MinCost = 6586
    BaseEffect = 1.25m
    PerLevel = 0.10m
    MaxLevel = 60
    MaxBonus = 7.15m
    }
    "Crew Quarters",
    {
    Name = "Crew Quarters"
    Boost = "Additional 20% roll for surges"
    MinCost = 9358
    BaseEffect = 0.0m // T0
    PerLevel = 1.0m // +1
    MaxLevel = 23
    MaxBonus = 23.0m // T23
    }
    "Eleven Forward",
    {
    Name = "Eleven Forward"
    Boost = "Additional 10% roll for surges"
    MinCost = 13267
    BaseEffect = 0.0m // T0
    PerLevel = 1.0m // +1
    MaxLevel = 23
    MaxBonus = 23.0m // T23
    }
    ]


    // Research definitions - each research provides specific bonuses
    let researches = Map [
    "Advanced Mining", {
    Name = "Advanced Mining"
    Description = "Improves mining efficiency"
    Multipliers = Map [
    "Mining", 1.25m
    ]
    Cost = 100
    }
    "Advanced Furnace", {
    Name = "Advanced Furnace"
    Description = "Improves smelting speed"
    Multipliers = Map [
    "Smelting", 1.2m
    ]
    Cost = 150
    }
    "Efficient Engines", {
    Name = "Efficient Engines"
    Description = "Improves ship speed"
    Multipliers = Map [
    "Speed", 1.15m
    ]
    Cost = 200
    }
    "Cargo Optimization", {
    Name = "Cargo Optimization"
    Description = "Increases cargo capacity"
    Multipliers = Map [
    "Cargo", 1.3m
    ]
    Cost = 180
    }
    "Hybrid Mining", {
    Name = "Hybrid Mining"
    Description = "Advanced mining techniques"
    Multipliers = Map [
    "Mining", 1.4m
    ]
    Cost = 300
    }
    "Super Smelting", {
    Name = "Super Smelting"
    Description = "Ultra-fast smelting process"
    Multipliers = Map [
    "Smelting", 1.5m
    ]
    Cost = 400
    }
    "Multi-Purpose Research", {
    Name = "Multi-Purpose Research"
    Description = "Improves multiple systems"
    Multipliers = Map [
    "Mining", 1.1m
    "Speed", 1.05m
    "Cargo", 1.08m
    ]
    Cost = 500
    }
    ]

    // Get research multipliers for a specific set of completed researches
    let getResearchMultipliers (completedResearches: string list) : PlanetMultiplier =
    let relevantResearches =
    completedResearches
    |> List.choose (fun researchName -> Map.tryFind researchName researches)

    match relevantResearches with
    | [] ->
    { Mining = 1.0m; Speed = 1.0m; Cargo = 1.0m } // No researches completed
    | _ ->
    let combinedMultipliers =
    relevantResearches
    |> List.fold (fun acc research ->
    research.Multipliers
    |> Map.fold (fun accMultipliers statName multiplier ->
    match statName with
    | "Mining" -> { accMultipliers with Mining = accMultipliers.Mining * multiplier }
    | "Speed" -> { accMultipliers with Speed = accMultipliers.Speed * multiplier }
    | "Cargo" -> { accMultipliers with Cargo = accMultipliers.Cargo * multiplier }
    | _ -> accMultipliers // Ignore unknown stats like "Smelting"
    ) acc
    ) { Mining = 1.0m; Speed = 1.0m; Cargo = 1.0m }
    combinedMultipliers

    // Get beacon multipliers for a specific planet
    let getBeaconMultipliers (beacons: Map<string, Beacon>) (planetNumber: int) : PlanetMultiplier =
    let applicableBeacons =
    beacons
    |> Map.filter (fun _ beacon ->
    let (start, endPlanet) = beacon.PlanetRange
    planetNumber >= start && planetNumber <= endPlanet
    )
    |> Map.toList

    match applicableBeacons with
    | [] -> {
    Mining = 1.0m
    Speed = 1.0m
    Cargo = 1.0m
    } // No beacons affect this planet
    | _ ->
    let result =
    applicableBeacons
    |> List.fold
    (fun acc (_, beacon) -> {
    Mining = acc.Mining * beacon.Multipliers.Mining
    Speed = acc.Speed * beacon.Multipliers.Speed
    Cargo = acc.Cargo * beacon.Multipliers.Cargo
    })
    {
    Mining = 1.0m
    Speed = 1.0m
    Cargo = 1.0m
    }

    result

    // Get global manager bonuses
    let getGlobalManagerBonuses (globalManagerBonuses: Map<string, string * decimal>) : PlanetMultiplier =
    globalManagerBonuses
    |> Map.fold (fun acc _ (bonusType, value) ->
    match bonusType.ToLower() with
    | "mining" -> { acc with Mining = acc.Mining * value }
    | "speed" -> { acc with Speed = acc.Speed * value }
    | "cargo" -> { acc with Cargo = acc.Cargo * value }
    | _ -> acc // Unknown bonus type, ignore
    ) { Mining = 1.0m; Speed = 1.0m; Cargo = 1.0m }

    // =============================================================================
    // FORMULAS MODULE - Pure calculation functions
    // =============================================================================
    module Formulas =
    // Base calculation functions (reverse-engineered from actual data)
    let calculateBaseMiningRate (level: int) : decimal =
    // Formula: level^2 * 0.0215 (improved coefficient for better accuracy)
    decimal (level * level) * 0.0215m

    let calculateBaseShipSpeed (level: int) : decimal =
    // Formula: level^2 * 0.029 (improved coefficient for better accuracy)
    decimal (level * level) * 0.029m

    let calculateBaseCargoSpace (level: int) : int =
    // Formula: level^2 * 0.24 (improved coefficient for better accuracy)
    int (decimal (level * level) * 0.24m)

    // Alternative formulas for testing
    let calculateBaseMiningRateV2 (level: int) : decimal =
    // Try: level^2 * 0.0215 (slightly higher coefficient)
    decimal (level * level) * 0.0215m

    let calculateBaseShipSpeedV2 (level: int) : decimal =
    // Try: level^2 * 0.029 (slightly higher coefficient)
    decimal (level * level) * 0.029m

    let calculateBaseCargoSpaceV2 (level: int) : int =
    // Try: level^2 * 0.24 (slightly higher coefficient)
    int (decimal (level * level) * 0.24m)

    // Formula testing functions
    let testFormulaCoefficients () =
    printfn "=== FORMULA COEFFICIENT TESTING ==="
    printfn "Testing different coefficients to find optimal values..."
    printfn ""

    // Test data points
    let testPoints = [
    (34, 76.62m, "Planet 1 Mining")
    (25, 43.21m, "Planet 2 Mining")
    (21, 24.17m, "Planet 3 Mining")
    ]

    // Test different mining coefficients
    let miningCoefficients = [0.0200m; 0.0205m; 0.0210m; 0.0215m; 0.0220m; 0.0225m]
    printfn "Mining Rate Coefficients:"
    miningCoefficients |> List.iter (fun coeff ->
    let totalError =
    testPoints |> List.sumBy (fun (level, actual, _) ->
    let calculated = decimal (level * level) * coeff * 3.1005m // Total multiplier
    let error = abs (calculated - actual) / actual * 100m
    error
    )
    let avgError = totalError / decimal testPoints.Length
    printfn " %f: Average error = %F%%" coeff avgError
    )

    printfn ""

    // Test speed data points
    let speedTestPoints = [
    (16, 15.93m, "Planet 1 Speed")
    (14, 13.32m, "Planet 2 Speed")
    (11, 9.86m, "Planet 3 Speed")
    ]

    // Test different speed coefficients
    let speedCoefficients = [0.027m; 0.028m; 0.029m; 0.030m; 0.031m]
    printfn "Speed Coefficients:"
    speedCoefficients |> List.iter (fun coeff ->
    let totalError =
    speedTestPoints |> List.sumBy (fun (level, actual, _) ->
    let calculated = decimal (level * level) * coeff * 2.1875m // Total speed multiplier
    let error = abs (calculated - actual) / actual * 100m
    error
    )
    let avgError = totalError / decimal speedTestPoints.Length
    printfn " %f: Average error = %F%%" coeff avgError
    )

    printfn ""

    // Test cargo data points
    let cargoTestPoints = [
    (16, 75, "Planet 1 Cargo")
    (14, 62, "Planet 2 Cargo")
    (11, 45, "Planet 3 Cargo")
    ]

    // Test different cargo coefficients
    let cargoCoefficients = [0.22m; 0.23m; 0.24m; 0.25m; 0.26m]
    printfn "Cargo Coefficients:"
    cargoCoefficients |> List.iter (fun coeff ->
    let totalError =
    cargoTestPoints |> List.sumBy (fun (level, actual, _) ->
    let calculated = int (decimal (level * level) * coeff * 1.25m) // Total cargo multiplier
    let error = abs (decimal calculated - decimal actual) / decimal actual * 100m
    error
    )
    let avgError = totalError / decimal cargoTestPoints.Length
    printfn " %f: Average error = %F%%" coeff avgError
    )

    // Calculate room effect at given level
    let calculateRoomEffect (roomName: string) (level: int) : decimal =
    match Map.tryFind roomName rooms with
    | Some room ->
    if room.Boost.Contains("time") || room.Boost.Contains("Surges") then
    // Special handling for time-based and surge rooms
    room.BaseEffect + (room.PerLevel * decimal (level - 1))
    else
    // Standard multiplier rooms
    room.BaseEffect + (room.PerLevel * decimal (level - 1))
    | None -> 1.0m

    // Get room multiplier for specific boost type
    let getRoomMultiplier (boostType: string) (roomLevels: Map<string, int>) : decimal =
    let relevantRooms =
    rooms
    |> Map.filter (fun _ room -> room.Boost.ToLower().Contains(boostType.ToLower()))
    |> Map.toList

    match relevantRooms with
    | [] -> 1.0m
    | _ ->
    relevantRooms
    |> List.map (fun (roomName, room) ->
    let level = Map.tryFind roomName roomLevels |> Option.defaultValue 0
    calculateRoomEffect roomName level
    )
    |> List.fold (*) 1.0m


    // Calculate final stats with all multipliers applied
    let calculatePlanetStats
    (planetNumber: int)
    (miningLevel: int)
    (speedLevel: int)
    (cargoLevel: int)
    (multipliers: Multipliers)
    (purchasedBonuses: {| MineBoost: decimal |})
    (daughtershipMultipliers: {| MiningRate: decimal; ShipSpeed: decimal; Cargo: decimal |})
    (beacons: Map<string, Beacon>)
    : PlanetCalculationResult =
    let baseMining = calculateBaseMiningRate miningLevel
    let baseSpeed = calculateBaseShipSpeed speedLevel
    let baseCargo = calculateBaseCargoSpace cargoLevel

    // Get room multipliers
    let miningRoomMultiplier = getRoomMultiplier "mine speed" multipliers.RoomLevels
    let speedRoomMultiplier = getRoomMultiplier "ship speed" multipliers.RoomLevels
    let cargoRoomMultiplier = getRoomMultiplier "cargo" multipliers.RoomLevels

    // Get beacon multipliers for this planet
    let beaconMultipliers = getBeaconMultipliers beacons planetNumber

    // Get global manager bonuses
    let globalManagerMultipliers = getGlobalManagerBonuses multipliers.GlobalManagerBonuses

    // Get research multipliers
    let researchMultipliers = getResearchMultipliers multipliers.CompletedResearches

    // Helper function to filter out neutral modifiers (1.0m)
    let filterActiveModifiers (modifiers: ModifierSource list) =
    modifiers |> List.filter (fun mod -> mod.Value <> 1.0m)

    // Collect mining modifiers (only include those that actually affect the result)
    let miningModifiers = [
    { Name = "Base Mining Rate"; Value = baseMining; Description = $"Level {miningLevel} base rate" }
    if purchasedBonuses.MineBoost <> 1.0m then { Name = "Mine Boost"; Value = purchasedBonuses.MineBoost; Description = "Purchased mine boost" }
    if multipliers.MiningRate <> 1.0m then { Name = "Mining Rate Multiplier"; Value = multipliers.MiningRate; Description = "Base mining rate multiplier" }
    if researchMultipliers.Mining <> 1.0m then { Name = "Research Bonus"; Value = researchMultipliers.Mining; Description = "Completed research bonuses" }
    if multipliers.ColonizationBonuses.ContainsKey(planetNumber) then
    let bonus = multipliers.ColonizationBonuses.[planetNumber]
    if bonus.Mining <> 1.0m then { Name = "Colonization Bonus"; Value = bonus.Mining; Description = $"Planet {planetNumber} colonized" }
    if miningRoomMultiplier <> 1.0m then { Name = "Engineering Room"; Value = miningRoomMultiplier; Description = "Engineering room bonus" }
    if beaconMultipliers.Mining <> 1.0m then { Name = "Beacon Bonus"; Value = beaconMultipliers.Mining; Description = $"Beacon bonus for planet {planetNumber}" }
    if globalManagerMultipliers.Mining <> 1.0m then { Name = "Global Manager Bonus"; Value = globalManagerMultipliers.Mining; Description = "Global manager mining bonus" }
    if multipliers.Daughtership && daughtershipMultipliers.MiningRate <> 1.0m then { Name = "Daughtership"; Value = daughtershipMultipliers.MiningRate; Description = "Daughtership mining bonus" }
    ] |> List.filter (fun mod -> mod.Value <> 1.0m)

    // Collect speed modifiers (only include those that actually affect the result)
    let speedModifiers = [
    { Name = "Base Ship Speed"; Value = baseSpeed; Description = $"Level {speedLevel} base speed" }
    if multipliers.ShipSpeed <> 1.0m then { Name = "Speed Multiplier"; Value = multipliers.ShipSpeed; Description = "Base speed multiplier" }
    if researchMultipliers.Speed <> 1.0m then { Name = "Research Bonus"; Value = researchMultipliers.Speed; Description = "Completed research bonuses" }
    if speedRoomMultiplier <> 1.0m then { Name = "Aeronautical Room"; Value = speedRoomMultiplier; Description = "Aeronautical room bonus" }
    if beaconMultipliers.Speed <> 1.0m then { Name = "Beacon Bonus"; Value = beaconMultipliers.Speed; Description = $"Beacon bonus for planet {planetNumber}" }
    if globalManagerMultipliers.Speed <> 1.0m then { Name = "Global Manager Bonus"; Value = globalManagerMultipliers.Speed; Description = "Global manager speed bonus" }
    if multipliers.Daughtership && daughtershipMultipliers.ShipSpeed <> 1.0m then { Name = "Daughtership"; Value = daughtershipMultipliers.ShipSpeed; Description = "Daughtership speed bonus" }
    ] |> List.filter (fun mod -> mod.Value <> 1.0m)

    // Collect cargo modifiers (only include those that actually affect the result)
    let cargoModifiers = [
    { Name = "Base Cargo Space"; Value = decimal baseCargo; Description = $"Level {cargoLevel} base cargo" }
    if multipliers.Cargo <> 1.0m then { Name = "Cargo Multiplier"; Value = multipliers.Cargo; Description = "Base cargo multiplier" }
    if researchMultipliers.Cargo <> 1.0m then { Name = "Research Bonus"; Value = researchMultipliers.Cargo; Description = "Completed research bonuses" }
    if cargoRoomMultiplier <> 1.0m then { Name = "Packaging Room"; Value = cargoRoomMultiplier; Description = "Packaging room bonus" }
    if beaconMultipliers.Cargo <> 1.0m then { Name = "Beacon Bonus"; Value = beaconMultipliers.Cargo; Description = $"Beacon bonus for planet {planetNumber}" }
    if globalManagerMultipliers.Cargo <> 1.0m then { Name = "Global Manager Bonus"; Value = globalManagerMultipliers.Cargo; Description = "Global manager cargo bonus" }
    if multipliers.Daughtership && daughtershipMultipliers.Cargo <> 1.0m then { Name = "Daughtership"; Value = daughtershipMultipliers.Cargo; Description = "Daughtership cargo bonus" }
    ] |> List.filter (fun mod -> mod.Value <> 1.0m)

    // Calculate final values (include all bonuses)
    let finalMiningRate =
    baseMining
    * purchasedBonuses.MineBoost
    * multipliers.MiningRate
    * researchMultipliers.Mining
    * (if multipliers.ColonizationBonuses.ContainsKey(planetNumber) then multipliers.ColonizationBonuses.[planetNumber].Mining else 1.0m)
    * miningRoomMultiplier
    * beaconMultipliers.Mining
    * globalManagerMultipliers.Mining
    * (if multipliers.Daughtership then daughtershipMultipliers.MiningRate else 1.0m)

    let finalSpeed =
    baseSpeed
    * multipliers.ShipSpeed
    * researchMultipliers.Speed
    * speedRoomMultiplier
    * beaconMultipliers.Speed
    * globalManagerMultipliers.Speed
    * (if multipliers.Daughtership then daughtershipMultipliers.ShipSpeed else 1.0m)

    let finalCargo =
    int (decimal baseCargo
    * multipliers.Cargo
    * researchMultipliers.Cargo
    * cargoRoomMultiplier
    * beaconMultipliers.Cargo
    * globalManagerMultipliers.Cargo
    * (if multipliers.Daughtership then daughtershipMultipliers.Cargo else 1.0m))

    {
    FinalStats = {
    OrePerSecond = finalMiningRate
    Speed = finalSpeed
    Cargo = finalCargo
    }
    MiningModifiers = miningModifiers
    SpeedModifiers = speedModifiers
    CargoModifiers = cargoModifiers
    }

    // =============================================================================
    // COMPOSITION MODULE - Combines formulas with user input
    // =============================================================================
    module Composition =
    // Convenience function using current user settings
    let calculatePlanetStatsDefault
    (planetNumber: int)
    (miningLevel: int)
    (speedLevel: int)
    (cargoLevel: int)
    : PlanetCalculationResult =
    calculatePlanetStats
    planetNumber
    miningLevel
    speedLevel
    cargoLevel
    UserInput.multipliers
    UserInput.purchasedBonuses
    UserInput.daughtershipMultipliers
    UserInput.beacons

    // =============================================================================
    // VALIDATION MODULE - Compare calculated vs actual results
    // =============================================================================
    module Validation =
    type PlanetValidationResult = {
    PlanetData: TestData.PlanetTestData
    Calculated: PlanetCalculationResult
    Actual: PlanetStats
    MiningAccuracy: decimal
    SpeedAccuracy: decimal
    CargoAccuracy: decimal
    OverallAccuracy: decimal
    }

    type TestCaseValidationResult = {
    TestCase: TestData.TestCase
    PlanetResults: PlanetValidationResult list
    AverageAccuracy: decimal
    }

    let validatePlanet (planetData: TestData.PlanetTestData) (testCase: TestData.TestCase) : PlanetValidationResult =
    let calculated = calculatePlanetStats
    planetData.PlanetNumber
    planetData.InputLevels.Mining
    planetData.InputLevels.ShipSpeed
    planetData.InputLevels.Cargo
    testCase.CapturedMultipliers
    testCase.CapturedPurchasedBonuses
    testCase.CapturedDaughtershipMultipliers
    testCase.CapturedBeacons

    let miningAccuracy = if planetData.ActualResults.OrePerSecond = 0m then 0m
    else (calculated.FinalStats.OrePerSecond / planetData.ActualResults.OrePerSecond) * 100m
    let speedAccuracy = if planetData.ActualResults.Speed = 0m then 0m
    else (calculated.FinalStats.Speed / planetData.ActualResults.Speed) * 100m
    let cargoAccuracy = if planetData.ActualResults.Cargo = 0 then 0m
    else (decimal calculated.FinalStats.Cargo / decimal planetData.ActualResults.Cargo) * 100m

    let overallAccuracy = (miningAccuracy + speedAccuracy + cargoAccuracy) / 3m

    {
    PlanetData = planetData
    Calculated = calculated
    Actual = planetData.ActualResults
    MiningAccuracy = miningAccuracy
    SpeedAccuracy = speedAccuracy
    CargoAccuracy = cargoAccuracy
    OverallAccuracy = overallAccuracy
    }

    let validateTestCase (testCase: TestData.TestCase) : TestCaseValidationResult =
    let planetResults = testCase.PlanetData |> List.map (fun planetData -> validatePlanet planetData testCase)
    let averageAccuracy = planetResults |> List.averageBy (fun result -> result.OverallAccuracy)

    // Update the test case with new accuracy and formula version
    let updatedTestCase = {
    testCase with
    LastCalculatedAccuracy = Some averageAccuracy
    FormulaVersion = Some TestData.currentFormulaVersion
    }

    {
    TestCase = updatedTestCase
    PlanetResults = planetResults
    AverageAccuracy = averageAccuracy
    }

    let validateAllTestCases () : TestCaseValidationResult list =
    TestData.testCases |> List.map validateTestCase

    let printValidationResults (results: TestCaseValidationResult list) =
    printfn "=== VALIDATION RESULTS ==="
    results |> List.iter (fun testCaseResult ->
    printfn "Test Case: %s" testCaseResult.TestCase.Description
    printfn " Test Date: %s" testCaseResult.TestCase.TestDate
    printfn " Average Accuracy: %f%%" testCaseResult.AverageAccuracy
    printfn " Captured Settings:"
    printfn " Research: %A" testCaseResult.TestCase.CapturedMultipliers.CompletedResearches
    printfn " Rooms: %A" testCaseResult.TestCase.CapturedMultipliers.RoomLevels
    printfn " Daughtership: %b" testCaseResult.TestCase.CapturedMultipliers.Daughtership
    printfn ""

    testCaseResult.PlanetResults |> List.iter (fun planetResult ->
    printfn " Planet %d:" planetResult.PlanetData.PlanetNumber
    printfn " Input Levels: Mining=%d, Speed=%d, Cargo=%d"
    planetResult.PlanetData.InputLevels.Mining
    planetResult.PlanetData.InputLevels.ShipSpeed
    planetResult.PlanetData.InputLevels.Cargo
    printfn " Results:"
    printfn " Mining: %f (actual: %f) - %f%% accurate"
    planetResult.Calculated.FinalStats.OrePerSecond
    planetResult.Actual.OrePerSecond
    planetResult.MiningAccuracy
    printfn " Speed: %f (actual: %f) - %f%% accurate"
    planetResult.Calculated.FinalStats.Speed
    planetResult.Actual.Speed
    planetResult.SpeedAccuracy
    printfn " Cargo: %d (actual: %d) - %f%% accurate"
    planetResult.Calculated.FinalStats.Cargo
    planetResult.Actual.Cargo
    planetResult.CargoAccuracy
    printfn " Overall: %f%% accurate" planetResult.OverallAccuracy
    printfn ""
    )
    printfn "---"
    )

    // Accuracy comparison functions
    type AccuracyComparison = {
    TestCaseDescription: string
    PreviousAccuracy: decimal option
    CurrentAccuracy: decimal
    AccuracyChange: decimal option
    FormulaVersion: string option
    }

    let compareAccuracyWithPrevious (results: TestCaseValidationResult list) : AccuracyComparison list =
    results |> List.map (fun result ->
    let accuracyChange =
    match result.TestCase.LastCalculatedAccuracy with
    | Some prevAccuracy -> Some (result.AverageAccuracy - prevAccuracy)
    | None -> None

    {
    TestCaseDescription = result.TestCase.Description
    PreviousAccuracy = result.TestCase.LastCalculatedAccuracy
    CurrentAccuracy = result.AverageAccuracy
    AccuracyChange = accuracyChange
    FormulaVersion = result.TestCase.FormulaVersion
    }
    )

    let printAccuracyComparison (comparisons: AccuracyComparison list) =
    printfn "=== ACCURACY COMPARISON ==="
    printfn "Formula Version: %s" TestData.currentFormulaVersion
    printfn ""

    comparisons |> List.iter (fun comparison ->
    printfn "Test Case: %s" comparison.TestCaseDescription
    match comparison.PreviousAccuracy with
    | Some prevAccuracy ->
    let change = comparison.AccuracyChange.Value
    let changeStr = if change > 0m then "+" + change.ToString("F2") else change.ToString("F2")
    let trend = if change > 0m then "📈 IMPROVED" elif change < 0m then "📉 DECLINED" else "➡️ SAME"
    printfn " Previous: %F%% (Formula: %s)" prevAccuracy (comparison.FormulaVersion.Value)
    printfn " Current: %F%% (Formula: %s)" comparison.CurrentAccuracy TestData.currentFormulaVersion
    printfn " Change: %s%% %s" changeStr trend
    | None ->
    printfn " Current: %F%% (Formula: %s) - First calculation" comparison.CurrentAccuracy TestData.currentFormulaVersion
    printfn ""
    )

    // Overall summary
    let hasPreviousData = comparisons |> List.exists (fun c -> c.PreviousAccuracy.IsSome)
    if hasPreviousData then
    let avgChange =
    comparisons
    |> List.choose (fun c -> c.AccuracyChange)
    |> List.average
    let trend = if avgChange > 0m then "📈 IMPROVED" elif avgChange < 0m then "📉 DECLINED" else "➡️ SAME"
    printfn "Overall Average Change: %F%% %s" avgChange trend

    // =============================================================================
    // EXAMPLE USAGE
    // =============================================================================
    module Example =
    // Example: Calculate planet stats with current user settings
    let calculatePlanet1 () =
    let result = Composition.calculatePlanetStatsDefault 1 34 16 16
    printfn "Planet 1 (Level 34/16/16):"
    printfn " Mining: %f ore/sec" result.FinalStats.OrePerSecond
    printfn " Speed: %f" result.FinalStats.Speed
    printfn " Cargo: %d" result.FinalStats.Cargo
    result

    // Example: Validate all test cases
    let runValidation () =
    let results = Validation.validateAllTestCases ()
    Validation.printValidationResults results
    results

    // Example: Run validation with accuracy comparison
    let runValidationWithComparison () =
    let results = Validation.validateAllTestCases ()
    Validation.printValidationResults results
    printfn ""
    let comparisons = Validation.compareAccuracyWithPrevious results
    Validation.printAccuracyComparison comparisons
    (results, comparisons)

    // Example: Test with different user settings
    let testWithAdvancedMining () =
    let customMultipliers = {
    UserInput.multipliers with
    CompletedResearches = ["Advanced Mining"]
    }
    let result = calculatePlanetStats
    1 34 16 16
    customMultipliers
    UserInput.purchasedBonuses
    UserInput.daughtershipMultipliers
    UserInput.beacons
    printfn "Planet 1 with Advanced Mining research:"
    printfn " Mining: %f ore/sec" result.FinalStats.OrePerSecond
    result

    // Example: Capture current state as new test case with multiple planets
    let captureNewTestCase () =
    // Example: Capture multiple planets with current settings
    let planetData = [
    TestData.createPlanetTestData
    1
    { Mining = 35; ShipSpeed = 17; Cargo = 17 }
    { OrePerSecond = 85.5m; Speed = 18.2m; Cargo = 85 }
    TestData.createPlanetTestData
    2
    { Mining = 26; ShipSpeed = 15; Cargo = 15 }
    { OrePerSecond = 48.3m; Speed = 14.8m; Cargo = 68 }
    ]

    let newTestCase = TestData.captureCurrentState
    planetData
    "Planets 1-2 with Advanced Mining research - after leveling up"

    printfn "Captured new test case:"
    printfn " Description: %s" newTestCase.Description
    printfn " Date: %s" newTestCase.TestDate
    printfn " Planets: %d" newTestCase.PlanetData.Length
    newTestCase.PlanetData |> List.iter (fun planet ->
    printfn " Planet %d: Mining=%d/%d/%d -> %f/%f/%d"
    planet.PlanetNumber
    planet.InputLevels.Mining
    planet.InputLevels.ShipSpeed
    planet.InputLevels.Cargo
    planet.ActualResults.OrePerSecond
    planet.ActualResults.Speed
    planet.ActualResults.Cargo
    )

    newTestCase

    // Example: Add new test case to the list
    let addNewTestCase () =
    let newTestCase = captureNewTestCase ()
    let updatedTestCases = TestData.addTestCase newTestCase
    printfn "Added new test case. Total test cases: %d" updatedTestCases.Length
    updatedTestCases

    // Example: Test different formula coefficients
    let testFormulaCoefficients () =
    Formulas.testFormulaCoefficients()

    // Example: Try improved formulas
    let testImprovedFormulas () =
    printfn "=== TESTING IMPROVED FORMULAS ==="
    printfn "Based on coefficient analysis, trying optimized formulas..."
    printfn ""

    // Let's try the formulas that should give better accuracy
    let testImprovedMining (level: int) = decimal (level * level) * 0.0215m
    let testImprovedSpeed (level: int) = decimal (level * level) * 0.029m
    let testImprovedCargo (level: int) = int (decimal (level * level) * 0.24m)

    // Test against our data
    let testData = [
    (34, 76.62m, 16, 15.93m, 16, 75)
    (25, 43.21m, 14, 13.32m, 14, 62)
    (21, 24.17m, 11, 9.86m, 11, 45)
    ]

    testData |> List.iter (fun (miningLevel, miningActual, speedLevel, speedActual, cargoLevel, cargoActual) ->
    let miningBase = testImprovedMining miningLevel
    let speedBase = testImprovedSpeed speedLevel
    let cargoBase = testImprovedCargo cargoLevel

    let miningFinal = miningBase * 3.1005m
    let speedFinal = speedBase * 2.1875m
    let cargoFinal = cargoBase * 1.25m

    let miningError = abs (miningFinal - miningActual) / miningActual * 100m
    let speedError = abs (speedFinal - speedActual) / speedActual * 100m
    let cargoError = abs (decimal cargoFinal - decimal cargoActual) / decimal cargoActual * 100m

    printfn "Level %d/%d/%d:" miningLevel speedLevel cargoLevel
    printfn " Mining: %f (actual: %f) - %F%% error" miningFinal miningActual miningError
    printfn " Speed: %f (actual: %f) - %F%% error" speedFinal speedActual speedError
    printfn " Cargo: %d (actual: %d) - %F%% error" cargoFinal cargoActual cargoError
    printfn ""
    )