ui/phase-22: races table with stance toggle and vote slot
Adds the Races View in the in-game shell. The table lists every non-extinct other race with tech levels (percent), totals, planets, votes received, and a per-row WAR | PEACE segmented control. A single vote-recipient slot above the table queues a `CommandRaceVote`; per-row buttons queue `CommandRaceRelation`. Both commands flow through the existing order draft store with collapse-by-acceptor (stance) and singleton (vote) rules. `GameReport` widens with `races`, `myVotes`, `myVoteFor`; the decoder walks `report.player[]` once for the richer projection. The optimistic overlay flips stance and vote target immediately; `votesReceived`, `myVotes`, and the alliance summary stay server-authoritative — alliance grouping and the 2/3 victory check are tallied on the server at turn cutoff and explicitly not surfaced client-side (`rules.txt` keeps foreign races' outgoing vote targets private). Includes Vitest component coverage of stance and vote collapse rules + a Playwright e2e that drives both commands through the dispatcher route and verifies the gateway saw the expected `CommandRaceRelation` / `CommandRaceVote` payloads. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@ import {
|
||||
CommandPlanetRename,
|
||||
CommandPlanetRouteRemove,
|
||||
CommandPlanetRouteSet,
|
||||
CommandRaceRelation,
|
||||
CommandRaceVote,
|
||||
CommandScienceCreate,
|
||||
CommandScienceRemove,
|
||||
CommandShipClassCreate,
|
||||
@@ -30,6 +32,7 @@ import {
|
||||
CommandShipGroupUpgrade,
|
||||
PlanetProduction,
|
||||
PlanetRouteLoadType,
|
||||
Relation,
|
||||
ShipGroupCargo,
|
||||
ShipGroupUpgradeTech,
|
||||
UserGamesOrderGet,
|
||||
@@ -39,6 +42,7 @@ import type {
|
||||
CargoLoadType,
|
||||
OrderCommand,
|
||||
ProductionType,
|
||||
Relation as RelationLiteral,
|
||||
ShipGroupCargo as ShipGroupCargoLiteral,
|
||||
ShipGroupUpgradeTech as ShipGroupUpgradeTechLiteral,
|
||||
} from "./order-types";
|
||||
@@ -354,6 +358,32 @@ function decodeCommand(item: CommandItemView): OrderCommand | null {
|
||||
name: inner.name() ?? "",
|
||||
};
|
||||
}
|
||||
case CommandPayload.CommandRaceRelation: {
|
||||
const inner = new CommandRaceRelation();
|
||||
item.payload(inner);
|
||||
const relation = relationFromFBS(inner.relation());
|
||||
if (relation === null) {
|
||||
console.warn(
|
||||
`fetchOrder: skipping CommandRaceRelation with unknown relation enum (${inner.relation()})`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
kind: "setDiplomaticStance",
|
||||
id,
|
||||
acceptor: inner.acceptor() ?? "",
|
||||
relation,
|
||||
};
|
||||
}
|
||||
case CommandPayload.CommandRaceVote: {
|
||||
const inner = new CommandRaceVote();
|
||||
item.payload(inner);
|
||||
return {
|
||||
kind: "setVoteRecipient",
|
||||
id,
|
||||
acceptor: inner.acceptor() ?? "",
|
||||
};
|
||||
}
|
||||
default:
|
||||
console.warn(
|
||||
`fetchOrder: skipping unknown command kind (payloadType=${payloadType})`,
|
||||
@@ -469,6 +499,24 @@ export function shipGroupUpgradeTechFromFBS(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* relationFromFBS reverses `relationToFBS` from `submit.ts`.
|
||||
* `Relation.UNKNOWN` and any out-of-band value yield `null` so the
|
||||
* caller drops the entry rather than fabricating a synthetic stance.
|
||||
*/
|
||||
export function relationFromFBS(value: Relation): RelationLiteral | null {
|
||||
switch (value) {
|
||||
case Relation.WAR:
|
||||
return "WAR";
|
||||
case Relation.PEACE:
|
||||
return "PEACE";
|
||||
case Relation.UNKNOWN:
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function decodeError(
|
||||
payload: Uint8Array,
|
||||
resultCode: string,
|
||||
|
||||
Reference in New Issue
Block a user