lane AZ merged: three species short-circuits not one, frac polarity inverted in two published findings, tscr is not a corpus constant
This commit is contained in:
parent
2ad6652eac
commit
aa8d3fb39e
5 changed files with 42 additions and 18 deletions
|
|
@ -1,30 +1,30 @@
|
|||
# SotS RE campaign — coverage dashboard
|
||||
|
||||
Generated 2026-09-09 06:52 UTC · `sots-re` @ 2ff901e,2026-09-09 · `sots-engine` @ 1654785,2026-09-09 (241 commits) · regenerate with `tools/dashboard.py`
|
||||
Generated 2026-09-09 07:03 UTC · `sots-re` @ 2ad6652,2026-09-09 · `sots-engine` @ dea127d,2026-09-09 (242 commits) · regenerate with `tools/dashboard.py`
|
||||
|
||||
> **North star:** A functional reimplementation of the engine — behavior-equivalent, NOT byte-for-byte
|
||||
|
||||
## 1. Map coverage (campaign/board.md)
|
||||
|
||||
432 targets · mapped-or-better **385/432** `[█████████░] 89%` · verified **343/432** `[████████░░] 79%`
|
||||
438 targets · mapped-or-better **390/438** `[█████████░] 89%` · verified **348/438** `[████████░░] 79%`
|
||||
|
||||
| Status | Count | % |
|
||||
|---|---:|---:|
|
||||
| verified | 343 | 79% |
|
||||
| verified | 348 | 79% |
|
||||
| mapped | 42 | 10% |
|
||||
| in-progress | 4 | 1% |
|
||||
| in-progress | 5 | 1% |
|
||||
| backlog | 41 | 9% |
|
||||
| blocked | 2 | 0% |
|
||||
|
||||
| Type | verified | mapped | in-progress | backlog | blocked | total |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| objects | 46 | 6 | 0 | 3 | 1 | 56 |
|
||||
| control-flow | 36 | 2 | 0 | 0 | 0 | 38 |
|
||||
| control-flow | 38 | 2 | 1 | 0 | 0 | 41 |
|
||||
| subsystems | 4 | 8 | 0 | 2 | 1 | 15 |
|
||||
| engine | 31 | 0 | 0 | 0 | 0 | 31 |
|
||||
| verify | 104 | 15 | 3 | 35 | 0 | 157 |
|
||||
| verify | 105 | 15 | 3 | 35 | 0 | 158 |
|
||||
| phase2 | 13 | 3 | 1 | 0 | 0 | 17 |
|
||||
| meta | 90 | 6 | 0 | 1 | 0 | 97 |
|
||||
| meta | 92 | 6 | 0 | 1 | 0 | 99 |
|
||||
| other | 19 | 2 | 0 | 0 | 0 | 21 |
|
||||
|
||||
## 2. Binary understanding
|
||||
|
|
@ -38,7 +38,7 @@ Generated 2026-09-09 06:52 UTC · `sots-re` @ 2ff901e,2026-09-09 · `sots-engine
|
|||
- Catalogs: **1,595/1,595** files parsed (91 block kinds in `schema_stats.json`), dangling cross-refs **0** (`crosslink.json`)
|
||||
- Oracle `mars-parse`: **1,531/1,531** files agree `[██████████] 100%`
|
||||
- Oracle `mars-text`: **64/64** files agree `[██████████] 100%`
|
||||
- Saves: **42/42** real saves strict-clean — strict exit 0, 0 errors, 0 warnings
|
||||
- Saves: **43/43** real saves strict-clean — strict exit 0, 0 errors, 0 warnings
|
||||
- Design rules: **127/127** stock designs pass `[██████████] 100%`
|
||||
- Value domains: **490/724** typed fields have been seen to vary `[███████░░░] 68%` — the other **234** have only ever held one value across the corpus, so their typing is untested (`value-domain-census.md`)
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ Detail: `verify/results/standalone/report.txt`.
|
|||
|
||||
## 7. Verification ledger
|
||||
|
||||
- ✅ Saves strict: 42/42 (strict exit 0, 0 errors, 0 warnings)
|
||||
- ✅ Saves strict: 43/43 (strict exit 0, 0 errors, 0 warnings)
|
||||
- ✅ Design rules: 127/127
|
||||
- ✅ oracle mars-parse 1531/1531 · ✅ oracle mars-text 64/64
|
||||
- ✅ Compare harness present (`verify/harness/compare/`)
|
||||
|
|
@ -133,11 +133,11 @@ Most recent open:
|
|||
|
||||
## 9. Delta since previous dashboard
|
||||
|
||||
- verified targets: 341 → 343 (+2) · mapped-or-better: 383 → 385 (+2)
|
||||
- verified targets: 343 → 348 (+5) · mapped-or-better: 385 → 390 (+5)
|
||||
- engine LOC: 58,647 → 58,647 (+0) · test files: 122 → 122 (+0) · checks: 4,257 → 4,257 (+0)
|
||||
- addresses verified: 1,160 → 1,160 (+0) · recovered layouts: 384 → 384 (+0) · open questions: 26 → 26 (+0)
|
||||
- standalone leaves closed: 45 → 45 (+0) · leaves still diverging: 63 → 63 (+0)
|
||||
|
||||
---
|
||||
warnings: board.md: unknown types subsystems; mars-rng.md: no oracle total row parsed; mars-stream.md: no oracle total row parsed; mars-vfs.md: no oracle total row parsed
|
||||
<!-- dashboard-metrics {"verified": 343, "mapped_plus": 385, "targets": 432, "loc": 58647, "tests": 122, "checks": 4257, "addr_verified": 1160, "addr_total": 1291, "layouts": 384, "open_q": 26, "sa_closed": 45, "sa_left": 63} -->
|
||||
<!-- dashboard-metrics {"verified": 348, "mapped_plus": 390, "targets": 438, "loc": 58647, "tests": 122, "checks": 4257, "addr_verified": 1160, "addr_total": 1291, "layouts": 384, "open_q": 26, "sa_closed": 45, "sa_left": 63} -->
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -532,7 +532,7 @@ along the raided route:
|
|||
owner = registry_lookup(S+0x84, rt->tro)
|
||||
a = systems[rt->trfr]->Pos b = systems[rt->trto]->Pos
|
||||
dist = |b - a| - 0.0
|
||||
if (owner->+0x5c == 1) frac = 1.0 ; species 1
|
||||
if (owner->+0x5c == 1) frac = 1.0 ; species 1 **[POLARITY CORRECTED 2026-09-09 by lane AZ (rule 11): the short-circuit arm sets `frac = 0.0`, NOT 1.0 (`fldz; fstp [ebp+0xc]` at 0x00820bf8); the DRAW arm computes `0.0 + (1.0-0.0)*NextFloat()`. So a Human/Zuul/Hiver interception happens at the raided route's SOURCE end, not its destination. Word counts unaffected; the sentence was wrong. Rule 28 practice 6 - read the polarity before the count.]**
|
||||
else if (FUN_0081a2d0(arg3) == 1) frac = 1.0
|
||||
else if (FUN_0053baf0(SpeciesDef(...))) frac = 1.0
|
||||
else frac = 0.0 + (1.0 - 0.0) * NextFloat() ; <<< 0x00820c1b, 1 WORD
|
||||
|
|
|
|||
|
|
@ -673,7 +673,7 @@ New rows:
|
|||
```
|
||||
|
||||
```
|
||||
| `B` costs ONE word on a success, not two - and the deciding value is a SPECIES FLAG set by a constructor for exactly two species | control-flow | verified | high | 100% | 2026-09-09 | Lane AR. Lane AG's committed §3.3 prediction was **3 words on a succeeding turn** (`A` 1 + `B`'s NextInt 1 + `FUN_00820af0`'s NextFloat 1) and §5.2 says `B` costs 2 on success. **MEASURED: the tail cost 5 with k=4 raiders and one success, i.e. `B` cost 1, and there is NO `0x00820c1b` row on that turn.** Why: `FUN_00820af0`'s third short-circuit is `SpeciesDef_HasInterceptFlag 0x0053baf0` - twelve bytes, `cmp [ecx+0x144],0; setne al` - and `InitTable 0x005453a0` sets that flag word at 0x0054562f/0x00545638 for **species 0 (HUMAN) bit 0 and species 5 (ZUUL) bit 1 ONLY**. `Get 0x00545cc0` returns `0x00b10a00 + k*0x184`, the same table InitTable fills. So **a Human or Zuul raider intercepts at frac=1.0 and draws nothing; the other five species draw a NextFloat at 0x00820c1b**. The general tail model is **`k + s + z`** words: k = permitted raiders parked with `Pos` bit-equal, s = their successes ON A SECTOR THAT SOURCES A ROUTE, z = s again unless the raider's species sets the flag. Measured at k+s with k = 4,4,4,4,4,3 and s = 0,0,0,0,1,0. THE CHEAPEST UNEXERCISED STATE IN THE TRADE HALF IS NOW A NON-HUMAN, NON-ZUUL RAIDER - z has never been observed through `B` by anyone. Rule 23's lesson pointing a new way: the deciding value was not a float literal but a FLAG WORD SET BY A CONSTRUCTOR, invisible to any amount of reading `FUN_00820af0` alone |
|
||||
| `B` costs ONE word on a success, not two - and the deciding value is a SPECIES FLAG set by a constructor for exactly two species | control-flow | verified | high | 100% | 2026-09-09 | Lane AR. Lane AG's committed §3.3 prediction was **3 words on a succeeding turn** (`A` 1 + `B`'s NextInt 1 + `FUN_00820af0`'s NextFloat 1) and §5.2 says `B` costs 2 on success. **MEASURED: the tail cost 5 with k=4 raiders and one success, i.e. `B` cost 1, and there is NO `0x00820c1b` row on that turn.** Why: `FUN_00820af0`'s third short-circuit is `SpeciesDef_HasInterceptFlag 0x0053baf0` - twelve bytes, `cmp [ecx+0x144],0; setne al` - and `InitTable 0x005453a0` sets that flag word at 0x0054562f/0x00545638 for **species 0 (HUMAN) bit 0 and species 5 (ZUUL) bit 1 ONLY**. `Get 0x00545cc0` returns `0x00b10a00 + k*0x184`, the same table InitTable fills. So **a Human or Zuul raider intercepts at frac=1.0 and draws nothing; the other five species draw a NextFloat at 0x00820c1b**. The general tail model is **`k + s + z`** words: k = permitted raiders parked with `Pos` bit-equal, s = their successes ON A SECTOR THAT SOURCES A ROUTE, z = s again unless the raider's species sets the flag. Measured at k+s with k = 4,4,4,4,4,3 and s = 0,0,0,0,1,0. THE CHEAPEST UNEXERCISED STATE IN THE TRADE HALF IS NOW A NON-HUMAN, NON-ZUUL RAIDER - z has never been observed through `B` by anyone. Rule 23's lesson pointing a new way: the deciding value was not a float literal but a FLAG WORD SET BY A CONSTRUCTOR, invisible to any amount of reading `FUN_00820af0` alone | **[POLARITY CORRECTED 2026-09-09 by lane AZ (rule 11): the short-circuit arm sets `frac = 0.0`, NOT 1.0 (`fldz; fstp [ebp+0xc]` at 0x00820bf8); the DRAW arm computes `0.0 + (1.0-0.0)*NextFloat()`. So a Human/Zuul/Hiver interception happens at the raided route's SOURCE end, not its destination. Word counts unaffected; the sentence was wrong. Rule 28 practice 6 - read the polarity before the count.]**
|
||||
```
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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 @ db4fda2, generated 2026-09-09 by tools/gen_addresses.py
|
||||
// Source: sots-re ghidra/addresses.json @ 2ad6652, generated 2026-09-09 by tools/gen_addresses.py
|
||||
// Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated).
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
|
@ -1259,7 +1259,7 @@ constexpr uint32_t TradeSector_FleetRaidsOwnRoutes = 0x0040ecf0;
|
|||
constexpr uint32_t TradeManager_ResolveRouteEndpointOwner = 0x00441cd0;
|
||||
// thiscall 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) [verified]
|
||||
constexpr uint32_t TradeManager_ComputeRaidInterceptPoint = 0x00420af0;
|
||||
// site call RNG_NextFloat (0x0047d830) inside TradeManager_ComputeRaidInterceptPoint, ECX = GetGame()->RNG(+0x16c) + 4. ONE STRATEGIC-GENERATOR WORD, unless the species short-circuit at 0x00820bc2/0x00820bd7/0x00820bf2 sets frac = 1.0 first. 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 [verified]
|
||||
// site 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 [verified]
|
||||
constexpr uint32_t TradeManager_RaidInterceptPoint_DrawSite = 0x00420c1b;
|
||||
// thiscall 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 [mapped]
|
||||
constexpr uint32_t TradeManager_BuildRaidCandidates = 0x0043b110;
|
||||
|
|
@ -1281,6 +1281,22 @@ constexpr uint32_t RaidEncounter_Helper848e50 = 0x00448e50;
|
|||
constexpr uint32_t RaidEncounter_Helper848e50_DrawSite = 0x00448fd9;
|
||||
// thiscall 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 [mapped]
|
||||
constexpr uint32_t RaidEncounter_Helper82ce00 = 0x0042ce00;
|
||||
// __thiscall bool (Game::StrategyServer* this, Game::Ship* ship) /* body 0x0078c930..0x0078c9e0 (next function start), `ret 4`. Entry of the STACK-BUILT SHIP-ACTION TABLE (lane B6's indirection class): no direct callers, no vtable slot, invisible to every call-graph sweep this campaign has run. One gate -- a 12-byte stack query object (vtable 0x00a09648) built at 0x0078c93d and asked about the ship at 0x0078c964; false => return with NO DRAW. Past it: `mov ecx,[esi+0x16c]; add ecx,4; call Mars_RNG_NextFloat` at 0x0078c97f -- ONE STRATEGIC-GENERATOR WORD PER APPLIED Deploy Spy ORDER, inside ApplyAllTurnCommands, i.e. the ProcessTurn bracket and NOT the tail's. The angle is lerp(0.0, 2*pi, r) computed through a FLOAT32 temp at [ebp+8] (fldz supplies LO; HI is the double 6.2831854820251465 at 0x009e21b0), stored to the stack slot pushed at 0x0078c998, which is DeploySpy's SECOND argument. 0x0080c860 is then called ACROSS that half-built frame and does not touch it. MEASURED lane AP 2026-09-09: 1 call / 1 word / no_draw_calls=0 / strategic=true at ret_rva 0x0078c984, and spy.cbh came out 5.033599376678467 -- BIT-IDENTICAL to lane AS's value from a different process, build and session, so this draw is reproducible from the save. ITS PREDICATE IS ON THE COMMAND STREAM, not on save fields: `a SHIPACTION_DEPLOYSPY command in this turn's command stream`. The gate-indexed audit has no column for that and should grow one */ [verified]
|
||||
constexpr uint32_t SpyManager_ShipActionDeploySpyHandler = 0x0038c930;
|
||||
// __thiscall int (Game::Ship* this) /* 7 bytes, complete: `mov eax,[ecx+0xa8]; ret`. A zero-argument getter for the ship's attached-spy id -- the same field ServerSpyManager_DeploySpy clears when it detaches the craft from its tender. Recorded because at its ONE call site (0x0078c9ac, in the SHIPACTION_DEPLOYSPY handler) it looks like it takes a float: the caller has already pushed DeploySpy's second argument and stored the deploy angle into it, and this call runs across that half-built frame without disturbing it. An eye following eax there misses where the angle goes */ [verified]
|
||||
constexpr uint32_t Ship_GetAttachedSpyId = 0x0040c860;
|
||||
// __thiscall void (Game::ServerSpyManager* this, int sid) /* Game::ServerSpyManager vftable 0x00a3073c SLOT 10. Looks the SpyCraft up by id through vtable slot 5, then: if spy->deat(+0x10) != 0, registry_lookup(server+0x84, deat) and ServerSystem_RemoveSpy 0x0074f550 on that system (which clears deat and erases sid from spies2); if spy->atto(+0xc) != 0, clear the tender's ship->[+0xa8] and spy->atto; erase the craft from the manager's vector at this+0x10 via 0x0059ec00; then the scalar deleting destructor with flag 1. CALLED FROM ServerSpyManager_vslot13's drain loop at 0x00887eb0, back to front over the local vector<int> of sids collected at 0x008878a1 -- so a spy detected on turn T is DESTROYED at the end of phase 23 on turn T+3, BEFORE vslot14 runs at phase 33. MEASURED lane AP 2026-09-09: nspy 1 -> 0 and spies2 emptied between the Frame-24 and Frame-25 autosaves, and 0x0088dc43 cost 0 on Frame 25 although vslot14 was still entered */ [verified]
|
||||
constexpr uint32_t SpyManager_DestroySpyById = 0x00438480;
|
||||
// site /* NOT A FUNCTION -- the `call Mars_RNG_Chance 0x008e6dd0` at 0x00840929 inside SpyManager_Slot13RngCallee 0x008408e0, return address 0x0084092e. Reached ONLY when the destroyed spy's owner has ServerPlayer.Species (+0x5c) == 6, i.e. MORRIGI (species table read from the save's own ISsp list: 0 Human, 1 Hiver, 2 Tarkas, 3 Liir, 4 _NPC, 5 Zuul, 6 Morrigi). Probability is the WIDENED FLOAT at 0x009e5ac4 = 0.75f -- the same four bytes SpyCraft_AccumulateDetectionOdds uses as its species-5 multiplier. Generator: `mov ecx,[eax+0x16c]` -- the STRATEGIC generator OBJECT (not the +4 sub-object). One word at 0 < p < 1. On success the function walks StrategyServer+0x54..+0x58 collecting every player with [+0xfb]==0 and [+0xf8]==0 that is neither the spy's owner nor the target system's owner, and picks one -- a FALSE FLAG; the true owner is never returned for a Morrigi spy. UNFIRED: lane AP measured twelve turns including the one turn the function ran and this branch was never taken, because its Species was 0. THE WORKLOAD THAT CLOSES IT is a Morrigi empire, not a different save of a Human one */ [verified]
|
||||
constexpr uint32_t SpyManager_AttributionRoll_MorrigiBranch = 0x00440929;
|
||||
// site /* NOT A FUNCTION -- the `call Mars_RNG_NextInt 0x004271c0` at 0x008409c7 inside SpyManager_Slot13RngCallee 0x008408e0, return address 0x008409cc. Generator: `mov ecx,[...+0x16c]; add ecx,4` -- the +4 SUB-OBJECT. Bound is `count - 1` passed BY POINTER at [ebp+8] (inclusive), so the index is uniform over the candidate list. TWO conditions past the Morrigi branch: the 0.75f roll at 0x00840929 must SUCCEED, and the candidate list must be NON-EMPTY -- `cmp ecx,eax; je 0x840a07` at 0x008409ac returns NULL with no draw when it is empty, which needs a third non-eliminated empire that is neither the spy's owner nor the target's. UNFIRED for the same reason as 0x00840929 */ [verified]
|
||||
constexpr uint32_t SpyManager_AttributionRoll_MorrigiPick = 0x004409c7;
|
||||
// site /* NOT A FUNCTION -- the `call Mars_RNG_Chance 0x008e6dd0` at 0x00840a3c inside SpyManager_Slot13RngCallee 0x008408e0, return address 0x00840a41. Taken for EVERY species except 6 (Morrigi), i.e. the `jne 0x840a29` at 0x00840910. Probability is the WIDENED FLOAT at 0x009e5ac0 = 0.25f. Generator: `mov ecx,[edx+0x16c]` -- the STRATEGIC generator object. One word. On success the function returns arg1, the spy's OWN owner: the victim correctly identifies who sent the spy, 25% of the time; otherwise NULL and the event names no empire. FIRED FOR THE FIRST TIME IN THIS CAMPAIGN, lane AP 2026-09-09: 1 call / 1 word / no_draw_calls=0 / strategic=true at ret_rva 0x00840a41, on the End Turn producing Frame sdet+3 (sdet=22, so Frame 25), from verify/results/saves/as-turn15-spydeployed.sav after seven End Turns. On that same turn the ENTRY PROBE on SpyManager_Slot13RngCallee 0x008408e0 read 1 -- its first non-zero ever, the complement of lane AS's demonstration that its zero said nothing about the subtree */ [verified]
|
||||
constexpr uint32_t SpyManager_AttributionRoll_DefaultBranch = 0x00440a3c;
|
||||
// site /* NOT A FUNCTION -- the `call Mars_RNG_Chance 0x008e6dd0` at 0x0088dc43 inside ServerSpyManager_vslot14 0x0088db80 (phase 33), return address 0x0088dc48. Decoded statically by lane AG (gate-indexed-rng-audit.md 3.2) with CORPUS COUNT 0 OF 22; FIRED FOR THE FIRST TIME lane AP 2026-09-09, 1 call / 1 word / strategic=true on each of Frames 19..24. Probability is spy.cmo (+0x28). MEASURED BEHAVIOUR: the bootstrap turn (Frame == tdep + 3, here Frame 18) sets spyon, runs the stage machine cm 0 -> 1 and cmo := 0.2f and DOES NOT DRAW; from the next turn the site costs one word until cmo reaches 1.0. cmo steps +0.2f per failure and resets to 0.2f on success. TWO CORRECTIONS TO LANE AG 3.2: (a) the CnTrd (+0xff) the stage machine consults at cm 1 -> 2/3 is the TARGET SYSTEM OWNER's, not the spy owner's -- measured, my player has CnTrd=False and the machine still took 1 -> 2 because Kepler's owner has CnTrd=True; (b) `ncp` is a scalar int counter of COMPLETED counter-mission stages (measured 0,0,1,1,2,2,3 in lock-step with cm), so the cm==0 branch's `ncp non-empty` test is `ncp != 0` -- and that branch was never reached, because vslot13 destroyed the spy at cm==4 */ [verified]
|
||||
constexpr uint32_t SpyManager_CounterMissionRollSite = 0x0048dc43;
|
||||
// site /* NOT A FUNCTION -- the `call Mars_RNG_NextFloat 0x0047d830` at 0x0078c97f inside SpyManager_ShipActionDeploySpyHandler 0x0078c930, return address 0x0078c984, ECX = GetGame()->RNG(+0x16c) + 4. ONE STRATEGIC-GENERATOR WORD PER APPLIED Deploy Spy ORDER, in the ProcessTurn bracket. Decoded but not measured by lane AS; MEASURED lane AP 2026-09-09 (1 call / 1 word / no_draw_calls=0 / strategic=true) and the resulting spy.cbh reproduced lane AS's 5.033599376678467 bit for bit across processes, builds and sessions. IN NO EARLIER INVENTORY -- not lane V2's eight, not lane I's 22, not lane AG's audit -- because it hangs off the stack-built ship-action table and leaves no static call edge. Its predicate is on the COMMAND STREAM, not on save fields */ [verified]
|
||||
constexpr uint32_t SpyManager_DeploySpyDrawSite = 0x0038c97f;
|
||||
// thiscall void __thiscall (TradeManager* this) // 214 B, 0x00841700-0x008417d5, decoded to the next function start. THE WRITER OF TradeManager+0x0c that lane AG's gate-indexed audit section 7 lists as NOT FOUND, and it settles AG section 3.3: (1) `lea ecx,[this+0x0c]; push 0; call 0x00459f70` clears the vector; (2) resizes it to `count(([this+4])+0x40 .. +0x44)` -- the SYSTEMS vector, the very container FUN_00841cd0 indexes with rt->trfr at 0x00841d36 -- growing with vector::_Insert_n 0x0050e6a0 (fill value NULL) or shrinking with erase 0x004ddfc0; (3) walks this->+0x1c (the SECTOR vector) x sector->+0x78 (that sector's MEMBER SYSTEM vector) and executes `mov [this->+0x0c + m->+0x5c * 4], sector` at 0x008417a8. ServerSystem+0x5c is the serialised `Idx` field (objects/layouts.md, grade verified), so this is literally `containingSector[system.Idx] = sector`. CONSEQUENCE: TradeManager+0x0c is a PER-SYSTEM table whose length is BY CONSTRUCTION the length of the container trfr indexes, so Slot13RngCalleeB's bounds check G_B1a can never reject a valid trfr and 0x0088b613 is reachable. Five callers; FUN_00858a10 is the DESERIALISER (ReadInt/ReadFloat throughout, calls this at 0x00858f07), so the table is rebuilt on load [verified]
|
||||
constexpr uint32_t TradeManager_RebuildSystemToSectorTable = 0x00441700;
|
||||
// thiscall void __thiscall (TradeManager* this, ServerSystem* sys) // 83 B, RET 4. Reads TradeManager+0x0c back the same way the writer fills it: bounds-checks `sys->+0x5c` (= the `Idx` tag) against `(this->+0x10 - this->+0x0c)/4` and indexes `((TradeSector**)this->+0x0c)[sys->Idx]`, then passes `sector + 0x78` -- the sector's MEMBER SYSTEM vector, the same vector the writer iterates -- to FUN_0059ec00 with the system. Independent confirmation that +0x5c on the indexing object and +0x78 on the table element are the two halves of one relation. Tail-calls TradeManager_RebuildSystemToSectorTable 0x00841700 [verified]
|
||||
|
|
@ -1307,6 +1323,8 @@ constexpr uint32_t SpyCraft_AccumulateDetectionOdds = 0x0041f570;
|
|||
constexpr uint32_t ServerSpyManager_DetectionRoll_DrawSite = 0x00487c8a;
|
||||
// __thiscall void (Game::SpyCraft* this) /* Called by ServerSpyManager_DeploySpy at 0x0088749e and by ServerSpyManager_vslot14 at 0x0088dc11 when the target system changes hands. NOT READ: only its position in those two chains and the fields its callers write immediately afterwards (sdo := 0, sdet := -1) are established here */ [mapped]
|
||||
constexpr uint32_t SpyCraft_ResetMission = 0x00438070;
|
||||
// data void*[7], the switch jump table SpeciesDef_InitTable 0x005453a0 dispatches through at 0x005453eb (`jmp [eax*4+0x545b60]`) to choose the `Species/%s/...` data-path prefix. READ AS BYTES, the seven entries are 0x005453f2 "Human", 0x005453f9 "Hiver", 0x00545400 "Tarkas", 0x00545407 "Liir", 0x0054541c "_NPC", 0x0054540e "Zuul", 0x00545415 "Morrigi" -- so the species enum is 0 Human, 1 HIVER, 2 TARKAS, 3 LIIR, 4 _NPC, 5 Zuul, 6 MORRIGI. NOTE the case bodies are NOT laid out in case order (index 4 jumps PAST index 5's body), so reading the disassembly top to bottom gives the wrong enum; only the table bytes settle it. SpeciesDef_Get 0x00545cc0 bounds-checks k <= 6, so there are exactly seven. This confirms lane AR's species 0 = Human / species 5 = Zuul from an independent direction and names 1, 2, 3, 4 and 6 for the first time [verified]
|
||||
constexpr uint32_t SpeciesDef_NameJumpTable = 0x00145b60;
|
||||
// thiscall void (Game::StrategyAIAgent::Streamable* this, Mars::Stream* s) // the body of every `Player.<id>.AIAgent` CD block. 36 wire items, NO conditionals: the only `if` the decompiler shows around `lnat` is an inlined std::vector destructor whose operator delete is marked noreturn, and both paths converge at 0x006c72e8. The agent object is *(this+4) [verified]
|
||||
constexpr uint32_t Game_StrategyAIAgent_Streamable_Write = 0x002c6f00;
|
||||
// thiscall bool (Game::StrategyAIAgent::Streamable* this, Mars::Stream* s) [verified]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue