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.
392 lines
21 KiB
C++
392 lines
21 KiB
C++
// Conformance: every typed shape in shapes.h vs the wire schema recovered from
|
|
// the game's own Mars::IStreamable serializers (include/generated/sots_stream_schema.h).
|
|
//
|
|
// Why a check and not a generator. The recovered table is a *specification*, not
|
|
// a program: the recovery is a linear pass over the game's Write and cannot see
|
|
// Write's branches, so a conditional field (StarShip's BQ2, gated by hbq) is
|
|
// listed unconditionally and the sequence is a superset of any single record.
|
|
// Container loops are flattened the same way. A codec driven straight off the
|
|
// table would desynchronise on the first branch. The hand-written io() shapes
|
|
// stay the codec — they can express the conditionals and the nesting that the
|
|
// binary facts cannot supply — and this test is what proves they agree with the
|
|
// binary, item for item, in order.
|
|
//
|
|
// SchemaProbe runs each io() with every branch taken, which is the same "all
|
|
// branches" view the recovery has, so the two sequences are comparable. They
|
|
// are aligned with an LCS and three numbers come out:
|
|
//
|
|
// matched the shape and the binary agree on tag and on-disk primitive
|
|
// MISMATCH same tag, different primitive — a real bug, and a hard failure
|
|
// wire-only the binary writes an item the shape does not name: either a
|
|
// conditional the shape models with opt_*/when, or genuine
|
|
// coverage debt
|
|
// shape-only the shape names an item the recovery did not resolve
|
|
//
|
|
// Opaque items (ar.any / ar.raw_frame — a body carried as a Node) are counted
|
|
// separately: they round-trip byte-for-byte but are not understood, and the
|
|
// count is this codebase's honest coverage number.
|
|
//
|
|
// No game data, no saves: the whole test is the generated table plus shapes.h.
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "generated/sots_stream_schema.h"
|
|
#include "mars/stream/probe.h"
|
|
#include "mars/stream/shapes.h"
|
|
|
|
using namespace mars::stream;
|
|
namespace sh = mars::stream::shapes;
|
|
|
|
static int fails = 0;
|
|
#define CHECK(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
|
++fails; \
|
|
} \
|
|
} while (0)
|
|
|
|
// --- primitive compatibility -------------------------------------------------
|
|
// The generated Prim is the DISK type: a member the original holds as int16 or
|
|
// int8 is written by Stream::WriteInt and is I32 on the wire, so there is no
|
|
// narrow integer to reconcile here.
|
|
static bool prim_ok(SchemaProbe::P got, sots::wire::Prim want) {
|
|
using P = SchemaProbe::P;
|
|
using W = sots::wire::Prim;
|
|
if (want == W::Unknown) return true; // recovery could not type it
|
|
switch (got) {
|
|
case P::Unknown: return true; // opaque on our side: any disk type fits
|
|
case P::I32: return want == W::I32;
|
|
case P::I64: return want == W::I64;
|
|
case P::F32: return want == W::F32;
|
|
case P::Bool: return want == W::Bool;
|
|
case P::Str: return want == W::Str;
|
|
case P::Frame: return want == W::Frame;
|
|
case P::Raw: return want == W::Raw || want == W::I64;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct Result {
|
|
int matched = 0, mismatch = 0, wire_only = 0, shape_only = 0;
|
|
int opaque = 0;
|
|
};
|
|
|
|
// LCS alignment on (tag, compatible primitive). Sequences are short (<= 120).
|
|
static Result align(const std::vector<SchemaProbe::Item>& a, const sots::wire::Class& c, bool verbose) {
|
|
const size_t n = a.size(), m = c.count;
|
|
std::vector<std::vector<int>> dp(n + 1, std::vector<int>(m + 1, 0));
|
|
auto eq = [&](size_t i, size_t j) {
|
|
return a[i].tag == c.fields[j].tag && prim_ok(a[i].prim, c.fields[j].prim);
|
|
};
|
|
for (size_t i = n; i-- > 0;)
|
|
for (size_t j = m; j-- > 0;)
|
|
dp[i][j] = eq(i, j) ? dp[i + 1][j + 1] + 1 : std::max(dp[i + 1][j], dp[i][j + 1]);
|
|
|
|
Result r;
|
|
for (const SchemaProbe::Item& it : a) r.opaque += it.opaque;
|
|
size_t i = 0, j = 0;
|
|
while (i < n && j < m) {
|
|
if (eq(i, j)) {
|
|
++r.matched;
|
|
++i;
|
|
++j;
|
|
continue;
|
|
}
|
|
// Same tag, incompatible primitive: not a gap, a disagreement.
|
|
if (a[i].tag == c.fields[j].tag) {
|
|
++r.mismatch;
|
|
std::printf(" MISMATCH %-20s shape %-6s vs wire %-6s\n", a[i].tag.c_str(),
|
|
probe_prim_name(a[i].prim), sots::wire::prim_name(c.fields[j].prim));
|
|
++i;
|
|
++j;
|
|
continue;
|
|
}
|
|
if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
++r.shape_only;
|
|
if (verbose)
|
|
std::printf(" shape-only %-20s %s\n", a[i].tag.c_str(), probe_prim_name(a[i].prim));
|
|
++i;
|
|
} else {
|
|
++r.wire_only;
|
|
if (verbose)
|
|
std::printf(" wire-only %-20s %s %s\n", c.fields[j].tag,
|
|
sots::wire::prim_name(c.fields[j].prim),
|
|
sots::wire::shape_name(c.fields[j].shape));
|
|
++j;
|
|
}
|
|
}
|
|
for (; i < n; ++i) {
|
|
++r.shape_only;
|
|
if (verbose) std::printf(" shape-only %-20s %s\n", a[i].tag.c_str(), probe_prim_name(a[i].prim));
|
|
}
|
|
for (; j < m; ++j) {
|
|
++r.wire_only;
|
|
if (verbose)
|
|
std::printf(" wire-only %-20s %s %s\n", c.fields[j].tag, sots::wire::prim_name(c.fields[j].prim),
|
|
sots::wire::shape_name(c.fields[j].shape));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int total_matched = 0, total_mismatch = 0, total_wire_only = 0, total_opaque = 0;
|
|
static int bound = 0, unresolved_classes = 0;
|
|
|
|
template <class T>
|
|
static void check(const char* shape_name, const char* cls) {
|
|
const sots::wire::Class* c = sots::wire::find(cls);
|
|
if (!c) {
|
|
std::printf(" %-18s -> %-34s NOT IN TABLE\n", shape_name, cls);
|
|
++fails;
|
|
return;
|
|
}
|
|
SchemaProbe p = SchemaProbe::of<T>();
|
|
bool verbose = std::getenv("SOTS_WIRE_VERBOSE") != nullptr;
|
|
Result r = align(p.items, *c, verbose);
|
|
++bound;
|
|
total_matched += r.matched;
|
|
total_mismatch += r.mismatch;
|
|
total_wire_only += r.wire_only;
|
|
total_opaque += r.opaque;
|
|
std::printf(" %-18s -> %-34s [%-8s %2u/%-2u] shape %2zu wire %2u match %2d"
|
|
" wire-only %2d shape-only %2d opaque %d\n",
|
|
shape_name, cls, c->grade, c->read_agree, c->read_comparable, p.items.size(), c->count,
|
|
r.matched, r.wire_only, r.shape_only, r.opaque);
|
|
// A tag both sides name, typed differently, is a real disagreement between
|
|
// our codec and the binary. Nothing else here is allowed to fail the build:
|
|
// wire-only is conditional-field or coverage debt and is reported, not fatal.
|
|
CHECK(r.mismatch == 0);
|
|
}
|
|
|
|
// --- Game::TurnCommands -------------------------------------------------------
|
|
//
|
|
// The generic check above cannot be used here, and the reason is the finding.
|
|
//
|
|
// Every item in this class is written with a NULL name, so the LCS degenerates:
|
|
// with all tags equal to "." the gap branches are unreachable and the walk becomes
|
|
// a strict positional comparison in which every primitive disagreement is a
|
|
// MISMATCH. That is fine when the two sequences describe the same thing. They do
|
|
// not. The writer's 44 recovered items are 17 member writes plus ONE ITEM PER
|
|
// CONTAINER CALL SITE -- there are exactly 27 std::list members, each written by
|
|
// its own helper as WriteInt(size) followed by `size` element records. The
|
|
// recovery, being a linear pass, keeps the call site, guesses its kind from an
|
|
// element field, and drops the count word. So the table's tail names 27 things
|
|
// that are on the wire as 27 counts (plus elements), and comparing an element kind
|
|
// against a count is comparing two different items.
|
|
//
|
|
// What IS checkable, and is checked here:
|
|
// * the 17-item prologue, item for item and primitive for primitive. Our shape
|
|
// models the six conditional groups the writer really has; SchemaProbe takes
|
|
// every branch, which is the same view the recovery has, so the two must agree
|
|
// exactly. They do -- 17/17 -- which is the evidence that the branch structure
|
|
// read out of the instruction stream is the one the recovery flattened.
|
|
// * the tail COUNT: the shape has exactly 27 top-level counted lists and the
|
|
// table has exactly 27 tail items. 27 == 27 is the whole content of that half
|
|
// of the table, and on a no-orders save it is also why the block is 8 + 27 = 35
|
|
// items: every list is empty and still writes its zero count.
|
|
static void check_turn_commands() {
|
|
using S = SchemaProbe::S;
|
|
const char* cls = "Game::TurnCommands";
|
|
const sots::wire::Class* c = sots::wire::find(cls);
|
|
if (!c) {
|
|
std::printf(" %-18s -> %-34s NOT IN TABLE\n", "TurnCommands", cls);
|
|
++fails;
|
|
return;
|
|
}
|
|
SchemaProbe p = SchemaProbe::of<sh::TurnCommands>();
|
|
|
|
size_t prologue = 0;
|
|
while (prologue < p.items.size() && !(p.items[prologue].member && p.items[prologue].shape == S::NArr)) ++prologue;
|
|
int lists = 0, opaque = 0;
|
|
for (const SchemaProbe::Item& i : p.items) {
|
|
if (i.member && i.shape == S::NArr) ++lists;
|
|
opaque += i.opaque;
|
|
}
|
|
|
|
int matched = 0, mismatch = 0;
|
|
const size_t n = std::min(prologue, size_t(c->count));
|
|
for (size_t i = 0; i < n; ++i) {
|
|
if (p.items[i].tag == c->fields[i].tag && prim_ok(p.items[i].prim, c->fields[i].prim)) {
|
|
++matched;
|
|
} else {
|
|
++mismatch;
|
|
std::printf(" MISMATCH prologue[%zu] shape %-6s vs wire %-6s\n", i,
|
|
probe_prim_name(p.items[i].prim), sots::wire::prim_name(c->fields[i].prim));
|
|
}
|
|
}
|
|
const int tail = int(c->count) - int(prologue);
|
|
++bound;
|
|
total_matched += matched;
|
|
total_mismatch += mismatch;
|
|
total_wire_only += tail; // the flattened container tail: reported, never claimed as matched
|
|
total_opaque += opaque;
|
|
std::printf(" %-18s -> %-34s [%-8s %2u/%-2u] shape %2zu wire %2u match %2d"
|
|
" wire-only %2d shape-only %2d opaque %d\n",
|
|
"TurnCommands", cls, c->grade, c->read_agree, c->read_comparable, p.items.size(), c->count,
|
|
matched, tail, 0, opaque);
|
|
std::printf(" prologue %d/%zu items agree; tail: %d wire item(s) vs %d container writer(s)"
|
|
" (one per call site, counts dropped by the linear recovery)\n",
|
|
matched, prologue, tail, lists);
|
|
CHECK(mismatch == 0);
|
|
CHECK(prologue == 17); // six flag-gated groups, all branches
|
|
CHECK(lists == sh::TurnCommands::kListCount); // 27 std::list members
|
|
CHECK(tail == lists); // and the table has one item per list
|
|
}
|
|
|
|
int main() {
|
|
std::printf("wire schema: %zu classes, generated from the game's serializers\n\n",
|
|
sots::wire::kClassCount);
|
|
|
|
// --- the table's own integrity -------------------------------------------------
|
|
for (const sots::wire::Class& c : sots::wire::kClasses) {
|
|
if (c.count == 0) continue;
|
|
for (uint16_t k = 0; k < c.count; ++k) unresolved_classes += c.fields[k].unresolved;
|
|
}
|
|
|
|
std::printf("shape -> class (recovery tier, Read/Write cross-check)\n");
|
|
// Bindings taken from the campaign's own shape->Write table
|
|
// (sots-re tools/serializers_golden.py DISK_ORDER, resolved through RTTI),
|
|
// extended with unambiguous name matches.
|
|
check<sh::Summary>("Summary", "Game::StrategyGameInfo");
|
|
check<sh::Slot>("Slot", "Game::SlotDef");
|
|
check<sh::PlayerSettings>("PlayerSettings", "Game::StrategyPlayerGameSettings");
|
|
check<sh::PlayerColor>("PlayerColor", "Game::PlayerColorID");
|
|
check<sh::Session>("Session", "Game::StrategySessionParams");
|
|
check<sh::CreateParams>("CreateParams", "Game::StrategyGameCreateParams");
|
|
check<sh::Scrp>("Scrp", "Game::StrategyScriptParams");
|
|
check<sh::MapP>("MapP", "Game::StarMapParams");
|
|
check<sh::Planet>("Planet", "Game::SystemParams");
|
|
check<sh::Sim>("Sim", "Game::StrategyServer");
|
|
check<sh::Sys>("Sys", "Game::ServerSystem");
|
|
check<sh::Player>("Player", "Game::ServerPlayer");
|
|
check<sh::Fleet>("Fleet", "Game::StarFleet");
|
|
check<sh::Ship>("Ship", "Game::StarShip");
|
|
check<sh::FlightPlan>("FlightPlan", "Game::FlightPlan");
|
|
check<sh::Waypoint>("Waypoint", "Game::FlightPlan::Waypoint");
|
|
check<sh::PrisonerHold>("PrisonerHold", "Game::PrisonerHold");
|
|
check<sh::Otch>("Otch", "Game::ObservedTech");
|
|
check<sh::Owep>("Owep", "Game::ObservedWeapon");
|
|
check<sh::Odes>("Odes", "Game::ObservedDesign");
|
|
check<sh::Rts>("Rts", "Game::StarSystem::OutputRates");
|
|
check<sh::PlayerView>("PlayerView", "Game::StarSystem::PlayerView");
|
|
check<sh::IndependenceInfo>("IndependenceInfo", "Game::IndependenceInfo");
|
|
check<sh::BuildOrder>("BuildOrder", "Game::ShipBuildOrder");
|
|
check<sh::BuildQueue>("BuildQueue", "Game::BuildQueue");
|
|
check<sh::Prep>("Prep", "Game::PlayerReport");
|
|
check<sh::DipStat>("DipStat", "Game::DiplomacyStats");
|
|
check<sh::Alliances>("Alliances", "Game::PlayerAlliances");
|
|
check<sh::NodeRoute>("NodeRoute", "Game::NodeRoute");
|
|
check<sh::SystemEvent>("SystemEvent", "Game::SystemEvent");
|
|
check<sh::PlayerTurnStats>("PlayerTurnStats", "Game::PlayerTurnStats");
|
|
check<sh::PlayerTurnHistory>("PlayerTurnHistory", "Game::PlayerTurnHistory");
|
|
check<sh::TurnStats>("TurnStats", "Game::GameTurnHistory");
|
|
check<sh::PopG>("PopG", "Game::PopulationGroup");
|
|
check<sh::Population>("Population", "Game::Population");
|
|
check<sh::Morale>("Morale", "Game::Morale");
|
|
check<sh::MoraleEvent>("MoraleEvent", "Game::MoraleEvent");
|
|
// Game::SimpleNodePath is a different, 2-item type — it is what MapP.nodePaths
|
|
// holds (VectorHelper<SimpleNodePath>), and it is empty in every real save.
|
|
check<sh::NodePath>("NodePath", "Game::NodePath");
|
|
|
|
// Bodies typed this round from the recovered schema (previously opaque Nodes).
|
|
check<sh::EventStorage>("EventStorage", "Game::EventStorage");
|
|
check<sh::TurnEvents>("TurnEvents", "Game::EventStorage::TurnEvents");
|
|
check<sh::EventRec>("EventRec", "Game::EventStorage::Event");
|
|
check<sh::FleetNameGen>("FleetNameGen", "Game::FleetNameGenerator");
|
|
check<sh::SpeciesRatios>("SpeciesRatios", "Game::SpeciesRatios");
|
|
check<sh::CivilianRatios>("CivilianRatios", "Game::CivilianRatios");
|
|
check<sh::TechTree>("TechTree", "Game::TechTree");
|
|
check<sh::ShipRecords>("ShipRecords", "Game::ShipRecords");
|
|
check<sh::AIEncounterFlags>("AIEncounterFlags", "Game::AIEncounterFlags");
|
|
check<sh::PlayerAid>("PlayerAid", "Game::PlayerAid");
|
|
check<sh::CommMessages>("CommMessages", "Game::CommMessageContainer");
|
|
check<sh::SpyReport>("SpyReport", "Game::SpyReport");
|
|
check<sh::SpyManager>("SpyManager", "Game::ServerSpyManager");
|
|
check<sh::ProjectNames>("ProjectNames", "Game::SpecialProjectNameGen");
|
|
check<sh::TradeManager>("TradeManager", "Game::ServerTradeManagerImpl");
|
|
check<sh::TradeSector>("TradeSector", "Game::ServerTradeSector");
|
|
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
|
|
// does check is that each body we bind agrees with that class's serializer.
|
|
check<sh::ScriptObjects>("ScriptObjects", "Game::SVSOSots");
|
|
check<sh::SVSOVonNeumann>("SVSOVonNeumann", "Game::SVSOVonNeumann");
|
|
check<sh::VonNeumannDefeat>("VonNeumannDefeat", "Game::SVSOVonNeumann::DefeatRecord");
|
|
check<sh::SVSOSwarm>("SVSOSwarm", "Game::SVSOSwarm");
|
|
check<sh::SwarmInfestation>("SwarmInfestation", "Game::SVSOSwarm::Infestation");
|
|
check<sh::SVSODerelict>("SVSODerelict", "Game::SVSODerelict");
|
|
check<sh::SVSOMonitor>("SVSOMonitor", "Game::SVSOMonitor");
|
|
check<sh::SVSOSlaversRefuel>("SVSOSlaversRefuel", "Game::SVSOSlaversRefuel");
|
|
check<sh::SVSOSwarmQueen>("SVSOSwarmQueen", "Game::SVSOSwarmQueen");
|
|
check<sh::SVSOSwarmQueenHive>("SVSOSwarmQueenHive", "Game::SVSOSwarmQueen::HiveInfo");
|
|
check<sh::SVSOSwarmQueenQueen>("SVSOSwarmQueenQueen", "Game::SVSOSwarmQueen::QueenInfo");
|
|
check<sh::SVSOCrowRuins>("SVSOCrowRuins", "Game::SVSOCrowRuins");
|
|
check<sh::SVSORefugees>("SVSORefugees", "Game::SVSORefugees");
|
|
check<sh::RefugeeStatus>("RefugeeStatus", "Game::SVSORefugees::PlayerStatus");
|
|
check<sh::SVSOTraps>("SVSOTraps", "Game::SVSOTraps");
|
|
check<sh::SVSOTrap>("SVSOTrap", "Game::SVSOTraps::Trap");
|
|
check<sh::SVSOCrowDefenders>("SVSOCrowDefenders", "Game::SVSOCrowDefenders");
|
|
check<sh::SVSOGrandMenaceTrigger>("SVSOGrandMenaceTrigger", "Game::SVSOGrandMenaceTrigger");
|
|
|
|
// --- the AIAgent custom-data block ------------------------------------------
|
|
// The block SELECTION (a CDT id ending ".AIAgent") is not a wire item, so it is
|
|
// not checkable here; what is checked is that each body agrees with its class's
|
|
// serializer. Game::AIPlayerRequestStamp has no entry: it is a POD reached only
|
|
// through a specialised helper, so it has no RTTI class for the recovery to find.
|
|
check<sh::StrategyAIAgent>("StrategyAIAgent", "Game::StrategyAIAgent::Streamable");
|
|
check<sh::AttribMap>("AttribMap", "Game::AttribMap");
|
|
check<sh::AISituation>("AISituation", "Game::AISituation");
|
|
check<sh::AISystem>("AISystem", "Game::AISystem");
|
|
check<sh::DesignNameGen>("DesignNameGen", "Game::StrategyAIAgent::DesignNameGen");
|
|
check<sh::AICombatReport>("AICombatReport", "Game::AICombatReport");
|
|
check<sh::CombatPlayerStats>("CombatPlayerStats", "Game::CombatPlayerStats");
|
|
check<sh::AIAutoPeaceRun>("AIAutoPeaceRun", "Game::AIAutoPeaceRun");
|
|
// One shape covers all four Game::AIWeightMap<K> instantiations: they differ
|
|
// only in the key body, which is its own frame on the wire.
|
|
check<sh::AIWeightMapSection>("AIWeightMap<Sec>", "Game::Game::VShipSectionID::?$AIWeightMap");
|
|
check<sh::AIWeightMapEnum>("AIWeightMap<Pur>",
|
|
"Game::Mars::Game::W4AIPurposeID::U?$StreamableEnum::?$AIWeightMap");
|
|
check<sh::AIWeightMapEnum>("AIWeightMap<Wep>",
|
|
"Game::Mars::Game::W4WeaponFamilyID::U?$StreamableEnum::?$AIWeightMap");
|
|
check<sh::AIWeightMapEnum>("AIWeightMap<uint>", "Game::Mars::I::U?$StreamableEnum::?$AIWeightMap");
|
|
|
|
// --- the TurnCommands custom-data block ---------------------------------------
|
|
// Bound by a dedicated check; see the note above check_turn_commands(). The
|
|
// element bodies it reaches are already bound above (OutputRates as Rts,
|
|
// Population, CivilianRatios, PlayerNotes as Note).
|
|
check_turn_commands();
|
|
|
|
std::printf(
|
|
"\ntotals: %d shapes bound, %d items matched, %d MISMATCH, %d wire-only, "
|
|
"%d opaque item(s) in bound shapes\n",
|
|
bound, total_matched, total_mismatch, total_wire_only, total_opaque);
|
|
std::printf("table: %d item(s) the recovery itself could not type\n", unresolved_classes);
|
|
std::printf("test_wire_schema: %s (%d failures)\n", fails ? "FAILED" : "ok", fails);
|
|
return fails ? 1 : 0;
|
|
}
|