rng: verify Seed/Twist/NextFloat signatures; lazy twist confirmed; flag x87 float-parity risk

This commit is contained in:
alex 2026-09-07 22:28:56 -04:00
parent 87a39b6731
commit cea76e2771
4 changed files with 15 additions and 14 deletions

View file

@ -56,4 +56,4 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`).
| P2-B1 ComputeBudget (behavioral) | phase2 | backlog | — | 0% | 2026-09-08 | FIRST BEHAVIORAL BRICK: hook ServerPlayer::ComputeBudget 0x00863030 (verified thiscall, pure, 8 calls/turn) vs game/sim/economy.cpp; workload = ref-turn2.sav + End Turn |
| P2-B2 OnTechResearched (behavioral) | phase2 | backlog | — | 0% | 2026-09-08 | hook ServerPlayer::OnTechResearched 0x00891790 (verified) vs the tech-effects layer from sim-pin |
| P2-B3 ProcessResearch (behavioral, RNG) | phase2 | backlog | — | 0% | 2026-09-08 | hook TechTree::ProcessResearch 0x005876c0 (verified); snapshot MT19937 state pre-call and drive ours from a copy -> validates RNG + odds + Zuul double roll together |
| RNG signatures (Ghidra) | meta | backlog | — | 0% | 2026-09-08 | RNG_Seed/Twist/NextFloat/NextInt still 'unverified' in the contract; verify before hooking (M0 lesson). Also StrategyServer::ProcessTurn/BeginProcessTurn signatures for the later whole-turn compare |
| RNG signatures (Ghidra) | meta | mapped | high | 75% | 2026-09-08 | Seed/Twist/NextFloat verified (own pass): lazy twist, left@+0x9c4, NextFloat via x87 = float-parity risk. NextInt still unverified |

View file

@ -40,3 +40,4 @@ Each links to the finding that raised it. Promoted to backlog or closed by **re-
- **RESOLVED: SAVE_FORMAT tag names** — all corrections confirmed by bytes and applied to the Python reader + spec (§10). Residual doc debt: `findings/objects/save-editor-structs.md` and `verify/design-rules/SHIP_DESIGN_RULES.md` still quote R1's `ontF/faiDes/nextId` spellings (R1-provenance; annotate rather than rewrite). (from [[SAVE_FORMAT]])
- **Notes disagree on MSVC-2010 `std::string` layout** — `struct-recovery.md` §0 vs `turn-spine.md` §1.1 give different offsets; pin both from the `_Myres >= 16` SSO branch in `Stream::WriteString` and correct the loser. (from [[re-windows-2000s-howto]])
- **RESOLVED: formula gaps (all 8)** — see [[formula-gaps]]; game/sim's low-confidence functions can now be pinned. **RESOLVED: std::string** = `_Bx@0,_Mysize@0x10,_Myres@0x14,_Alval@0x18`, sizeof 0x1c (struct-recovery §0 right; turn-spine §1.1 WRONG — annotated). **CORRECTION:** the 116-entry table @0x00a19718 is a name-membership list, not the effects table; use `g_TechIdNames` (196) + `OnTechResearched`. Open: values of the 6-entry AI-tech bonus table (0x00a17888); producer of ServerPlayer +0x224/+0x228/+0x22c beyond the setup-record copy (0x0077b620). (from [[tech-effects]])
- **RESOLVED: RNG semantics** — `Seed` is `thiscall(this, uint32)` RET 4 (MT19937 init + immediate twist); `Twist` takes `this` in ECX only; **twist is LAZY** (`if (left==0) Twist()` inside the draw), `left` lives at `+0x9c4`, confirming the save blob layout. **NEW RISK: `NextFloat` returns via the x87 FPU (80-bit extended)** — our `(float)(y*2^-32)` may not be bit-identical; B3 must compare the float mapping explicitly and we need the float-parity policy from the RE how-to before the x64 standalone. `RNG_NextInt` signature still unverified. (own Ghidra pass)

View file

@ -174,25 +174,25 @@
"name": "RNG_Twist",
"addr": "0x00426e00",
"convention": "thiscall",
"prototype": "void (RNG* this)",
"status": "unverified",
"source": "findings/subsystems/strategic-turn-internals.md#rng"
"prototype": "void (RNG* this) // this in ECX, no stack args, RET 0; 227/396 split",
"status": "verified",
"source": "own Ghidra pass 2026-09-08 (RngSig)"
},
{
"name": "RNG_Seed",
"addr": "0x0049fdf0",
"convention": "thiscall",
"prototype": "void (RNG* this, uint32_t seed)",
"status": "unverified",
"source": "findings/subsystems/strategic-turn-internals.md#rng"
"prototype": "RNG* (RNG* this, uint32_t seed) // RET 4; MT19937 init mt[i]=(mt[i-1]^(mt[i-1]>>30))*0x6c078965+i for i=1..623, then twists. this: vftable@0, mt[624]@+4, left@+0x9c4",
"status": "verified",
"source": "own Ghidra pass 2026-09-08 (RngSig)"
},
{
"name": "RNG_NextFloat",
"addr": "0x0047d830",
"convention": "thiscall",
"prototype": "float (RNG* this)",
"status": "unverified",
"source": "findings/subsystems/strategic-turn-internals.md#rng"
"prototype": "float (RNG* this) // returns via x87 (float10 in decompile) -- FLOAT PARITY RISK; lazy twist: if (left==0) Twist()",
"status": "verified",
"source": "own Ghidra pass 2026-09-08 (RngSig)"
},
{
"name": "RNG_NextInt",

View file

@ -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 @ b24f13c, generated 2026-09-07 by tools/gen_addresses.py
// Source: sots-re ghidra/addresses.json @ 87a39b6, generated 2026-09-07 by tools/gen_addresses.py
// Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated).
#pragma once
#include <cstdint>
@ -49,11 +49,11 @@ constexpr uint32_t StrategyServer_Read = 0x003d27a0;
constexpr uint32_t StrategyServer_Write = 0x0039fa70;
// thiscall void (Stream* this, const char* tag, NetworkObject* obj) [unverified]
constexpr uint32_t Stream_WriteNetworkObjectId = 0x00416490;
// thiscall void (RNG* this) [unverified]
// thiscall void (RNG* this) // this in ECX, no stack args, RET 0; 227/396 split [verified]
constexpr uint32_t RNG_Twist = 0x00026e00;
// thiscall void (RNG* this, uint32_t seed) [unverified]
// thiscall RNG* (RNG* this, uint32_t seed) // RET 4; MT19937 init mt[i]=(mt[i-1]^(mt[i-1]>>30))*0x6c078965+i for i=1..623, then twists. this: vftable@0, mt[624]@+4, left@+0x9c4 [verified]
constexpr uint32_t RNG_Seed = 0x0009fdf0;
// thiscall float (RNG* this) [unverified]
// thiscall float (RNG* this) // returns via x87 (float10 in decompile) -- FLOAT PARITY RISK; lazy twist: if (left==0) Twist() [verified]
constexpr uint32_t RNG_NextFloat = 0x0007d830;
// thiscall uint32_t (RNG* this) [unverified]
constexpr uint32_t RNG_NextInt = 0x000271c0;