Commit Graph

16 Commits

Author SHA1 Message Date
Ilia Denisov 723885e74e fix(order): surface rejection reason, keep sync green, hydrate verdicts
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>
2026-05-29 11:42:27 +02:00
Ilia Denisov af30846091 fix(game): #59 — per-command rejection on PUT /api/v1/order
Tests · Go / test (push) Successful in 2m2s
Tests · Go / test (pull_request) Successful in 3m3s
Tests · Integration / integration (pull_request) Successful in 1m40s
Validation of a player's order now applies every command against a
transient game-state snapshot and records the per-command outcome
(cmdApplied, cmdErrorCode) in each command's meta. The order is
persisted even when some commands are rejected, and the response is
202 + UserGamesOrder so clients can surface the partial failure
without the chain collapsing into "downstream service is unavailable".

Pkg/error consts are reshelved onto three explicit ranges with a
package doc and helpers (IsInternalCode/IsInputCode/IsGameStateCode):
1xxx internal/server (500/501), 2xxx structural input (400), 3xxx
game-state per-command rejection (400 when escaping HTTP, otherwise
recorded as cmdErrorCode). Two pre-existing typos fixed mechanically
(ErrBeakGroupNumberNotEnough -> ErrBreakGroupNumberNotEnough,
ErrRaceExinct -> ErrRaceExtinct) along with all callsites.

Engine errorResponse maps *GenericError by shelf rather than mapping
everything to 500. The Quit-not-last structural check in
Controller.ValidateOrder is preserved and its type assertion fixed
(was a value assertion against a pointer-typed command, so the check
silently never fired).

Backend, gateway and UI are unchanged — they were already correct on
the 202 path; only the engine collapsing per-command rejection into
500 was needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 09:36:29 +02:00
Ilia Denisov 24c68e9846 feat(model+ui): F8-05 — race on OtherGroup, real attribution + N×M label
Tests · UI / test (push) Has been cancelled
Tests · Go / test (pull_request) Successful in 2m6s
Tests · Go / test (push) Successful in 2m6s
Tests · Integration / integration (pull_request) Successful in 1m51s
Tests · UI / test (pull_request) Successful in 3m53s
Issue #48 п.32 ("Stationed ship groups") shipped with a fragile race
fallback: when a foreign group sat on a non-`other`-kind planet the
inspector printed a generic "foreign" label, which collapsed the
race dropdown to a single uninformative bucket. The engine FBS
contract did not carry per-group race either, so live games hit the
same gap. This patch carries race authoritatively from the engine
through every layer down to the inspector.

Wire format & engine
- `pkg/schema/fbs/report.fbs`: add `race:string` to `OtherGroup` and
  `LocalGroup` (additive — old clients ignore).
- `pkg/schema/fbs/report/`: regenerated Go bindings.
- `ui/frontend/src/proto/galaxy/fbs/report/`: regenerated TS bindings.
- `pkg/model/report.OtherGroup.Race`: new field; carried through
  `LocalGroup` via the embedded `OtherGroup`.
- `pkg/transcoder/report.go`: encode + decode `race` on both
  `LocalGroup` and `OtherGroup`.
- `game/internal/controller/report.go.otherGroup`: set `v.Race`
  from `c.g.Race[c.RaceIndex(sg.OwnerID)].Name` so every emitted
  group — own or foreign — carries the resolved race name.

Legacy parser
- `tools/local-dev/legacy-report/parser.go`: capture the
  `<Race> Groups` header into `pendingOtherGroup.race`, fill local
  group `Race` from `p.rep.Race`, propagate both into the
  `report.OtherGroup` rows.
- Tests + smoke counts updated; regenerated `KNNTS{039,041}.json`
  fixtures so the synthetic loader carries the new field.

UI
- `ui/frontend/src/api/`: `ReportShipGroupBase.race` field;
  synthetic loader + FBS decoder populate it.
- `ui/frontend/src/lib/inspectors/planet/ship-groups.svelte`: the
  stationed-groups inspector picks race directly from
  `group.race` (own falls back to `localRace`, both finally to the
  `race.unknown` placeholder). The planet-owner / "foreign"
  heuristic is gone.
