9ae7b88b89
Fuse the standalone ship-class designer (Phases 17/18) into a sidebar calculator: live mass/speed/attack/defence/bombing results, a planet build-rate readout, single-target goal-seek, a modernization-cost mode, and auto reach circles on the map for the selected planet. pkg/calc becomes the single source for the new math (no mirroring): extract BombingPower from the engine model and the per-turn ship-production loop from controller.ProduceShip into pkg/calc (engine now delegates), and add inverse goal-seek solvers in pkg/calc/solve.go. Thin-bridge the combat, planet-build, and solver functions through ui/core/calc + ui/wasm and rebuild core.wasm. Remove the standalone designer view/route; the ship-classes table and the view/bottom menus open the calculator via a shared request store. Docs: rewrite ui/PLAN.md Phase 30, adjust Phase 34 (realistic forecast + CAP/COL ownership), add ui/docs/calculator-ux.md, extend calc-bridge.md, fix navigation.md; remove ui/CALCULATOR.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
59 lines
1.8 KiB
Go
59 lines
1.8 KiB
Go
package calc_test
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
|
|
"galaxy/calc"
|
|
)
|
|
|
|
func TestBlockUpgradeCost(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
blockMass float64
|
|
currentTech float64
|
|
targetTech float64
|
|
want float64
|
|
}{
|
|
{"zero block mass returns zero", 0, 1.0, 2.0, 0},
|
|
{"target equal to current returns zero", 5, 2.0, 2.0, 0},
|
|
{"target below current returns zero", 5, 2.0, 1.0, 0},
|
|
{"doubling tech on mass 5 costs 25", 5, 1.0, 2.0, 25},
|
|
{"doubling tech on mass 10 costs 50", 10, 1.0, 2.0, 50},
|
|
{"partial step from 2.0 to 2.5 on mass 5", 5, 2.0, 2.5, 10},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := calc.BlockUpgradeCost(tc.blockMass, tc.currentTech, tc.targetTech)
|
|
if math.Abs(got-tc.want) > 1e-9 {
|
|
t.Errorf("BlockUpgradeCost(%v, %v, %v) = %v, want %v",
|
|
tc.blockMass, tc.currentTech, tc.targetTech, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBombingPower(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
weapons, weaponsTech, armament, number float64
|
|
want float64
|
|
}{
|
|
// Parity with the engine's Battle_Station fixture
|
|
// (game/internal/model/game/group_test.go): (sqrt(30)/10+1)*30*3.
|
|
{"battle station, single ship", 30, 1, 3, 1, 139.29503},
|
|
{"battle station, two ships scale linearly", 30, 1, 3, 2, 278.59006},
|
|
{"no armament: zero power", 30, 1, 0, 5, 0},
|
|
{"no weapons: zero power", 0, 1, 3, 5, 0},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := calc.BombingPower(tc.weapons, tc.weaponsTech, tc.armament, tc.number)
|
|
if math.Abs(got-tc.want) > 1e-3 {
|
|
t.Errorf("BombingPower(%v, %v, %v, %v) = %v, want %v",
|
|
tc.weapons, tc.weaponsTech, tc.armament, tc.number, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|