Three bodies, one reason: the corpus grew from 22 saves to 43 and started carrying content the shapes did not name. ProjectName read `usnc` as a count of ONE item, on the strength of a comment saying "usnc is 0 in every save available". Game::SpecialProjectNameGen::Write (0x008147e0) says otherwise: each 88-byte record opens with a 32-byte table of per-suffix use counts, `usnc` is the number of NON-ZERO slots, and the loop then writes, for every non-zero slot, its INDEX as `usp` and its count byte -- movzx widened -- as `usc`. Both go through WriteInt, so both are i32 on the wire. Twelve corpus saves have one such slot and were round-tripping exactly 12 bytes short: one i32 item, 4 length + 3 tag + 4 value + 1 pad. `Sprj` is a polymorphic frame and both halves of its mapping are now measured. ServerPlayer::Write emits `SprjT` from the plain member at project+0x3c, and ServerPlayer::Read feeds that value to the factory at 0x008610a0, whose table at 0x008611e8 is 0 BackEngProject / 1 MonitorProject / 2 JewelsProject / 3 TechOfferProject -- and each of those constructors stores its own index back at +0x3c. Only SprjT 0 is exercised by any save; the other three arms are typed from the recovered schema and labelled as the hypotheses they are, and an unknown SprjT falls to rest() so it shows up as opaque coverage rather than being mis-read in silence. FieldTemplate carried its points because no save had ever put one on the wire. Six now do, so Game::FieldTemplate::Point is typed and bound. FTPPosX/PosY/Sqd read 0 in every observed point, so their i32 disk type is still the schema's word and not the corpus's, and the comment says so. All 43 saves now round-trip byte-identically; the wire-schema conformance test binds six new shapes with 0 MISMATCH and every one matching item for item.
401 lines
21 KiB
C++
401 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");
|
|
// The `Sprj` variant: the SELECTION (SprjT -> class) is not a wire item and so
|
|
// is not checkable here; it came from the game's own factory at 0x8610a0 (see
|
|
// the note on sh::SpecialProjectBody). What is checked is that each arm agrees
|
|
// with its class's serializer. Only SprjT 0 is exercised by any save.
|
|
check<sh::SpecialProject>("SpecialProject", "Game::SpecialProject");
|
|
check<sh::BackEngProject>("BackEngProject", "Game::BackEngProject");
|
|
check<sh::MonitorProject>("MonitorProject", "Game::MonitorProject");
|
|
check<sh::JewelsProject>("JewelsProject", "Game::JewelsProject");
|
|
check<sh::TechOfferProject>("TechOfferProject", "Game::TechOfferProject");
|
|
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 bound now that the corpus exercises it: six
|
|
// saves carry stored tactical formations, so the element framing is measured
|
|
// rather than assumed (see the note on sh::FieldTemplate).
|
|
check<sh::FieldTemplate>("FieldTemplate", "Game::FieldTemplate");
|
|
check<sh::FieldPoint>("FieldPoint", "Game::FieldTemplate::Point");
|
|
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;
|
|
}
|