Read TechTree::SetResearched (0x00581e10) end to end while wiring the research event
posts into ours (sots-engine wip/post-events). Two things it settles:
- SetResearched invokes the owner callback as vft+0x10(def, (flags>>2)&1), so bit 2 of
flags IS OnTechResearched's "silent" argument. ProcessResearch passes flags = 2, so
silent is FALSE and the completion event IS posted. Previously this was only
inferable from lane R's observation that a completion call moved EvNxID by two.
- The unlock cascade in full: the node's child edges set child state 0->1 and
child.costRP = min(child.costRP, edge.costRP); then a whole-tree sweep sets state 2
and stamps turnAvailable from ModCount ONLY when it currently reads -1 (so it is
sticky, and a node re-entering state 2 is not re-announced), and recurses into any
zero-cost available node. Both loops test the node itself, not a parent -- there is
no parent clause anywhere in the function, confirming lane E's correction.
Also records what is and is not known about vector<ObservedTech> at ServerPlayer+0x274:
the on-disk element shape is confirmed against turn3-state.sav (its string holds a tech
name), while sizeof(ObservedTech) and the append call site are still unpinned. The
cheapest route to the stride is now a measurement from the new observed_techs region,
not a search -- find-constant-uses does not index lea displacements.
New entries: ServerPlayer_off_ObservedTechs, TechNode_off_TurnAvailable /
TurnResearched / Order / Children, TechEdge_off_CostRP / ChildDef,
TechTree_off_OrderCounter, TechTree_SetResearched_flag_Force / _flag_Silent.
Generated header regenerated with tools/gen_addresses.py (386 -> 396 entries).