Files
galaxy-game/tools/local-dev/legacy-report/README.md
T
Ilia Denisov 99962b295f tools/local-dev: legacy-report-to-json CLI for synthetic UI testing
A Go module under tools/local-dev/legacy-report that converts the
"dg" / "gplus" engine .REP files in tools/local-dev/reports/ into the
JSON shape of pkg/model/report.Report. The output drives a DEV-only
synthetic-mode loader on the UI lobby so the map, inspectors, and
order-overlay can be exercised against rich game states without
playing many turns end-to-end.

Scope is intentionally narrow: only the fields the UI client decodes
today (planets, players, own ship classes, header). Importing
pkg/model/report keeps the parser and the typed contract in lockstep
— any backwards-incompatible schema change breaks the tool's
compilation before it ships. The README spells out the parity rule
for extending the parser alongside future UI decoders.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 11:07:50 +02:00

122 lines
5.3 KiB
Markdown

# legacy-report-to-json
Converts legacy text-format Galaxy turn reports (the *dg* and *gplus*
engines that lived under `tools/local-dev/reports/`) into the JSON
shape of [`pkg/model/report.Report`](../../../pkg/model/report).
The output is consumed by the **DEV-only synthetic-report loader** on
the UI client's lobby (`import.meta.env.DEV`). With it, the map view,
inspectors, and order-overlay can be exercised against rich game
states without playing many turns end-to-end against a real backend.
The tool is part of the synthetic-report parity rule documented in
[`ui/PLAN.md`](../../../ui/PLAN.md).
## Build / run
```sh
# from the repo root, with the Go workspace active
go run ./tools/local-dev/legacy-report/cmd/legacy-report-to-json \
--in tools/local-dev/reports/dg/KNNTS039.REP \
--out tools/local-dev/reports/dg/KNNTS039.json
```
`--in` reads `-` as stdin; `--out` defaults to stdout when empty or
`-`. The tool exits non-zero on any I/O or parse failure.
## Supported input variants
| Variant | Sample dir | Status |
| ------- | ------------------------------------- | ------------- |
| dg | `tools/local-dev/reports/dg/*.REP` | First-class |
| gplus | `tools/local-dev/reports/gplus/*.REP` | First-class |
| ng | `tools/local-dev/reports/ng/*.rep` | Not supported |
| lucky | `tools/local-dev/reports/lucky/*.rep` | Not supported |
dg uses CRLF line endings, gplus uses LF and tabs in section indentation;
both are space-aligned tabular inside data blocks. The parser splits on
runs of whitespace (`strings.Fields`) so the same code handles both.
Pseudo-Cyrillic glyphs (`MbI`, `KAMA3`, `9IMA`) appear in some races
and ship class names but are stored as plain ASCII letter substitutions
— no encoding conversion is needed.
## In-scope fields (current)
The parser only fills the subset of `report.Report` that the UI client
already decodes from server responses
(`ui/frontend/src/api/game-state.ts``decodeReport`):
| `report.Report` field | Source section in legacy file |
| --------------------- | ------------------------------------ |
| `Race` | `<Race> Report for Galaxy ...` line |
| `Turn` | same |
| `Width`, `Height` | `Size: N` (square galaxies) |
| `PlanetCount` | `Planets: N` |
| `VoteFor`, `Votes` | `Your vote:` block |
| `Player[]` | `Status of Players (total ...)` |
| `LocalPlanet[]` | `Your Planets` |
| `OtherPlanet[]` | `<Race> Planets` (one per race) |
| `UninhabitedPlanet[]` | `Uninhabited Planets` |
| `UnidentifiedPlanet[]`| `Unidentified Planets` |
| `LocalShipClass[]` | `Your Ship Types` |
Players whose name in the legacy file ends with `_RIP` are emitted with
the suffix stripped and `Extinct: true`.
## Skipped sections (today)
These exist in legacy reports but have no UI decoder yet, so the
parser ignores them. Each becomes in-scope as soon as its UI phase
lands (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`)
- Bombings (`Bombings`)
- Approaching / foreign groups (`Approaching Groups`, `<Race> Groups`)
- Ships in production (`Ships In Production`)
- 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`.
## Adding a new field
`ui/PLAN.md` carries a global rule: every UI phase that extends
`decodeReport` to read a new `report.Report` field also extends this
parser, in the same PR, to populate it from legacy text — or, if the
field cannot be derived, adds an entry to the **Skipped sections**
list above with a one-line explanation.
The Go side of the rule is enforced mechanically: this tool imports
`galaxy/model/report`, so any backwards-incompatible change to the
schema breaks the tool's compilation before the change ships.
When extending:
1. Identify the legacy section in `tools/local-dev/reports/dg/*.REP`
(and `gplus/*.REP`) that carries the field, using `game/rules.txt`
section "Отчет о результатах хода" as the column-layout reference.
2. Add a section to the state machine in `parser.go`
(`classifySection`, the `section` constants, the `parse*` methods).
3. Cover the new section with a unit test in `parser_test.go` (inline
minimal fixture) and update the smoke counts in
`TestParseDgKNNTS039` / `TestParseGplus40` so a future regression
that drops the section is caught.
4. Run `go test ./tools/local-dev/legacy-report/...`, then re-run the
CLI on `dg/KNNTS039.REP` and `gplus/40.REP` and visually skim the
JSON — the field should appear with sensible values.
## Tests
```sh
go test ./tools/local-dev/legacy-report/...
```
Inline fixtures exercise the per-section row parsers; smoke tests
parse the real `dg/KNNTS039.REP` and `gplus/40.REP` and assert
top-level counts (number of planets, players, extinct races, ship
classes). Field-level fidelity is the inline tests' responsibility;
the smoke tests catch regressions where a refactor of the section
classifier silently drops a whole table.