- Row label changes from "N ships mass M" to a compact
  `<class>` | `<N ×>` | `<mass>` three-column layout: the count
  cell is right-aligned tabular, the mass cell is right-aligned
  monospace + tabular, matching the inspector / calculator number
  conventions. Stale i18n keys removed
  (`ship_groups.row.count`, `.row.mass`, `.race.foreign`).
- All affected unit tests (8 files) carry the new `race` field.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 16:23:17 +02:00
Ilia Denisov 9ae7b88b89 feat(ui): Phase 30 ship-class calculator with goal-seek and reach circles
Tests · UI / test (push) Successful in 2m14s
Tests · Go / test (push) Successful in 2m25s
Fuse the standalone ship-class designer (Phases 17/18) into a sidebar calculator: live mass/speed/attack/defence/bombing results, a planet build-rate readout, single-target goal-seek, a modernization-cost mode, and auto reach circles on the map for the selected planet.

pkg/calc becomes the single source for the new math (no mirroring): extract BombingPower from the engine model and the per-turn ship-production loop from controller.ProduceShip into pkg/calc (engine now delegates), and add inverse goal-seek solvers in pkg/calc/solve.go. Thin-bridge the combat, planet-build, and solver functions through ui/core/calc + ui/wasm and rebuild core.wasm.

Remove the standalone designer view/route; the ship-classes table and the view/bottom menus open the calculator via a shared request store.

Docs: rewrite ui/PLAN.md Phase 30, adjust Phase 34 (realistic forecast + CAP/COL ownership), add ui/docs/calculator-ux.md, extend calc-bridge.md, fix navigation.md; remove ui/CALCULATOR.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:04:07 +02:00
Ilia Denisov bd11cd80da ui/phase-27: root-cause aggregation of duplicate (race, className) rows
Legacy reports list the same `(race, className)` pair across several
roster rows; the engine likewise creates one ShipGroup per arrival.
Both the legacy parser and `TransformBattle` were keyed on shipClass
without summing — only the last row / group's counts survived, so a
protocol's destroy count appeared to exceed the recorded initial
roster. The UI worked around this with phantom-frame logic.

Both parser and engine now SUM `Number`/`NumberLeft` across rows /
groups sharing the same class; the phantom-frame workaround is gone.
KNNTS041 turn 41 planet #7 reconciles: `Nails:pup` 1168 initial −
86 survivors = 1082 destroys.

The engine's previously latent nil-map write on `bg.Tech` (would
have paniced on any group with non-empty Tech) is fixed in the same
patch — it blocked the aggregation regression test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:52:40 +02:00
Ilia Denisov 969c0480ba ui/phase-27: battle viewer (radial scene, playback, map markers)
Engine wire change: Report.battle switched from []uuid.UUID to
[]BattleSummary{id, planet, shots} so the map can place battle
markers without N extra fetches. FBS schema + generated Go/TS
regenerated; transcoder + report controller updated; openapi
adds the BattleSummary schema with a freeze test.

Backend gateway forwards engine GET /api/v1/battle/:turn/:uuid as
/api/v1/user/games/{game_id}/battles/{turn}/{battle_id} (handler
plus engineclient.FetchBattle, contract test stub, openapi spec).

UI:
- BattleViewer (lib/battle-player/) is a logically isolated SVG
  radial scene that consumes a BattleReport prop. Planet at the
  centre, races on the outer ring at equal angular spacing, race
  clusters by (race, className) with <class>:<numLeft> labels;
  observer groups (inBattle: false) are not drawn; eliminated
  races drop out and survivors re-distribute on the next frame.
- Shot line per frame: red on destroyed, green otherwise; erased
  on the next frame. Playback controls: play/pause + step ± +
  rewind + 1x/2x/4x speed (400/200/100 ms per frame).
- Page wrapper (lib/active-view/battle.svelte) loads BattleReport
  via api/battle-fetch.ts; synthetic-gameId prefix routes to a
  fixture loader, otherwise REST through the gateway. Always-
  visible <ol> text protocol satisfies the accessibility ask.
- section-battles.svelte links every battle UUID into the viewer.
- map/battle-markers.ts: yellow X cross of 2 LinePrim through the
  corners of the planet's circumscribed square (stroke width
  clamps from 1 px at 1 shot to 5 px at 100+ shots); bombing
  marker is a stroke-only ring (yellow when damaged, red when
  wiped). Wired into state-binding.ts; click handler dispatches
  battle clicks to the viewer and bombing clicks to the matching
  Reports row.
