ui/phase-15: planet inspector production controls + order-draft collapse
Adds the second end-to-end command (`setProductionType`) with a collapse-by-`planetNumber` rule on the order draft, the segmented production-controls component on the planet inspector, the FBS encoder/decoder pair for `CommandPlanetProduce`, and the `localShipClass` projection on `GameReport`. Forecast number is deferred and tracked in the new `ui/docs/calc-bridge.md`.
This commit is contained in:
@@ -26,6 +26,7 @@ import { UUID } from "../src/proto/galaxy/fbs/common";
|
||||
import {
|
||||
LocalPlanet,
|
||||
Report,
|
||||
ShipClass,
|
||||
} from "../src/proto/galaxy/fbs/report";
|
||||
|
||||
const listMyGamesSpy = vi.fn();
|
||||
@@ -102,6 +103,7 @@ function buildReportPayload(opts: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
planets?: PlanetFixture[];
|
||||
shipClasses?: { name: string }[];
|
||||
}): Uint8Array {
|
||||
const builder = new Builder(256);
|
||||
const planetOffsets = (opts.planets ?? []).map((planet) => {
|
||||
@@ -115,10 +117,20 @@ function buildReportPayload(opts: {
|
||||
LocalPlanet.addResources(builder, 0.5);
|
||||
return LocalPlanet.endLocalPlanet(builder);
|
||||
});
|
||||
const shipClassOffsets = (opts.shipClasses ?? []).map((cls) => {
|
||||
const name = builder.createString(cls.name);
|
||||
ShipClass.startShipClass(builder);
|
||||
ShipClass.addName(builder, name);
|
||||
return ShipClass.endShipClass(builder);
|
||||
});
|
||||
const localPlanetVec =
|
||||
planetOffsets.length === 0
|
||||
? null
|
||||
: Report.createLocalPlanetVector(builder, planetOffsets);
|
||||
const localShipClassVec =
|
||||
shipClassOffsets.length === 0
|
||||
? null
|
||||
: Report.createLocalShipClassVector(builder, shipClassOffsets);
|
||||
|
||||
Report.startReport(builder);
|
||||
Report.addTurn(builder, BigInt(opts.turn));
|
||||
@@ -128,6 +140,9 @@ function buildReportPayload(opts: {
|
||||
if (localPlanetVec !== null) {
|
||||
Report.addLocalPlanet(builder, localPlanetVec);
|
||||
}
|
||||
if (localShipClassVec !== null) {
|
||||
Report.addLocalShipClass(builder, localShipClassVec);
|
||||
}
|
||||
const reportOff = Report.endReport(builder);
|
||||
builder.finish(reportOff);
|
||||
return builder.asUint8Array();
|
||||
@@ -261,4 +276,23 @@ describe("GameStateStore", () => {
|
||||
expect(store.status).toBe("error");
|
||||
expect(store.error).toBe("device session missing");
|
||||
});
|
||||
|
||||
test("decodeReport surfaces the localShipClass projection by name", async () => {
|
||||
listMyGamesSpy.mockResolvedValue([makeGameSummary(1)]);
|
||||
const client = makeFakeClient(async () => ({
|
||||
resultCode: "ok",
|
||||
payloadBytes: buildReportPayload({
|
||||
turn: 1,
|
||||
planets: [{ number: 1, name: "Earth", x: 100, y: 100 }],
|
||||
shipClasses: [{ name: "Scout" }, { name: "Destroyer" }],
|
||||
}),
|
||||
}));
|
||||
const store = new GameStateStore();
|
||||
await store.init({ client, cache, gameId: GAME_ID });
|
||||
expect(store.report?.localShipClass).toEqual([
|
||||
{ name: "Scout" },
|
||||
{ name: "Destroyer" },
|
||||
]);
|
||||
store.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user