Reads the whole colony output chain off the instruction stream (every range disassembled to the next function start) and compares two of its functions against the running game. The population -> output law is linear and is carried by the executable: output points per head are typeOutputModifier x 1.8 / 500000, and the three-row population-type table is built in code rather than loaded, so the imperial (1.0) and civilian (0.33f) modifiers are facts about the binary. A system's total output is a SUM of three terms, not one multiplicative chain. The station bonus scales only the imperial term and morale only the civilian one, so OutputModifiers no longer carries either; they belong to GroupOutputInputs. The function previously described as the base-output term is the over-harvest RESOURCE demand, and it is corrected in place. Live on VM140, both hooks in compare mode over two species and two workloads: GroupOutput 13,105 calls / 0 divergences; ComputeTotalOutput 11,252 calls / 1 divergence of one ulp, in a value its caller rounds to an integer. Both functions declare a whole-object Guard: 0 undeclared writes in 24,357 calls, which is what makes the side-effect-free claim a measurement. sim::Narrow forces the double rounding a 32-bit x87 build otherwise skips; without it every civilian row came out one ulp low. Also fixes ComputeBankruptcyLimits' elimination divisor, which was the decimal -0.15 rather than the image's widened float -0.15000000596046448. The two disagree for every maximum income divisible by 3 and for essentially every empire above ~3,000,000. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
104 lines
5.4 KiB
Markdown
104 lines
5.4 KiB
Markdown
# N — the population → base-output term
|
|
|
|
The formula that a colony's whole economy hangs off, and the one `src/app` names as its
|
|
largest unmodelled input. It is now read from the instruction stream and compared against the
|
|
running game.
|
|
|
|
## What it is
|
|
|
|
Output points are **linear in population**:
|
|
|
|
```
|
|
outputPerHead = populationTypeOutputModifier x 1.8 / 500000
|
|
```
|
|
|
|
`1.8` and `500000` are literals in the executable, and so are the imperial (`1.0`) and civilian
|
|
(`0.33f`) type modifiers — the three-row population-type table is *built in code*, not loaded
|
|
from the data files. Only the slave row's modifier comes from the data. An imperial population
|
|
with no station therefore contributes exactly `3.6e-6` output points per head.
|
|
|
|
A system's total output is a **sum of three independent terms**, not one multiplicative chain:
|
|
|
|
```
|
|
base = overHarvestDemand x speciesResourceOutputFactor
|
|
+ (transitResources + availableResources) x stripMineFraction x 0.9
|
|
+ imperialOutput + civilianOutput + slaveOutput
|
|
|
|
total = addictionModifier x base
|
|
x playerOutMod x systemOutMod x setupOutputMult x rebOutMod x scOutMod
|
|
```
|
|
|
|
The station bonus multiplies **only** the imperial term; the morale modifier multiplies **only**
|
|
the civilian term. The previous model in this module applied both to the whole thing, which is
|
|
why `OutputModifiers` no longer carries `morale` or `stations` — they belong to
|
|
`GroupOutputInputs`, one population row at a time.
|
|
|
|
## API
|
|
|
|
`src/game/sim/colony.h`:
|
|
|
|
| function | what |
|
|
|---|---|
|
|
| `PopTypeOf(group, tuning)` | the population-type row: output/income modifiers and the row's population cap (imperial 50,000,000, civilian 20,000,000) |
|
|
| `GroupOutput(GroupOutputInputs, tuning)` | one (group, species) row's contribution — the per-capita law |
|
|
| `MoraleOutputMultiplier(morale, tuning)` | the civilian morale factor, with both of the original's guards |
|
|
| `StripMineFraction(StripMineInputs)` | resource extraction efficiency |
|
|
| `OverHarvestDemand(OverHarvestInputs)` | the strip-mining resource demand, which is also a summand of output |
|
|
| `SystemBaseOutput(BaseOutputInputs, tuning)` | the three terms summed in the original's association |
|
|
| `TotalSystemOutputRaw` / `TotalSystemOutput` | the multiplier tail, unrounded and rounded half-to-even |
|
|
|
|
## Verified against the running game
|
|
|
|
Two hooks in `src/shim/hooks/system_output.{h,cpp}`, both **compare** mode, config
|
|
`src/shim/shim.cfg.output`:
|
|
|
|
* `Game::ServerSystem::GroupOutput` — the per-capita law itself. **13,105 calls, 0 divergences**
|
|
across two species and two workloads.
|
|
* `Game::ServerSystem::ComputeTotalOutput` — the whole sum and multiplier tail. **11,252 calls,
|
|
1 divergence**, and that one is a single ulp on one system, in a value the caller rounds to an
|
|
integer before using — so it cannot move any number the game stores. It is recorded, not fixed.
|
|
|
|
Both functions were checked for stores to the game state before being chosen as compare
|
|
targets, and each declares a Guard region over the whole system object: **0 undeclared writes in
|
|
24,357 calls** turns that check from a claim into a measurement. The neighbouring
|
|
`ComputeOutputFromRates` is deliberately *not* hooked — it repairs damaged ships in orbit.
|
|
|
|
### Coverage — read this before quoting the zero
|
|
|
|
24,357 calls is **13 distinct system states**. Unexercised, and therefore hypotheses:
|
|
|
|
* the over-harvest branch (`SRoh` is 0 on every call in the corpus), including its `max(v, 1)`
|
|
floor;
|
|
* the station factor (no system in the corpus has a station);
|
|
* the slave term (no system in the corpus holds slaves);
|
|
* both morale branches (every colony sits at 75, strictly between the two thresholds);
|
|
* the addiction multiplier and the civilian capacity surplus.
|
|
|
|
## `sim::Narrow`
|
|
|
|
A 32-bit x87 build may leave an intermediate in a register at the register's own precision; the
|
|
original's x87 runs with its precision-control field at 53 bits and rounds every multiply to
|
|
double. The first live run made that visible as a one-ulp low result on **every** civilian row —
|
|
4,957 divergences that were all the same defect, and none of them the model's. `sim::Narrow`
|
|
forces the round the original performs anyway, and is a no-op on any SSE2 target.
|
|
|
|
## Correction shipped alongside
|
|
|
|
`ComputeBankruptcyLimits` used the decimal `-0.15` as the elimination-limit divisor. The image
|
|
holds `-0.15000000596046448`, a widened float literal. The two disagree for **every** maximum
|
|
income divisible by 3 — from `maxIncome = 3` upward — and for essentially every empire above
|
|
about 3,000,000, which is six of the twenty-five bankruptcy records recoverable from the save
|
|
corpus. Fixed, with `kBankruptcyInterestDivisor` named in the header and a test at the value
|
|
where it first bites.
|
|
|
|
## What this does and does not unblock
|
|
|
|
It does **not** unblock `P01`. `ComputeBudget`'s missing input is a system's *money*, which runs
|
|
this verified total through a second chain with its own population law (`incomeModifier / 14000`,
|
|
no `1.8`) and its own multipliers. Checked against the bankruptcy-limit oracle over the whole
|
|
save corpus, that composition reproduces **6 of 25 player-records exactly** — every record whose
|
|
owner is the human player or an independent colony — and the single-system misses are short by a
|
|
clean factor of 1.1, the AI trade/income difficulty multiplier, which is not on the wire.
|
|
|
|
So the blocker has moved from the output term to the income tail. `src/app` is unchanged:
|
|
5 leaves closed, 0 regressed, on both save pairs.
|