sots-re/ghidra/addresses.d/lane-n.json
alex 3c48d3a39e lane N: the population -> base-output term, read and live-verified
findings/subsystems/output-term.md is the whole reading: the call chain with
real function boundaries, the formula, the closed list of nine values the data
files supply (down from an unbounded fear), the advance prediction with its
falsification table, and the live result.

Corrects strategic-turn-internals.md 3.3 in place. That block had the SHAPE
wrong, not just the detail: a system's output is a sum of three terms, and the
function it named as the population base-output term is the over-harvest
resource demand.

Live on VM140, two builds, two species, both hooks in compare mode:
GroupOutput 13,105 calls / 0 divergences, ComputeTotalOutput 11,252 / 1 (one
ulp), 0 undeclared writes in 24,357 guarded calls. Thirteen distinct system
states, and every unexercised branch is listed rather than counted as covered.

tools/max_income_predict.py is the other half: it computes lane Y's
bankruptcy-limit oracle from colony state and reports 6 of 25 player-records
matching exactly, with the misses all AI-owned and the single-system ones short
by exactly the 1.1 difficulty income multiplier.
2026-09-08 12:11:52 -04:00

188 lines
No EOL
12 KiB
JSON

{
"entries": [
{
"name": "ServerSystem_ComputeTotalOutput",
"addr": "0x00750480",
"convention": "thiscall",
"prototype": "double (ServerSystem* sys, double overHarvestRate) // `ret 8`, real end 0x007505a5. Returns 0 when the system has no owner (+0x100) or rbfl (+0x1dc) is non-zero. Otherwise the SUM of three terms -- the over-harvest demand x SpeciesDef+0x50, (TRes + available resources) x StripMineFraction x 0.9, and the population output of groups 0, 1 and 2 -- multiplied in one uninterrupted 80-bit chain by player OutMod, sys.OutMod, player +0x224, RebOutMod, ScOutMod, and finally by ADDICTION_OUTPUT_MOD when the addiction phase is >= 3. SIDE-EFFECT FREE: it and all seven callees were checked for stores to the game state (0x0074a6d0's only writes are through its int64 out-parameters), which is why this and not ComputeOutputFromRates is the compare target -- that one repairs ships in orbit",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "ServerSystem_GroupOutput",
"addr": "0x0074b7a0",
"convention": "thiscall",
"prototype": "double (ServerSystem* sys, int groupType, int species, double count) // `ret 0x10`, real end 0x0074b871. THE population -> output term: returns 0 for count <= 0, else max(0, POPTYPE[groupType].outputMod x (stationFactor x 1.8) x moraleMod x (count / 500000)). stationFactor is 1 + stations x STATION_BONUS_IMPERIAL_OUTPUT and applies to groupType 0 of an owned system only (and only while that constant is > 0); moraleMod applies to groupType 1 only. So output points per head are typeOutputMod x 1.8 / 500000 -- exactly 3.6e-6 for an imperial population with no stations",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "ServerSystem_PopOutput",
"addr": "0x0074d8f0",
"convention": "thiscall",
"prototype": "double (ServerSystem* sys, int groupType) // `ret 4`, real end 0x0074da01. Sums GroupOutput over species 0..6 using GroupPopulation(groupType, species). For groupType 1 and the owner's own species on a non-independent system it first adds a capacity surplus: two calls to 0x0074a6d0 with the int64 out-pointer in argument slot 4 and then in slot 6, surplus = max(0, B - A). The income analogue is 0x0074d760",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "ServerSystem_SlaveOutput",
"addr": "0x0074b880",
"convention": "thiscall",
"prototype": "double (ServerSystem* sys) // real end 0x0074b901. Sums over species 0..6: max(0, POPTYPE[2].outputMod x 1.8 x (slaves / 500000)). Does NOT go through GroupOutput, so it carries neither the station factor nor the morale multiplier. Slave counts come from 0x0074b610",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "ServerSystem_StripMineFraction",
"addr": "0x00747d30",
"convention": "thiscall",
"prototype": "float (ServerSystem* sys) // real end 0x00747ddd. R = clamp01(signed_cbrt((pbon + Pop) / 100) x 0.01); if 0.0001 + R >= 1 the infrastructure term SUBSTITUTES it outright (R := float32(ibon + Infra)); returns float32(min(R, float32(ibon + Infra))). The substitution is not a clamp: a colony with a pending infrastructure bonus can return a fraction above 1",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "ServerSystem_OverHarvestDemand",
"addr": "0x007483b0",
"convention": "thiscall",
"prototype": "double (ServerSystem* sys, double overHarvestRate) // `ret 8`, real end 0x007484c4. CORRECTS strategic-turn-internals.md 3.3, which called this the population base-output term: its population is the RESOURCE stock (Res, plus MRes + ARes2 when the owner strip-mines), and the species constant at +0x4c is ADDED, not a cap -- the cap is the resource stock. B = rate > 0 ? max(rate x available x clamp01((pbon + Pop) x 1e-5), 1.0) : 0; return min(available, max(speciesBaseDemand + B, 0)). Called twice per output pass: once inside ComputeTotalOutput and once by ComputeOutputFromRates as the resource ledger's strip-mine demand",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "ServerSystem_GroupPopulation",
"addr": "0x00747ba0",
"convention": "thiscall",
"prototype": "int64 (ServerSystem* sys, int groupType, int species) // groupType 0 returns (pbon + Pop) sign-extended, but ONLY for the system's effective species (indi->+4 when independent, else owner->Species) and 0 for every other; otherwise Population::Count(pbon2) + Population::Count(Pop2) for that (type, species)",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "ServerSystem_MoraleOutputMod",
"addr": "0x00746910",
"convention": "thiscall",
"prototype": "double (ServerSystem* sys, int species) // `ret 4`. Returns 1.0 when the system has no owner, is independent, or its Morale int[7] entry for the species is exactly 0 -- the zero guard matters, because 0 <= MORALE_DECREASE_OUTPUT would otherwise apply the penalty to every species with no record. Otherwise m >= MORALE_INCREASE_OUTPUT -> MORALE_INCREASE_OUTPUT_MOD, m <= MORALE_DECREASE_OUTPUT -> MORALE_DECREASE_OUTPUT_MOD, each used only when strictly positive, else 1.0",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "PopTypeRow",
"addr": "0x00535e00",
"convention": "cdecl",
"prototype": "PopTypeRow* (int groupType) // table base 0x00b104e8, stride 0x30, three rows; groupType > 2 falls back to a lazily-initialised sentinel row at 0x00b10578 that no caller reaches. Row fields used here: +0x10 output modifier, +0x14 income modifier, +8 maximum population",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "InitPopTypeTable",
"addr": "0x00535ca0",
"convention": "cdecl",
"prototype": "void () // builds the three-row population-type table IN CODE from x87 literals, rotating six values with fxch rather than storing them. Imperial {+4 1.0, maxpop 50000000, out 1.0, income 1.0, +0x18 1.0, +0x1c 1.0, +0x20 1.0}; civilian {+4 0.25, maxpop 20000000, out 0.33, income 0.33, +0x18 1.0, +0x1c 0.5, +0x20 2.0}; slaves {+4 0.0, maxpop 0, out SLAVES_OUTPUT_MOD, income SLAVES_INCOME_MOD, +0x18 SLAVES_REPAIR_MOD, +0x1c 0.0}. So the whole population -> output law is carried by the executable except the three slave modifiers",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "GroupIncome",
"addr": "0x00535e80",
"convention": "cdecl",
"prototype": "int (int groupType, int64 count) // ftol(POPTYPE[groupType].incomeModifier x (count / 14000.0)). The income analogue of GroupOutput; note the divisor is 14000, not the output law's 500000, and there is no 1.8 factor",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "SpeciesDefTable_Get",
"addr": "0x00545cc0",
"convention": "cdecl",
"prototype": "SpeciesDef* (int species) // table base 0x00b10a00, stride 0x184, seven rows; species > 6 returns a lazily-constructed default at 0x00b105b0. Fields read by the output chain: +0x4c the base resource demand (an int) and +0x50 the resource output factor (a float). The table is .bss, so both come from the data files",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "SignedCubeRoot",
"addr": "0x008e5680",
"convention": "cdecl",
"prototype": "double (double x) // x >= 0 ? pow(x, 1/3) : -pow(-x, 1/3), with the exponent taken from the double 0.3333333333333333 at 0x00a3a7c8. Used by StripMineFraction",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "ServerPlayer_off_ScOutMod",
"offset": "0x12c",
"prototype": "offset float ScOutMod -- the last of the five output multipliers ComputeTotalOutput applies, and the innermost in the x87 chain",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)",
"convention": "offset"
},
{
"name": "ServerPlayer_off_SetupOutputMult",
"offset": "0x224",
"prototype": "offset float -- the game-setup handicap OUTPUT multiplier, the sibling of SetupIncomeMult (+0x228) and SetupResearchMult (+0x22c). Read by ComputeTotalOutput as the third of its five multipliers",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)",
"convention": "offset"
},
{
"name": "PopTypeTable_base",
"addr": "0x00b104e8",
"convention": "data",
"prototype": "PopTypeRow[3] -- .bss, filled by InitPopTypeTable at startup. Stride 0x30",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "SpeciesDefTable_base",
"addr": "0x00b10a00",
"convention": "data",
"prototype": "SpeciesDef[7] -- .bss, filled from the data files. Stride 0x184",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "GlobalConst_slot_STATION_BONUS_IMPERIAL_OUTPUT",
"addr": "0x00af08f4",
"convention": "data",
"prototype": "float** -- pointer slot; storage 0x00af08f0, which unusually carries a value (0.1f) in the file image rather than being .bss",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "GlobalConst_slot_MORALE_INCREASE_OUTPUT",
"addr": "0x00aec784",
"convention": "data",
"prototype": "int** -- pointer slot read by MoraleOutputMod; the threshold is an INT compared against the Morale int[7] entry",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "GlobalConst_slot_MORALE_INCREASE_OUTPUT_MOD",
"addr": "0x00aec78c",
"convention": "data",
"prototype": "float** -- pointer slot read by MoraleOutputMod",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "GlobalConst_slot_MORALE_DECREASE_OUTPUT",
"addr": "0x00aec794",
"convention": "data",
"prototype": "int** -- pointer slot read by MoraleOutputMod",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "GlobalConst_slot_MORALE_DECREASE_OUTPUT_MOD",
"addr": "0x00aec79c",
"convention": "data",
"prototype": "float** -- pointer slot read by MoraleOutputMod",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
},
{
"name": "GlobalConst_storage_SLAVES_OUTPUT_MOD",
"addr": "0x00b0e9b0",
"convention": "data",
"prototype": "float -- .bss storage reached through the slot at 0x00ae2e88; the output modifier of the slave row of the population-type table. SLAVES_INCOME_MOD is 0x00b0e9ac (slot 0x00ae2e84) and SLAVES_REPAIR_MOD is 0x00b0e9b4 (slot 0x00ae2e8c)",
"status": "verified",
"source": "findings/subsystems/output-term.md (lane N 2026-09-08)"
}
]
}