legacy-report: parse battles + envelope JSON output
Side activity on top of Phase 27: the legacy-report tool now extracts
the "Battle at (#N) Name" / "Battle Protocol" blocks the parser used
to skip. Both the per-battle summary (Report.Battle: []BattleSummary)
and the full BattleReport (rosters + protocol) flow through.
Parser:
- new sectionBattle / sectionBattleProtocol states, with handle()
trapping the per-race "<Race> Groups" sub-headers so the roster
stays attributed to the right race;
- parseBattleHeader extracts (planet, planetName) from
"Battle at (#NN) <Name>";
- parseBattleRosterRow maps the 10-token row into
BattleReportGroup; column 8 ("L") is NumberLeft, confirmed against
KNNTS fixtures;
- parseBattleProtocolLine counts shots and builds
BattleActionReport entries from the 8-token "X Y fires on A B :
Destroyed|Shields" lines;
- flushPendingBattle finalises a battle on next "Battle at" or any
top-level section change and appends both the summary and the
full report;
- syntheticBattleID(idx) + syntheticBattleRaceID(name) synthesise
stable UUIDs in dedicated namespaces so re-runs produce
byte-identical JSON.
Parse() signature widens to (Report, []BattleReport, error); the
single caller — the CLI — is updated.
CLI emits a v1 envelope:
{ "version": 1, "report": <Report>, "battles": { <uuid>: <BR>, ... } }
Bare-Report JSONs still load on the UI side for backward compat.
UI synthetic loader: loadSyntheticReportFromJSON detects the v1
envelope, decodes the report as before, and forwards every battle
through registerSyntheticBattle so the Battle Viewer resolves any
UUID offline. Pre-envelope JSON files (no `version` field) still
load — the battle registry stays empty for them.
Docs: legacy-report README moves Battles from "Skipped" to
in-scope, documents the envelope and UUID namespaces;
docs/FUNCTIONAL.md §6.5 (and the ru mirror) note that synthetic
mode is now end-to-end via the envelope.
Tests:
- TestParseBattles covers two battles with full rosters,
per-shot destroyed/shielded mapping, NumberLeft from column 8,
deterministic UUIDs across re-parses, and proves a trailing
top-level section still parses (battle state closes cleanly);
- smokeWant gains a battles count; runSmoke cross-checks
BattleSummary ↔ BattleReport alignment (id/planet/shots);
- all six real-fixture smoke tests pinned to their `Battle at`
counts (28, 79, 56, 30, 83, 57);
- Vitest covers the synthetic-report envelope path (battles
forwarded, missing-battles tolerated, bare-Report backward
compat);
- KNNTS041.json regenerated against the new parser (existing
diff was stale w.r.t. Phase 23 anyway; this commit brings it
in line with the v1 envelope).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,25 @@
|
||||
# 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).
|
||||
engines that lived under `tools/local-dev/reports/`) into a JSON
|
||||
envelope around [`pkg/model/report.Report`](../../../pkg/model/report)
|
||||
plus full `BattleReport`s (Phase 27).
|
||||
|
||||
## Output envelope
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"version": 1,
|
||||
"report": { /* report.Report */ },
|
||||
"battles": { "<uuid>": { /* report.BattleReport */ }, ... }
|
||||
}
|
||||
```
|
||||
|
||||
`version: 1` lets the UI distinguish a current-format envelope from a
|
||||
bare `Report` JSON. The synthetic-report loader accepts both — pre-
|
||||
envelope synthetic JSON files still load, just without battle
|
||||
fixtures. `battles` is omitted when the legacy file has no combat
|
||||
events.
|
||||
|
||||
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,
|
||||
@@ -17,8 +34,8 @@ The tool is part of the synthetic-report parity rule documented in
|
||||
```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 tools/local-dev/reports/dg/KNNTS041.REP \
|
||||
--out tools/local-dev/reports/dg/KNNTS041.json
|
||||
```
|
||||
|
||||
`--in` reads `-` as stdin; `--out` defaults to stdout when empty or
|
||||
@@ -68,6 +85,21 @@ already decodes from server responses
|
||||
| `LocalGroup[]` | `Your Groups` (Phase 19) |
|
||||
| `LocalFleet[]` | `Your Fleets` (Phase 19) |
|
||||
| `IncomingGroup[]` | `Incoming Groups` (Phase 19) |
|
||||
| `Battle[]` (summary) | `Battle at (#N) Name` headers + `Battle Protocol` (Phase 27 follow-up) |
|
||||
|
||||
The envelope's `battles` map carries the full `BattleReport`-s parsed
|
||||
out of the same blocks: every roster row turns into a
|
||||
`BattleReportGroup` (`Number`/`Tech`/`LoadType`/`LoadQuantity`/
|
||||
`NumberLeft`/`InBattle`), every `... fires on ... : Destroyed|Shields`
|
||||
line turns into a `BattleActionReport`. UUIDs are synthesised
|
||||
deterministically — `syntheticBattleID(idx)` for the battle
|
||||
identifier (per-report 0-based index, SHA1 namespace
|
||||
`be01a000-0000-0000-0000-000000000002`) and
|
||||
`syntheticBattleRaceID(name)` for `BattleReport.Races` entries (SHA1
|
||||
namespace `be01a000-0000-0000-0000-000000000003`). Re-running the
|
||||
converter on the same input file yields byte-identical JSON, so
|
||||
synthetic-mode UI URLs (`/games/synthetic-…/battle/<uuid>?turn=N`)
|
||||
stay stable across regenerations.
|
||||
|
||||
Players whose name in the legacy file ends with `_RIP` are emitted with
|
||||
the suffix stripped and `Extinct: true`.
|
||||
@@ -103,15 +135,9 @@ 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).
|
||||
|
||||
- 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: []`.
|
||||
only inside battle rosters; the synthetic JSON emits
|
||||
`otherGroup: []`.
|
||||
- `UnidentifiedGroup[]` — no legacy section at all; synthetic JSON
|
||||
emits `unidentifiedGroup: []`.
|
||||
- Cargo routes — no dedicated section in the legacy text format; the
|
||||
|
||||
Reference in New Issue
Block a user