feat(ui): Phase 30 ship-class calculator with goal-seek and reach circles
Tests · UI / test (push) Successful in 2m14s
Tests · Go / test (push) Successful in 2m25s

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>
This commit is contained in:
Ilia Denisov
2026-05-21 19:52:08 +02:00
parent 00159ddf7c
commit 9ae7b88b89
53 changed files with 3748 additions and 1298 deletions
@@ -25,6 +25,7 @@ import {
} from "../src/sync/order-draft.svelte";
import { CORE_CONTEXT_KEY, CoreHolder } from "../src/lib/core-context.svelte";
import type { Core } from "../src/platform/core/index";
import { makeFakeCore } from "./fake-core";
import { IDBCache } from "../src/platform/store/idb-cache";
import { openGalaxyDB, type GalaxyDB } from "../src/platform/store/idb";
import type { Cache } from "../src/platform/store/index";
@@ -107,34 +108,12 @@ function group(
};
}
// stubCore mirrors `pkg/calc/ship.go.BlockUpgradeCost` exactly so the
// preview line shows the same number the WASM bridge would produce.
// The other Core methods are no-ops because the modernize preview
// only consults `weaponsBlockMass` (returns null when armament is
// zero) and `blockUpgradeCost`.
// stubCore mirrors `pkg/calc` exactly (via the shared makeFakeCore) so
// the preview line shows the same number the WASM bridge would produce.
// The modernize preview only consults `weaponsBlockMass` (returns null
// when armament is zero) and `blockUpgradeCost`.
function stubCore(): Core {
return {
signRequest: () => new Uint8Array(),
verifyResponse: () => true,
verifyEvent: () => true,
verifyPayloadHash: () => true,
driveEffective: ({ drive, driveTech }) => drive * driveTech,
emptyMass: () => 0,
weaponsBlockMass: ({ weapons, armament }) => {
if ((armament === 0 && weapons !== 0) || (armament !== 0 && weapons === 0)) {
return null;
}
return (armament + 1) * (weapons / 2);
},
fullMass: ({ emptyMass, carryingMass }) => emptyMass + carryingMass,
speed: () => 0,
cargoCapacity: () => 0,
carryingMass: () => 0,
blockUpgradeCost: ({ blockMass, currentTech, targetTech }) => {
if (blockMass === 0 || targetTech <= currentTech) return 0;
return (1 - currentTech / targetTech) * 10 * blockMass;
},
};
return makeFakeCore();
}
function mount(