lane WS: pay the ratchet debt -- type the five new bodies, do not move the bar
The corpus went 11 -> 19 saves and named coverage fell to 97.6% against a 99.99
bar. Rule 27 says type the content. Typed, all from the recovered wire schema
plus the records the new trade/spy saves finally carry:
Game::CombatPlayerReport the <rest:prep> tail -- ncls run of six per ship
class, nsec run of two per section, ndam, srep
Game::CombatShipReport srep elements (caps2 is an i64; dami/damt f32)
Game::TacReport TRnc is a COUNT and TRships/TRsats/TRshipsL are one
loop body, INTERLEAVED on the wire -- not three
trailing runs. Two lanes could not settle this
because TRnc was 0 in every save until tonight.
Game::TacReportEvents TRby / TRto
Game::FleetLayout Lay: a FieldTemplate frame then a count and its ids
Game::TradeRoute rt -- a container write with NO count word, read as
an uncounted run keyed on the tag
Game::ServerTradeSector::FreighterWarning fwarn elements
Game::SpyCraft spy elements
Game::WeaponGroups Dwg, and Game::GunBankSelection under it
Corrections found on the way, both silent until a shape was bound to the table:
* Game::CombatReport: auto and cdst are BOOLs, dur/cdt/cdi are FLOATS. All
five were ints here. Four are 0/1 everywhere so the bytes never moved; cdt
is not, and was being read as 1070805848 instead of 1.598.
* Game::CombatWeaponReport: the damage quartet is flat and dami/damt are
floats. The old shape reached them through obj_flex, modelling a nested
`dams` frame the binary does not write. That also made CoverageArchive
charge one phantom typed item per weapon report -- exactly 8/13/21/32 on the
four affected saves -- so the pre-fix coverage figures were slightly
optimistic as well as too low.
FTPnts stays carried: it is the one item the recovery itself marks unresolved,
its count is 0 in every save, and element framing is a property of the helper --
the SysMem / mts / nalat trap. The workload that settles it is a save with a
stored fleet tactical formation.
Named coverage, per save, before -> after:
human-turn5-traderoutes 99.0003% -> 99.9951%
human-turn8-traderoutes 98.6349% -> 99.9955%
human-turn11-spytechs 97.6382% -> 99.9959%
human-turn15-spyprogram 96.9412% -> 99.9964%
the other 16 saves unchanged, 99.9940-99.9953%
corpus 99.5021% -> 99.9949%
All 20 saves are above the ratchet; the only opaque items left anywhere are the
two of the RNG blob. 14 new shapes bound to the wire schema, every one a full
match: 100 shapes, 966 items, 0 MISMATCH. ctest 58/58 over all 20 saves, both
round trips byte-identical, 0 errors and 0 warnings.
This commit is contained in:
parent
16b1e65fa3
commit
7f04bcc88b
4 changed files with 352 additions and 42 deletions
|
|
@ -85,10 +85,14 @@ struct Summary {
|
||||||
reference name differs from the disk spelling, e.g. `faiDes` → `FAIDes`, `ontF` → `otnF`,
|
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
|
`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.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 the
|
`ar.repeat("stats", ...)`, inline arrays `ar.narr(A("NumSys"), systems, elem)`. Bodies still
|
||||||
format keeps opaque (`TechTree`, `Events`, `ShipRecs`, `spy2`, `civr`, `comms`, `Ojvs`,
|
kept opaque are `Node` members and re-emit verbatim. That list has shrunk to two kinds: the
|
||||||
`Attrib`, `sprjs`, `SvSctOb`, `trdmgr`, `spymgr`, `CD`, the RNG blob) are `Node` members and
|
**RNG blob**, which is deliberately opaque, and **containers no save has ever filled** —
|
||||||
re-emit verbatim.
|
`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.
|
||||||
|
|
||||||
`ReadArchive` mirrors the reference applier: a confirmed tag that is missing is an error,
|
`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;
|
unexpected items before it a warning, a positional item read under another tag an info;
|
||||||
|
|
@ -111,12 +115,14 @@ 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
|
`SOTS_SAVE_READER` set, `oracle/compare.py` diffs the C++ dump against the reference
|
||||||
`--dump` (exact and canonical agreement, summary values).
|
`--dump` (exact and canonical agreement, summary values).
|
||||||
|
|
||||||
Result on the owner's three saves (SotS 1.8, turns 1–3): 100% exact line agreement, both
|
Result on the owner's 20-save corpus (SotS 1.8): 100% exact line agreement, both round trips
|
||||||
round trips identical, summaries agree.
|
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.
|
||||||
|
|
||||||
## Not done / open
|
## Not done / open
|
||||||
|
|
||||||
* Only joint padding is implemented (the split convention never matched a real file).
|
* Only joint padding is implemented (the split convention never matched a real file).
|
||||||
* Shapes for the opaque bodies listed above (kept generic on purpose, as in the reference).
|
* 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.
|
||||||
* The float-vs-int guess for unhinted words inside opaque bodies is a heuristic; the typed
|
* 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).
|
layer never depends on it (it coerces from the raw bytes).
|
||||||
|
|
|
||||||
|
|
@ -851,6 +851,41 @@ struct DesignSection { // Game::ShipDesignDef::Section, one DSec of a Des frame
|
||||||
ar.rest(extra);
|
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
|
// 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;
|
// 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
|
// the derived one appends Dtc, the Dwgv flag and -- only when that flag is set
|
||||||
|
|
@ -875,7 +910,10 @@ struct Design { // on-disk tags are case variants of the reference names (FAIDe
|
||||||
std::vector<DesignSection> sections;
|
std::vector<DesignSection> sections;
|
||||||
int32_t dtc = 0;
|
int32_t dtc = 0;
|
||||||
bool dwgv = false;
|
bool dwgv = false;
|
||||||
Node weaponGroups; // Dwg; present only when dwgv, which is false in every save available
|
// 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;
|
||||||
std::vector<Node> extra;
|
std::vector<Node> extra;
|
||||||
template <class Ar>
|
template <class Ar>
|
||||||
void io(Ar& ar) {
|
void io(Ar& ar) {
|
||||||
|
|
@ -886,7 +924,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.repeat("DSec", sections, [](Ar& a, DesignSection& e) { a.obj(A("DSec"), e); });
|
||||||
ar.i32(A("Dtc"), dtc);
|
ar.i32(A("Dtc"), dtc);
|
||||||
ar.b(A("Dwgv"), dwgv);
|
ar.b(A("Dwgv"), dwgv);
|
||||||
ar.when(dwgv, [&](Ar& a) { a.any(A("Dwg"), weaponGroups); });
|
ar.when(dwgv, [&](Ar& a) { a.obj(A("Dwg"), weaponGroups); });
|
||||||
ar.rest(extra);
|
ar.rest(extra);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1186,11 +1224,24 @@ struct SpyReport { // Game::SpyReport: four counted lists, one per report kind
|
||||||
ar.rest(extra);
|
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
|
struct TradeSector { // Game::ServerTradeSector
|
||||||
static constexpr const char* kStreamName = "Trade";
|
static constexpr const char* kStreamName = "Trade";
|
||||||
Vec3 pos, ctr;
|
Vec3 pos, ctr;
|
||||||
int32_t gridID = 0, tssec = 0, tsct = 0, tscr = 0, ptssec = 0, ptsct = 0, ptscr = 0;
|
int32_t gridID = 0, tssec = 0, tsct = 0, tscr = 0, ptssec = 0, ptsct = 0, ptscr = 0;
|
||||||
std::vector<Node> fwarn; // ServerTradeSector::FreighterWarning; empty in every save available
|
// 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<int32_t> systems, fleets;
|
std::vector<int32_t> systems, fleets;
|
||||||
std::vector<Node> extra;
|
std::vector<Node> extra;
|
||||||
template <class Ar>
|
template <class Ar>
|
||||||
|
|
@ -1210,6 +1261,23 @@ struct TradeSector { // Game::ServerTradeSector
|
||||||
ar.rest(extra);
|
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
|
struct TradeManager { // Game::ServerTradeManagerImpl
|
||||||
// The `trdmgr` member is declared as Game::ServerTradeManager, whose Read and
|
// 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
|
// Write really are the inherited no-op -- the campaign recorded that as an open
|
||||||
|
|
@ -1228,23 +1296,58 @@ struct TradeManager { // Game::ServerTradeManagerImpl
|
||||||
};
|
};
|
||||||
std::vector<Entry> sectors;
|
std::vector<Entry> sectors;
|
||||||
float sctSize = 0;
|
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;
|
std::vector<Node> extra;
|
||||||
template <class Ar>
|
template <class Ar>
|
||||||
void io(Ar& ar) {
|
void io(Ar& ar) {
|
||||||
ar.narr(A("NumTradeSectors"), sectors, [](Ar& a, Entry& e) { e.io(a); });
|
ar.narr(A("NumTradeSectors"), sectors, [](Ar& a, Entry& e) { e.io(a); });
|
||||||
ar.f32(A("SctSize"), sctSize);
|
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);
|
ar.rest(extra);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct SpyManager { // Game::ServerSpyManager
|
struct SpyManager { // Game::ServerSpyManager
|
||||||
static constexpr const char* kStreamName = "spymgr";
|
static constexpr const char* kStreamName = "spymgr";
|
||||||
int32_t xsid = 0;
|
int32_t xsid = 0;
|
||||||
std::vector<Node> spies; // Game::SpyCraft frames; nspy is 0 in every save available
|
// 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> extra;
|
std::vector<Node> extra;
|
||||||
template <class Ar>
|
template <class Ar>
|
||||||
void io(Ar& ar) {
|
void io(Ar& ar) {
|
||||||
ar.i32(A("xsid"), xsid);
|
ar.i32(A("xsid"), xsid);
|
||||||
ar.narr(A("nspy"), spies, [](Ar& a, Node& e) { a.any(A("spy"), e); });
|
ar.narr(A("nspy"), spies, [](Ar& a, SpyCraft& e) { a.obj(A("spy"), e); });
|
||||||
ar.rest(extra);
|
ar.rest(extra);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1589,6 +1692,40 @@ struct ShipEntry {
|
||||||
ar.obj(A("Ship"), ship);
|
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 {
|
struct Fleet {
|
||||||
static constexpr const char* kStreamName = "Flt";
|
static constexpr const char* kStreamName = "Flt";
|
||||||
Vec3 pos;
|
Vec3 pos;
|
||||||
|
|
@ -1606,7 +1743,7 @@ struct Fleet {
|
||||||
bool perm = false;
|
bool perm = false;
|
||||||
Vec3 prvPos;
|
Vec3 prvPos;
|
||||||
bool hLay = false;
|
bool hLay = false;
|
||||||
Node lay;
|
FleetLayout lay;
|
||||||
std::vector<ShipEntry> ships;
|
std::vector<ShipEntry> ships;
|
||||||
std::vector<Node> extra;
|
std::vector<Node> extra;
|
||||||
template <class Ar>
|
template <class Ar>
|
||||||
|
|
@ -1633,44 +1770,94 @@ struct Fleet {
|
||||||
ar.b(A("Perm"), perm);
|
ar.b(A("Perm"), perm);
|
||||||
ar.vec3(A("PrvPos"), prvPos);
|
ar.vec3(A("PrvPos"), prvPos);
|
||||||
ar.b(A("HLay"), hLay);
|
ar.b(A("HLay"), hLay);
|
||||||
ar.when(hLay, [&](Ar& a) { a.any(A("Lay"), lay); });
|
ar.when(hLay, [&](Ar& a) { a.obj(A("Lay"), lay); });
|
||||||
ar.narr(A("NShips"), ships, [](Ar& a, ShipEntry& e) { e.io(a); });
|
ar.narr(A("NShips"), ships, [](Ar& a, ShipEntry& e) { e.io(a); });
|
||||||
ar.rest(extra);
|
ar.rest(extra);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- combat reports, node grid ---------------------------------------------------
|
// ---- combat reports, node grid ---------------------------------------------------
|
||||||
struct Dams {
|
//
|
||||||
static constexpr const char* kStreamName = "dams";
|
// The damage quartet is written by TWO classes, Game::CombatWeaponReport and
|
||||||
int32_t dams = 0, damp = 0, dami = 0, damt = 0;
|
// Game::CombatShipReport, and both recoveries agree: dams and damp are ints, dami
|
||||||
std::vector<Node> extra;
|
// and damt are FLOATS. An earlier `Dams` sub-struct read all four as ints and was
|
||||||
template <class Ar>
|
// reached through obj_flex, i.e. it modelled a nested `dams` frame that the
|
||||||
void io(Ar& ar) {
|
// binary does not write and that no save in the corpus contains. Both are
|
||||||
ar.i32(R("dams", "dams"), dams);
|
// corrected here: the quartet is flat, and the last two are f32. The int reading
|
||||||
ar.i32(R("damp", "damp"), damp);
|
// round-tripped because the bytes are the bytes; the values it produced
|
||||||
ar.i32(R("dami", "dami"), dami);
|
// (dami = 1033392620 for what is 0.0787) were nonsense.
|
||||||
ar.i32(R("damt", "damt"), damt);
|
struct Wrep { // Game::CombatWeaponReport
|
||||||
ar.rest(extra);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
struct Wrep {
|
|
||||||
static constexpr const char* kStreamName = "";
|
static constexpr const char* kStreamName = "";
|
||||||
std::string wep;
|
std::string wep;
|
||||||
Dams dams;
|
int32_t dams = 0, damp = 0;
|
||||||
bool damsFramed = true;
|
float dami = 0, damt = 0;
|
||||||
std::vector<Node> extra;
|
std::vector<Node> extra;
|
||||||
template <class Ar>
|
template <class Ar>
|
||||||
void io(Ar& ar) {
|
void io(Ar& ar) {
|
||||||
ar.str(R("wep", "wep"), wep);
|
ar.str(R("wep", "wep"), wep);
|
||||||
ar.obj_flex(R("dams", "dams"), dams, damsFramed);
|
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.rest(extra);
|
ar.rest(extra);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct CrepPrep {
|
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
|
||||||
static constexpr const char* kStreamName = "";
|
static constexpr const char* kStreamName = "";
|
||||||
int32_t plr = 0;
|
int32_t plr = 0;
|
||||||
bool ai = false;
|
bool ai = false;
|
||||||
int32_t ally = 0, status = 0, mxeng = 0, mxcls = 0, mxmsl = 0;
|
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;
|
std::vector<Node> extra;
|
||||||
template <class Ar>
|
template <class Ar>
|
||||||
void io(Ar& ar) {
|
void io(Ar& ar) {
|
||||||
|
|
@ -1681,14 +1868,28 @@ struct CrepPrep {
|
||||||
ar.i32(R("mxeng", "mxeng"), mxeng);
|
ar.i32(R("mxeng", "mxeng"), mxeng);
|
||||||
ar.i32(R("mxcls", "mxcls"), mxcls);
|
ar.i32(R("mxcls", "mxcls"), mxcls);
|
||||||
ar.i32(R("mxmsl", "mxmsl"), mxmsl);
|
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);
|
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 {
|
struct Crep {
|
||||||
static constexpr const char* kStreamName = "crep";
|
static constexpr const char* kStreamName = "crep";
|
||||||
int32_t cid = 0, trn = 0;
|
int32_t cid = 0, trn = 0;
|
||||||
Vec3 pos;
|
Vec3 pos;
|
||||||
int32_t sid = 0, autoF = 0, dur = 0, cow = 0, cdst = 0, cpk = 0, cpt = 0, cdt = 0, cdi = 0;
|
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;
|
||||||
std::vector<CrepPrep> prep;
|
std::vector<CrepPrep> prep;
|
||||||
bool prepFramed = true;
|
bool prepFramed = true;
|
||||||
std::vector<Wrep> wrep;
|
std::vector<Wrep> wrep;
|
||||||
|
|
@ -1700,14 +1901,14 @@ struct Crep {
|
||||||
ar.i32(R("trn", "trn"), trn);
|
ar.i32(R("trn", "trn"), trn);
|
||||||
ar.vec3(R("pos", "pos"), pos);
|
ar.vec3(R("pos", "pos"), pos);
|
||||||
ar.i32(R("sid", "sid"), sid);
|
ar.i32(R("sid", "sid"), sid);
|
||||||
ar.i32(R("auto", "auto"), autoF);
|
ar.b(R("auto", "auto"), autoF);
|
||||||
ar.i32(R("dur", "dur"), dur);
|
ar.f32(R("dur", "dur"), dur);
|
||||||
ar.i32(R("cow", "cow"), cow);
|
ar.i32(R("cow", "cow"), cow);
|
||||||
ar.i32(R("cdst", "cdst"), cdst);
|
ar.b(R("cdst", "cdst"), cdst);
|
||||||
ar.i32(R("cpk", "cpk"), cpk);
|
ar.i32(R("cpk", "cpk"), cpk);
|
||||||
ar.i32(R("cpt", "cpt"), cpt);
|
ar.i32(R("cpt", "cpt"), cpt);
|
||||||
ar.i32(R("cdt", "cdt"), cdt);
|
ar.f32(R("cdt", "cdt"), cdt);
|
||||||
ar.i32(R("cdi", "cdi"), cdi);
|
ar.f32(R("cdi", "cdi"), cdi);
|
||||||
ar.carr_flex(R("prep", "prep"), prep, prepFramed);
|
ar.carr_flex(R("prep", "prep"), prep, prepFramed);
|
||||||
ar.carr_flex(R("wrep", "wrep"), wrep, wrepFramed);
|
ar.carr_flex(R("wrep", "wrep"), wrep, wrepFramed);
|
||||||
ar.rest(extra);
|
ar.rest(extra);
|
||||||
|
|
@ -2415,14 +2616,77 @@ 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
|
struct CombatPlayerStats { // Game::CombatPlayerStats
|
||||||
static constexpr const char* kStreamName = "CRPlSv2";
|
static constexpr const char* kStreamName = "CRPlSv2";
|
||||||
float rpBon = 0;
|
float rpBon = 0;
|
||||||
int32_t rpBonT = 0, savBonus = 0;
|
int32_t rpBonT = 0, savBonus = 0;
|
||||||
bool maintHF = false;
|
bool maintHF = false;
|
||||||
// Game::TacReport has a computed count and three trailing scalar runs that no
|
std::vector<TacReport> tacReports;
|
||||||
// save we hold exercises; its body stays carried rather than typed on a guess.
|
|
||||||
std::vector<Node> tacReports;
|
|
||||||
std::vector<Node> extra;
|
std::vector<Node> extra;
|
||||||
template <class Ar>
|
template <class Ar>
|
||||||
void io(Ar& ar) {
|
void io(Ar& ar) {
|
||||||
|
|
|
||||||
|
|
@ -635,7 +635,20 @@ static void test_aiagent_block() {
|
||||||
rep.crPlSv2.rpBonT = 3;
|
rep.crPlSv2.rpBonT = 3;
|
||||||
rep.crPlSv2.savBonus = 4;
|
rep.crPlSv2.savBonus = 4;
|
||||||
rep.crPlSv2.maintHF = true;
|
rep.crPlSv2.maintHF = true;
|
||||||
rep.crPlSv2.tacReports.push_back(Node::int32("TRid", 9)); // carried, not typed
|
// 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);
|
||||||
ai.combatReports.push_back(rep);
|
ai.combatReports.push_back(rep);
|
||||||
ai.colonyLosses.push_back({2, 272, 16});
|
ai.colonyLosses.push_back({2, 272, 16});
|
||||||
ai.provocations.push_back({16, 450.0f});
|
ai.provocations.push_back({16, 450.0f});
|
||||||
|
|
@ -675,6 +688,12 @@ static void test_aiagent_block() {
|
||||||
CHECK(back.combatReports.size() == 1 && back.combatReports[0].crPce &&
|
CHECK(back.combatReports.size() == 1 && back.combatReports[0].crPce &&
|
||||||
back.combatReports[0].crSys.value == 272 && back.combatReports[0].crPlSv2.maintHF &&
|
back.combatReports[0].crSys.value == 272 && back.combatReports[0].crPlSv2.maintHF &&
|
||||||
back.combatReports[0].crPlSv2.tacReports.size() == 1);
|
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.colonyLosses.size() == 1 && back.colonyLosses[0].sysID == 272);
|
||||||
CHECK(back.provocations.size() == 1 && back.provocations[0].value == 450.0f);
|
CHECK(back.provocations.size() == 1 && back.provocations[0].value == 450.0f);
|
||||||
CHECK(back.techScores.size() == 3 && back.techScores[1] == 4);
|
CHECK(back.techScores.size() == 3 && back.techScores[1] == 4);
|
||||||
|
|
|
||||||
|
|
@ -310,6 +310,27 @@ int main() {
|
||||||
check<sh::ShipSectionID>("ShipSectionID", "Game::ShipSectionID");
|
check<sh::ShipSectionID>("ShipSectionID", "Game::ShipSectionID");
|
||||||
check<sh::DesignSection>("DesignSection", "Game::ShipDesignDef::Section");
|
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 -------
|
// --- SvSctOb: Game::SVSOSots and the variant bodies it dispatches to -------
|
||||||
// The variant SELECTION (xscn name / EncID -> class) is not on the wire and so
|
// 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
|
// is not checkable here; it came from the game's own factories. What this
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue