THE HUNT (the deterministic rig repro made it a two-hour arc):
1. cdb write-watch armed from the Torso ctor (bp plants `ba w4 this+0x21c`
per torso -- ASLR-proof). The poison reproduced (atUpd=-1250 this run;
-3750 and int-15-as-float before)... and the watch stayed SILENT. Nobody
writes the garbage. The field is never INITIALIZED.
2. Confirmed in our ctor reconstruction: it inits every neighbour but skips
targetTwist @0x218 and twistAtUpdate @0x21C (the function that zeroes
them is a death-reset handler, not the ctor).
3. Confirmed in the BINARY: the real ctor @004b6b0c contains no store to
either offset -- a genuine 1995 latent bug (uninitialized read on the
copy path).
4. Why the pod never showed it: MemoryBlock arenas carve fresh OS-zeroed
pages, one pool per type, low churn -- first allocations read as zero.
The engine's own DEBUG_NEW_ON NaN-fill proves the developers knew the
hazard class. Why WE show it: mechrecon.hpp's Memory::Allocate shim
("a plain heap allocation is behaviour-equivalent" -- false) recycles
dirty heap. Replicant torsos spawned with garbage twist targets; the
limit clamp turned any garbage into FULL TWIST -- rendered "twisted full
right while not using TT" to every peer (playtest night 4, 3 reporters).
THE FIX -- environmental, class-wide, byte-faithful to the binary's code:
Memory::Allocate / AllocateArray / Alloc now ZERO-FILL, reproducing the pod's
EFFECTIVE allocation semantics for every never-stored field in every
reconstructed factory at once (Torso, Reservoir, all of them). No ctor gains
stores the binary lacks.
PROVEN on the rig: the replicant thor now spawns cur=0 target=0 atUpd=0
(was cur=-3.31613 = hard against the limit). Teardown clean.
Ships with part 1 (the dead-reckoning clock fix) in the next zip. Remaining
on #67 for the next games night: live confirmation that twist TRACKING looks
right in play, and whether the fire-from-centre facet (very plausibly the same
never-stored-field class, now zeroed) is cured with it.
Tools kept: scratchpad/torso_watch.cdb + rig_watch.ps1 (the ctor-armed
write-watch pattern -- reusable for any "who wrote this field" hunt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
613 lines
29 KiB
C++
613 lines
29 KiB
C++
//===========================================================================//
|
|
// File: mechrecon.hpp //
|
|
// Project: BattleTech Brick: Entity Manager //
|
|
// Contents: Reconstruction support shim for the "mech core" family //
|
|
// (mech / mech2..4 / mechsub / mechdmg / dmgtable). //
|
|
//---------------------------------------------------------------------------//
|
|
// This header is NOT part of the shipped game. It supplies the small set of //
|
|
// reconstruction helpers, decompiler-artifact globals and proxy stand-in //
|
|
// types that the recovered mech-core bodies reference but the modern engine //
|
|
// headers do not provide under those exact names. It exists only so the //
|
|
// reconstructed translation units COMPILE against the WinTesla engine; the //
|
|
// proxies preserve the call structure of the recovered code (no logic is //
|
|
// deleted). Behaviour-equivalent semantics are filled where trivial. //
|
|
// //
|
|
// MUST be included AFTER <bt.hpp> / the engine headers (so Scalar, Quaternion //
|
|
// Vector3D, AffineMatrix, CString, Subsystem ... are already declared). //
|
|
//===========================================================================//
|
|
|
|
#if !defined(MECHRECON_HPP)
|
|
# define MECHRECON_HPP
|
|
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
// Quaternion / Vector3D / AffineMatrix used below (kIdentityQuat etc.). Include
|
|
// directly so mechrecon.hpp is self-sufficient regardless of the includer's order
|
|
// (myomers.cpp pulls this via mechsub.hpp BEFORE bt.hpp). ROTATION.hpp DEFINES
|
|
// Quaternion (affnmtrx.hpp only forward-declares it).
|
|
#include <ROTATION.hpp>
|
|
#include <affnmtrx.hpp>
|
|
|
|
//===========================================================================//
|
|
// Universal decompiler-artifact value.
|
|
//
|
|
// Stands in for the result of an unresolved FUN_/helper artifact. Converts
|
|
// to any scalar, pointer or default-constructible type so the recovered
|
|
// expression typechecks.
|
|
//===========================================================================//
|
|
struct Recon
|
|
{
|
|
Recon() {}
|
|
template<class T> Recon(const T &) {}
|
|
template<class T> operator T() const { return T(); }
|
|
};
|
|
|
|
//===========================================================================//
|
|
// Memory shim -- the decomp's "Memory::Allocate / Memory::Free" map onto the
|
|
// engine's per-object MemoryBlock allocators.
|
|
//
|
|
// ZERO-FILLED ON PURPOSE (#67 root cause, 2026-07-27). "A plain heap
|
|
// allocation is behaviour-equivalent" turned out FALSE: the 1995 binary
|
|
// leaves fields uninitialized that its ctors never store (proven: the Torso
|
|
// ctor @004b6b0c stores neither targetTwist @0x218 nor twistAtUpdate @0x21C),
|
|
// and on the POD that was latent -- MemoryBlock arenas carve fresh OS-zeroed
|
|
// pages, one pool per type, low churn, so first allocations read as zero
|
|
// (the engine's own DEBUG_NEW_ON NaN-fill shows the developers knew the
|
|
// hazard). Our ::operator new recycles dirty heap: replicant torsos spawned
|
|
// with garbage twist targets, clamped to the limit -- rendered "twisted full
|
|
// right" to everyone (playtest night 4, three reporters). Zero-filling
|
|
// reproduces the pod's EFFECTIVE allocation semantics for the whole class of
|
|
// never-stored fields at once; the ctors stay byte-faithful to the binary.
|
|
//===========================================================================//
|
|
namespace Memory
|
|
{
|
|
inline void *Allocate(size_t bytes)
|
|
{ void *p = ::operator new(bytes); ::memset(p, 0, bytes); return p; }
|
|
inline void *AllocateArray(size_t bytes)
|
|
{ void *p = ::operator new(bytes); ::memset(p, 0, bytes); return p; }
|
|
inline void Free(void *where) { ::operator delete(where); }
|
|
}
|
|
|
|
inline void *Alloc(size_t bytes)
|
|
{ void *p = ::operator new(bytes); ::memset(p, 0, bytes); return p; }
|
|
inline void *AllocateArray(size_t bytes)
|
|
{ void *p = ::operator new(bytes); ::memset(p, 0, bytes); return p; }
|
|
|
|
//===========================================================================//
|
|
// Ref-count / name helpers (decomp artifacts).
|
|
//===========================================================================//
|
|
template<class A> inline void *NewRefCount(A) { return 0; }
|
|
inline void *NewRefCounted() { return 0; }
|
|
template<class A,class B> inline void BindName(A, B) {}
|
|
template<class A,class B,class C> inline void BindName(A, B, C) {}
|
|
template<class A> inline void ReleaseRefCounted(A) {}
|
|
template<class A> inline void ZeroBlock(A, int) {}
|
|
template<class A,class B> inline void Assign(A, B) {}
|
|
template<class A> inline Recon NewTableEntry(A) { return Recon(); }
|
|
|
|
//===========================================================================//
|
|
// Small dynamic string used by the resource-table builders
|
|
// ( TextString("Layer") + itoa(n) ).
|
|
//===========================================================================//
|
|
struct TextString
|
|
{
|
|
char buf[64];
|
|
TextString() { buf[0] = 0; }
|
|
TextString(const char *s) { buf[0] = 0; if (s) { strncpy(buf, s, 63); buf[63] = 0; } }
|
|
TextString &operator+=(const char *s) { if (s) strncat(buf, s, 63 - strlen(buf)); return *this; }
|
|
TextString operator+ (const char *s) const { TextString t(*this); t += s; return t; }
|
|
operator const char *() const { return buf; }
|
|
};
|
|
|
|
//===========================================================================//
|
|
// Misc free-function artifacts (variadic -> Recon).
|
|
//===========================================================================//
|
|
template<class...A> inline Recon WorldTransform(A&&...) { return Recon(); }
|
|
template<class...A> inline Recon ResourceFind(A&&...) { return Recon(); }
|
|
template<class...A> inline Recon LoadSkeleton(A&&...) { return Recon(); }
|
|
template<class...A> inline int ResourceManagerHas(A&&...) { return 0; }
|
|
template<class...A> inline void GenerateFaultDefault(A&&...) {}
|
|
// NOTE: renamed from Random() to avoid colliding with the engine's
|
|
// `extern RandomGenerator Random;` (RANDOM.h). Reconstructed code that wants the
|
|
// original zero-arg unit-random (FUN_00408050 -> Scalar in [0,1)) uses RandomUnit().
|
|
// Backed by the engine RNG: `RandomGenerator::operator Scalar()` returns a value
|
|
// in [0,1) (RANDOM.h), exactly matching FUN_00408050's contract. Variadic tail
|
|
// kept so call sites (dmgtable: roll; theta*TwoPi) bind unchanged; Scalar return
|
|
// satisfies the dmgtable Scalar consumers.
|
|
#include <RANDOM.hpp> // fwd/ -> engine RANDOM.h: extern RandomGenerator Random;
|
|
template<class...A> inline Scalar RandomUnit(A&&...) { return (Scalar)Random; }
|
|
|
|
//===========================================================================//
|
|
// String helpers (decomp inlined strcpy/strcat/stricmp etc.).
|
|
//===========================================================================//
|
|
inline void Strcpy(char *d, const char *s) { while ((*d++ = *s++) != 0) {} }
|
|
inline void Strcat(char *d, const char *s) { while (*d) ++d; while ((*d++ = *s++) != 0) {} }
|
|
inline int Strcmp(const char *a, const char *b) { return strcmp(a, b); }
|
|
|
|
// Env gate with a DEFAULT: the authentic reconstructed paths (gait cutover/state
|
|
// machine, collision+gravity, real controls) are ON by default; set the var to
|
|
// "0" to fall back to the historical bring-up path (e.g. BT_GAIT_SM=0).
|
|
inline int BTEnvOn(const char *name, int on_by_default)
|
|
{
|
|
const char *v = getenv(name);
|
|
return v ? (v[0] != '0') : on_by_default;
|
|
}
|
|
|
|
// THE AUTHENTIC GROUND MODEL gate (task #15 -- the 1995 probe/snap/response
|
|
// ground block, ground-model-decode workflow). ONE function so the Mech ctor
|
|
// (template lift, mech.cpp), the per-frame ground block and the ProcessCollision
|
|
// override (mech4.cpp) can never disagree: every TU reads the same env exactly
|
|
// once. DEFAULT ON (verification passed: grass/arena/dbase census + anti-ladder
|
|
// soak + slope tracking + render-side screenshots + combat & heapcheck
|
|
// regressions -- see the task-#15 ground commit). BT_GROUND_REAL=0 falls back
|
|
// to the bring-up gravity+push-out+clamp baseline.
|
|
inline int GroundReal()
|
|
{
|
|
static const int v = BTEnvOn("BT_GROUND_REAL", 1);
|
|
return v;
|
|
}
|
|
inline int GroundLog()
|
|
{
|
|
static const int v = getenv("BT_GROUND_LOG") ? 1 : 0;
|
|
return v;
|
|
}
|
|
inline int Streq (const char *a, const char *b) { return strcmp(a, b) == 0; }
|
|
|
|
//===========================================================================//
|
|
// Multi-level status/alarm indicator.
|
|
//
|
|
// The recovered code names this AlarmIndicator and drives it with a level
|
|
// count ctor + SetLevel(level). In the WinTesla engine the equivalent is
|
|
// StateIndicator (SetState / GetState). We provide a thin proxy that exposes
|
|
// BOTH spellings so the recovered call sites compile unchanged.
|
|
//===========================================================================//
|
|
struct ReconAlarm
|
|
{
|
|
unsigned level;
|
|
ReconAlarm() : level(0) {}
|
|
ReconAlarm(int n) : level(0) { (void)n; }
|
|
ReconAlarm(unsigned n) : level(0) { (void)n; }
|
|
ReconAlarm &operator=(const ReconAlarm &) { return *this; }
|
|
void SetLevel(int n) { level = (unsigned)n; }
|
|
void SetState(unsigned n){ level = n; }
|
|
unsigned GetLevel() const { return level; }
|
|
unsigned GetState() const { return level; }
|
|
int GetLevelCount() const { return 0; }
|
|
};
|
|
|
|
//===========================================================================//
|
|
// Filtered (running-average) scalar -- decomp uses Initialize(n, value).
|
|
//===========================================================================//
|
|
struct ReconFiltered
|
|
{
|
|
ReconFiltered() {}
|
|
template<class...A> void Initialize(A&&...) {}
|
|
template<class...A> void Add(A&&...) {}
|
|
Scalar Value() const { return Scalar(0); }
|
|
// static-style accumulator verbs: FilteredScalar::Push(filter, sample) etc.
|
|
template<class...A> static void Push(A&&...) {}
|
|
template<class...A> static Scalar Sum(A&&...) { return Scalar(0); }
|
|
template<class...A> static Scalar Average(A&&...) { return Scalar(0); }
|
|
};
|
|
|
|
typedef unsigned int uint;
|
|
|
|
//===========================================================================//
|
|
// Universal word-cell proxy + word accessor.
|
|
//
|
|
// The recovered mech-core bodies address the object as an int[] and assign
|
|
// raw words by index ( this[0xNN] = ... ). Pointer-subscripting `this`
|
|
// cannot be made an assignable scalar lvalue, so each `this[0xNN]` is
|
|
// rewritten to Wword(0xNN), which hands back a distinct, universally
|
|
// assignable/convertible cell (one per word index). This preserves the
|
|
// per-offset write structure of the decomp while compiling against the
|
|
// modern engine; the cell is behaviour-neutral storage for the offline
|
|
// reconstruction (it is NOT aliased onto the real member at that offset).
|
|
//===========================================================================//
|
|
struct BTVal
|
|
{
|
|
Scalar max, min, field8; // named sub-fields used by the decomp
|
|
|
|
BTVal() : max(0), min(0), field8(0) {}
|
|
template<class T> BTVal(const T &) : max(0), min(0), field8(0) {}
|
|
|
|
BTVal &operator=(const BTVal &) { return *this; } // real copy-assign
|
|
template<class T> BTVal &operator=(const T &) { return *this; }
|
|
template<class T> operator T() const { return T(); }
|
|
|
|
// member-call passthrough (this[]-proxy + pose-member verbs)
|
|
template<class...A> BTVal &Construct(A&&...) { return *this; }
|
|
template<class...A> BTVal &LinkTo(A&&...) { return *this; }
|
|
template<class...A> BTVal &SetIdentity(A&&...) { return *this; }
|
|
template<class...A> BTVal &Mul(A&&...) { return *this; }
|
|
template<class...A> BTVal &Copy(A&&...) { return *this; }
|
|
|
|
// arithmetic kept closed over BTVal (avoids operator T() ambiguity)
|
|
friend BTVal operator+(const BTVal &, const BTVal &) { return BTVal(); }
|
|
friend BTVal operator-(const BTVal &, const BTVal &) { return BTVal(); }
|
|
BTVal &operator+=(const BTVal &) { return *this; }
|
|
template<class T> BTVal &operator+=(const T &) { return *this; }
|
|
|
|
// comparisons (exact-match friends beat the templated operator T())
|
|
friend bool operator==(const BTVal &, const BTVal &) { return false; }
|
|
friend bool operator!=(const BTVal &, const BTVal &) { return false; }
|
|
friend bool operator==(const BTVal &, int) { return false; }
|
|
friend bool operator!=(const BTVal &, int) { return false; }
|
|
friend bool operator==(int, const BTVal &) { return false; }
|
|
friend bool operator!=(int, const BTVal &) { return false; }
|
|
};
|
|
|
|
inline BTVal &Wword(int i) { static BTVal bank[0x400]; return bank[i & 0x3ff]; }
|
|
|
|
static Vector3D ZeroVector; // neutral zero vector (decomp ZeroVector datum)
|
|
|
|
//===========================================================================//
|
|
// Affine-matrix proxy -- adds the SetRotation / FromRotation / FromQuaternion
|
|
// spellings the decomp uses (the engine AffineMatrix expresses these through
|
|
// operator= / dedicated helpers).
|
|
//===========================================================================//
|
|
struct ReconMatrix
|
|
{
|
|
AffineMatrix m;
|
|
ReconMatrix() {}
|
|
template<class T> ReconMatrix &operator=(const T &) { return *this; }
|
|
template<class...A> void FromRotation(A&&...) {}
|
|
// P3 GAIT CUTOVER: backed by the real engine AffineMatrix (was no-op stubs, so
|
|
// IntegrateMotion's world-step committed nothing). Identity == BuildIdentity;
|
|
// FromQuaternion/SetRotation == AffineMatrix::operator=(const Quaternion&) (the
|
|
// engine's build-rotation-from-quaternion). Signatures match every call site
|
|
// (mech4.cpp IntegrateMotion + Simulate).
|
|
static void SetRotation(ReconMatrix *dst, const Quaternion *q) { dst->m = *q; }
|
|
static void FromQuaternion(ReconMatrix *dst, const Quaternion *q) { dst->m = *q; }
|
|
static void Identity(ReconMatrix *dst) { dst->m.BuildIdentity(); }
|
|
operator AffineMatrix &() { return m; }
|
|
};
|
|
|
|
//===========================================================================//
|
|
// SequenceController -- the BT-specific keyframe animation player embedded in the
|
|
// Mech as legAnimation (@0x65c) / bodyAnimation (@0x6bc). RECONSTRUCTED from the
|
|
// binary (part_003.c: ctor @00427768, SelectSequence @004277a8, Advance @0042790c,
|
|
// Reset @004283b8). NOT in the RP411 engine. Plays a gait clip: walks keyframes,
|
|
// interpolates each joint's rotation, writes it through the engine Joint API
|
|
// (same as gyro/torso), and returns the root-translation distance advanced.
|
|
// Modelled as a plain struct (accessed only by name; no Plug base needed). The
|
|
// keyframeCount/keyframeTimes/keyframeData accessors are preserved for mech3's
|
|
// LoadLocomotionClips/MeasureClipStride; full spec in btbuild/P3_LOCOMOTION.md.
|
|
//===========================================================================//
|
|
class Mech; // owner (forward decl -- mechrecon.hpp precedes the Mech class)
|
|
|
|
struct SequenceController
|
|
{
|
|
// rootTranslation entry (binary 0xC/frame); ".stride" == the .z the gait reads.
|
|
struct Keyframe { Scalar x, y, stride; };
|
|
|
|
// --- clip data parsed by SelectSequence (keyframe accessors read by mech3) ---
|
|
int keyframeCount; // frameCount (binary +0x14)
|
|
int jointCount; // animated joints (binary +0x18)
|
|
// (AUDIO_FIDELITY F5) the ANI header's footstep CONTACT threshold -- the
|
|
// clip-authored root height below which the foot is planted. The engine's
|
|
// own AnimationInstance captures the same word (JMOVER.cpp:1415); the
|
|
// binary's per-frame perf compares jointlocal.y against it (+0x20).
|
|
Scalar *footStepThreshold; // &hdr[2] (binary +0x20)
|
|
int *jointIndices; // keyframe->skeleton map (binary +0x1c)
|
|
Scalar *keyframeTimes; // frameTimes[frameCount] (binary +0x24)
|
|
void *keyframeBase; // keyframe pose data base (binary +0x28)
|
|
void *keyframeCursor; // current pose cursor (binary +0x2c)
|
|
Keyframe *keyframeData; // rootTranslations[] (binary +0x34)
|
|
|
|
// --- runtime state / links ---
|
|
int currentFrame; // (binary +0x3c)
|
|
Scalar currentTime; // clip playback time (binary +0x54)
|
|
void *jointSubsystem; // resolved owner joint subsystem (binary +0x38)
|
|
Mech *owner; // (binary +0x40)
|
|
void *clipResource; // locked clip resource handle (binary +0x44)
|
|
void *cbCode; // finished-callback code ptr (binary +0x48)
|
|
unsigned cbArg2, cbArg3; // finished-callback context words (binary +0x4c/0x50)
|
|
|
|
SequenceController()
|
|
: keyframeCount(0), jointCount(0), footStepThreshold(0), jointIndices(0),
|
|
keyframeTimes(0), keyframeBase(0), keyframeCursor(0), keyframeData(0),
|
|
currentFrame(0), currentTime(0.0f), jointSubsystem(0), owner(0),
|
|
clipResource(0), cbCode(0), cbArg2(0), cbArg3(0) {}
|
|
|
|
void Init(Mech *owner_mech); // @00427768 (ctor logic)
|
|
void SelectSequence(int clip_id, void *cb, unsigned a2, unsigned a3); // @004277a8
|
|
Scalar Advance(Scalar time_slice, int move_joints); // @0042790c
|
|
void Reset(int loop); // @004283b8
|
|
~SequenceController(); // @004278d4 (release clip)
|
|
};
|
|
typedef SequenceController ReconSeq;
|
|
|
|
//===========================================================================//
|
|
// Controls motion source -- the controls subsystem the Mech reads its live
|
|
// commanded ground speed from (this[0x128] -> +0x128).
|
|
//===========================================================================//
|
|
struct ReconMotionSource
|
|
{
|
|
Scalar commandedSpeed; // +0x128
|
|
};
|
|
|
|
// Gait-state name table ( <AnimationNames> + state*0x3c ); display data only.
|
|
static char AnimationNames[0x1e * 0x3c] = { 0 };
|
|
|
|
//===========================================================================//
|
|
// MechSubsystem-coupled damage zone view.
|
|
//
|
|
// The recovered MechSubsystem code addresses its DamageZone through a small
|
|
// set of fields (structure level, per-facing armour, structure reference and
|
|
// the zone's own alarm) that are reconstruction names rather than the modern
|
|
// engine DamageZone member spellings; this proxy carries them so the bodies
|
|
// compile. damageZone is stored through it.
|
|
//===========================================================================//
|
|
struct ReconDamageZone
|
|
{
|
|
Scalar structureLevel;
|
|
Scalar structureReference;
|
|
Scalar armour[5];
|
|
ReconAlarm alarm;
|
|
};
|
|
|
|
// (Gitea #46: the ReconSub/CriticalEntry/CriticalChain stand-ins that lived
|
|
// here are DELETED. They existed only for DistributeCriticalHit, whose real
|
|
// @004ac274 body is the per-DamageZone TakeDamage fan-out -- see mechsub.cpp
|
|
// + Mech::AmmoExplosionFanOut in mechdmg.cpp. The stubs iterated nothing and
|
|
// returned a fabricated count.)
|
|
|
|
//===========================================================================//
|
|
// Callable returned by the "base vtable slot 0x18" thunk: (*BaseSlot0x18())(...)
|
|
//===========================================================================//
|
|
struct ReconCallable
|
|
{
|
|
template<class...A> Recon operator()(A&&...) { return Recon(); }
|
|
};
|
|
inline ReconCallable *BaseSlot0x18() { static ReconCallable c; return &c; }
|
|
|
|
//===========================================================================//
|
|
// Resource-table proxies (dmgtable).
|
|
//
|
|
// The recovered damage-table classes drive their entry pools through a small
|
|
// verb set (Init / Insert / Lookup / count) and a forward iterator
|
|
// (First / Next / Current) that differ from the modern TableOf/TableIterator
|
|
// API, so the table members are declared with these proxies instead.
|
|
//===========================================================================//
|
|
template<class T> struct ReconTable
|
|
{
|
|
int count;
|
|
ReconTable() : count(0) {}
|
|
void Init() { count = 0; }
|
|
template<class...A> void Insert(A&&...) {}
|
|
template<class...A> void *Lookup(A&&...) const { return 0; }
|
|
};
|
|
|
|
struct ReconTableIter
|
|
{
|
|
template<class A> ReconTableIter(const A &) {}
|
|
void *First() { return 0; }
|
|
void *Next() { return 0; }
|
|
void *Current() { return 0; }
|
|
};
|
|
|
|
//===========================================================================//
|
|
// Generic object stream + notation-line + skeleton proxies (dmgtable parsers).
|
|
//===========================================================================//
|
|
struct GenericObjectStream
|
|
{
|
|
template<class...A> void Read(A&&...) {}
|
|
template<class...A> void Write(A&&...) {}
|
|
};
|
|
|
|
struct NotationLine
|
|
{
|
|
NotationLine *Next() { return 0; }
|
|
const char *Format() { return ""; }
|
|
const char *Value() { return ""; }
|
|
int Count() { return 0; }
|
|
template<class...A> void Parse(A&&...) {}
|
|
};
|
|
|
|
struct Skeleton
|
|
{
|
|
int SegmentCount() { return 1; }
|
|
};
|
|
|
|
template<class...A> inline NotationLine *ReconGetPage(A&&...) { static NotationLine l; return &l; }
|
|
template<class...A> inline Recon ReconDir(A&&...) { return Recon(); }
|
|
template<class A,class B> inline int ReconSegIndex(A, B) { return 0; }
|
|
|
|
//===========================================================================//
|
|
// Vector math namespace artifact ( Vector::Add / AddScaled / Scale / Dot ... ).
|
|
// (The decomp emits free vector ops under a "Vector" qualifier; the engine uses
|
|
// Vector3D member ops. Behaviour-neutral stand-ins for the reconstruction.)
|
|
//===========================================================================//
|
|
namespace Vector
|
|
{
|
|
template<class...A> inline void Add(A&&...) {}
|
|
template<class...A> inline void AddScaled(A&&...) {}
|
|
template<class...A> inline void Scale(A&&...) {}
|
|
template<class...A> inline void Copy(A&&...) {}
|
|
template<class...A> inline void Subtract(A&&...) {}
|
|
template<class...A> inline Scalar Dot(A&&...) { return Scalar(0); }
|
|
template<class...A> inline Scalar Sum(A&&...) { return Scalar(0); }
|
|
}
|
|
|
|
//
|
|
// Quaternion free-function artifacts (the decomp writes these as
|
|
// Quaternion::Identity(dst,&id) / Quaternion::IntegrateInto(...) -- calling the
|
|
// engine's static const Identity datum as a function; routed to free shims).
|
|
//
|
|
// P3 GAIT CUTOVER: backed by the real engine Quaternion (were no-op stubs).
|
|
// ReconQuatIdentity(q, &id) -> q = Quaternion::Identity
|
|
// ReconQuatIntegrate(out,in,d) -> out = in composed with an EXACT rotation by
|
|
// angular delta d (= the real FUN_00409f58).
|
|
// All callers pass (Quaternion*,Quaternion*) / (Quaternion*,Quaternion*,Vector3D*).
|
|
inline void ReconQuatIdentity(Quaternion *q, const Quaternion * /*id*/) { *q = Quaternion::Identity; }
|
|
// EXACT axis-angle quaternion integration -- the real FUN_00409f58 (part_000.c:9359),
|
|
// verified from the decomp. A prior stand-in wrongly used `out->Add(*in,*delta)` (a
|
|
// small-angle VECTOR add): correct every-frame on the master (tiny angle) but it
|
|
// DIVERGES over a long dead-reckon gap -- the spinning-peer heading drifting to ~180deg
|
|
// then snapping. The binary builds the unit rotation quaternion from the rotation vector
|
|
// `delta` (angle=|delta|, axis=delta/angle) as { axis*sin(angle/2), cos(angle/2) } and
|
|
// Hamilton-multiplies it onto `in` (FUN_00409d9c: out = in (X) deltaQuat).
|
|
inline void ReconQuatIntegrate(Quaternion *out, const Quaternion *in, const Vector3D *delta)
|
|
{
|
|
const Scalar ang = delta->Length();
|
|
if (ang > 1.0e-6f)
|
|
{
|
|
const Scalar half = (Scalar)(fmodf((float)ang, 6.2831853f) * 0.5f); // reduce2pi, then halve
|
|
const Scalar s = (Scalar)(sinf((float)half) / ang); // sin(angle/2)/angle
|
|
const Quaternion dq((Scalar)(delta->x * s), (Scalar)(delta->y * s),
|
|
(Scalar)(delta->z * s), (Scalar)cosf((float)half));
|
|
out->Multiply(*in, dq); // in (X) dq
|
|
}
|
|
else
|
|
{
|
|
*out = *in;
|
|
}
|
|
}
|
|
template<class...A> inline Recon ReconQuatSlerp(A&&...) { return Recon(); }
|
|
template<class...A> inline Recon ComputeImpactDamage(A&&...) { return Recon(); }
|
|
|
|
//
|
|
// Raw FUN_ helper artifacts referenced in code (vector/matrix/transform glue).
|
|
//
|
|
template<class...A> inline Recon FUN_00408644(A&&...) { return Recon(); }
|
|
// FUN_00408744 (part_000.c:8331) -- rotate a 3-vector by a matrix's 3x3 rotation:
|
|
// out[i] = v[0]*M(i,0) + v[1]*M(i,1) + v[2]*M(i,2) (row i . v).
|
|
// Backed by the engine AffineMatrix column basis: GetFromAxis(j) returns the "from"
|
|
// axis = column j = (M(0,j),M(1,j),M(2,j)), so out = v.x*colX + v.y*colY + v.z*colZ
|
|
// reproduces the raw row-dot formula EXACTLY (out[i] = sum_j v[j]*M(i,j)). This is
|
|
// IntegrateMotion's world-step: worldVelocity = bodyOrientation * localVelocity. The
|
|
// same GetFromAxis convention the drive facing uses (mech4.cpp), so it is sign-consistent.
|
|
inline Vector3D *FUN_00408744(Vector3D *out, const Scalar *v, ReconMatrix *m)
|
|
{
|
|
Vector3D colX, colY, colZ;
|
|
m->m.GetFromAxis(X_Axis, &colX);
|
|
m->m.GetFromAxis(Y_Axis, &colY);
|
|
m->m.GetFromAxis(Z_Axis, &colZ);
|
|
out->x = v[0]*colX.x + v[1]*colY.x + v[2]*colZ.x;
|
|
out->y = v[0]*colX.y + v[1]*colY.y + v[2]*colZ.y;
|
|
out->z = v[0]*colX.z + v[1]*colY.z + v[2]*colZ.z;
|
|
return out;
|
|
}
|
|
template<class...A> inline Recon FUN_00408848(A&&...) { return Recon(); }
|
|
template<class...A> inline Recon FUN_00433ed4(A&&...) { return Recon(); }
|
|
template<class...A> inline Recon FUN_0040e5f0(A&&...) { return Recon(); }
|
|
template<class...A> inline Recon FUN_0040e36c(A&&...) { return Recon(); }
|
|
template<class...A> inline Recon FUN_00406ff8(A&&...) { return Recon(); }
|
|
|
|
// Free vector op + pose/notify artifacts used unqualified in mech4.
|
|
template<class...A> inline Scalar Dot(A&&...) { return Scalar(0); }
|
|
template<class...A> inline Recon Build(A&&...) { return Recon(); }
|
|
template<class...A> inline void ReconBeginPose(A&&...) {}
|
|
template<class...A> inline void ReconEndPose(A&&...) {}
|
|
template<class...A> inline int ReconIsDerived(A&&...) { return 0; }
|
|
|
|
//
|
|
// Damage notification message (DamageMessage msg; DamageMessage::Build(&msg,...)).
|
|
//
|
|
struct DamageMessage
|
|
{
|
|
template<class...A> static void Build(A&&...) {}
|
|
};
|
|
|
|
// Geometry / pose constants.
|
|
static Quaternion kIdentityQuat;
|
|
static Vector3D kZeroVector;
|
|
static Scalar _DAT_004ac048 = 0;
|
|
static Scalar _DAT_004ab9c8 = 0;
|
|
static Scalar _DAT_004ab9d0 = 0;
|
|
static Scalar _DAT_004ab9d4 = 0;
|
|
static Recon vtable_0050bb90;
|
|
|
|
//===========================================================================//
|
|
// Subsystem-roster chain proxy.
|
|
//
|
|
// The Mech keeps several ChainOf<Subsystem*> capability views. The engine
|
|
// ChainOf has no default ctor / SetOwner / Construct / iterate-in-place verbs
|
|
// that the recovered ctor uses, so the roster members are declared with this
|
|
// proxy instead.
|
|
//===========================================================================//
|
|
template<class T> struct ReconChain
|
|
{
|
|
ReconChain() {}
|
|
ReconChain(void *) {}
|
|
template<class...A> void SetOwner(A&&...) {}
|
|
template<class...A> void Construct(A&&...) {}
|
|
template<class...A> void Reset(A&&...) {}
|
|
void Add(T) {}
|
|
T First() { return T(); }
|
|
T Next() { return T(); }
|
|
};
|
|
|
|
//===========================================================================//
|
|
// Debug-stream artifact ( DebugStream << x << endl; ).
|
|
//===========================================================================//
|
|
struct ReconStream
|
|
{
|
|
template<class T> ReconStream &operator<<(const T &) { return *this; }
|
|
void Flush() {}
|
|
void Emit() {}
|
|
};
|
|
|
|
//===========================================================================//
|
|
// Resolve-able connection slot proxy (e.g. the power-bus / death-effect slot).
|
|
//===========================================================================//
|
|
struct ReconSlot
|
|
{
|
|
template<class...A> Recon Resolve(A&&...) { return Recon(); }
|
|
template<class...A> void LinkTo(A&&...) {}
|
|
template<class...A> void Construct(A&&...){}
|
|
};
|
|
static ReconStream DebugStream;
|
|
static int endl = 0;
|
|
|
|
//===========================================================================//
|
|
// Type aliases consumed by the recovered headers. Defining the guard here
|
|
// suppresses the (now superseded) in-header alias blocks.
|
|
//===========================================================================//
|
|
#if !defined(BT_RECON_TYPE_ALIASES)
|
|
# define BT_RECON_TYPE_ALIASES
|
|
typedef ReconMatrix Matrix34; // orientation matrix proxy
|
|
typedef ReconAlarm AlarmIndicator; // multi-level status indicator
|
|
typedef ReconFiltered FilteredScalar; // running-average scalar
|
|
#endif
|
|
|
|
//===========================================================================//
|
|
// Decompiler-artifact globals actually referenced by mech-core code.
|
|
// * .data pointers used in address arithmetic -> char*
|
|
// * scalar tuning constants -> Scalar
|
|
// * opaque label/vtable/handle operands -> Recon
|
|
//===========================================================================//
|
|
static char *DAT_004efc94 = 0; // TheApp base (App*)
|
|
static Scalar DAT_005209d0 = 0; // FLT_MAX seed
|
|
static Scalar DAT_0052140c = 1; // runtime frame time scale (divisor)
|
|
static char DAT_004e0f8c[1] = { 0 }; // "" empty string literal
|
|
static Scalar _DAT_004ab9cc = 0;
|
|
|
|
//===========================================================================//
|
|
// Shared empty handler / attribute sets used to satisfy the 4-arg
|
|
// Simulation::SharedData ctor for the reconstructed DefaultData objects (the
|
|
// shipped game passes each class's own generated sets; an empty shared set is
|
|
// structurally correct for the offline reconstruction).
|
|
//===========================================================================//
|
|
static Receiver::MessageHandlerSet ReconMessageHandlers;
|
|
static Simulation::AttributeIndexSet ReconAttributeIndex;
|
|
|
|
static Recon PTR_FUN_0050cfa8; // Mech vtable
|
|
static Recon PTR_LAB_0050c0e8; // copy Performance
|
|
static Recon PTR_LAB_0050c0f4; // master Performance
|
|
|
|
// Animation SelectSequence operand triples (label + 2 floats per gait clip).
|
|
static Recon PTR_LAB_0050d6f0, DAT_0050d6f4, DAT_0050d6f8;
|
|
static Recon PTR_LAB_0050d6fc, DAT_0050d700, DAT_0050d704;
|
|
static Recon PTR_LAB_0050d708, DAT_0050d70c, DAT_0050d710;
|
|
static Recon PTR_LAB_0050d714, DAT_0050d718, DAT_0050d71c;
|
|
static Recon PTR_LAB_0050d720, DAT_0050d724, DAT_0050d728;
|
|
static Recon PTR_LAB_0050d72c, DAT_0050d730, DAT_0050d734;
|
|
static Recon PTR_LAB_0050d738, DAT_0050d73c, DAT_0050d740;
|
|
static Recon PTR_LAB_0050d744, DAT_0050d748, DAT_0050d74c;
|
|
|
|
#endif // MECHRECON_HPP
|