ui/phase-21: sciences CRUD list, designer, and production-picker integration

Lights up the player-defined sciences feature: a table view with sort
and filter, a designer with four percent inputs and a strict
sum-equals-100 gate, and a Research-sub-row integration so the
planet production picker lists the user's sciences alongside the
four tech buttons. Phase 21 decisions are baked back into ui/PLAN.md
(no UpdateScience on the wire — write-once via createScience +
removeScience; percentages instead of fractions; sciences live under
the existing Research segment).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-10 21:32:37 +02:00
parent 0509f2cde2
commit 7bea22b0b5
31 changed files with 2751 additions and 71 deletions
+19
View File
@@ -27,6 +27,7 @@ import type {
ReportPlanet,
ReportRoute,
ReportUnidentifiedShipGroup,
ScienceSummary,
ShipClassSummary,
ShipGroupTech,
} from "./game-state";
@@ -144,6 +145,14 @@ interface SyntheticLocalFleet {
state?: string;
}
interface SyntheticScience {
name?: string;
drive?: number;
weapons?: number;
shields?: number;
cargo?: number;
}
interface SyntheticReportRoot {
turn?: number;
mapWidth?: number;
@@ -156,6 +165,7 @@ interface SyntheticReportRoot {
uninhabitedPlanet?: SyntheticPlanet[];
unidentifiedPlanet?: SyntheticPlanet[];
localShipClass?: SyntheticShipClass[];
localScience?: SyntheticScience[];
localGroup?: SyntheticShipGroup[];
otherGroup?: SyntheticShipGroup[];
incomingGroup?: SyntheticIncomingGroup[];
@@ -194,6 +204,14 @@ function decodeSyntheticReport(json: unknown): GameReport {
}),
);
const localScience: ScienceSummary[] = (root.localScience ?? []).map((sc) => ({
name: typeof sc.name === "string" ? sc.name : "",
drive: numOr0(sc.drive),
weapons: numOr0(sc.weapons),
shields: numOr0(sc.shields),
cargo: numOr0(sc.cargo),
}));
const race = typeof root.race === "string" ? root.race : "";
const tech = findLocalPlayerTech(root.player ?? [], race);
@@ -260,6 +278,7 @@ function decodeSyntheticReport(json: unknown): GameReport {
planets,
race,
localShipClass,
localScience,
routes,
localPlayerDrive: tech.drive,
localPlayerWeapons: tech.weapons,