263 lines
9.8 KiB
C++
263 lines
9.8 KiB
C++
// B4 adapter tests: a ServerSystem snapshot -> sim::ColonyTurnState/Inputs -> one colony turn
|
|
// -> back into the snapshot, which is what the hook lays into its scratch regions.
|
|
//
|
|
// Hand-written fixtures only. The "reference" one is the turn-2 shape of the human player's
|
|
// home system in the reference save (a stable, owned, home colony with no plague, no
|
|
// rebellion and no slaves), so the mapping is exercised on the numbers the live compare will
|
|
// also see.
|
|
#include "shim/hooks/colony_inputs.h"
|
|
|
|
#include "check.h"
|
|
|
|
using namespace shim::hooks;
|
|
using sots::sim::AddictionPhase;
|
|
using sots::sim::AddictionPhaseOf;
|
|
using sots::sim::ColonyCountdowns;
|
|
using sots::sim::ColonyTurnInputs;
|
|
using sots::sim::ColonyTurnResult;
|
|
using sots::sim::ColonyTurnState;
|
|
using sots::sim::ProcessColonyTurn;
|
|
|
|
namespace {
|
|
|
|
ColonyTuning Tuning() {
|
|
ColonyTuning t;
|
|
t.SYSTEMBONUS_MINTURNS = 10;
|
|
t.SYSTEMBONUS_POPBONUS = 0.1;
|
|
t.SYSTEMBONUS_POPBONUS_INC = 0.01;
|
|
t.SYSTEMBONUS_INFRABONUS = 0.2;
|
|
t.SYSTEMBONUS_INFRABONUS_INC = 0.05;
|
|
return t;
|
|
}
|
|
|
|
// A stable, owned, home colony well past the bonus gates.
|
|
ColonySnapshot Reference() {
|
|
ColonySnapshot s;
|
|
s.systemIndex = 7;
|
|
s.ownerIndex = 0;
|
|
s.ownerSpecies = 6; // Morrigi
|
|
s.owned = true;
|
|
s.homeSystem = true;
|
|
s.stable = true;
|
|
s.playerCount = 8;
|
|
s.currentTurn = 2;
|
|
s.turnAcquired = 0;
|
|
s.infra = 1.0f;
|
|
s.infraBonus = 0.0f;
|
|
s.pop = 2000000000;
|
|
s.popBonus = 0;
|
|
s.turnsDeveloping = 2;
|
|
s.totalResources = 5000;
|
|
s.imperialCapacity = 2000000000;
|
|
s.addictionPhase2Start = 10;
|
|
s.addictionPhase3Start = 15;
|
|
return s;
|
|
}
|
|
|
|
void test_state_round_trip() {
|
|
ColonySnapshot s = Reference();
|
|
s.infra = 0.25f;
|
|
s.infraBonus = 0.125f;
|
|
s.popBonus = 4242;
|
|
s.totalResources = 99;
|
|
s.growthHalted[1] = 1;
|
|
s.battles = 0x00000000000000f3ull;
|
|
s.battlesMask = 0x5u;
|
|
s.recon = 0x0000000000000021ull;
|
|
s.reconMask = 0x3u;
|
|
|
|
const ColonyTurnState st = ToColonyTurnState(s);
|
|
CHECK_NEAR(st.infra, 0.25, 0.0);
|
|
CHECK_NEAR(st.infraBonus, 0.125, 0.0);
|
|
CHECK_EQ(st.popBonus, std::int64_t{4242});
|
|
CHECK_EQ(st.totalResources, 99);
|
|
CHECK(!st.growthHalted[0] && st.growthHalted[1] && !st.growthHalted[2]);
|
|
CHECK_EQ(st.battles.counters, 0x00000000000000f3ull);
|
|
CHECK_EQ(st.battles.active, 0x5u);
|
|
|
|
ColonySnapshot back = Reference();
|
|
ApplyColonyTurnState(st, back);
|
|
CHECK_NEAR(back.infra, 0.25f, 0.0);
|
|
CHECK_EQ(back.popBonus, 4242);
|
|
CHECK_EQ(back.battles, 0x00000000000000f3ull);
|
|
CHECK_EQ(back.reconMask, 0x3u);
|
|
}
|
|
|
|
void test_inputs_mapping() {
|
|
ColonySnapshot s = Reference();
|
|
s.currentTurn = 30;
|
|
s.turnAcquired = 4;
|
|
s.temperance[2] = 1;
|
|
s.civilianPresent[2] = 1;
|
|
s.addictionStart[2] = 11;
|
|
const ColonyTurnInputs in = ToColonyTurnInputs(s);
|
|
CHECK_EQ(in.turnsOwned, 26); // ModCount - TAcq
|
|
CHECK(in.owned && in.homeSystem && in.stable);
|
|
CHECK(in.temperance[2] && in.civilianPresent[2]);
|
|
CHECK_EQ(in.addictionStart[2], 11);
|
|
CHECK_EQ(in.playerCount, 8);
|
|
CHECK_EQ(in.imperialCapacity, std::int64_t{2000000000});
|
|
}
|
|
|
|
// The reference colony's turn: nothing to do but the housekeeping the dispatcher owns.
|
|
void test_reference_turn() {
|
|
const sots::sim::TuningTable t = ToTuningTable(Tuning());
|
|
ColonySnapshot s = Reference();
|
|
s.battles = 0x0000000000000231ull; // players 0, 1, 2 counting down from 1, 3, 2
|
|
s.battlesMask = 0x7u;
|
|
s.recon = 0;
|
|
s.reconMask = 0x2u; // stale bit with no counter: the sweep is skipped
|
|
|
|
ColonyTurnState st = ToColonyTurnState(s);
|
|
const ColonyTurnResult r = ProcessColonyTurn(st, ToColonyTurnInputs(s), t);
|
|
|
|
CHECK_EQ(r.rngDraws, 0); // a colony turn draws only from ProcessRebellion
|
|
CHECK(r.moraleEvents.empty()); // no civilians recorded, so no addiction sweep
|
|
CHECK_EQ(st.totalResources, 0); // TRes is reset every turn
|
|
CHECK(!st.growthHalted[0] && !st.growthHalted[1] && !st.growthHalted[2]);
|
|
CHECK_EQ(st.turnsDeveloping, 3); // stable: ntdev++
|
|
CHECK_NEAR(st.infra, 1.0, 0.0); // already built out, no pending bonus
|
|
CHECK_EQ(st.battles.counters, 0x0000000000000120ull); // 1->0, 3->2, 2->1
|
|
// The mask bit is cleared on the turn a counter is FOUND at zero, not on the turn it
|
|
// reaches zero -- player 0's counter is 0 now, so its bit goes next turn.
|
|
CHECK_EQ(st.battles.active, 0x7u);
|
|
ColonyTurnState st2 = st;
|
|
sots::sim::TickCountdowns(st2.battles, 8);
|
|
CHECK_EQ(st2.battles.active, 0x6u);
|
|
CHECK_EQ(st.recon.counters, 0ull);
|
|
CHECK_EQ(st.recon.active, 0x2u); // the whole rcex sweep is skipped when the word is 0
|
|
|
|
// an unstable colony resets the counter instead
|
|
ColonySnapshot u = Reference();
|
|
u.stable = false;
|
|
u.turnsDeveloping = 9;
|
|
ColonyTurnState us = ToColonyTurnState(u);
|
|
ProcessColonyTurn(us, ToColonyTurnInputs(u), t);
|
|
CHECK_EQ(us.turnsDeveloping, 0);
|
|
}
|
|
|
|
void test_bonus_and_accrual() {
|
|
const sots::sim::TuningTable t = ToTuningTable(Tuning());
|
|
ColonySnapshot s = Reference();
|
|
s.currentTurn = 40;
|
|
s.turnAcquired = 0;
|
|
s.turnsDeveloping = 30;
|
|
s.infra = 0.5f;
|
|
s.infraBonus = 0.1f;
|
|
s.pop = 1000;
|
|
s.popBonus = 500;
|
|
s.imperialCapacity = 1000000;
|
|
|
|
ColonyTurnState st = ToColonyTurnState(s);
|
|
ProcessColonyTurn(st, ToColonyTurnInputs(s), t);
|
|
// the pending pools drain first, then the accrual tops them back up
|
|
CHECK_NEAR(st.infra, 0.6, 1e-6);
|
|
CHECK_NEAR(st.infraBonus, 0.05, 1e-6); // drained to 0, then + INFRABONUS_INC
|
|
CHECK_EQ(st.pop, std::int64_t{1500});
|
|
CHECK_EQ(st.popBonus, std::int64_t{10000}); // drained to 0, then + 1e6 x 0.01
|
|
|
|
// a non-home colony absorbing a bonus has its ntdev reset, which then costs it the
|
|
// system-bonus gate on the very same turn
|
|
ColonySnapshot n = s;
|
|
n.homeSystem = false;
|
|
ColonyTurnState ns = ToColonyTurnState(n);
|
|
ProcessColonyTurn(ns, ToColonyTurnInputs(n), t);
|
|
CHECK_EQ(ns.turnsDeveloping, 1);
|
|
CHECK_EQ(ns.popBonus, std::int64_t{0}); // the accrual gate failed
|
|
CHECK_NEAR(ns.infraBonus, 0.0, 1e-6);
|
|
}
|
|
|
|
void test_addiction_sweep() {
|
|
const sots::sim::TuningTable t = ToTuningTable(Tuning());
|
|
ColonySnapshot s = Reference();
|
|
s.currentTurn = 40;
|
|
s.civilianPresent[0] = 1; // never addicted -> no event
|
|
s.civilianPresent[1] = 1; // onset
|
|
s.addictionStart[1] = 35; // elapsed 5 <= 10
|
|
s.civilianPresent[2] = 1; // established -> no event at all
|
|
s.addictionStart[2] = 28; // elapsed 12, > 10 and <= 15
|
|
s.civilianPresent[3] = 1; // terminal
|
|
s.addictionStart[3] = 20; // elapsed 20 > 15
|
|
s.civilianPresent[4] = 1; // temperance wins regardless of the phase
|
|
s.addictionStart[4] = 20;
|
|
s.temperance[4] = 1;
|
|
s.civilianPresent[5] = 0; // no civilians: never looked at
|
|
s.addictionStart[5] = 20;
|
|
|
|
ColonyTurnState st = ToColonyTurnState(s);
|
|
const ColonyTurnResult r = ProcessColonyTurn(st, ToColonyTurnInputs(s), t);
|
|
CHECK_EQ(r.moraleEvents.size(), std::size_t{3});
|
|
CHECK_EQ(r.moraleEvents[0].species, 1);
|
|
CHECK_EQ(r.moraleEvents[0].eventId, sots::sim::kMoraleEventAddictionOnset);
|
|
CHECK_EQ(r.moraleEvents[0].moraleDelta, 1);
|
|
CHECK_EQ(r.moraleEvents[1].species, 3);
|
|
CHECK_EQ(r.moraleEvents[1].eventId, sots::sim::kMoraleEventAddictionTerminal);
|
|
CHECK_EQ(r.moraleEvents[1].moraleDelta, -2);
|
|
CHECK_EQ(r.moraleEvents[2].species, 4);
|
|
CHECK_EQ(r.moraleEvents[2].eventId, sots::sim::kMoraleEventAddictionSuppressed);
|
|
CHECK_EQ(r.moraleEvents[2].moraleDelta, -1);
|
|
|
|
// an independent colony does not sweep at all
|
|
ColonySnapshot i = s;
|
|
i.independent = true;
|
|
ColonyTurnState is = ToColonyTurnState(i);
|
|
CHECK(ProcessColonyTurn(is, ToColonyTurnInputs(i), t).moraleEvents.empty());
|
|
|
|
// phase boundaries are strict
|
|
CHECK(AddictionPhaseOf(0, 40, 10, 15) == AddictionPhase::None);
|
|
CHECK(AddictionPhaseOf(30, 40, 10, 15) == AddictionPhase::Onset); // elapsed 10
|
|
CHECK(AddictionPhaseOf(29, 40, 10, 15) == AddictionPhase::Established); // elapsed 11
|
|
CHECK(AddictionPhaseOf(25, 40, 10, 15) == AddictionPhase::Established); // elapsed 15
|
|
CHECK(AddictionPhaseOf(24, 40, 10, 15) == AddictionPhase::Terminal); // elapsed 16
|
|
}
|
|
|
|
void test_unowned_colony() {
|
|
const sots::sim::TuningTable t = ToTuningTable(Tuning());
|
|
ColonySnapshot s = Reference();
|
|
s.owned = false;
|
|
s.homeSystem = false;
|
|
s.stable = false;
|
|
s.infra = 0.5f;
|
|
s.infraBonus = 0.25f;
|
|
s.popBonus = 900;
|
|
ColonyTurnState st = ToColonyTurnState(s);
|
|
ProcessColonyTurn(st, ToColonyTurnInputs(s), t);
|
|
// decay first (0.5 - 0.02f), then the pending infrastructure pool drains into it
|
|
CHECK_NEAR(st.infra, 0.48 + 0.25, 1e-6);
|
|
CHECK_NEAR(st.infraBonus, 0.0, 1e-6);
|
|
CHECK_EQ(st.popBonus, std::int64_t{0}); // an unowned system drops the population pool
|
|
CHECK_EQ(st.turnsDeveloping, 0);
|
|
}
|
|
|
|
void test_countdown_edges() {
|
|
ColonyCountdowns c;
|
|
c.counters = 0;
|
|
c.active = 0xffffu;
|
|
sots::sim::TickCountdowns(c, 8);
|
|
CHECK_EQ(c.active, 0xffffu); // an all-zero word skips the sweep entirely
|
|
|
|
c.counters = 0xfULL << (4 * 14); // player 14 is the last one with a nibble
|
|
c.active = 0xffffu;
|
|
sots::sim::TickCountdowns(c, 16);
|
|
CHECK_EQ(sots::sim::CountdownFor(c, 14), 14);
|
|
CHECK_EQ(c.active & (1u << 15), 1u << 15); // player 15 has no nibble and is untouched
|
|
CHECK_EQ(sots::sim::CountdownFor(c, 15), 0);
|
|
|
|
sots::sim::SetCountdown(c, 3, 99);
|
|
CHECK_EQ(sots::sim::CountdownFor(c, 3), 15); // clamped
|
|
sots::sim::SetCountdown(c, 20, 5); // out of range: ignored
|
|
CHECK_EQ(sots::sim::CountdownFor(c, 20), 0);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
test_state_round_trip();
|
|
test_inputs_mapping();
|
|
test_reference_turn();
|
|
test_bonus_and_accrual();
|
|
test_addiction_sweep();
|
|
test_unowned_colony();
|
|
test_countdown_edges();
|
|
return simtest::finish("shim_colony_unit");
|
|
}
|