diff --git a/TODO.md b/TODO.md index 678f9e9..f8f7611 100644 --- a/TODO.md +++ b/TODO.md @@ -17,7 +17,7 @@ problem, never code. ## What to do next, in order -Twenty-one things, ranked. The tiers are the argument: the first finishes what +Twenty-two things, ranked. The tiers are the argument: the first finishes what `plan/unit-rules.md` already promises, the second widens what helm can score at all, and the third is what makes any of it reach a player. Everything below appears again in its own section with the detail; this is the order to take @@ -83,29 +83,34 @@ to helm. in `helm-query`, which helm-mcp and the boundary both read. What is left is headquarters': `web/src/units.ts` still has a filter table of its own, and that is the second implementation the epic warns about. -15. **An identity for the catalogue.** `units.json` and `loadout.json` carry a +15. **Drop the index's duplicated header.** `units.json` writes `megamek` and + `helm` at the top level as well as inside `build`, because + headquarters-api refuses an index it cannot find a top-level `megamek` in + and falls back to silhouettes. Delete both once + `services/api/src/units.rs` reads `build`. +16. **An identity for the catalogue.** `units.json` and `loadout.json` carry a `build` block and refuse to be joined across runs; `catalogue.jsonl` is JSON Lines with nowhere to put one, and is keyed only by the prefix it is published under. Two dumps of one MegaMek release are not interchangeable - see the note in CLAUDE.md - so the prefix is not enough. -16. **The redistribution question.** MegaMek's data is CC BY-NC-SA and serving +17. **The redistribution question.** MegaMek's data is CC BY-NC-SA and serving an index of it to browsers is redistribution. It needs a decision and an attribution mechanism *before* 13 ships. -17. **Provenance on every record.** Which MegaMek, which producer, which +18. **Provenance on every record.** Which MegaMek, which producer, which `RULES_VERSION`. The constants exist and nothing writes them into the database or into an ATProto record, so a stored figure cannot be told from one computed against a different MegaMek. -18. **The attribution and the repair list on a match page.** +19. **The attribution and the repair list on a match page.** `plan/after-action.md`. The pieces are built and cross the wasm boundary; what is missing is the screen that reads them. -19. **Damage that persists between matches.** `plan/campaign.md`. The +20. **Damage that persists between matches.** `plan/campaign.md`. The mechanical half exists; storing a force's condition as a record and reading it back is the rest, and the construct-write-read round trip supports it. -20. **Formation validation.** `list_formations` and `validate_formation` +21. **Formation validation.** `list_formations` and `validate_formation` against the Campaign Operations blueprints - "is this a legal Fire Lance". Validation before generation, and it needs no faction data: it is role and count, both of which helm holds. -21. **Ingest `data/forcegenerator/`.** 40 era files of per-faction availability +22. **Ingest `data/forcegenerator/`.** 40 era files of per-faction availability weights, `factions.xml`, and 61 faction rulesets that are a small DSL of their own. This is the foundation for force generation, faction filtering and era-correct scenarios, and nothing above it should be attempted until diff --git a/crates/helm-cli/src/build.rs b/crates/helm-cli/src/build.rs index 15f1ba8..037f87e 100644 --- a/crates/helm-cli/src/build.rs +++ b/crates/helm-cli/src/build.rs @@ -443,6 +443,8 @@ pub(crate) fn index(args: &[String]) -> Result<(), String> { } let mut document = helm_query::Index { + megamek: build.megamek.clone(), + helm: build.helm.clone(), build: build.clone(), sprite_base: "data/images/units".to_string(), counts, diff --git a/crates/helm-query/src/facets.rs b/crates/helm-query/src/facets.rs index 96c4eb7..40a0448 100644 --- a/crates/helm-query/src/facets.rs +++ b/crates/helm-query/src/facets.rs @@ -137,6 +137,17 @@ pub struct Counts { #[derive(Serialize, Deserialize)] pub struct Index { pub build: Build, + /// The release, again, at the top level. + /// + /// Where it was before there was a build block, and where headquarters-api + /// still reads it: that service refuses an index it cannot find a + /// `megamek` in and falls back to drawing silhouettes. Written until it + /// reads `build.megamek` instead, and no longer. + #[serde(default, skip_serializing_if = "String::is_empty")] + pub megamek: String, + /// The same, for the helm build. See `megamek` above. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub helm: Option, /// Relative to the release, and the prefix a sprite path is joined to. /// The bucket holds the art tree at this path under the same version, so /// a consumer needs no second convention to turn one into an address. @@ -242,6 +253,8 @@ mod tests { fn document(names: &[&str]) -> Index { Index { build: build(names), + megamek: "0.51.0".into(), + helm: None, sprite_base: "data/images/units".into(), counts: Counts::default(), units: names