Compare commits
No commits in common. "1d3f8116c391e970c5385df0c27346fdc9606422" and "16b1e65fa3db4e2cd7552e8cdb443faa8bbc7646" have entirely different histories.
1d3f8116c3
...
16b1e65fa3
4 changed files with 42 additions and 352 deletions
|
|
@ -85,14 +85,10 @@ struct Summary {
|
|||
reference name differs from the disk spelling, e.g. `faiDes` → `FAIDes`, `ontF` → `otnF`,
|
||||
`nextId` → `nextid`); the second argument of `R` is the tag to write. Conditionals are plain
|
||||
`ar.when(vnh, ...)`, optional legacy tags `ar.opt_i32(A("ARes"), aRes)`, uncounted lists
|
||||
`ar.repeat("stats", ...)`, inline arrays `ar.narr(A("NumSys"), systems, elem)`. Bodies still
|
||||
kept opaque are `Node` members and re-emit verbatim. That list has shrunk to two kinds: the
|
||||
**RNG blob**, which is deliberately opaque, and **containers no save has ever filled** —
|
||||
`nodePaths`, `Ojvs`, `usnc`, `FTPnts`, the four `SpyReport` sub-lists, the `deflay`/`rtgt`
|
||||
pairs, and the polymorphic keys with no factory entry (`SvSctOb` variants, `CD` block
|
||||
suffixes). Those are hypotheses under rule 6, not modelling debt: the element framing is a
|
||||
property of the writer's helper and no record has ever shown one. On every save in the
|
||||
corpus the only opaque items left are the RNG blob's two.
|
||||
`ar.repeat("stats", ...)`, inline arrays `ar.narr(A("NumSys"), systems, elem)`. Bodies the
|
||||
format keeps opaque (`TechTree`, `Events`, `ShipRecs`, `spy2`, `civr`, `comms`, `Ojvs`,
|
||||
`Attrib`, `sprjs`, `SvSctOb`, `trdmgr`, `spymgr`, `CD`, the RNG blob) are `Node` members and
|
||||
re-emit verbatim.
|
||||
|
||||
`ReadArchive` mirrors the reference applier: a confirmed tag that is missing is an error,
|
||||
unexpected items before it a warning, a positional item read under another tag an info;
|
||||
|
|
@ -115,14 +111,12 @@ same `io()` drives `WriteArchive`, so a shape loaded from a real save writes bac
|
|||
`SOTS_SAVE_READER` set, `oracle/compare.py` diffs the C++ dump against the reference
|
||||
`--dump` (exact and canonical agreement, summary values).
|
||||
|
||||
Result on the owner's 20-save corpus (SotS 1.8): 100% exact line agreement, both round trips
|
||||
identical, summaries agree. Named coverage is 99.994–99.996% per save; the residual is the
|
||||
two items of the RNG blob and nothing else.
|
||||
Result on the owner's three saves (SotS 1.8, turns 1–3): 100% exact line agreement, both
|
||||
round trips identical, summaries agree.
|
||||
|
||||
## Not done / open
|
||||
|
||||
* Only joint padding is implemented (the split convention never matched a real file).
|
||||
* Element shapes for the never-filled containers listed above — each needs a save that puts
|
||||
something in it before the framing can be typed rather than guessed.
|
||||
* Shapes for the opaque bodies listed above (kept generic on purpose, as in the reference).
|
||||
* The float-vs-int guess for unhinted words inside opaque bodies is a heuristic; the typed
|
||||
layer never depends on it (it coerces from the raw bytes).
|
||||
|
|
|
|||
|
|
@ -851,41 +851,6 @@ struct DesignSection { // Game::ShipDesignDef::Section, one DSec of a Des frame
|
|||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
// Game::GunBankSelection -- the `wgb` frame of a weapon group. The recovery
|
||||
// resolves ONE NULL-named int here, which is a loop body: the only record in the
|
||||
// corpus (human-turn15-spyprogram, the first save with Dwgv set) carries three.
|
||||
// Neither a count word nor a fixed arity is on the wire, so this is read as an
|
||||
// uncounted run of "." ints rather than as a fixed three -- a design with a
|
||||
// different bank arity cannot desynchronise the frame. What settles the arity is
|
||||
// a save whose design has a non-three gun-bank layout.
|
||||
struct GunBankSelection {
|
||||
static constexpr const char* kStreamName = "";
|
||||
std::vector<int32_t> banks;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.repeat(".", banks, [](Ar& a, int32_t& e) { a.i32(R("bank"), e); });
|
||||
}
|
||||
};
|
||||
struct WeaponGroup { // one wgng element: the group id and its bank selection
|
||||
static constexpr const char* kStreamName = "";
|
||||
int32_t wgid = 0;
|
||||
GunBankSelection wgb;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.i32(A("wgid"), wgid);
|
||||
ar.obj(A("wgb"), wgb);
|
||||
}
|
||||
};
|
||||
struct WeaponGroups { // Game::WeaponGroups, the Dwg frame
|
||||
static constexpr const char* kStreamName = "Dwg";
|
||||
std::vector<WeaponGroup> groups;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.narr(A("wgng"), groups, [](Ar& a, WeaponGroup& e) { e.io(a); });
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
// A design is written by TWO serializers, a base and a derived one. The base
|
||||
// emits FAIDes / DHide / DWep / DName and then EXACTLY THREE section frames;
|
||||
// the derived one appends Dtc, the Dwgv flag and -- only when that flag is set
|
||||
|
|
@ -910,10 +875,7 @@ struct Design { // on-disk tags are case variants of the reference names (FAIDe
|
|||
std::vector<DesignSection> sections;
|
||||
int32_t dtc = 0;
|
||||
bool dwgv = false;
|
||||
// Dwg; present only when dwgv, which is set on exactly one design in the
|
||||
// corpus (human-turn15-spyprogram). Typed from that record plus the
|
||||
// recovered Game::WeaponGroups serializer.
|
||||
WeaponGroups weaponGroups;
|
||||
Node weaponGroups; // Dwg; present only when dwgv, which is false in every save available
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
|
|
@ -924,7 +886,7 @@ struct Design { // on-disk tags are case variants of the reference names (FAIDe
|
|||
ar.repeat("DSec", sections, [](Ar& a, DesignSection& e) { a.obj(A("DSec"), e); });
|
||||
ar.i32(A("Dtc"), dtc);
|
||||
ar.b(A("Dwgv"), dwgv);
|
||||
ar.when(dwgv, [&](Ar& a) { a.obj(A("Dwg"), weaponGroups); });
|
||||
ar.when(dwgv, [&](Ar& a) { a.any(A("Dwg"), weaponGroups); });
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
|
|
@ -1224,24 +1186,11 @@ struct SpyReport { // Game::SpyReport: four counted lists, one per report kind
|
|||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct FreighterWarning { // Game::ServerTradeSector::FreighterWarning, one fwarn element
|
||||
static constexpr const char* kStreamName = "";
|
||||
int32_t pid = 0, ntrns = 0;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.i32(A("pid"), pid);
|
||||
ar.i32(A("ntrns"), ntrns);
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct TradeSector { // Game::ServerTradeSector
|
||||
static constexpr const char* kStreamName = "Trade";
|
||||
Vec3 pos, ctr;
|
||||
int32_t gridID = 0, tssec = 0, tsct = 0, tscr = 0, ptssec = 0, ptsct = 0, ptscr = 0;
|
||||
// ServerTradeSector::FreighterWarning. The count was 0 in every save until
|
||||
// human-turn11-spytechs, which raided a route and produced one.
|
||||
std::vector<FreighterWarning> fwarn;
|
||||
std::vector<Node> fwarn; // ServerTradeSector::FreighterWarning; empty in every save available
|
||||
std::vector<int32_t> systems, fleets;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
|
|
@ -1261,23 +1210,6 @@ struct TradeSector { // Game::ServerTradeSector
|
|||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct TradeRoute { // Game::TradeRoute, one `rt` frame of the trade manager
|
||||
static constexpr const char* kStreamName = "rt";
|
||||
int32_t tro = 0, trfow = 0, trfr = 0, trfrs = 0, trtow = 0, trto = 0, trtos = 0, trtc = 0;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.i32(A("tro"), tro);
|
||||
ar.i32(A("trfow"), trfow);
|
||||
ar.i32(A("trfr"), trfr);
|
||||
ar.i32(A("trfrs"), trfrs);
|
||||
ar.i32(A("trtow"), trtow);
|
||||
ar.i32(A("trto"), trto);
|
||||
ar.i32(A("trtos"), trtos);
|
||||
ar.i32(A("trtc"), trtc);
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct TradeManager { // Game::ServerTradeManagerImpl
|
||||
// The `trdmgr` member is declared as Game::ServerTradeManager, whose Read and
|
||||
// Write really are the inherited no-op -- the campaign recorded that as an open
|
||||
|
|
@ -1296,58 +1228,23 @@ struct TradeManager { // Game::ServerTradeManagerImpl
|
|||
};
|
||||
std::vector<Entry> sectors;
|
||||
float sctSize = 0;
|
||||
// `rt` is a container write with NO count word: the recovery marks it a loop
|
||||
// body (member == false) and the wire has the frames back to back after
|
||||
// SctSize, so it is read as an uncounted run keyed on the tag. Every save
|
||||
// that has any route at all (human-turn11, human-turn15) carries exactly one
|
||||
// here, so "uncounted" is the framing the evidence supports, not "one".
|
||||
std::vector<TradeRoute> routes;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.narr(A("NumTradeSectors"), sectors, [](Ar& a, Entry& e) { e.io(a); });
|
||||
ar.f32(A("SctSize"), sctSize);
|
||||
ar.repeat("rt", routes, [](Ar& a, TradeRoute& e) { a.obj(A("rt"), e); });
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct SpyCraft { // Game::SpyCraft, one `spy` frame
|
||||
static constexpr const char* kStreamName = "spy";
|
||||
int32_t sid = 0, sown = 0, atto = 0, deat = 0, tdep = 0;
|
||||
float cbh = 0, sdo = 0;
|
||||
int32_t sdet = 0, spyon = 0, spyat = 0, cm = 0;
|
||||
float cmo = 0;
|
||||
std::vector<int32_t> cpl; // ncp: compromised players; 0 in the one record we hold
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.i32(A("sid"), sid);
|
||||
ar.i32(A("sown"), sown);
|
||||
ar.i32(A("atto"), atto);
|
||||
ar.i32(A("deat"), deat);
|
||||
ar.i32(A("tdep"), tdep);
|
||||
ar.f32(A("cbh"), cbh);
|
||||
ar.f32(A("sdo"), sdo);
|
||||
ar.i32(A("sdet"), sdet);
|
||||
ar.i32(A("spyon"), spyon);
|
||||
ar.i32(A("spyat"), spyat);
|
||||
ar.i32(A("cm"), cm);
|
||||
ar.f32(A("cmo"), cmo);
|
||||
ar.narr(A("ncp"), cpl, [](Ar& a, int32_t& e) { a.i32(A("cpl"), e); });
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct SpyManager { // Game::ServerSpyManager
|
||||
static constexpr const char* kStreamName = "spymgr";
|
||||
int32_t xsid = 0;
|
||||
// nspy was 0 in every save until human-turn15-spyprogram, which ran a spy
|
||||
// program and produced one craft.
|
||||
std::vector<SpyCraft> spies;
|
||||
std::vector<Node> spies; // Game::SpyCraft frames; nspy is 0 in every save available
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.i32(A("xsid"), xsid);
|
||||
ar.narr(A("nspy"), spies, [](Ar& a, SpyCraft& e) { a.obj(A("spy"), e); });
|
||||
ar.narr(A("nspy"), spies, [](Ar& a, Node& e) { a.any(A("spy"), e); });
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
|
|
@ -1692,40 +1589,6 @@ struct ShipEntry {
|
|||
ar.obj(A("Ship"), ship);
|
||||
}
|
||||
};
|
||||
struct FieldTemplate { // Game::FieldTemplate, the NULL-named first frame of a Lay
|
||||
static constexpr const char* kStreamName = "";
|
||||
// FTPnts is the ONE item in this round's work that the recovery itself marks
|
||||
// unresolved: it names the element class from the decorated helper name
|
||||
// (Game::FieldTemplate::Point, whose own serializer is fully recovered --
|
||||
// FTPShID/FTPDesID/FTPPosX/FTPPosY/FTPSqd) but could not type the item, and
|
||||
// the count is 0 in every Lay in the corpus. That is exactly the pair of
|
||||
// conditions under which SysMem, mts and nalat were all typed wrong: the
|
||||
// element FRAMING is a property of the helper, not of the element class, and
|
||||
// no save has ever shown one. So the elements stay carried. What settles it
|
||||
// is a save whose fleet has a stored tactical formation -- set a fleet's
|
||||
// combat layout in the tactical setup screen, then save.
|
||||
std::vector<Node> points;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.carr(A("FTPnts"), points);
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
// Game::FleetLayout, the `Lay` frame gated by HLay. Every item in it is
|
||||
// NULL-named: a FieldTemplate frame, then a count, then that many ship ids.
|
||||
struct FleetLayout {
|
||||
static constexpr const char* kStreamName = "Lay";
|
||||
FieldTemplate tmpl;
|
||||
std::vector<int32_t> shipIDs;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.obj(R("tmpl"), tmpl);
|
||||
ar.narr(R("nships"), shipIDs, [](Ar& a, int32_t& e) { a.i32(R("shipID"), e); });
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct Fleet {
|
||||
static constexpr const char* kStreamName = "Flt";
|
||||
Vec3 pos;
|
||||
|
|
@ -1743,7 +1606,7 @@ struct Fleet {
|
|||
bool perm = false;
|
||||
Vec3 prvPos;
|
||||
bool hLay = false;
|
||||
FleetLayout lay;
|
||||
Node lay;
|
||||
std::vector<ShipEntry> ships;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
|
|
@ -1770,94 +1633,44 @@ struct Fleet {
|
|||
ar.b(A("Perm"), perm);
|
||||
ar.vec3(A("PrvPos"), prvPos);
|
||||
ar.b(A("HLay"), hLay);
|
||||
ar.when(hLay, [&](Ar& a) { a.obj(A("Lay"), lay); });
|
||||
ar.when(hLay, [&](Ar& a) { a.any(A("Lay"), lay); });
|
||||
ar.narr(A("NShips"), ships, [](Ar& a, ShipEntry& e) { e.io(a); });
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
|
||||
// ---- combat reports, node grid ---------------------------------------------------
|
||||
//
|
||||
// The damage quartet is written by TWO classes, Game::CombatWeaponReport and
|
||||
// Game::CombatShipReport, and both recoveries agree: dams and damp are ints, dami
|
||||
// and damt are FLOATS. An earlier `Dams` sub-struct read all four as ints and was
|
||||
// reached through obj_flex, i.e. it modelled a nested `dams` frame that the
|
||||
// binary does not write and that no save in the corpus contains. Both are
|
||||
// corrected here: the quartet is flat, and the last two are f32. The int reading
|
||||
// round-tripped because the bytes are the bytes; the values it produced
|
||||
// (dami = 1033392620 for what is 0.0787) were nonsense.
|
||||
struct Wrep { // Game::CombatWeaponReport
|
||||
struct Dams {
|
||||
static constexpr const char* kStreamName = "dams";
|
||||
int32_t dams = 0, damp = 0, dami = 0, damt = 0;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.i32(R("dams", "dams"), dams);
|
||||
ar.i32(R("damp", "damp"), damp);
|
||||
ar.i32(R("dami", "dami"), dami);
|
||||
ar.i32(R("damt", "damt"), damt);
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct Wrep {
|
||||
static constexpr const char* kStreamName = "";
|
||||
std::string wep;
|
||||
int32_t dams = 0, damp = 0;
|
||||
float dami = 0, damt = 0;
|
||||
Dams dams;
|
||||
bool damsFramed = true;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.str(R("wep", "wep"), wep);
|
||||
ar.i32(R("dams", "dams"), dams);
|
||||
ar.i32(R("damp", "damp"), damp);
|
||||
ar.f32(R("dami", "dami"), dami);
|
||||
ar.f32(R("damt", "damt"), damt);
|
||||
ar.obj_flex(R("dams", "dams"), dams, damsFramed);
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct Srep { // Game::CombatShipReport, one srep element
|
||||
static constexpr const char* kStreamName = "";
|
||||
std::string name;
|
||||
int32_t did = 0, cls = 0;
|
||||
int64_t caps2 = 0;
|
||||
int32_t nshp = 0, nfld = 0, nlst = 0, dtak = 0, dams = 0, damp = 0;
|
||||
float dami = 0, damt = 0;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.str(A("name"), name);
|
||||
ar.i32(A("did"), did);
|
||||
ar.i32(A("cls"), cls);
|
||||
ar.i64(A("caps2"), caps2);
|
||||
ar.i32(A("nshp"), nshp);
|
||||
ar.i32(A("nfld"), nfld);
|
||||
ar.i32(A("nlst"), nlst);
|
||||
ar.i32(A("dtak"), dtak);
|
||||
ar.i32(A("dams"), dams);
|
||||
ar.i32(A("damp"), damp);
|
||||
ar.f32(A("dami"), dami);
|
||||
ar.f32(A("damt"), damt);
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct CrepClass { // one ncls loop body of a CombatPlayerReport (per ship class)
|
||||
static constexpr const char* kStreamName = "";
|
||||
int32_t cls = 0, nshp = 0, nsat = 0, nshfld = 0, nshlst = 0, nsatl = 0;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.i32(A("cls"), cls);
|
||||
ar.i32(A("nshp"), nshp);
|
||||
ar.i32(A("nsat"), nsat);
|
||||
ar.i32(A("nshfld"), nshfld);
|
||||
ar.i32(A("nshlst"), nshlst);
|
||||
ar.i32(A("nsatl"), nsatl);
|
||||
}
|
||||
};
|
||||
struct CrepSector { // one nsec loop body of a CombatPlayerReport (per ship section)
|
||||
static constexpr const char* kStreamName = "";
|
||||
int32_t ssec = 0, nshp = 0;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.i32(A("ssec"), ssec);
|
||||
ar.i32(A("nshp"), nshp);
|
||||
}
|
||||
};
|
||||
struct CrepPrep { // Game::CombatPlayerReport
|
||||
struct CrepPrep {
|
||||
static constexpr const char* kStreamName = "";
|
||||
int32_t plr = 0;
|
||||
bool ai = false;
|
||||
int32_t ally = 0, status = 0, mxeng = 0, mxcls = 0, mxmsl = 0;
|
||||
std::vector<CrepClass> classes;
|
||||
std::vector<CrepSector> sectors;
|
||||
int32_t ndam = 0;
|
||||
std::vector<Srep> srep;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
|
|
@ -1868,28 +1681,14 @@ struct CrepPrep { // Game::CombatPlayerReport
|
|||
ar.i32(R("mxeng", "mxeng"), mxeng);
|
||||
ar.i32(R("mxcls", "mxcls"), mxcls);
|
||||
ar.i32(R("mxmsl", "mxmsl"), mxmsl);
|
||||
ar.narr(A("ncls"), classes, [](Ar& a, CrepClass& e) { e.io(a); });
|
||||
ar.narr(A("nsec"), sectors, [](Ar& a, CrepSector& e) { e.io(a); });
|
||||
ar.i32(A("ndam"), ndam);
|
||||
ar.carr(A("srep"), srep);
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
// Game::CombatReport. auto and cdst are BOOLs and dur, cdt and cdi are FLOATS --
|
||||
// all five were read as ints here. Four of them are 0 or 1 in every save, so the
|
||||
// bytes never moved; cdt is not (0x3FCC... = 1.598 turns of combat), and reading
|
||||
// it as an int gave 1070805848.
|
||||
struct Crep {
|
||||
static constexpr const char* kStreamName = "crep";
|
||||
int32_t cid = 0, trn = 0;
|
||||
Vec3 pos;
|
||||
int32_t sid = 0;
|
||||
bool autoF = false;
|
||||
float dur = 0;
|
||||
int32_t cow = 0;
|
||||
bool cdst = false;
|
||||
int32_t cpk = 0, cpt = 0;
|
||||
float cdt = 0, cdi = 0;
|
||||
int32_t sid = 0, autoF = 0, dur = 0, cow = 0, cdst = 0, cpk = 0, cpt = 0, cdt = 0, cdi = 0;
|
||||
std::vector<CrepPrep> prep;
|
||||
bool prepFramed = true;
|
||||
std::vector<Wrep> wrep;
|
||||
|
|
@ -1901,14 +1700,14 @@ struct Crep {
|
|||
ar.i32(R("trn", "trn"), trn);
|
||||
ar.vec3(R("pos", "pos"), pos);
|
||||
ar.i32(R("sid", "sid"), sid);
|
||||
ar.b(R("auto", "auto"), autoF);
|
||||
ar.f32(R("dur", "dur"), dur);
|
||||
ar.i32(R("auto", "auto"), autoF);
|
||||
ar.i32(R("dur", "dur"), dur);
|
||||
ar.i32(R("cow", "cow"), cow);
|
||||
ar.b(R("cdst", "cdst"), cdst);
|
||||
ar.i32(R("cdst", "cdst"), cdst);
|
||||
ar.i32(R("cpk", "cpk"), cpk);
|
||||
ar.i32(R("cpt", "cpt"), cpt);
|
||||
ar.f32(R("cdt", "cdt"), cdt);
|
||||
ar.f32(R("cdi", "cdi"), cdi);
|
||||
ar.i32(R("cdt", "cdt"), cdt);
|
||||
ar.i32(R("cdi", "cdi"), cdi);
|
||||
ar.carr_flex(R("prep", "prep"), prep, prepFramed);
|
||||
ar.carr_flex(R("wrep", "wrep"), wrep, wrepFramed);
|
||||
ar.rest(extra);
|
||||
|
|
@ -2616,77 +2415,14 @@ struct AIAutoPeaceRun { // Game::AIAutoPeaceRun
|
|||
}
|
||||
};
|
||||
|
||||
struct TacReportEvents { // Game::TacReportEvents, the TRby / TRto frames
|
||||
static constexpr const char* kStreamName = "";
|
||||
int32_t hd = 0, hi = 0;
|
||||
float d = 0, dp = 0, di = 0, dt = 0;
|
||||
bool b = false;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.i32(A("TREhd"), hd);
|
||||
ar.i32(A("TREhi"), hi);
|
||||
ar.f32(A("TREd"), d);
|
||||
ar.f32(A("TREdp"), dp);
|
||||
ar.f32(A("TREdi"), di);
|
||||
ar.f32(A("TREdt"), dt);
|
||||
ar.b(A("TREb"), b);
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct TacClass { // one TRnc loop body: the per-ship-class tail of a TacReport
|
||||
static constexpr const char* kStreamName = "";
|
||||
int32_t ships = 0, sats = 0, shipsL = 0;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.i32(A("TRships"), ships);
|
||||
ar.i32(A("TRsats"), sats);
|
||||
ar.i32(A("TRshipsL"), shipsL);
|
||||
}
|
||||
};
|
||||
// Game::TacReport. Two lanes left this carried: the recovery ends with a computed
|
||||
// count (TRnc) and three trailing scalar items, and with TRnc == 0 in every save
|
||||
// held at the time there was no way to tell three trailing scalars from a loop
|
||||
// body of three. The trade/spy saves settle it -- TRnc == 3 and the wire carries
|
||||
// TRships, TRsats, TRshipsL three times, INTERLEAVED, so the recovery's three
|
||||
// items are one loop body, not three runs.
|
||||
struct TacReport {
|
||||
static constexpr const char* kStreamName = "";
|
||||
TacReportEvents by, to;
|
||||
// `mine` not `min`: windows.h defines min as a macro and this header is
|
||||
// compiled by the MinGW cross-build too.
|
||||
int32_t id = 0, al = 0, bal = 0, las = 0, mis = 0, mine = 0, nrg = 0, bio = 0, brd = 0;
|
||||
bool sld = false, sldd = false, sldc = false, sldi = false, sldr = false;
|
||||
std::vector<TacClass> classes;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
ar.obj(A("TRby"), by);
|
||||
ar.obj(A("TRto"), to);
|
||||
ar.i32(A("TRid"), id);
|
||||
ar.i32(A("TRal"), al);
|
||||
ar.i32(A("TRbal"), bal);
|
||||
ar.i32(A("TRlas"), las);
|
||||
ar.i32(A("TRmis"), mis);
|
||||
ar.i32(A("TRmin"), mine);
|
||||
ar.i32(A("TRnrg"), nrg);
|
||||
ar.i32(A("TRbio"), bio);
|
||||
ar.i32(A("TRbrd"), brd);
|
||||
ar.b(A("TRsld"), sld);
|
||||
ar.b(A("TRsldd"), sldd);
|
||||
ar.b(A("TRsldc"), sldc);
|
||||
ar.b(A("TRsldi"), sldi);
|
||||
ar.b(A("TRsldr"), sldr);
|
||||
ar.narr(A("TRnc"), classes, [](Ar& a, TacClass& e) { e.io(a); });
|
||||
ar.rest(extra);
|
||||
}
|
||||
};
|
||||
struct CombatPlayerStats { // Game::CombatPlayerStats
|
||||
static constexpr const char* kStreamName = "CRPlSv2";
|
||||
float rpBon = 0;
|
||||
int32_t rpBonT = 0, savBonus = 0;
|
||||
bool maintHF = false;
|
||||
std::vector<TacReport> tacReports;
|
||||
// Game::TacReport has a computed count and three trailing scalar runs that no
|
||||
// save we hold exercises; its body stays carried rather than typed on a guess.
|
||||
std::vector<Node> tacReports;
|
||||
std::vector<Node> extra;
|
||||
template <class Ar>
|
||||
void io(Ar& ar) {
|
||||
|
|
|
|||
|
|
@ -635,20 +635,7 @@ static void test_aiagent_block() {
|
|||
rep.crPlSv2.rpBonT = 3;
|
||||
rep.crPlSv2.savBonus = 4;
|
||||
rep.crPlSv2.maintHF = true;
|
||||
// Game::TacReport: two event frames, the scalar block, then the TRnc run of
|
||||
// (TRships, TRsats, TRshipsL) triples -- interleaved, which is the shape of
|
||||
// the "three trailing scalars" the linear recovery reports.
|
||||
shapes::TacReport tac;
|
||||
tac.by.hi = 4;
|
||||
tac.by.d = 900.0f;
|
||||
tac.to.dt = -1.5f;
|
||||
tac.to.b = true;
|
||||
tac.id = 9;
|
||||
tac.bal = 3;
|
||||
tac.sldc = true;
|
||||
tac.classes.push_back({1, 2, 3});
|
||||
tac.classes.push_back({4, 5, 6});
|
||||
rep.crPlSv2.tacReports.push_back(tac);
|
||||
rep.crPlSv2.tacReports.push_back(Node::int32("TRid", 9)); // carried, not typed
|
||||
ai.combatReports.push_back(rep);
|
||||
ai.colonyLosses.push_back({2, 272, 16});
|
||||
ai.provocations.push_back({16, 450.0f});
|
||||
|
|
@ -688,12 +675,6 @@ static void test_aiagent_block() {
|
|||
CHECK(back.combatReports.size() == 1 && back.combatReports[0].crPce &&
|
||||
back.combatReports[0].crSys.value == 272 && back.combatReports[0].crPlSv2.maintHF &&
|
||||
back.combatReports[0].crPlSv2.tacReports.size() == 1);
|
||||
{
|
||||
const shapes::TacReport& t = back.combatReports[0].crPlSv2.tacReports[0];
|
||||
CHECK(t.by.hi == 4 && t.by.d == 900.0f && t.to.dt == -1.5f && t.to.b);
|
||||
CHECK(t.id == 9 && t.bal == 3 && t.sldc && !t.sld);
|
||||
CHECK(t.classes.size() == 2 && t.classes[0].sats == 2 && t.classes[1].shipsL == 6);
|
||||
}
|
||||
CHECK(back.colonyLosses.size() == 1 && back.colonyLosses[0].sysID == 272);
|
||||
CHECK(back.provocations.size() == 1 && back.provocations[0].value == 450.0f);
|
||||
CHECK(back.techScores.size() == 3 && back.techScores[1] == 4);
|
||||
|
|
|
|||
|
|
@ -310,27 +310,6 @@ int main() {
|
|||
check<sh::ShipSectionID>("ShipSectionID", "Game::ShipSectionID");
|
||||
check<sh::DesignSection>("DesignSection", "Game::ShipDesignDef::Section");
|
||||
|
||||
// Bodies typed by lane WS from the trade/spy workload saves. Every one of
|
||||
// these was carried as a Node until a save existed that put content in it;
|
||||
// the schema was always there, the data was not (rule 6).
|
||||
check<sh::WeaponGroups>("WeaponGroups", "Game::WeaponGroups");
|
||||
check<sh::GunBankSelection>("GunBankSelection", "Game::GunBankSelection");
|
||||
check<sh::FreighterWarning>("FreighterWarning", "Game::ServerTradeSector::FreighterWarning");
|
||||
check<sh::TradeRoute>("TradeRoute", "Game::TradeRoute");
|
||||
check<sh::SpyCraft>("SpyCraft", "Game::SpyCraft");
|
||||
check<sh::FleetLayout>("FleetLayout", "Game::FleetLayout");
|
||||
// Game::FieldTemplate::Point is deliberately NOT bound: FTPnts is empty in
|
||||
// every save and the recovery marks the item unresolved, so its element
|
||||
// framing is a hypothesis and FieldTemplate carries the elements (see the
|
||||
// note on sh::FieldTemplate).
|
||||
check<sh::FieldTemplate>("FieldTemplate", "Game::FieldTemplate");
|
||||
check<sh::Crep>("Crep", "Game::CombatReport");
|
||||
check<sh::CrepPrep>("CrepPrep", "Game::CombatPlayerReport");
|
||||
check<sh::Srep>("Srep", "Game::CombatShipReport");
|
||||
check<sh::Wrep>("Wrep", "Game::CombatWeaponReport");
|
||||
check<sh::TacReport>("TacReport", "Game::TacReport");
|
||||
check<sh::TacReportEvents>("TacReportEvents", "Game::TacReportEvents");
|
||||
|
||||
// --- SvSctOb: Game::SVSOSots and the variant bodies it dispatches to -------
|
||||
// The variant SELECTION (xscn name / EncID -> class) is not on the wire and so
|
||||
// is not checkable here; it came from the game's own factories. What this
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue