723885e74e
Tests · UI / test (push) Has been cancelled
Tests · Go / test (push) Successful in 2m3s
Tests · Go / test (pull_request) Successful in 2m5s
Tests · Integration / integration (pull_request) Successful in 1m44s
Tests · UI / test (pull_request) Failing after 4m28s
Three issues surfaced once the per-command rejection from the previous commit actually reached the UI: 1. Sync banner falsely red. `OrderDraftStore.runSync` flipped `syncStatus = "error"` whenever any command was rejected and advertised a Retry button. A per-command rejection is a player-correctable state — the round trip succeeded, the engine just refused that command — so the retry can't help. Keep `syncStatus = "synced"` on `success`; the red row highlight is the visible cue. 2. Rejection reason missing. Add `cmd_error_message: string` to `CommandItem` in `pkg/schema/fbs/order.fbs` (appended last to preserve existing slot offsets) and regenerate the Go + TS stubs for that one type. Plumb the message through `CommandMeta`, `Controller.applyCommand`'s `m.Result(code, message)` call, the Go transcoder, the UI decoders in `submit.ts` / `order-load.ts`, and the `OrderDraftStore.errorMessages` map. `order-tab.svelte` renders it as an italic danger-coloured line under rejected commands, with new CSS for `.error-reason`. 3. Verdict lost on navigation. `order-load.ts.decodeCommand` never read `cmdApplied`/`cmdErrorCode`, so `hydrateFromServer` fell back to a blanket "applied" status — a previously-rejected command came back green after a lobby → game round trip. Extend the fetch decoder to populate `statuses`/`errorCodes`/ `errorMessages` maps and have `hydrateFromServer` use them. Engine-side persistence already records the verdict on disk — verified against the live `0000/order/<id>.json`. `flatbuffers@25` elides default-int8/int64 fields on write; the Go transcoder force-slots `cmd_applied=false` / `cmd_error_code=0` already, the new test fixtures flip `builder.forceDefaults(true)` to mirror that behaviour so the round trip survives. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
257 lines
5.4 KiB
Plaintext
257 lines
5.4 KiB
Plaintext
// order reflects model/order/Order data object
|
|
include "common.fbs";
|
|
|
|
namespace order;
|
|
|
|
enum Relation : byte {
|
|
UNKNOWN = 0,
|
|
WAR = 1,
|
|
PEACE = 2
|
|
}
|
|
|
|
enum ShipGroupCargo : byte {
|
|
UNKNOWN = 0,
|
|
COL = 1,
|
|
MAT = 2,
|
|
CAP = 3
|
|
}
|
|
|
|
enum ShipGroupUpgradeTech : byte {
|
|
UNKNOWN = 0,
|
|
ALL = 1,
|
|
DRIVE = 2,
|
|
WEAPONS = 3,
|
|
SHIELDS = 4,
|
|
CARGO = 5
|
|
}
|
|
|
|
enum PlanetProduction : byte {
|
|
UNKNOWN = 0,
|
|
MAT = 1,
|
|
CAP = 2,
|
|
DRIVE = 3,
|
|
WEAPONS = 4,
|
|
SHIELDS = 5,
|
|
CARGO = 6,
|
|
SCIENCE = 7,
|
|
SHIP = 8
|
|
}
|
|
|
|
enum PlanetRouteLoadType : byte {
|
|
UNKNOWN = 0,
|
|
MAT = 1,
|
|
CAP = 2,
|
|
COL = 3,
|
|
EMP = 4
|
|
}
|
|
|
|
table CommandRaceQuit {}
|
|
|
|
table CommandRaceVote {
|
|
acceptor: string;
|
|
}
|
|
|
|
table CommandRaceRelation {
|
|
acceptor: string;
|
|
relation: Relation = UNKNOWN;
|
|
}
|
|
|
|
table CommandShipClassCreate {
|
|
name: string;
|
|
drive: float64;
|
|
armament: int64;
|
|
weapons: float64;
|
|
shields: float64;
|
|
cargo: float64;
|
|
}
|
|
|
|
table CommandShipClassMerge {
|
|
name: string;
|
|
target: string;
|
|
}
|
|
|
|
table CommandShipClassRemove {
|
|
name: string;
|
|
}
|
|
|
|
table CommandShipGroupBreak {
|
|
id: string;
|
|
new_id: string;
|
|
quantity: int64;
|
|
}
|
|
|
|
table CommandShipGroupLoad {
|
|
id: string;
|
|
cargo: ShipGroupCargo = UNKNOWN;
|
|
quantity: float64;
|
|
}
|
|
|
|
table CommandShipGroupUnload {
|
|
id: string;
|
|
quantity: float64;
|
|
}
|
|
|
|
table CommandShipGroupSend {
|
|
id: string;
|
|
destination: int64;
|
|
}
|
|
|
|
table CommandShipGroupUpgrade {
|
|
id: string;
|
|
tech: ShipGroupUpgradeTech = UNKNOWN;
|
|
level: float64;
|
|
}
|
|
|
|
table CommandShipGroupMerge {}
|
|
|
|
table CommandShipGroupDismantle {
|
|
id: string;
|
|
}
|
|
|
|
table CommandShipGroupTransfer {
|
|
id: string;
|
|
acceptor: string;
|
|
}
|
|
|
|
table CommandShipGroupJoinFleet {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
table CommandFleetMerge {
|
|
name: string;
|
|
target: string;
|
|
}
|
|
|
|
table CommandFleetSend {
|
|
name: string;
|
|
destination: int64;
|
|
}
|
|
|
|
table CommandScienceCreate {
|
|
name: string;
|
|
drive: float64;
|
|
weapons: float64;
|
|
shields: float64;
|
|
cargo: float64;
|
|
}
|
|
|
|
table CommandScienceRemove {
|
|
name: string;
|
|
}
|
|
|
|
table CommandPlanetRename {
|
|
number: int64;
|
|
name: string;
|
|
}
|
|
|
|
table CommandPlanetProduce {
|
|
number: int64;
|
|
production: PlanetProduction = UNKNOWN;
|
|
subject: string;
|
|
}
|
|
|
|
table CommandPlanetRouteSet {
|
|
origin: int64;
|
|
destination: int64;
|
|
load_type: PlanetRouteLoadType = UNKNOWN;
|
|
}
|
|
|
|
table CommandPlanetRouteRemove {
|
|
origin: int64;
|
|
load_type: PlanetRouteLoadType = UNKNOWN;
|
|
}
|
|
|
|
union CommandPayload {
|
|
CommandRaceQuit,
|
|
CommandRaceVote,
|
|
CommandRaceRelation,
|
|
CommandShipClassCreate,
|
|
CommandShipClassMerge,
|
|
CommandShipClassRemove,
|
|
CommandShipGroupBreak,
|
|
CommandShipGroupLoad,
|
|
CommandShipGroupUnload,
|
|
CommandShipGroupSend,
|
|
CommandShipGroupUpgrade,
|
|
CommandShipGroupMerge,
|
|
CommandShipGroupDismantle,
|
|
CommandShipGroupTransfer,
|
|
CommandShipGroupJoinFleet,
|
|
CommandFleetMerge,
|
|
CommandFleetSend,
|
|
CommandScienceCreate,
|
|
CommandScienceRemove,
|
|
CommandPlanetRename,
|
|
CommandPlanetProduce,
|
|
CommandPlanetRouteSet,
|
|
CommandPlanetRouteRemove
|
|
}
|
|
|
|
table CommandItem {
|
|
cmd_id: string;
|
|
cmd_applied: bool = null;
|
|
cmd_error_code: int64 = null;
|
|
payload: CommandPayload (required);
|
|
// Human-readable failure reason returned by the engine when
|
|
// `cmd_applied = false`. Appended after `payload` to preserve the
|
|
// wire offsets of existing slots (FBS field IDs are allocated in
|
|
// declaration order, so inserting in the middle would shift every
|
|
// later slot). Omitted on requests and on applied commands.
|
|
cmd_error_message: string;
|
|
}
|
|
|
|
// UserGamesCommand is the signed-gRPC request payload for
|
|
// `MessageTypeUserGamesCommand`. game_id selects the target running
|
|
// game; gateway re-encodes commands into the engine JSON shape and
|
|
// forwards through `POST /api/v1/user/games/{game_id}/commands`.
|
|
table UserGamesCommand {
|
|
game_id: common.UUID (required);
|
|
commands: [CommandItem];
|
|
}
|
|
|
|
// UserGamesOrder is the signed-gRPC request payload for
|
|
// `MessageTypeUserGamesOrder`. Identical to UserGamesCommand but
|
|
// carries `updated_at` so the order-validate path can reject stale
|
|
// submissions.
|
|
table UserGamesOrder {
|
|
game_id: common.UUID (required);
|
|
updated_at: int64;
|
|
commands: [CommandItem];
|
|
}
|
|
|
|
// UserGamesCommandResponse is the success acknowledgement returned
|
|
// for `MessageTypeUserGamesCommand`. The engine answers with
|
|
// `204 No Content` on success, so the FB shape is intentionally empty
|
|
// — kept as a typed envelope for future extension.
|
|
table UserGamesCommandResponse {}
|
|
|
|
// UserGamesOrderResponse mirrors the engine's `PUT /api/v1/order`
|
|
// success body: it echoes the stored order back to the caller with
|
|
// the engine-assigned `updated_at` timestamp and per-command
|
|
// `cmd_applied` / `cmd_error_code` populated on every entry.
|
|
table UserGamesOrderResponse {
|
|
game_id: common.UUID;
|
|
updated_at: int64;
|
|
commands: [CommandItem];
|
|
}
|
|
|
|
// UserGamesOrderGet is the signed-gRPC request payload for
|
|
// `MessageTypeUserGamesOrderGet`. Fetches the player's stored order
|
|
// for the given turn — the caller always knows the current turn from
|
|
// the lobby record so `turn` is required and must be non-negative.
|
|
table UserGamesOrderGet {
|
|
game_id: common.UUID (required);
|
|
turn: int64;
|
|
}
|
|
|
|
// UserGamesOrderGetResponse carries the result of
|
|
// `MessageTypeUserGamesOrderGet`. `found = false` is how the FBS
|
|
// envelope conveys the engine's `204 No Content` (no order stored
|
|
// for this player on this turn). When `found = true`, `order` is
|
|
// the engine's stored order for the turn.
|
|
table UserGamesOrderGetResponse {
|
|
found: bool;
|
|
order: UserGamesOrder;
|
|
}
|