feat(game): race exit warnings in the turn report (#12)
Surface the inactivity-removal countdown the rules promise but the engine never reported. A race within five turns of being auto-removed for inactivity gets a personal warning in its own report; every race within three turns is listed publicly to all participants. - model: Report.PersonalExitWarning + RacesLeavingSoon ([]RaceExitNotice) - fbs: RaceExitNotice table + Report.personal_exit_warning / races_leaving_soon (regenerated Go + TS bindings) - transcoder: encode/decode both fields - engine: ReportExitWarnings fills the recipient's TTL (1..5) and lists other non-extinct races with TTL 1..3, excluding the recipient itself - ui: danger-styled personal banner + "races leaving soon" section (hidden when empty), wired into the report view, EN/RU i18n - docs: rules.txt report-section list, FUNCTIONAL.md 6.4 + RU mirror Voluntary quit and idle timeout share the TTL countdown and are not distinguished, per the agreed scope.
This commit is contained in:
@@ -9,6 +9,14 @@
|
||||
// AND the Go CLI must learn to populate that field — see the
|
||||
// synthetic-report parity rule in `ui/PLAN.md`.
|
||||
//
|
||||
// `personalExitWarning` / `racesLeavingSoon` are the exception the
|
||||
// rule allows: they are runtime inactivity-countdown state the engine
|
||||
// derives per turn, not anything present in a static legacy text
|
||||
// report, so the legacy CLI leaves them empty (the parity rule's
|
||||
// "cannot be derived from the legacy text format" escape hatch). This
|
||||
// decoder still reads them defensively so a hand-authored synthetic
|
||||
// JSON fixture can exercise the report's exit-warning UI.
|
||||
//
|
||||
// The in-memory map deliberately does not survive a page reload:
|
||||
// synthetic mode is a debug affordance, not a session, and the
|
||||
// layout redirects to /lobby when a synthetic id is opened with no
|
||||
@@ -259,6 +267,11 @@ interface SyntheticShipProductionRow {
|
||||
free?: number;
|
||||
}
|
||||
|
||||
interface SyntheticRaceExitNotice {
|
||||
race?: string;
|
||||
turnsLeft?: number;
|
||||
}
|
||||
|
||||
interface SyntheticReportRoot {
|
||||
turn?: number;
|
||||
mapWidth?: number;
|
||||
@@ -284,6 +297,8 @@ interface SyntheticReportRoot {
|
||||
battle?: SyntheticBattle[];
|
||||
bombing?: SyntheticBombing[];
|
||||
shipProduction?: SyntheticShipProductionRow[];
|
||||
personalExitWarning?: number;
|
||||
racesLeavingSoon?: SyntheticRaceExitNotice[];
|
||||
}
|
||||
|
||||
function decodeSyntheticReport(json: unknown): GameReport {
|
||||
@@ -465,6 +480,13 @@ function decodeSyntheticReport(json: unknown): GameReport {
|
||||
return a.class.localeCompare(b.class);
|
||||
});
|
||||
|
||||
const racesLeavingSoon: { race: string; turnsLeft: number }[] = (
|
||||
root.racesLeavingSoon ?? []
|
||||
).map((n) => ({
|
||||
race: typeof n.race === "string" ? n.race : "",
|
||||
turnsLeft: numOr0(n.turnsLeft),
|
||||
}));
|
||||
|
||||
return {
|
||||
turn: numOr0(root.turn),
|
||||
mapWidth: numOr0(root.mapWidth),
|
||||
@@ -495,6 +517,8 @@ function decodeSyntheticReport(json: unknown): GameReport {
|
||||
battleIds,
|
||||
bombings,
|
||||
shipProductions,
|
||||
personalExitWarning: numOr0(root.personalExitWarning),
|
||||
racesLeavingSoon,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user