module IdlePlanetMiner // ============================================================================= // CORE DATA TYPES // ============================================================================= // Planet information from the wiki type Planet = { Name: string BasePrice: int Distance: int Tele: int option Resources: Map } // 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 // 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 Daughtership: bool RoomLevels: Map // Room name -> level CompletedResearches: string list // List of completed research names GlobalManagerBonuses: Map // 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 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.2.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 } 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 // 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) (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) : 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 = // 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 = // 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 = // 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) : 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) : 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 "" )