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

88 lines
3.9 KiB
C++

// The byte-level adapter between a live `ServerPlayer` and game/effects' PlayerEconomyState,
// plus the region table and struct describers the B2 hook declares.
//
// Split out of the hook (as B1 split `budget_inputs`) so that every offset, every float32
// round-trip and every field the tech-effect callback writes can be exercised on the host
// against a synthetic player buffer, without the game and without a VM window. The hook
// itself then only has to resolve the tech id and delegate to the game's tech tree.
//
// The regions do not cover the whole object: they are exactly the field groups the callback
// writes, one region each, so a divergence names the field. `Views` is the indirection that
// lets the same code run against the scratch copies (compare mode) and against the live
// object (replace mode).
#pragma once
#include <cstddef>
#include <cstdint>
#include "game/effects/tech_effects.h"
#include "game/sim/species.h"
#include "shim/trace/tracer.h"
namespace shim::hooks::techfx {
enum RegionId {
R_SUIT = 0, // SuitTol, MaxOH
R_RESMOD, // ResMod
R_ABILITIES, // RebAI .. harcc
R_MODIFIERS, // pddm .. CstT
R_MASKS, // design-option masks A / B
R_TRANSLATION, // sticky translation mask
R_VACCINES, // HasVac, HasImm, NPTrk
R_RESEARCH_TARGET, // ResT
R_NODEBORE_PTR, // pointer to the node-bore block
R_INCMOD, // IncMod
R_CAPTURE, // cdp
R_SPECIES_FLAGS, // flags[7] + count
R_ROLL, // pending plague-cure roll + AIRebellion*
R_NODEBORE, // the 3-word node-bore block itself (behind R_NODEBORE_PTR)
R_RNG, // the strategic Mars::RNG object (behind player+8 -> server+0x16c)
kRegionCount
};
struct RegionDef {
const char* name;
std::uint32_t off; // from the ServerPlayer base; meaningless for R_NODEBORE
std::size_t size;
trace::Tv (*describe)(const void* data, std::size_t size, unsigned inline_max);
};
extern const RegionDef kRegions[kRegionCount];
// The last byte the region table reaches, for one readability check up front.
std::size_t PlayerSpan();
// Where each region's bytes live for this call. A null entry means "not present": only
// R_NODEBORE is ever legitimately null (no bore drive researched yet).
struct Views {
void* base[kRegionCount] = {};
// The generator's live address, for the describer's next-pointer arithmetic: `next` is a
// heap address, so it is reported as its index into mt, which is what it means and what
// survives being rewritten by a reimplementation.
void* liveRng = nullptr;
// Point every region at its offset inside a live (or synthetic) player object, and
// R_NODEBORE at whatever the block pointer holds.
static Views OverPlayer(void* player);
};
// Seed our model from the pre-call field values. `species` is read from the live object by
// the caller because no region covers it (the callback never writes it).
sots::effects::PlayerEconomyState ReadPlayerState(const Views& v, sots::sim::Species species);
// Write back exactly the words the callback writes. The node-bore block is only written
// where it already exists.
void WritePlayerState(const Views& v, const sots::effects::PlayerEconomyState& s);
// The callback's first act when the completing definition is the current research target:
// clear the target and the pending-roll byte. Returns whether the roll was pending (the
// roll itself draws randomness, so a differential run reports it instead of running it).
bool ClearResearchTargetIfMatched(const Views& v, const void* def);
// The two design-option masks, written straight into R_MASKS.
void WriteDesignOptionMasks(const Views& v, std::uint32_t a, std::uint32_t b);
// The describer needs the live generator address; set it before a snapshot is described.
void SetLiveRngBase(const void* p);
} // namespace shim::hooks::techfx