b3 live: fpu_cw settles x87 (53-bit); correct Q8 - research target is state 3 so it never decays
This commit is contained in:
parent
83107f91f0
commit
8a6c0e8404
3 changed files with 37 additions and 7 deletions
|
|
@ -113,9 +113,17 @@ Also note the speed is per **segment** (closest approach of the whole chord), no
|
|||
|
||||
## Q8. Does `DecayAllResearch` also hit the current target?
|
||||
|
||||
Yes. `TechTree::ProcessResearch` 0x005876c0, loop 0x00587c20–0x00587c90: for **every** node with `state == 2 &&
|
||||
progress != 0`: `progress = max(0, progress − ftol(Cost(node) × 0.05))`. The just-funded node is not excluded; it only
|
||||
escapes by completing (state 4) in the same call. Net gain of the current tech per turn is `spend − 5 %·cost`.
|
||||
**No — corrected 2026-09-08 by the B3 live trace.** The loop reading is right: `TechTree::ProcessResearch`
|
||||
0x005876c0, loop 0x00587c20–0x00587c90, for every node with `state == 2 && progress != 0`,
|
||||
`progress = max(0, progress − ftol(Cost(node) × 0.05f))`. What was wrong is the assumption that the
|
||||
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
|
||||
|
||||
|
|
@ -166,3 +174,24 @@ 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
|
||||
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.
|
||||
|
||||
|
||||
## 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
|
||||
order counter. Node (0x34 B, ctor in 0x005822d0): `[0] TechDef*`, `+4..+0xc vector<Edge*> children`,
|
||||
`+0x14 state` (0 hidden, 1 parent researched, 2 available, 4 researched), `+0x18 costRP` (0x7fffffff = none;
|
||||
`+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;
|
||||
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`,
|
||||
`+0x20 + 4×species` float availability (0..1), `+0x40 child TechDef*`.
|
||||
|
|
@ -229,6 +229,7 @@ for each {node, points} in alloc: // one entry: the current Re
|
|||
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
|
||||
// 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)
|
||||
```
|
||||
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",
|
||||
"offset": "0x14",
|
||||
"convention": "offset",
|
||||
"prototype": "int state (0 hidden, 1 parent researched, 2 available, 4 researched); ctor writes 0",
|
||||
"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",
|
||||
"status": "verified",
|
||||
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
|
||||
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5 + B3 live trace 2026-09-08"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue