Static half, verified and correcting two published sentences: FUN_00820af0 short-circuits on the ROUTE OWNER being Hiver (0x00820bc2), on the RAIDER's crew species being Hiver (0x00820bd7), and only then on SpeciesDef+0x144 (Human, Zuul). The campaign's 'Human or Zuul draws nothing, every other species draws' is incomplete on both sides. And the short-circuit sets frac = 0.0, not 1.0 - corrected in ag.json in place. Live half NOT taken. hooks=off throughout; no instrument was armed. The workload was built to within one conjunct: az-turn23-tarka-comraid.sav is the corpus's first Tarka empire, has Commerce Raiding, three AI routes and five AI Tarka fleets parked on sector nodes with Pos bit-equal, two of which clear G0-G4. The failed conjunct is G_B3 - the qualifying raiders own every route, so relation-to-self is 3 and B empties its candidate list. The next workload is named from that: give player 0 a route sourced in sector 768, where the AI raider already stands. tscr reads 127 on this map, not 252/253 - the mask is not a corpus constant and my own P3 gave the right count for the wrong reason.
180 lines
14 KiB
JSON
180 lines
14 KiB
JSON
{
|
|
"entries": [
|
|
{
|
|
"name": "ServerSystem_GetAddictionPhase",
|
|
"addr": "0x00745e40",
|
|
"convention": "thiscall",
|
|
"prototype": "int __thiscall (ServerSystem* this, int speciesIdx) // 91 B, RET 4. Reads this->addiction[speciesIdx] at +0x1e4+4k -- the int[7] table serialised as `nadct` + sparse (`ads`,`adt`) pairs. Returns 0 when the slot is 0; otherwise 1/2/3 by (Frame - stamp) against *StrategyVar_ADDICTION_PHASE2_START (10) and *_PHASE3_START (15). EVERY non-zero return path ends in `setne al`, so a caller reading AL gets the BOOLEAN `addiction[k] != 0` and the phase is discarded -- which is how ServerTradeManagerImpl_vslot15 uses it",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09); disassembled from dumps/sots.exe to the next function start"
|
|
},
|
|
{
|
|
"name": "ServerSystem_BeginAddiction",
|
|
"addr": "0x0074ef70",
|
|
"convention": "thiscall",
|
|
"prototype": "void __thiscall (ServerSystem* this, int speciesIdx) // 621 B, RET 4. Refuses if this->+0x100 (owner) is null, if bit 5 of owner->[0x348 + 4*speciesIdx] is set, or if ServerSystem_GetAddictionPhase already reports the slot stamped. Otherwise computes a money term, posts EVENTSUM/EVENTMSG_ADDICTION_OUTBREAK and writes this->addiction[speciesIdx] = GetGame()->Frame(+0xc). DRAWS NOTHING. ONLY TWO CALLERS IMAGE-WIDE, both inside ServerTradeManagerImpl_vslot15 (0x0082cd50 the un-rolled seed grant, 0x0082cdc4 behind the Chance) -- so this function is the sole way any system becomes addicted, and the Chance-gated spread cannot fire until the un-rolled grant has seeded it",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "SpeciesDef_AddictTechForSpecies",
|
|
"addr": "0x0053b620",
|
|
"convention": "thiscall",
|
|
"prototype": "void __thiscall (int speciesIdx /*ECX*/, int* out /*EAX*/) // jump table at 0x0053b6e8, seven entries; Ghidra's size 78 truncates before the table (rule 17). Writes the 10000-based TechID of that species' ADDICT TECH: k=0 0x2799, 1 0x279a, 2 0x279b, 3 0x279c, 4 and 5 -> 0xC5 (the image's `no tech` sentinel), 6 0x279d. Out-of-range logs `Species %s: No addict tech.` (0x009f9630). Called once, from InitTable 0x005453a0 at 0x005458a7, filling SpeciesDef+0x88 -- which is BIT 4 of ServerPlayer+0x348+4k in ServerPlayer_RebuildSpeciesTechFlags, the bit that gates the un-rolled addiction grant. NOT a wire techId, so tools/techid_table.py does not name these",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "StrategyVar_ADDICTION_SPREAD_ODDS",
|
|
"addr": "0x00af0c4c",
|
|
"convention": "data",
|
|
"prototype": "float -- the probability ServerTradeManagerImpl_vslot15 passes to Mars::RNG::Chance at 0x0082cdb8, reached as `mov ecx,ds:0x00af0c50; fld [ecx]` (0x00af0c50 holds a pointer to this storage word). Image default 0.2f, strictly inside (0,1), so Chance takes NEITHER early-out and the site costs EXACTLY ONE WORD per evaluation. Registered by a GlobalConst static-init stub against Data/Strategy/StrategyVars.txt with the %f parser 0x008b7020",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09); name resolved from the M1 loader stub, findings/subsystems/loader-prototypes.md"
|
|
},
|
|
{
|
|
"name": "StrategyVar_ADDICTION_PHASE2_START",
|
|
"addr": "0x00aeca54",
|
|
"convention": "data",
|
|
"prototype": "int -- image default 10. ServerSystem_GetAddictionPhase returns 2 rather than 1 once (Frame - addiction[k]) exceeds this. Pointer at 0x00aeca58. Dead for the vslot15 gate, which only reads the boolean",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "StrategyVar_ADDICTION_PHASE3_START",
|
|
"addr": "0x00aeca5c",
|
|
"convention": "data",
|
|
"prototype": "int -- image default 15. ServerSystem_GetAddictionPhase returns 3 once (Frame - addiction[k]) exceeds this. Pointer at 0x00aeca60",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "ServerSystem_IsIndependent",
|
|
"addr": "0x00743fa0",
|
|
"convention": "thiscall",
|
|
"prototype": "bool __thiscall (ServerSystem* this) // 12 B. `return this->+0x1c8 != 0` -- the IndependenceInfo* that the wire carries as `hindi` (present) + `indi`. ServerSpyManager_vslot14 rejects a spy whose target system passes this, which is conjunct G3 of the 0x0088dc43 counter-mission roll",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "ServerSpyManager_vslot14_RealEnd",
|
|
"addr": "0x0088dd2b",
|
|
"convention": "marker",
|
|
"prototype": "The last byte of ServerSpyManager_vslot14's body (the `ret`). GHIDRA REPORTS 387 BYTES, i.e. an end of 0x0088dd03, WHICH IS INSIDE THE FUNCTION -- a third instance of rule 17 after 0x007aa240 and 0x00893290. A five-entry jump table for the mission-stage switch sits at 0x0088dd2c..0x0088dd3f, past both the Ghidra end and the real end; padding then runs to the next function start 0x0088dd40",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "TradeSector_FleetRaidsOwnRoutes",
|
|
"addr": "0x0080ecf0",
|
|
"convention": "cdecl",
|
|
"prototype": "bool __cdecl (StarFleet* fleet) // 91 B. `fleet && GetLocationAsTradeSector(fleet) && !FUN_00813ab0(fleet,0x80000,0) && TradeSector_PlayerTradesHere(sector, fleet->owner) && !FUN_006fe1d0(fleet, 0x800)`. TradeManager_Slot13RngCalleeB uses it to choose between two candidate filters. IT IS DETERMINISTICALLY FALSE for any fleet that reached B through vslot13: TradeSector_FleetMayRaid (0x0080ed50) demands the OPPOSITE on the last two conjuncts, so the `raid your own routes` arm of B is unreachable from the tail. PlayerAlliances_Relation returning 3 for self is the second, independent reason",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "TradeManager_ResolveRouteEndpointOwner",
|
|
"addr": "0x00841cd0",
|
|
"convention": "thiscall",
|
|
"prototype": "ServerTradeManager method, 974 B. The function that DISAMBIGUATES Game::TradeRoute's endpoint fields: `if (rt->trfrs(+0x10) == 0) endpoint = GetGame()->Systems(+0x44)[rt->trfr(+0xc)] else endpoint = registry_lookup(GetGame()+0x84, rt->trfrs)`. So trfr / trto are SYSTEM INDICES into StrategyServer's system vector and trfrs / trtos are non-system endpoint IDS with 0 meaning `use the index`. Only the endpoint-resolution head of this function was read; the rest is unread",
|
|
"status": "mapped",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "TradeManager_ComputeRaidInterceptPoint",
|
|
"addr": "0x00820af0",
|
|
"convention": "thiscall",
|
|
"prototype": "bool __thiscall (ServerTradeManager* this, Vector3* out, TradeRoute* rt, void* arg3) // 425 B, RET 0xC. Places the raid interception point along the route: a = Systems[rt->trfr]->Pos, b = Systems[rt->trto]->Pos, dist = |b-a|; frac = 1.0 when the route owner's species (+0x5c) is 1 or when either of two species tests on arg3 passes, ELSE frac = float(NextFloat()) drawn at 0x00820c1b; out = a + dir*(frac*dist + <double 0x009e20a0>). TWO CALLERS: 0x0082cf65 (inside CreateRaidEncounter's subtree) and 0x0088b62c (TradeManager_Slot13RngCalleeB, immediately after the target pick)",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "TradeManager_RaidInterceptPoint_DrawSite",
|
|
"addr": "0x00820c1b",
|
|
"convention": "site",
|
|
"prototype": "call RNG_NextFloat (0x0047d830) inside TradeManager_ComputeRaidInterceptPoint, ECX = GetGame()->RNG(+0x16c) + 4. ONE STRATEGIC-GENERATOR WORD, unless one of THREE short-circuits fires first and sets frac = **0.0** (NOT 1.0 -- corrected by lane AZ 2026-09-09 from the x87 stack at 0x00820bf8, `fldz; fstp [ebp+0xc]`; the draw arm computes 0.0 + (1.0-0.0)*NextFloat()). The three are: 0x00820bc2 `cmp [routeOwner+0x5c],1` -- the ROUTE OWNER's Species, so a HIVER-owned route is intercepted with no draw whoever raids it; 0x00820bd7 `cmp eax,1` on StarFleet_GetCrewSpeciesForIntercept(raider) -- a HIVER raider draws nothing; and 0x00820bf2 SpeciesDef_HasInterceptFlag -- Human (0) and Zuul (5) only. So the site draws only for a raider of species 2 Tarkas / 3 Liir / 6 Morrigi (or 4 _NPC) against a non-Hiver-owned route. NOT IN ANY EARLIER INVENTORY. It makes TradeManager_Slot13RngCalleeB cost TWO words on a success (this plus RNG_NextInt at 0x0088b613), not the 0 lane AC measured on a state whose candidate list was empty",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "TradeManager_BuildRaidCandidates",
|
|
"addr": "0x0083b110",
|
|
"convention": "thiscall",
|
|
"prototype": "ServerTradeManager method called at 0x008938ed as the FIRST act of ServerTradeManager_CreateRaidEncounter; fills the local vector at [ebp-0x1c]. When it comes back EMPTY, CreateRaidEncounter returns false at 0x008938fa -- BEFORE every draw in the function and its subtree. This is the gate lane H measured as `entered 3 times, 0 words'. Body not read",
|
|
"status": "mapped",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "CreateRaidEncounter_EmptyCandidateListReturn",
|
|
"addr": "0x008938fa",
|
|
"convention": "site",
|
|
"prototype": "`jne 0x00893932` -- taken when TradeManager_BuildRaidCandidates produced a non-empty vector; the fall-through frees the vector and returns false with AL = 0. EVERY draw in CreateRaidEncounter and its subtree lies past this branch: 0x008939ee, then through 0x00892640 the five sites 0x0088f1eb, 0x0088f356, 0x0083b460, 0x00848fd9, 0x00820c1b. tail-rng-ledger.md section 11.1's `a success costs 0 or 1 further word' is right about the corpus and wrong about the code",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "TradeManager_BuildRaidEncounter",
|
|
"addr": "0x00892640",
|
|
"convention": "thiscall",
|
|
"prototype": "ServerTradeManager method, single caller ServerTradeManager_CreateRaidEncounter at 0x00893a25 (i.e. past the empty-candidate-list gate AND past the target pick at 0x008939ee). Calls TradeManager_SpawnEncounterSquadron 0x0088f070 (@0x008926ce), 0x00848e50 (@0x008926d9) and 0x0082ce00 (@0x00892700). The whole draw subtree of a SUCCESSFUL raid encounter hangs off it and has never been reached by any measurement in this campaign",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "SpawnEncounterSquadron_DrawSite1",
|
|
"addr": "0x0088f1eb",
|
|
"convention": "site",
|
|
"prototype": "call RNG_NextInt (0x004271c0) inside TradeManager_SpawnEncounterSquadron 0x0088f070, generator loaded at 0x0088f1da from +0x16c. One word plus its rejection loop. Reachable only past CreateRaidEncounter_EmptyCandidateListReturn",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "SpawnEncounterSquadron_DrawSite2",
|
|
"addr": "0x0088f356",
|
|
"convention": "site",
|
|
"prototype": "call RNG_NextInt (0x004271c0) inside TradeManager_SpawnEncounterSquadron 0x0088f070, generator loaded at 0x0088f334. Same reachability",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "RaidEncounter_SquadronHelper",
|
|
"addr": "0x0083b1e0",
|
|
"convention": "thiscall",
|
|
"prototype": "Called only from TradeManager_SpawnEncounterSquadron at 0x0088f0f7. Draws RNG_NextInt at 0x0083b460 on the strategic generator (loaded at 0x0083b44c). Body not read; listed because the sweep places it inside CreateRaidEncounter's unreached subtree",
|
|
"status": "mapped",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "RaidEncounter_SquadronHelper_DrawSite",
|
|
"addr": "0x0083b460",
|
|
"convention": "site",
|
|
"prototype": "call RNG_NextInt (0x004271c0) inside 0x0083b1e0, generator from +0x16c at 0x0083b44c",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "RaidEncounter_Helper848e50",
|
|
"addr": "0x00848e50",
|
|
"convention": "thiscall",
|
|
"prototype": "Called only from TradeManager_BuildRaidEncounter at 0x008926d9. Draws RNG_NextInt at 0x00848fd9 on the strategic generator (loaded at 0x00848fc8). Body not read",
|
|
"status": "mapped",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "RaidEncounter_Helper848e50_DrawSite",
|
|
"addr": "0x00848fd9",
|
|
"convention": "site",
|
|
"prototype": "call RNG_NextInt (0x004271c0) inside 0x00848e50, generator from +0x16c at 0x00848fc8",
|
|
"status": "verified",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
},
|
|
{
|
|
"name": "RaidEncounter_Helper82ce00",
|
|
"addr": "0x0082ce00",
|
|
"convention": "thiscall",
|
|
"prototype": "Called only from TradeManager_BuildRaidEncounter at 0x00892700; calls TradeManager_ComputeRaidInterceptPoint at 0x0082cf65, which is where the NextFloat at 0x00820c1b is spent on this path. Body not read",
|
|
"status": "mapped",
|
|
"source": "findings/control-flow/gate-indexed-rng-audit.md (lane AG 2026-09-09)"
|
|
}
|
|
]
|
|
}
|