Last active
September 4, 2025 21:24
-
-
Save ImaginaryDevelopment/5c8646d229c3acd0ab968349f2c18d1c to your computer and use it in GitHub Desktop.
IdlePlanetMiner spike
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.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<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 = | |
// 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<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 "" | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment