{ "_note": "Lane E1, the output -> money chain. Six addresses this lane read are already held elsewhere and AGREE, so they are dropped from this fragment rather than duplicated (rule 14): ServerSystem_ComputeMaxIncome 0x007521c0 (lane-k.json), ServerSystem_ComputeOutputFromRates 0x00751bb0 and ServerSystem_TradePointsToMoney 0x007505b0 and ServerSystem_CalcSuitMod 0x007484d0 (addresses.json), GroupIncome 0x00535e80 and PopTypeRow 0x00535e00 (lane-n.json). Every one of those six was re-read from the instruction stream this lane and the address, convention and boundary matched what was already recorded.", "entries": [ { "name": "ServerSystem_PopIncome", "addr": "0x0074d760", "convention": "thiscall", "prototype": "double (ServerSystem* sys, int groupType) // `ret 4`, real end 0x0074d8e1. The income analogue of PopOutput 0x0074d8f0, and NOT the same law: it sums, over species 0..6, `(double)ftol( (double)GroupIncome(groupType, count) x moraleMod x addictionMod )` -- so the value truncates TWICE per (group, species) row, once inside GroupIncome and once after both factors. moraleMod is 0x00746910 (the same helper the output term uses) and applies to groupType 1 only; addictionMod is the float behind slot 0x00aeca48 when the system's int[7] addiction table at +0x1e4 has a non-zero entry for that species. For groupType 1 and the owner's own species on a non-independent system the count first gains the capacity surplus from two calls to 0x0074a6d0 (out slot 4, then out slot 6), max(0, B - A)", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" }, { "name": "ServerSystem_SlaveIncome", "addr": "0x0074b700", "convention": "thiscall", "prototype": "double (ServerSystem* sys) // plain `ret`, real end 0x0074b793. groupType 2 of the same loop as PopIncome, over SlaveCount(species) (0x0074b610): no morale factor and no capacity surplus, but the addiction factor still applies. Unexercised: slave counts are 0 on every call in the corpus", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" }, { "name": "ServerSystem_SlaveCount", "addr": "0x0074b610", "convention": "thiscall", "prototype": "int64 (ServerSystem* sys, int species) // the slave-group population of one species; the group-2 counterpart of GroupPopulation 0x00747ba0", "status": "unverified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08) -- reached from SlaveIncome, body not read" }, { "name": "ServerSystem_ComputeOutput", "addr": "0x00751fb0", "convention": "thiscall", "prototype": "void (ServerSystem* sys, int out[12]) // zeroes `out`, returns immediately when the caller's pointer is null or the system has no owner (+0x100), else calls ComputeOutputFromRates(out, &sys->Rts /*+0x88*/) -- the system's OWN rate sliders, not a max-mods vector. THE DISTINCTION THAT MATTERS: ComputeBudget's real (non-projected) per-system money is this function's out[3], while its projected mode and UpdateBankruptcyLimits use ComputeMaxIncome 0x007521c0. They are different numbers: this path funds the science, construction and terraform channels, so the repair pass runs and the unspent-industry and unspent-terraforming cascades into the money channel are live", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" }, { "name": "StrategyServer_IncomeDifficultyMod", "addr": "0x0080f470", "convention": "thiscall", "prototype": "float (StrategyServer* srv, ServerPlayer* p) // `ret 4`, real end 0x0080f49f. Returns float32( float32(DifficultyMods_Select(p->diffMods /*+0x36c*/, p)[1]) x float32(srv->IncMod /*raw base +0xbc, the Sim block's `IncMod` tag*/) ), or just the server modifier when p is null. Every step is stored back through a 4-byte float. `ecx` here is the RAW StrategyServer base (ServerSystem+0x10), four bytes above the base the class's own methods get. THIS IS THE MISSING x1.1: at the difficulty level every corpus save carries (aidf == 1) the AI column of the table is 1.1f", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" }, { "name": "DifficultyMods_Select", "addr": "0x0059b490", "convention": "thiscall", "prototype": "float* (DifficultyMods* rec, ServerPlayer* p) // `ret 4`, real end 0x0059b4b3. Returns &rec->f[0] (the AI triple, at +0x04) when p is non-null AND p->[0xf9] (is-AI) is set AND p->NPC (+0xfb) is clear; otherwise &rec->f[3] (the non-AI triple, at +0x10). The three consumers read offset +0 (fleet maintenance divisor, ComputeBudget 0x0086338b), +4 (system and trade-route money, 0x0080f470 and 0x00833938) and +8 (research points bought with money, 0x0080e229 and 0x00863618)", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" }, { "name": "LoadDifficultyRow", "addr": "0x005a3990", "convention": "cdecl", "prototype": "void (int level, DifficultyMods* out) // real end 0x005a3a53 (Ghidra size 193 stops 2 bytes short). memcpy's the default {id 1, 1.0f x6} into `out` FIRST, then builds the table with BuildDifficultyTable 0x005a3870 and linear-searches it for id == level (stride 0x1c, from the 0x92492493 magic divide), copying the six floats on a hit. An out-of-range level therefore yields all ones rather than failing. Called from ServerPlayer::Read 0x008804d0 at 0x00880fa3, gated on 0 <= aidf < 3, which is also where ServerPlayer+0x368 (`aidf`) is stored", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" }, { "name": "BuildDifficultyTable", "addr": "0x005a3870", "convention": "thiscall", "prototype": "vector* (vector* out) // real end 0x005a3989. THE TABLE IS BUILT IN CODE, from .rdata float literals -- no data-file key, no GlobalConst slot, same shape as lane N's pop-type table. Three rows of {int id; float ai[3]; float other[3]} (0x1c): id 0 = ai {1,1,1} / other {1.5,1.5,1.5} (0x00a1b000); id 1 = ai {3.0 (0x00a0451c), 1.1 (0x009f957c), 1.5} / other {1,1,1}; id 2 = ai {1e6 (0x009ebd7c), 1.7 (0x009f9580), 2.0 (0x00a04518)} / other {1,1,1}. Read as: level 0 gives the break to the human player, levels 1 and 2 give it to the AI, and on level 2 the AI's fleet maintenance is divided by a million", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" }, { "name": "ServerPlayer_GetIncMod", "addr": "0x0080dd10", "convention": "thiscall", "prototype": "float (ServerPlayer* p) // seven bytes: `fld DWORD [ecx+0x30c]; ret`. The save's per-player `IncMod`", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" }, { "name": "ServerPlayer_GetSpeciesCostFactor", "addr": "0x0080dd20", "convention": "thiscall", "prototype": "float (ServerPlayer* p) // `SpeciesDef(p->Species /*+0x5c*/)->+0x24`, the multiplier on the suitability MONEY cost (Zuul 0.7). A data-file value", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" }, { "name": "StrategyServer_IdealSuit", "addr": "0x0080f4b0", "convention": "thiscall", "prototype": "float (StrategyServer* srv, int species) // `ret 4`; one instruction of work: `fld DWORD [ecx + species*4 + 0xf8]`. The per-species ideal-suitability array on the RAW server base. IT IS ON THE WIRE: the Sim block's `ISsp`/`ISsu` pairs are this float[7] in species-index order, and the array is randomised per game by the map generator -- verified against every ServerPlayer's own `IdealSuit` field in all 11 corpus saves. CalcSuitMod reads THIS, not the player's field", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" }, { "name": "ServerSystem_TerraformPointsNeeded", "addr": "0x00746890", "convention": "thiscall", "prototype": "double (ServerSystem* sys) // real end 0x0074690d. Returns 0 with no owner, else float32(|IdealSuit(species) - sys->Suit|) / |owner->TerraMod (+0x134) x [0x00a1f928] / 20000|. The sign term (-1.0 when the planet's suitability is STRICTLY above the ideal) is multiplied in BEFORE the fabs at 0x00746906 and therefore cancels: the result is always >= 0. That is why the unspent-terraform cascade into the money channel is provably zero under the max-income rate vector", "status": "verified", "source": "findings/subsystems/income-term.md (lane E1 2026-09-08)" } ] }