Compare commits
No commits in common. "ef3858c2ca5fd4aa22e803d384aa8d0db406cd62" and "83107f91f0f60a998762e13c977866c897fa31f2" have entirely different histories.
ef3858c2ca
...
83107f91f0
4 changed files with 9 additions and 39 deletions
|
|
@ -113,17 +113,9 @@ Also note the speed is per **segment** (closest approach of the whole chord), no
|
||||||
|
|
||||||
## Q8. Does `DecayAllResearch` also hit the current target?
|
## Q8. Does `DecayAllResearch` also hit the current target?
|
||||||
|
|
||||||
**No — corrected 2026-09-08 by the B3 live trace.** The loop reading is right: `TechTree::ProcessResearch`
|
Yes. `TechTree::ProcessResearch` 0x005876c0, loop 0x00587c20–0x00587c90: for **every** node with `state == 2 &&
|
||||||
0x005876c0, loop 0x00587c20–0x00587c90, for every node with `state == 2 && progress != 0`,
|
progress != 0`: `progress = max(0, progress − ftol(Cost(node) × 0.05))`. The just-funded node is not excluded; it only
|
||||||
`progress = max(0, progress − ftol(Cost(node) × 0.05f))`. What was wrong is the assumption that the
|
escapes by completing (state 4) in the same call. Net gain of the current tech per turn is `spend − 5 %·cost`.
|
||||||
funded node is in state 2. **The selected research target carries state 3**, so the equality test skips
|
|
||||||
it and the current tech keeps its whole gain; only *idle* partially-researched techs decay. Observed
|
|
||||||
directly in `b3-trace-golden.jsonl` / `b3-compare.jsonl`: the funded nodes (144, 142, 9) are state 3
|
|
||||||
before and after, and one traced tree's states are 164×0, 7×1, 23×2, 1×3, 22×4. Net gain of the
|
|
||||||
current tech per turn is therefore `spend`, not `spend − 5 %·cost`.
|
|
||||||
|
|
||||||
(State 3 is presumably "available and selected"; nothing in the traced runs had a state-2 node with
|
|
||||||
non-zero progress, so the decay branch itself is still unexercised behaviourally.)
|
|
||||||
|
|
||||||
## Extras resolved on the way
|
## Extras resolved on the way
|
||||||
|
|
||||||
|
|
@ -174,24 +166,3 @@ below is now in `ghidra/addresses.json` (status `verified`) and folded into
|
||||||
created without `FPU_PRESERVE` would leave 24-bit precision). It changes only the last bit of
|
created without `FPU_PRESERVE` would leave 24-bit precision). It changes only the last bit of
|
||||||
a draw (0.094 % of words) and of the odds. The B3 shim records the control word with every
|
a draw (0.094 % of words) and of the odds. The B3 shim records the control word with every
|
||||||
call, so the first trace settles it.
|
call, so the first trace settles it.
|
||||||
|
|
||||||
|
|
||||||
## B3 live verification (2026-09-08) — three more facts
|
|
||||||
|
|
||||||
From the `ProcessResearch` trace/compare/replace runs (engine repo `docs/B3.md`; artefacts
|
|
||||||
`/srv/re-lab/shim/traces/b3-*`):
|
|
||||||
|
|
||||||
* **x87 precision control is 53-bit.** `fnstcw` inside the hooked call returns `0x127f` (PC = 10b =
|
|
||||||
double, RC = nearest; bit 12 is the legacy infinity-control flag). At DLL init it is `0x027f`.
|
|
||||||
So the FPU is *not* left in single precision by the D3D9 device, and the `NextFloat` product
|
|
||||||
rounds to double before the caller narrows it to float32. The 24-bit contingency is moot.
|
|
||||||
* **`ServerPlayer::OnTechResearched` can consume an RNG draw.** Two techs completed during the
|
|
||||||
compare run; one cascade consumed no word and the other consumed exactly one more than the
|
|
||||||
research arithmetic accounts for. The draw is inside the owner's tech-effect callback, not in
|
|
||||||
`SetResearched` itself. Relevant to the B2 lane: at least one strategic effect rolls.
|
|
||||||
* **`EVENT_RESEARCH_OVERBUDGET` is raised in the same branch that sets `node.flag = 2`**, with
|
|
||||||
`EvDsc "Research Over Budget"`, `EvMsg "Research for <tech> has gone overbudget."`,
|
|
||||||
`EvImg "EVENT_RESEARCH_OVERBUDGET"`, `EvAct 1`, `EvPos {inf,inf,inf}`, and it bumps the player's
|
|
||||||
`EvNxID`. A replace-mode run that sets only the flag differs from the oracle by exactly this one
|
|
||||||
event and nothing else in 40,300 save items.
|
|
||||||
* Tech-tree size in this game: **293 nodes** per player tree.
|
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ not "bTurnDone".
|
||||||
|
|
||||||
TechTree object: `+0xc` owner `ServerPlayer*`, `+0x10/+0x14` `vector<Node*>` indexed by tech id, `+0x20` research
|
TechTree object: `+0xc` owner `ServerPlayer*`, `+0x10/+0x14` `vector<Node*>` indexed by tech id, `+0x20` research
|
||||||
order counter. Node (0x34 B, ctor in 0x005822d0): `[0] TechDef*`, `+4..+0xc vector<Edge*> children`,
|
order counter. Node (0x34 B, ctor in 0x005822d0): `[0] TechDef*`, `+4..+0xc vector<Edge*> children`,
|
||||||
`+0x14 state` (0 hidden, 1 parent researched, 2 available, **3 available *and selected* as the current research target**, 4 researched — state 3 confirmed by the B3 live trace and it is why the decay loop, which tests `== 2`, skips the funded node), `+0x18 costRP` (0x7fffffff = none;
|
`+0x14 state` (0 hidden, 1 parent researched, 2 available, 4 researched), `+0x18 costRP` (0x7fffffff = none;
|
||||||
min over researched parents' edge cost), `+0x1c progress`, `+0x20 turnAvailable`, `+0x24 turnResearched`,
|
min over researched parents' edge cost), `+0x1c progress`, `+0x20 turnAvailable`, `+0x24 turnResearched`,
|
||||||
`+0x28 order`, `+0x2c flag` (1 default, 0 "completed early", 2 "over-budget notified"). Edge: `+0x1c RP cost`,
|
`+0x28 order`, `+0x2c flag` (1 default, 0 "completed early", 2 "over-budget notified"). Edge: `+0x1c RP cost`,
|
||||||
`+0x20 + 4×species` float availability (0..1), `+0x40 child TechDef*`.
|
`+0x20 + 4×species` float availability (0..1), `+0x40 child TechDef*`.
|
||||||
|
|
@ -229,7 +229,6 @@ for each {node, points} in alloc: // one entry: the current Re
|
||||||
SetResearched(node.def, 2)
|
SetResearched(node.def, 2)
|
||||||
}
|
}
|
||||||
for each node with state==2 && progress!=0: progress = max(0, progress - ftol(Cost(node)*0.05f)) // 0x009e5060 = (double)0.05f
|
for each node with state==2 && progress!=0: progress = max(0, progress - ftol(Cost(node)*0.05f)) // 0x009e5060 = (double)0.05f
|
||||||
// NB the funded node is state 3, so it is NOT in this sweep (B3 live trace; formula-gaps Q8 corrected)
|
|
||||||
for each node with state==2 && turnAvailable == currentTurn && parent researched → EVENT_TECHS_UNLOCKED (names list)
|
for each node with state==2 && turnAvailable == currentTurn && parent researched → EVENT_TECHS_UNLOCKED (names list)
|
||||||
```
|
```
|
||||||
Net effect: the current tech gains `spend − 5 %·cost`; every other partially researched tech decays 5 % of its cost per turn.
|
Net effect: the current tech gains `spend − 5 %·cost`; every other partially researched tech decays 5 % of its cost per turn.
|
||||||
|
|
|
||||||
|
|
@ -990,9 +990,9 @@
|
||||||
"name": "TechNode_off_State",
|
"name": "TechNode_off_State",
|
||||||
"offset": "0x14",
|
"offset": "0x14",
|
||||||
"convention": "offset",
|
"convention": "offset",
|
||||||
"prototype": "int state (0 hidden, 1 parent researched, 2 available, 3 available AND selected as the current research target, 4 researched); ctor writes 0. The decay sweep tests == 2, so a state-3 node never decays -- confirmed by the B3 live trace",
|
"prototype": "int state (0 hidden, 1 parent researched, 2 available, 4 researched); ctor writes 0",
|
||||||
"status": "verified",
|
"status": "verified",
|
||||||
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5 + B3 live trace 2026-09-08"
|
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "TechNode_off_CostRP",
|
"name": "TechNode_off_CostRP",
|
||||||
|
|
@ -1691,4 +1691,4 @@
|
||||||
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
|
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).
|
// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).
|
||||||
// Source: sots-re ghidra/addresses.json @ 8a6c0e8, generated 2026-09-08 by tools/gen_addresses.py
|
// Source: sots-re ghidra/addresses.json @ ead46e2, generated 2026-09-08 by tools/gen_addresses.py
|
||||||
// Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated).
|
// Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated).
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
@ -253,7 +253,7 @@ constexpr uint32_t TechTree_off_Nodes = 0x00000010;
|
||||||
constexpr uint32_t TechNode_size = 0x00000034;
|
constexpr uint32_t TechNode_size = 0x00000034;
|
||||||
// offset TechDef* def; *(int*)def is the tech id used to index TechTree_off_Nodes [verified]
|
// offset TechDef* def; *(int*)def is the tech id used to index TechTree_off_Nodes [verified]
|
||||||
constexpr uint32_t TechNode_off_Def = 0x00000000;
|
constexpr uint32_t TechNode_off_Def = 0x00000000;
|
||||||
// offset int state (0 hidden, 1 parent researched, 2 available, 3 available AND selected as the current research target, 4 researched); ctor writes 0. The decay sweep tests == 2, so a state-3 node never decays -- confirmed by the B3 live trace [verified]
|
// offset int state (0 hidden, 1 parent researched, 2 available, 4 researched); ctor writes 0 [verified]
|
||||||
constexpr uint32_t TechNode_off_State = 0x00000014;
|
constexpr uint32_t TechNode_off_State = 0x00000014;
|
||||||
// offset int costRP, INT_MAX = no researched parent yet; ctor writes 0x7fffffff [verified]
|
// offset int costRP, INT_MAX = no researched parent yet; ctor writes 0x7fffffff [verified]
|
||||||
constexpr uint32_t TechNode_off_CostRP = 0x00000018;
|
constexpr uint32_t TechNode_off_CostRP = 0x00000018;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue