// game::design -- hull size and the defence-platform class flag, and the // per-hull-class ship census built from them. // // WHY THIS EXISTS // --------------- // The per-player turn record the game archives every turn carries six ship // counts: ships by hull size 0/1/2, and *defence platforms* by hull size // 0/1/2, written to the wire as three `cls` groups of `shpt` (ships total) // and `satt` (satellites total). Neither the hull size nor the platform flag // is on the wire anywhere -- both are recomputed from the section catalog // whenever a design changes -- so the census cannot be reproduced from a save // alone until a reader can classify a design the way the game does. // // HOW THE ORIGINAL DOES IT // ------------------------ // A design caches two derived words that are recomputed by its stats pass: // // * a 64-bit role-flag word, the OR of every resolved section's own flag // word. `defence_platform` is one bit of it (the low word's 0x400). // * a hull-size ordinal, assigned -- not OR-ed, not max-ed -- from each // resolved section's `section_class` in slot order, so the last resolved // section wins. Every shipped design is class-homogeneous (rule A6), so // first-wins and last-wins agree on all 503 design records in the save // corpus; the assignment order is reproduced anyway because it is what // the original does, and a hand-built mixed-class design would show it. // // The census then walks the fleets a player owns, and for every ship takes // its design's two words: platform bit set -> the platform row, else the ship // row; hull size picks the column. // // NOT THE SAME 0x400 // ------------------ // A *fleet* also carries a flag word whose 0x400 bit is set when the retreat // pipeline creates a fleet, and it appears on the wire as `FtFlg`. It is a // different word on a different object and has nothing to do with this one. // Reusing the numeral is a coincidence of two bit layouts. #pragma once #include #include #include #include "game/data/catalog.h" #include "game/data/shipsection.h" #include "game/design/design.h" namespace game::design { // What the census needs to know about one design. struct HullClass { int hull_size = 0; // 0 destroyer, 1 cruiser, 2 dreadnought bool defence_platform = false; // any resolved section carries the flag int resolved_sections = 0; // sections that named a section the catalog holds int unresolved_sections = 0; // named a section the catalog does not hold // True when at least one section resolved. A design none of whose sections // resolve is not classifiable and must not be silently counted as a // destroyer: callers should treat it as a gap, not as class 0. bool ok() const { return resolved_sections > 0; } }; // Classify from already-resolved section definitions, in the design's slot // order. Null entries are empty slots and are skipped. HullClass classify_sections(const std::vector& sections); // Classify a design record against a catalog. Slots are visited in // command / mission / engine order; an empty slot is skipped, and a slot that // names a section the catalog does not hold is counted in // `unresolved_sections` and otherwise ignored. HullClass classify_design(const data::Catalog& cat, const Design& d); // Six counters: ships and defence platforms, each by hull size. struct ShipCensus { std::array ships{}; // designs WITHOUT the platform flag std::array platforms{}; // designs WITH it // The original also accumulates the two grand totals and then throws them // away without storing them; they are kept here because they are free and // because a test that checks only the six stored counters cannot tell a // miscounted ship from an unclassified one. int total_ships = 0, total_platforms = 0; void add(const HullClass& h); int ship_total() const { return total_ships; } int platform_total() const { return total_platforms; } }; } // namespace game::design