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
82 lines
5.1 KiB
Markdown
82 lines
5.1 KiB
Markdown
# Civilian population growth, and a savings-interest correction (lane G3)
|
|
|
|
The colony sub-pass that runs between the build queue and the resource ledger inside each
|
|
system's turn, plus the per-ship repair-cost term that was the last unmodelled input of the
|
|
output turn path, plus a one-money defect in `ComputeBudget` that landing the first of those
|
|
exposed.
|
|
|
|
## What decides the value
|
|
|
|
The whole system's civilian delta is clamped to **20,000,000** per turn. That number is an
|
|
int64 column of the three-row population-type table, which the executable builds from its own
|
|
literals; it is not a data-file value and it is not a carrying capacity. On both reference
|
|
pairs the uncapped delta is 150,000,005 and the capacity headroom is 500,000,000, so the clamp
|
|
is what produces the answer and neither the growth curve nor the capacity chain can change it
|
|
without being wrong by more than an order of magnitude.
|
|
|
|
That is why the pass commits with **no tuning table loaded**: the growth fraction it computes
|
|
under an unloaded table (0.25) and under the shipped one (0.30000001192092896) both land on the
|
|
same committed value, and so would anything in `[0.04, 1.0]`.
|
|
|
|
Ranked by consequence, the float that actually decides the value is neither of those. It is the
|
|
**rescale**, `trunc(applied x (clamped / total))`. The relative error of the quotient reaches
|
|
`2^-53` and the product's absolute error can exceed half an ulp of 20,000,000, so a
|
|
single-species colony landing exactly on the cap is a measurement, not a theorem. It does, at
|
|
both 53-bit and x87 64-bit precision, on both pairs; `tests/game_sim/test_colony.cpp` pins it.
|
|
One ulp the other way costs a whole person and moves the human's savings.
|
|
|
|
## The capacity, and how it is handled without the data files
|
|
|
|
`MaxPopGeneric` multiplies `Size x 1e8` by a per-species, per-group factor that lives in the
|
|
data files and is nowhere on the wire. Rather than assume it, the phase runs the pass **twice**:
|
|
once with the modelled capacity and once with the capacity discarded in favour of the system's
|
|
own `dcs` limit, which *is* on the wire, and commits only when the two agree. It also reports
|
|
the threshold the factor would have to fall below before a committed value moved (0.260 on the
|
|
reference pair), against a lower bound of 0.27 that the observed growth itself establishes.
|
|
|
|
For the imperial group the corpus pins the factor exactly, with no data files at all: Gamma
|
|
Cephei's pending population bonus of 1e9 never drains, and the bonus apply returns exactly when
|
|
`Pop >= MaxPop`, so `MaxPop <= 1e9`; and its `Pop` never shrinks, and the imperial apply shrinks
|
|
exactly when `pop > cap`, so `MaxPop >= 1e9`. Two behaviours, one capacity, no assumption.
|
|
|
|
**Imperial growth is deliberately not committed.** It is a no-op on this corpus and committing
|
|
it would need a capacity the corpus can bound from below but not from above; that trades a
|
|
regression risk for nothing.
|
|
|
|
## The interest literals
|
|
|
|
Landing growth left the human's savings **one money high** on the first reference pair and exact
|
|
on the second. The residual was not growth. `ComputeBudget` multiplies a treasury by widened
|
|
**float** literals — `(double)0.01f` and `(double)0.15f` — and then truncates, so a treasury of
|
|
exactly 50,000 earns 499, not 500. This module used the exact decimals. Corrected, with the
|
|
constants named in `game/sim/economy.h`; sixteen hand-computed test expectations moved by one.
|
|
|
|
Worth recording why it survived: `ComputeBudget` has been compared live against the original for
|
|
**4,437 calls with 0 divergences**. That run presented only 20 distinct states and none of them
|
|
sat on a rounding boundary. A green behavioural compare is not coverage.
|
|
|
|
## Ship repair cost
|
|
|
|
`ShipRepairCost` is `max(0, buildTarget - (buildProgress + (allowance ? designAllowance : 0)))`
|
|
— plain 32-bit integers. The ship's `ConCap` word is the *progress*, not a capacity: the repair
|
|
apply adds points to it and clamps it to the design's build target.
|
|
|
|
The demand is still taken as 0, because the two design fields are cached stats the save does not
|
|
carry. What changed is that the zero is now **evidenced**: the phase reports the candidate set,
|
|
and the independent colony keeps a ten-ship fleet in orbit over a colony whose savings close
|
|
exactly with the demand at zero — which cannot happen if any of those hulls carried a cost.
|
|
|
|
## Result
|
|
|
|
Reference pair `turn1 -> turn2`: **81 leaves closed, 0 regressed** (was 78/0). Pair
|
|
`turn2 -> turn3`: **39 closed, 0 regressed** (was 36/0). The three new leaves per pair are the
|
|
two colonies' civilian population and the human's savings. With `--commit-blocked=T31
|
|
--ai-player 1` the counts are **83/0** and **41/0**; the extra two are the human's and the AI's
|
|
bankruptcy elimination limits, which that phase could not close before because they move with
|
|
the population.
|
|
|
|
Every gate ran as a separate command: clean-room OK, host `ctest` 49/49, and the CT111 shim
|
|
cross-build (exit 0) — required here because `game/sim` is compiled into the shim.
|
|
|
|
The reverse-engineering evidence, the falsification table and the honest list of what no corpus
|
|
save exercises are in the notes repo, `findings/subsystems/population-growth.md`.
|