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>
This commit is contained in:
Ilia Denisov
2026-05-11 14:33:56 +02:00
parent 81d8be08b2
commit c58027c034
48 changed files with 5368 additions and 103 deletions
+28 -15
View File
@@ -60,6 +60,11 @@ already decodes from server responses
| `UninhabitedPlanet[]` | `Uninhabited Planets` |
| `UnidentifiedPlanet[]`| `Unidentified Planets` |
| `LocalShipClass[]` | `Your Ship Types` |
| `OtherShipClass[]` | `<Race> Ship Types` (Phase 23) |
| `LocalScience[]` | `Your Sciences` (Phase 23) |
| `OtherScience[]` | `<Race> Sciences` (Phase 23) |
| `Bombing[]` | `Bombings` (Phase 23) |
| `ShipProduction[]` | `Ships In Production` (Phase 23) |
| `LocalGroup[]` | `Your Groups` (Phase 19) |
| `LocalFleet[]` | `Your Fleets` (Phase 19) |
| `IncomingGroup[]` | `Incoming Groups` (Phase 19) |
@@ -78,29 +83,37 @@ tables (foreign-only knowledge the local player lacks) cause the entire
group / fleet / incoming row to be dropped — preferable to fabricating
a destination.
`ShipProduction.ProdUsed` is derived from the on-disk `Percent` and the
producing planet's material/resources via [`pkg/calc.ShipBuildCost`]
(the same helper the engine's `controller.ProduceShip` uses). The
legacy text format does not carry a `prod_used` column directly; the
derivation gives the cumulative production-equivalent of the build
progress so far. The real engine's `ProdUsed` is the per-turn
residual production poured into the partial ship, which is not
recoverable from a single legacy snapshot. The two numbers stay in
the same units and the same ballpark, which is good enough for the
synthetic-mode UI — live engine reports come over the FBS wire and
do not flow through this parser. A ships-in-production row pointing
at a planet that did not appear in `Your Planets` (which would be a
malformed legacy file) is dropped.
## Skipped sections (today)
These exist in legacy reports but either have no UI decoder yet or
cannot be derived from the legacy text format at all. Each becomes
in-scope as soon as its UI phase lands (see "Adding a new field"
below).
These exist in legacy reports but cannot be derived from the legacy
text format at all. Each could become in-scope if a strong enough
reason arises (see "Adding a new field" below).
- Foreign / other ship types (`<Race> Ship Types`)
- Sciences, both local (`Your Sciences`) and foreign (`<Race> Sciences`)
- Battles (`Battle at (#N) Name`, `Battle Protocol`) — battle rosters
inside these blocks carry minimal columns (no origin / range /
destination) and are intentionally skipped: parsing them would
produce mostly-empty `OtherGroup` records that drift away from the
typed contract.
- Bombings (`Bombings`)
- Ships in production (`Ships In Production`)
- Battles (`Battle at (#N) Name`, `Battle Protocol`) — the wire schema
carries battle UUIDs (`Report.Battle: []uuid.UUID`); the legacy text
carries per-battle rosters with stripped columns (no origin / range /
destination) and no stable identifier. Synthesising UUIDs from the
text would invent data that future Phase 27 work would have to drop;
the synthetic JSON therefore emits `battle: []`.
- `OtherGroup[]` — no top-level legacy section. Foreign groups appear
only inside battle rosters (see above), with stripped columns; the
synthetic JSON emits `otherGroup: []`.
- `UnidentifiedGroup[]` — no legacy section at all; synthetic JSON
emits `unidentifiedGroup: []`.
- `OtherShipClass[]` — present in legacy as `<Race> Ship Types`, but
no UI decoder yet; synthetic JSON emits `otherShipClass: []`.
- Cargo routes — no dedicated section in the legacy text format; the
synthetic JSON emits `route: []`. The UI's overlay path
(`applyOrderOverlay`) supports running on top of an empty `routes`.