regenerate the tracked header after lane-l4.json (rule 14: regenerated, never hand-resolved; verified identical to a fresh run of gen_addresses.py)
This commit is contained in:
parent
4ef630b266
commit
f761fb1e1c
1 changed files with 15 additions and 1 deletions
|
|
@ -1,5 +1,5 @@
|
|||
// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).
|
||||
// Source: sots-re ghidra/addresses.json @ ea7881a, generated 2026-09-08 by tools/gen_addresses.py
|
||||
// Source: sots-re ghidra/addresses.json @ 940aeca, generated 2026-09-08 by tools/gen_addresses.py
|
||||
// Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated).
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
|
@ -1951,6 +1951,20 @@ constexpr uint32_t AITBuildDeepScanShips_Execute = 0x002901a0;
|
|||
constexpr uint32_t AITBuildPoliceShips_Execute = 0x00290380;
|
||||
// thiscall bool __thiscall Game::StrategyClient::OrderList16(int objectId, bool flag) -- RET 8. THE LIST-16 PRODUCER. Opens with `cmp byte [this+0x15c], 0` -- the End-Turn latch StrategyClient::EndTurn 0x00783be0 sets at 0x00783c59 before BuildTurnCommands runs -- so like every other order method it refuses once the turn has been submitted. Element record for list 16 is {i32, bool} (lane Q), matching the (objectId, flag) pair; list 16 is in the PAYING half of the ModCount table (bump at 0x00821e23 in its applier 0x00821e20). Reached from AITRaid::Execute at depth 1. The method is named for the list it fills, NOT for what the flag means: no save has ever carried a list-16 element and nothing has been observed [unverified]
|
||||
constexpr uint32_t StrategyClient_OrderList16 = 0x003635f0;
|
||||
// thiscall void* __thiscall Game::StrategyAIAgent::SelectResearchTarget(void** outSlot) -- RET 4, the THIRD and last research producer tried by Process Turn phase 18 (0x006caf70), reached only when 0x006a84f0 and 0x006c27c0 both answer null. Body: (1) `if (agent->+0x10->+0x150 /*ClientPlayer*/ ->+0x294 != 0) return 0` -- a player that already has a target picks nothing; (2) build a std::vector of 0x0c-stride candidates with 0x006c2490(agent, &vec); (3) walk it FRONT TO BACK calling 0x006c8580(ecx = outSlot, edx = cand[1], stack: agent, cand[0]) and return the FIRST non-null answer; (4) if none answered, fall through to the three-arm rotation seeded by 0x006b36e0(agent) & 7 over the .data tables at 0x00a1a544 / 0x00a1a564. THERE IS NO SORT AND NO SCORE IN THE WALK: the vector's arrival order IS the priority, which is what makes a per-process ordering difference change the outcome [unverified]
|
||||
constexpr uint32_t StrategyAIAgent_SelectResearchTarget = 0x002c8890;
|
||||
// cdecl void __cdecl Game::StrategyAIAgent::BuildResearchCandidates(StrategyAIAgent* agent, std::vector<Candidate>* out) -- constructs a working object on the stack (0x006bcca0(agent)), emits the candidate vector into `out` (0x006bc500), destroys it (0x006bcef0). Element stride 0x0c. The emitter is a nested walk over groups with NO std::sort at that level, so the output order is the walk order [unverified]
|
||||
constexpr uint32_t StrategyAIAgent_BuildResearchCandidates = 0x002c2490;
|
||||
// fastcall void* __fastcall Game::StrategyAIAgent::TryResearchCandidate(void** outSlot /*ecx*/, int candWord1 /*edx*/, StrategyAIAgent* agent, int candWord0) -- called once per candidate in arrival order by SelectResearchTarget; the FIRST call that returns non-null ends the walk and that return value is the chosen tech object (its +0x4 is the std::string the order method is given). An entry probe on this address therefore records the whole candidate stream in the order the selector sees it [unverified]
|
||||
constexpr uint32_t StrategyAIAgent_TryResearchCandidate = 0x002c8580;
|
||||
// cdecl void* __cdecl (StrategyAIAgent* agent) -- the FIRST research producer tried by phase 18. Body unread; probed for reachability only, because a non-null answer here means the candidate walk never runs [unverified]
|
||||
constexpr uint32_t StrategyAIAgent_ResearchProducerA = 0x002a84f0;
|
||||
// thiscall void* __thiscall (StrategyAIAgent* this) -- the SECOND research producer tried by phase 18, entered with `mov eax,esi; call` (the receiver is in EAX at the call site, so the real convention is not confirmed). Body unread; probed for reachability only [unverified]
|
||||
constexpr uint32_t StrategyAIAgent_ResearchProducerB = 0x002c27c0;
|
||||
// thiscall int __thiscall (StrategyAIAgent* this) -- SelectResearchTarget's fallback index source, masked with 0x80000007 and used to index the eight-entry .data tables at 0x00a1a544 (2 0 2 0 2 1 2 0) and 0x00a1a564 (0 2 0 2 1 0 2 0). It is NOT an RNG draw: it reads ClientPlayer+0xf4 and calls 0x0080da80. A hit here means the research pick came from the three-arm rotation, not from the candidate list [unverified]
|
||||
constexpr uint32_t StrategyAIAgent_ResearchFallbackIndex = 0x002b36e0;
|
||||
// fastcall void* __fastcall (void** outSlot /*ecx*/, StrategyAIAgent* agent) -- one arm of SelectResearchTarget's three-arm fallback rotation; the arm index is (i + table[seed]) % 3 for i = 0,1,2 and the first non-null answer wins. Its outcome space is therefore at most three [unverified]
|
||||
constexpr uint32_t StrategyAIAgent_ResearchFallbackArm = 0x002c8670;
|
||||
// offset int aidf -- the difficulty level (0..2) stored by ServerPlayer::Read 0x008804d0 at 0x00880fa3, the selector LoadDifficultyRow 0x005a3990 uses to pick the row it copies for this player. VERIFIED LIVE on VM146: read at ComputeBudget entry on every one of the 8 players of turn1-state across 3,895 calls, it is 1 on all eight, which is the difficulty level the whole corpus carries and the level whose AI column holds the 1.1x income modifier [verified]
|
||||
constexpr uint32_t ServerPlayer_off_Aidf = 0x00000368;
|
||||
// offset DifficultyMods* -- a POINTER to the per-player difficulty record, NOT the record inline. This entry exists to correct a lane L5 mistake and to save the next lane the run. addresses.json already records DifficultyMods_Select 0x0059b490 being called as Select(p->diffMods /*+0x36c*/, p), and lane L5 read the 0x1c bytes AT +0x36c expecting {int id; float ai[3]; float other[3]}. They are not that: the first dword is 0x0da0ec20..0x0da13360 on the eight players of turn1-state -- eight distinct heap addresses within 18 KB of each other and of the ServerPlayer allocations themselves (the player object was at 0x0da08600) -- and the floats behind it decode as denormal garbage (1.7e-38, 1.4e-43). So the field is a pointer and the record is one dereference further on. HYPOTHESIS, not verified: this run did not follow the pointer, so it is consistent with +0x36c being a pointer to the DifficultyMods record and equally consistent with its being some other per-player sub-object pointer. What IS established is the thing the reading was for: the difficulty row is reachable from a ServerPlayer, so compute_budget.h's old coverage note ('not reachable from a ServerPlayer, so the two relevant entries are fitted constants') is wrong as written. The fitted pair {3.0, 1.5} / {1.0, 1.0} remains UNVERIFIED against the running game; the next run should dereference this field behind a readability guard and compare the selected triple against those constants [hypothesis]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue