sots-engine/src/shim/hooks/research.h

62 lines
3.2 KiB
C++

// Hook descriptor for the per-turn research pass (B3):
//
// Game::TechTree::ProcessResearch(this, rng, alloc, overbudget)
//
// A verified __thiscall in sots_addresses.h, so it goes through Hook<> with
// CallConv::Thiscall. It is called once per player per turn, from ServerPlayer::ProcessTurn,
// and it is the only caller. Its four parameters are all confirmed at that call site: the
// second is the strategy server's Mars::RNG *object* (the function re-bases it to the state
// block with +4 before every draw), the third the {tech, points} allocation the budget built,
// the fourth an accumulator for the points that would not fit under the 150 % cap.
//
// Why this call is worth a compare: it exercises the MT19937, the completion-odds formula and
// the Zuul double roll in one place, and its RNG consumption is observable. So the declared
// regions are
//
// rng the whole 0x9cc-byte generator object -- mt[624] plus the stream position
// overbudget the caller's accumulator
// node[i] every non-null TechNode in the tree, 0x34 bytes each
//
// and the compare is run with our own MT19937 seeded by load_state() from the *pre-call*
// snapshot, so both implementations read the same stream. If the post-call generator state
// matches as well, we consumed the same words in the same order -- which is the real evidence.
//
// Scope of `ours`: exactly what ProcessResearch itself writes. On the turn a tech completes,
// the original goes on to call TechTree::SetResearched, which stamps the turn/order words,
// walks the unlock cascade into the child nodes and invokes the owner's tech-effect callback.
// None of that is reproduced (it is its own milestone, and the callback would write live
// player state that compare mode must never touch), so a completion record is expected to
// diverge in those fields and only in those fields. The effective cost of a node is taken
// from the game's own TechTree::Cost, which is read-only -- the cost multiplier is a separate,
// lower-confidence formula and not what this milestone is measuring.
#pragma once
#include <cstdint>
#include <tuple>
#include <vector>
#include "shim/trace/hook.h"
namespace shim::hooks {
struct TechTreeProcessResearchHook {
static constexpr const char* name = "Game::TechTree::ProcessResearch";
static constexpr trace::CallConv conv = trace::CallConv::Thiscall;
using Ret = void;
// this (TechTree*), rng (Mars::RNG*), alloc (vector<{TechDef*,int}>*), overbudget (int*)
using Args = std::tuple<void*, void*, void*, int*>;
static void describe_args(std::vector<trace::Tv>& out, void* tree, void* rng, void* alloc,
int* overbudget);
static void regions(std::vector<trace::Region>& out, void* tree, void* rng, void* alloc,
int* overbudget);
static Args rebind(trace::Scratch& s, void* tree, void* rng, void* alloc, int* overbudget);
static void ours(void* tree, void* rng, void* alloc, int* overbudget);
static trace::HookPolicy policy() { return trace::HookPolicy{}; }
};
// Process facts the hook needs (exe base for the RVAs, a line logger). Call once before
// installing.
void init_research(std::uintptr_t exe_base, void (*log_line)(const char* line));
} // namespace shim::hooks