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:
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user