ui/phase-17: ship-class CRUD without calc

Phase 17 lights up the ship-class table and designer active views,
extends the order-draft pipeline with createShipClass and
removeShipClass commands, and projects pending Save/Delete actions
through applyOrderOverlay so the table reflects the player's
intent before auto-sync lands. The plan is corrected in the same
patch: per game/rules.txt, ship classes are designed once and
cannot be edited — the engine has no Update command, so the UI
exposes only Create + Delete.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-09 21:44:21 +02:00
parent 8a236bef14
commit 785c3483f8
23 changed files with 2456 additions and 99 deletions
+43 -5
View File
@@ -98,12 +98,21 @@ interface PlanetFixture {
y: number;
}
interface ShipClassFixture {
name: string;
drive?: number;
armament?: number;
weapons?: number;
shields?: number;
cargo?: number;
}
function buildReportPayload(opts: {
turn: number;
width?: number;
height?: number;
planets?: PlanetFixture[];
shipClasses?: { name: string }[];
shipClasses?: ShipClassFixture[];
}): Uint8Array {
const builder = new Builder(256);
const planetOffsets = (opts.planets ?? []).map((planet) => {
@@ -121,6 +130,11 @@ function buildReportPayload(opts: {
const name = builder.createString(cls.name);
ShipClass.startShipClass(builder);
ShipClass.addName(builder, name);
ShipClass.addDrive(builder, cls.drive ?? 0);
ShipClass.addArmament(builder, BigInt(cls.armament ?? 0));
ShipClass.addWeapons(builder, cls.weapons ?? 0);
ShipClass.addShields(builder, cls.shields ?? 0);
ShipClass.addCargo(builder, cls.cargo ?? 0);
return ShipClass.endShipClass(builder);
});
const localPlanetVec =
@@ -277,21 +291,45 @@ describe("GameStateStore", () => {
expect(store.error).toBe("device session missing");
});
test("decodeReport surfaces the localShipClass projection by name", async () => {
test("decodeReport surfaces the localShipClass projection with full attributes", 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" }],
shipClasses: [
{
name: "Scout",
drive: 1,
armament: 0,
weapons: 0,
shields: 0,
cargo: 0,
},
{
name: "Destroyer",
drive: 6,
armament: 1,
weapons: 8,
shields: 4,
cargo: 0,
},
],
}),
}));
const store = new GameStateStore();
await store.init({ client, cache, gameId: GAME_ID });
expect(store.report?.localShipClass).toEqual([
{ name: "Scout" },
{ name: "Destroyer" },
{ name: "Scout", drive: 1, armament: 0, weapons: 0, shields: 0, cargo: 0 },
{
name: "Destroyer",
drive: 6,
armament: 1,
weapons: 8,
shields: 4,
cargo: 0,
},
]);
store.dispose();
});