- i18n keys for the viewer in en + ru.

Docs: ui/docs/battle-viewer-ux.md, FUNCTIONAL.md §6.5 + ru
mirror, ui/PLAN.md Phase 27 decisions + deferred TODOs (push
event, richer class visuals, animated re-distribution).

Tests: Vitest unit (radial layout + timeline frame builder +
marker stroke formula + marker primitives), Playwright e2e for
the viewer (Reports link → viewer, playback step, not-found),
backend engineclient FetchBattle (200 / 404 / bad input), engine
openapi freezes (BattleReport, BattleReportGroup,
BattleActionReport, BattleSummary, Report.battle items).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:24:20 +02:00
Ilia Denisov a9adbad7ef feat: game engine fetch battle api
ui-test / test (push) Failing after 47s
2026-05-13 10:50:45 +02:00
Ilia Denisov c58027c034 ui/phase-23: turn-report view with twenty sections and TOC
Replaces the Phase 10 report stub with a scrollable orchestrator that
renders every FBS array as a dedicated section (galaxy summary, votes,
player status, my/foreign sciences, my/foreign ship classes, battles,
bombings, approaching groups, my/foreign/uninhabited/unknown planets,
ships in production, cargo routes, my fleets, my/foreign/unidentified
ship groups). A sticky table of contents (a <select> on mobile),
"back to map" affordance, IntersectionObserver-driven active-section
highlight, and SvelteKit Snapshot-based scroll save/restore round out
the view.

GameReport gains six new fields (players, otherScience, otherShipClass,
battleIds, bombings, shipProductions); decodeReport, the synthetic-
report loader, the e2e fixture builder, and EMPTY_SHIP_GROUPS extend
in lockstep. ~90 new i18n keys land in en + ru together.

The legacy-report parser is extended to populate the new sections from
the dg/gplus text formats (Your Sciences, <Race> Sciences, <Race> Ship
Types, Bombings, Ships In Production). Ships-in-production prod_used
is derived through a new pkg/calc.ShipBuildCost helper; the engine's
controller.ProduceShip refactors to call the same helper without any
behaviour change (engine tests stay unchanged and green). Battles
remain in the parser's Skipped list — the legacy text carries no
stable per-battle UUID.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:33:56 +02:00
Ilia Denisov 3626998a33 ui/phase-20: ship-group inspector actions
Eight ship-group operations land on the inspector behind a single
inline-form panel: split, send, load, unload, modernize, dismantle,
transfer, join fleet. Each action either appends a typed command to
the local order draft or surfaces a tooltip explaining the
disabled state. Partial-ship operations emit an implicit
breakShipGroup command before the targeted action so the engine
sees a clean (Break, Action) pair on the wire.

`pkg/calc.BlockUpgradeCost` migrates from
`game/internal/controller/ship_group_upgrade.go` so the calc
bridge can wrap a pure pkg/calc formula; the controller now
imports it. The bridge surfaces the function as
`core.blockUpgradeCost`, which the inspector calls once per ship
block to render the modernize cost preview.

`GameReport.otherRaces` is decoded from the report's player block
(non-extinct, ≠ self) and feeds the transfer-to-race picker. The
planet inspector's stationed-ship rows become clickable for own
groups so the actions panel is reachable from the standard click
flow (the renderer continues to hide on-planet groups).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 16:27:55 +02:00
Ilia Denisov 408097e3aa feat: move func to calc package 2026-05-10 14:55:14 +02:00
Ilia Denisov 2a1e80053a feat: game order api methods 2026-05-09 10:36:44 +02:00
Ilia Denisov 3e2622757e feat: gamemaster 2026-05-03 07:59:03 +02:00
Ilia Denisov b4591cabd4 game stats shows planes and population 2026-04-26 21:12:51 +02:00
Ilia Denisov a7793f5416 ui calculator 2026-03-30 19:38:24 +02:00
Ilia Denisov ac3ed31a23 connector impl 2026-03-14 21:11:51 +02:00
Ilia Denisov 9ade76e21d fs storage 2026-03-13 21:07:23 +02:00