Reference pair turn1->turn2: 81 leaves closed, 0 regressed (was 78/0). Pair turn2->turn3: 39 closed, 0 regressed (was 36/0). With --commit-blocked=T31 --ai-player 1: 83/0 and 41/0. game/sim/colony: GrowCivilianPopulations models ServerSystem's civilian growth sub-pass. The whole system's delta is clamped to 20,000,000 -- an int64 column of the population-type table, built in the executable from its own literals -- and on both reference pairs that clamp, not the growth curve and not any carrying capacity, is what decides the value: the uncapped delta is 7.5x it and the capacity headroom 25x it. So the pass commits with no tuning table loaded, and says by how much each unmodelled input would have to be wrong before it mattered. The one input genuinely off the wire is the per-species civilian capacity factor. It is handled by running the pass twice, once with the modelled capacity and once with the system's own wire-known dcs limit, and committing only when the two agree. Imperial growth is deliberately NOT committed: it is a no-op on this corpus and would need a capacity the corpus can bound from below but not from above. game/sim/economy: both interest rates in ComputeBudget are WIDENED FLOAT literals, (double)0.01f and (double)0.15f, and are then truncated -- so a treasury of exactly 50,000 earns 499, not 500. This module used the exact decimals, which left the human's savings one money high on the first reference pair and exact on the second. Sixteen hand-computed test expectations moved by one; they were derived from the model, not measured. The live ComputeBudget compare (4,437 calls, 0 divergences) did not catch this because it presented only 20 distinct states and none sat on a rounding boundary. game/sim/colony: ShipRepairCost, the last unmodelled input of the output turn path. The demand is still 0 -- its two design fields are cached stats the save does not carry -- but the zero is now evidenced rather than silent: S13 reports the candidate set, and the independent colony keeps a ten-ship fleet over a colony whose savings close exactly at zero demand. Gates run as separate commands: clean-room OK, host ctest 49/49, and the CT111 shim cross-build exit 0 (required: game/sim is compiled into the shim). The host build and report were also re-run on CT111 and produced identical numbers. docs/G3-civilian-growth.md; notes repo findings/subsystems/population-growth.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
61 lines
3.1 KiB
C++
61 lines
3.1 KiB
C++
// Species enumeration used by every per-species table in the strategic sim, plus the
|
|
// handful of per-species constants the executable carries itself (not the data files).
|
|
//
|
|
// The index order is the one the save format and the per-species arrays use; note that
|
|
// index 4 is the independent/NPC race, which the growth and capacity formulas treat
|
|
// specially (no imperial growth, capacity scaled by INDSYS_IMPERIAL_POPULATION_MOD).
|
|
// CONFIDENCE: high.
|
|
#pragma once
|
|
|
|
namespace sots::sim {
|
|
|
|
enum class Species : int {
|
|
Human = 0,
|
|
Hiver = 1,
|
|
Tarkas = 2,
|
|
Liir = 3,
|
|
NPC = 4,
|
|
Zuul = 5,
|
|
Morrigi = 6,
|
|
};
|
|
|
|
constexpr int kSpeciesCount = 7;
|
|
|
|
constexpr bool IsNpcSpecies(Species s) { return s == Species::NPC; }
|
|
|
|
// Per-species constants that live in the engine's own species table rather than in the
|
|
// data files. Only the fields whose readers are known are exposed here.
|
|
struct SpeciesConstants {
|
|
double incomeFactor = 1.0; // multiplies a system's money income (Zuul 1.1, Morrigi 0.8)
|
|
double hazardCostFactor = 1.0; // multiplies the suitability money cost (Zuul 0.7)
|
|
bool systemBonusEligible = true; // long-stability system bonus accrues (Zuul never)
|
|
// The species' base resource demand and resource-output factor -- the two fields the
|
|
// output term's harvest summand reads, off the SYSTEM OWNER's species rather than the
|
|
// colony's population species. Unlike the three above, these come from the DATA FILES;
|
|
// the values here are the ones measured live on VM140 for the three species the corpus
|
|
// exercises (Human, Tarkas, Zuul) and are 0/10 for the rest, which is UNVERIFIED for
|
|
// Hiver, Liir and Morrigi. Getting the Zuul pair wrong costs a Zuul colony exactly
|
|
// 400 output points -- 2000 money -- which is how it was found.
|
|
int resourceDemand = 0; // SpeciesDef +0x4c
|
|
double resourceOutput = 10.0; // SpeciesDef +0x50
|
|
// The per-species multiplier on every carrying capacity (the helper at 0x0053bb00 off the
|
|
// same SpeciesDef). Also a DATA FILE value. 1.0 for Human is MEASURED, not assumed, and
|
|
// not from the data files either: Gamma Cephei's imperial `pbon` of 1e9 never drains --
|
|
// and the bonus apply returns exactly when `Pop >= MaxPop` -- while its `Pop` of 1e9 never
|
|
// shrinks, and the imperial apply shrinks exactly when `pop > cap`. The two behaviours
|
|
// bracket the capacity at exactly `Size x 1e8 x 1.0`, which forces this factor to 1.0 for
|
|
// a Human colony at its ideal suitability. UNVERIFIED for every other species.
|
|
double growthFactor = 1.0; // SpeciesDef, read by MaxPopGeneric
|
|
};
|
|
|
|
// CONFIDENCE: high on the first three fields (read with their constants and their
|
|
// consumers); the two resource fields are data-file values, measured not read.
|
|
constexpr SpeciesConstants ConstantsOf(Species s) {
|
|
switch (s) {
|
|
case Species::Zuul: return SpeciesConstants{1.1, 0.7, false, 10, 40.0};
|
|
case Species::Morrigi: return SpeciesConstants{0.8, 1.0, true, 0, 10.0};
|
|
default: return SpeciesConstants{1.0, 1.0, true, 0, 10.0};
|
|
}
|
|
}
|
|
|
|
} // namespace sots::sim
|