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:
@@ -16,6 +16,8 @@ import {
|
||||
CommandPlanetRename,
|
||||
CommandPlanetRouteRemove,
|
||||
CommandPlanetRouteSet,
|
||||
CommandScienceCreate,
|
||||
CommandScienceRemove,
|
||||
CommandShipClassCreate,
|
||||
CommandShipClassRemove,
|
||||
PlanetProduction,
|
||||
@@ -82,13 +84,29 @@ export interface RemoveShipClassResultFixture extends CommandResultFixtureBase {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CreateScienceResultFixture extends CommandResultFixtureBase {
|
||||
kind: "createScience";
|
||||
name: string;
|
||||
drive: number;
|
||||
weapons: number;
|
||||
shields: number;
|
||||
cargo: number;
|
||||
}
|
||||
|
||||
export interface RemoveScienceResultFixture extends CommandResultFixtureBase {
|
||||
kind: "removeScience";
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type CommandResultFixture =
|
||||
| PlanetRenameResultFixture
|
||||
| SetProductionTypeResultFixture
|
||||
| SetCargoRouteResultFixture
|
||||
| RemoveCargoRouteResultFixture
|
||||
| CreateShipClassResultFixture
|
||||
| RemoveShipClassResultFixture;
|
||||
| RemoveShipClassResultFixture
|
||||
| CreateScienceResultFixture
|
||||
| RemoveScienceResultFixture;
|
||||
|
||||
export function buildOrderResponsePayload(
|
||||
gameId: string,
|
||||
@@ -215,6 +233,28 @@ function encodeItem(builder: Builder, c: CommandResultFixture): number {
|
||||
payloadType = CommandPayload.CommandShipClassRemove;
|
||||
break;
|
||||
}
|
||||
case "createScience": {
|
||||
const nameOffset = builder.createString(c.name);
|
||||
inner = CommandScienceCreate.createCommandScienceCreate(
|
||||
builder,
|
||||
nameOffset,
|
||||
c.drive,
|
||||
c.weapons,
|
||||
c.shields,
|
||||
c.cargo,
|
||||
);
|
||||
payloadType = CommandPayload.CommandScienceCreate;
|
||||
break;
|
||||
}
|
||||
case "removeScience": {
|
||||
const nameOffset = builder.createString(c.name);
|
||||
inner = CommandScienceRemove.createCommandScienceRemove(
|
||||
builder,
|
||||
nameOffset,
|
||||
);
|
||||
payloadType = CommandPayload.CommandScienceRemove;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CommandItem.startCommandItem(builder);
|
||||
CommandItem.addCmdId(builder, cmdIdOffset);
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
// against realistic values. Phase 15 adds a minimal `LocalShipClass`
|
||||
// projection so the planet inspector's Build-Ship sub-picker has data
|
||||
// in e2e specs (`name` only — Phase 17 widens this when ship-class
|
||||
// CRUD lands). Later phases extend the helper as fleets, sciences,
|
||||
// etc. land.
|
||||
// CRUD lands). Phase 21 adds a `LocalScience` projection so the
|
||||
// sciences table and the planet production picker's Research sub-row
|
||||
// have data in e2e specs.
|
||||
|
||||
import { Builder } from "flatbuffers";
|
||||
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
Report,
|
||||
Route,
|
||||
RouteEntry,
|
||||
Science,
|
||||
ShipClass,
|
||||
UnidentifiedPlanet,
|
||||
UninhabitedPlanet,
|
||||
@@ -60,6 +62,14 @@ export interface ShipClassFixture {
|
||||
cargo?: number;
|
||||
}
|
||||
|
||||
export interface ScienceFixture {
|
||||
name: string;
|
||||
drive?: number;
|
||||
weapons?: number;
|
||||
shields?: number;
|
||||
cargo?: number;
|
||||
}
|
||||
|
||||
export interface PlayerFixture {
|
||||
name: string;
|
||||
drive?: number;
|
||||
@@ -84,6 +94,7 @@ export interface ReportFixture {
|
||||
uninhabitedPlanets?: PlanetFixture[];
|
||||
unidentifiedPlanets?: { number: number; x: number; y: number }[];
|
||||
localShipClass?: ShipClassFixture[];
|
||||
localScience?: ScienceFixture[];
|
||||
race?: string;
|
||||
players?: PlayerFixture[];
|
||||
routes?: RouteFixture[];
|
||||
@@ -178,6 +189,17 @@ export function buildReportPayload(fixture: ReportFixture): Uint8Array {
|
||||
return ShipClass.endShipClass(builder);
|
||||
});
|
||||
|
||||
const localScienceOffsets = (fixture.localScience ?? []).map((sci) => {
|
||||
const name = builder.createString(sci.name);
|
||||
Science.startScience(builder);
|
||||
Science.addName(builder, name);
|
||||
Science.addDrive(builder, sci.drive ?? 0);
|
||||
Science.addWeapons(builder, sci.weapons ?? 0);
|
||||
Science.addShields(builder, sci.shields ?? 0);
|
||||
Science.addCargo(builder, sci.cargo ?? 0);
|
||||
return Science.endScience(builder);
|
||||
});
|
||||
|
||||
const playerOffsets = (fixture.players ?? []).map((p) => {
|
||||
const name = builder.createString(p.name);
|
||||
Player.startPlayer(builder);
|
||||
@@ -221,6 +243,10 @@ export function buildReportPayload(fixture: ReportFixture): Uint8Array {
|
||||
localShipClassOffsets.length === 0
|
||||
? null
|
||||
: Report.createLocalShipClassVector(builder, localShipClassOffsets);
|
||||
const localScienceVec =
|
||||
localScienceOffsets.length === 0
|
||||
? null
|
||||
: Report.createLocalScienceVector(builder, localScienceOffsets);
|
||||
const playerVec =
|
||||
playerOffsets.length === 0
|
||||
? null
|
||||
@@ -251,6 +277,8 @@ export function buildReportPayload(fixture: ReportFixture): Uint8Array {
|
||||
if (unidentifiedVec !== null) Report.addUnidentifiedPlanet(builder, unidentifiedVec);
|
||||
if (localShipClassVec !== null)
|
||||
Report.addLocalShipClass(builder, localShipClassVec);
|
||||
if (localScienceVec !== null)
|
||||
Report.addLocalScience(builder, localScienceVec);
|
||||
if (routeVec !== null) Report.addRoute(builder, routeVec);
|
||||
const reportOff = Report.endReport(builder);
|
||||
builder.finish(reportOff);
|
||||
|
||||
Reference in New Issue
Block a user