//===========================================================================//
// File:	mech.hpp                                                            //
// Project: BattleTech           Brick: Entity Manager                        //
// Contents: Mech -- the BattleMech player/AI entity (declaration)            //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
//  --/--/95  ??   Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, BTL4OPT.EXE).
//
// The Mech class is the heart of the game and its implementation is split
// across mech.cpp .. mech4.cpp.  This header declares the *whole* class so
// the recovered mech.cpp slice compiles; methods whose bodies live in the
// sibling translation units are marked  [mech2] / [mech3] / [mech4].
//
//   mech.cpp slice recovered here:  0x004a1232 .. 0x004a5027
//     (bounded below by the inter-module gap 0x0049ffc8..0x004a1232 that
//      separates it from btmssn.cpp, and above by mech2.cpp @0x004a5028.)
//
// Structural template: Red Planet's player vehicle entity VTV (RP/VTV.h),
// which -- like Mech -- derives from JointedMover and hosts a roster of
// streamed Subsystem objects.  Surviving BT headers MECHDMG.HPP / MECHTECH.HPP
// supplied the DamageZone / MechTech shapes.
//
// Field offsets in comments are BYTE offsets into the shipped object
// (sizeof(Mech) == 0x854, see Mech::Make @004a2d48).  The decomp addresses
// the object as int words, so  this[0xNN]  ==  byte offset 0xNN*4.  Names are
// taken from VTV / the surviving BT headers where an analog exists and are
// inferred from usage otherwise (flagged TODO / "best-effort").
//

#if !defined(MECH_HPP)
#	define MECH_HPP

#if !defined(JMOVER_HPP)
#	include <jmover.hpp>		// JointedMover -- engine base (MUNGA)
#endif
#if !defined(CONTROLS_HPP)
#	include <controls.hpp>
#endif
#if !defined(DAMAGE_HPP)
#	include <damage.hpp>
#endif
#if !defined(ALARM_HPP)
#	include <alarm.hpp>			// GaugeAlarm
#endif
#include <tool.hpp>				// PlatformTool
#include <affnmtrx.hpp>			// AffineMatrix
#include <average.hpp>			// AverageOf<T>
#include <scalar.hpp>			// Scalar
#include <reticle.hpp>			// Reticle (the TargetReticle attribute struct)

#include "mechrecon.hpp"		// reconstruction shim (proxies, artifact globals, type aliases)

//#####################    Reconstruction type aliases   #####################
// Names inferred by the decompiler mapped to the real engine types.
// (The actual aliases are now defined in mechrecon.hpp under the same guard.)
#if !defined(BT_RECON_TYPE_ALIASES)
#	define BT_RECON_TYPE_ALIASES
	typedef ReconMatrix			Matrix34;
	typedef ReconAlarm			AlarmIndicator;
	typedef ReconFiltered		FilteredScalar;
#endif

//#####################    Forward Class Declarations  #######################
class Mech;
class Subsystem;
class MechSubsystem;
class MechWeapon;
class MechControlsMapper;
class MechTech;
class Mech__DamageZone;			// see MECHDMG.HPP
class HitQuery;					// weapon sweep query (mech4)
class Joint;					// engine skeleton node (JOINT.h); ResolveJoint result

//
// Streamed-subsystem resource blob handed to each <Class>::CreateStreamedSubsystem.
// The recovered factory only passes it through, so an opaque alias suffices.
//
typedef void SubsystemResourceBlob;

//
// Per-shot impact descriptor consumed by Mech::ResolveWeaponImpact (mech4).
// Fields are those the recovered body reads.
//
struct ShotDescriptor
{
	Vector3D	dir;			// +0x14  shot direction
	Scalar		damage;			// computed impact damage
	Scalar		fieldAt(int) const	{ return Scalar(0); }	// raw +0x240/0x244/0x248 reads
};

//###########################################################################
//#########################   Mech::ModelResource   #########################
//###########################################################################
//
//	Parsed by Mech::CreateModelResource @004a2da8 from the "gamedata" notation
//	block.  Layout mirrors the float[] the decomp fills (pfVar3, a 200-byte /
//	0x32-word record).  Indices in comments are the word index used by the
//	parser (e.g. pfVar3+0x11 == MaxAcceleration).
//
	struct Mech__ModelResource:
		public JointedMover::ModelResource
	{
		char	animationPrefix[12];		// +0x10  "AnimationPrefix" (3-letter)
		Scalar	maxAcceleration;			// +0x11  "MaxAcceleration"
		Scalar	superStopAcceleration;		// +0x12  "SuperStopAcceleration"
		Scalar	throttleAdjustment;			// +0x13  "ThrottleAdjustment"
		Scalar	lookLeftAngle;				// +0x14  "LookLeftAngle"
		Scalar	lookRightAngle;				// +0x15  "LookRightAngle"
		Scalar	lookFrontAngle;				// +0x16  "LookFrontAngle"
		Scalar	lookBackAngle;				// +0x17  "LookBackAngle"
		Scalar	walkingTurnRate;			// +0x18  "WalkingTurnRate"
		Scalar	runningTurnRate;			// +0x19  "RunningTurnRate"
		char	skeletonName[64];			// +0x1a  (.skl spec, 0x10 words)
		Scalar	deathEffectResourceID;		// +0x1d  resolved "DeathEffect" id
		Scalar	deathSplashDamage;			// +0x1e  "DeathSplashDamage"
		Scalar	deathSplashRadius;			// +0x1f  "DeathSplashRadius"
		Scalar	maxUnstableAcceleration;	// +0x20  "MaxUnstableAcceleration"
		Scalar	unstableAccelerationEffect;	// +0x21  "UnstableAccelerationEffect"
		Scalar	unstableGunTheEngineEffect;	// +0x22  "UnstableGunTheEngineEffect"
		Scalar	unstableSuperStopEffect;	// +0x23  "UnstableSuperStopEffect"
		Scalar	unstableHighVelocityEffect;	// +0x24  "UnstableHighVelocityEffect"
		Scalar	unstableStopedTurnEffect;	// +0x25  "UnstableStopedTurnEffect"
		Scalar	updatePositionDiffrence;	// +0x26  "UpdatePositionDiffrence"
		Scalar	updateTurnVelocityDiffrence;// +0x27  "UpdateTurnVelocityDiffrence"
		Scalar	updateTurnDegreeDiffrence;	// +0x28  "UpdateTurnDegreeDiffrence"
		Scalar	timeDelay;					// +0x29  "TimeDelay"
		Vector3D cameraOffset;				// +0x2a  "CameraOffset"
		char	shadowJointName[20];		// +0x2d  "ShadowJointName" (<20 chars)
		Scalar	relativeMechValue;			// +0x1c  "RelativeMechValue"

		// --- raw recovered fields read by the ctor (Pass 2, model record) -----
		// Offsets are the decomp's record offsets; names are best-effort.
		char	*base;						// record base pointer (model->base + 0xa8)
		int		deathEffectField;			// resolved death-effect handle source
		Scalar	field48, field4c, field60, field64;
		int		field68, field6c;
		Scalar	field70, field74, field78, field7c;
		Scalar	field80, field84, field88, field8c, field90, field94, field98, field9c;
		Scalar	fieldA0;
		int		fieldA4;
		int		classID;					// stamped by CreateModelResourceStub (0xBB9)
	};

//###########################################################################
//#########################   Mech::UpdateRecord   ##########################
//###########################################################################
//
//	The network/replay update packet applied by Mech::ReadUpdateRecord
//	@004a1232.  The switch is keyed on a 16-bit record-type field at +6 of the
//	message (cases 0,2..8).  Field layout is best-effort -- only the offsets
//	touched by the decomp are named.
//
	struct Mech__UpdateRecord:
		public JointedMover::UpdateRecord
	{
		// case 0 (full pose)        : position (+0x10), orientation (+0x74)
		// case 2/3 (alarm/state)    : alarm levels, score/heat state
		// case 4 (snap+time)        : pose + server timestamp re-sync
		// case 5/6/7 (subsystem)    : per-subsystem alarm + flags
		// case 8 (single scalar)    : misc field [0xfd]
		int			recordType;		// *(u16*)(msg+6) -- switch selector
		int			sequence;		// trailing replication sequence word
		int			field4, field5, field6, field7, field9, fieldA;	// per-case payload words
		Vector3D	position;		// case 0/4 world position
		EulerAngles	orientation;	// case 0/4 net orientation
	};

//###########################################################################
//##########################   Mech::MakeMessage   ##########################
//###########################################################################
//
//	Creation message.  Carries the three resource-name strings copied into the
//	badge/color/insignia slots (this[0x211..0x213]) at +0x7c / +0x90 / +0xa4.
//
	class Mech__MakeMessage:
		public JointedMover::MakeMessage
	{
	public:
		char	resourceNameA[20];	// +0x7c  -> this[0x211] (vehicle badge)
		char	resourceNameB[20];	// +0x90  -> this[0x212] (vehicle color)
		char	resourceNameC[20];	// +0xa4  -> this[0x213] (vehicle patch / insignia)

		//
		// Construction message, mirrored on VTV__MakeMessage (RP/VTV.h): the
		// entity_ID form carrying the three resource-name strings copied into
		// the badge/color/patch slots.  Built by BTPlayer::CreatePlayerVehicle.
		//
		Mech__MakeMessage(
			Receiver::MessageID				message_ID,
			size_t							length,
			const EntityID					&entity_ID,
			Entity::ClassID					class_ID,
			const EntityID					&owner_ID,
			ResourceDescription::ResourceID	resource_ID,
			LWord							instance_flags,
			const Origin					&origin,
			const Motion					&velocity,
			const Motion					&acceleration,
			const char						*badge,
			const char						*color,
			const char						*patch
		):
			JointedMover::MakeMessage(
				message_ID, length, entity_ID, class_ID, owner_ID,
				resource_ID, instance_flags, origin, velocity, acceleration
			)
		{
			Str_Copy(resourceNameA, badge, sizeof(resourceNameA));
			Str_Copy(resourceNameB, color, sizeof(resourceNameB));
			Str_Copy(resourceNameC, patch, sizeof(resourceNameC));
		}
	};

//###########################################################################
//##############################     Mech     ###############################
//###########################################################################
//
//	The BattleMech entity.  vtable @0050cfa8 (16 slots); slot 0 == ~Mech
//	@004a452c.  Engine-base slots (0x41xxxx / 0x42xxxx) are inherited from
//	JointedMover/Mover/Entity and are NOT redefined here.  In-module overrides:
//	    slot  0  (0050cfa8)  ~Mech                @004a452c   [mech.cpp]
//	    slot  6  (0050cfc0)  ReadUpdateRecord     @004a122c   [mech.cpp]
//	    slot  7  (0050cfc4)  (pose write/simulate)@004a0c2c   [mech.cpp gap]
//	    slot 15  (0050cfe4)  (state/print)        @004abb40   [mech3/4]
//
	class Mech:
		public JointedMover
	{
		friend class Mech__DamageZone;		// per-zone damage code reads the roster / segment table
		friend struct MechBaseLayoutCheck;	// base-region layout locks (mech4.cpp; P3 STEP-6 audit)
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Shared Data Support
	//
	public:
		static Derivation	ClassDerivations;	// @0050bdb4
		static SharedData	DefaultData;		// @0050bde4

		enum { MechClassID = 0xBB9 };			// stamped by CreateModelResource

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Subsystem roster -- streamed-class registry
	//
	//	The constructor (@004a1674) walks the model's segment table and
	//	instantiates one Subsystem per segment, dispatching on the segment's
	//	streamed ClassID.  ClassIDs confirmed against CLASSMAP.md are named;
	//	the rest are best-effort from the factory address + object size.
	//
	public:
		enum SubsystemClassID {
			CockpitClassID			= 0xBBD,	// ctor @004ae568  (size 0x230)
			SensorClassID			= 0xBBE,	// ctor @004ae8d0  -> this[0x1f7]
			CondenserClassID		= 0xBC0,	// ctor @004af408  (size 0x230)
			GeneratorClassID		= 0xBC1,	// ctor @004b225c  (CLASSMAP)
			PoweredSubsystemClassID	= 0xBC2,	// ctor @004b0f74  (CLASSMAP)
			MyomersClassID			= 0xBC3,	// ctor @004b1d18  (CLASSMAP)
			GyroClassID				= 0xBC4,	// ctor @004b3778  -> this[0x14a]
			SinkSourceClassID		= 0xBC5,	// ctor @004b6b0c  -> this[0x10e]
			ActuatorClassID			= 0xBC6,	// ctor @004b8fec
			WeaponEmitterClassID	= 0xBC8,	// ctor @004bb120  (+weapon count)
			JumpJetClassID			= 0xBCB,	// ctor @004bd5c4
			MechWeaponClassID		= 0xBCD,	// ctor @004bc3fc  (CLASSMAP)
			MissileWeaponClassID	= 0xBCE,	// ctor @004bdcb4  (+weapon count)
			BallisticWeaponClassID	= 0xBD0,	// ctor @004bcff0  (+weapon count)
			MechControlsMapperID	= 0xBD3,	// ctor @0049bca4  -> this[0x10d]
			GaussWeaponClassID		= 0xBD4,	// ctor @004bb888  (+weapon count)
			MechTechClassID			= 0xBD6,	// ctor @004b7f94  -> this[0x16d]
			LegSubsystemClassID		= 0xBD8,	// ctor @004b84dc
			HeatableClassID			= 0xBDC,	// ctor @004ad228  (heat.cpp family; exact class TBD)
			DisplayClassID			= 0xBDE		// ctor @004b8718
		};

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Test Class Support
	//
	public:
		Logical
			TestInstance() const;				// @004a4c7c

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// App / cockpit interface (called by the BT_L4 layer: btl4app/mppr/pb).
	// Declarations only -- definitions live in mech.cpp (factory pass / bring-up).
	//
	public:
		enum ResetMode { MissionReviewReset = 0 };		// reset-mode selector (Reset arg, value 0)

		void	SetMappingSubsystem(Subsystem *mapper);	// btl4app.cpp:567
		Logical	GetMissionReviewMode();					// reads this+0x414  (btl4mppr.cpp:366)
		void	SetTargetRange(Scalar range);			// writes this+0x404 (btl4mppr.cpp:407)
		void	Reset(const Origin &origin, int mode);	// btl4pb.cpp:555 (FUN_0049fb74)

		//~~~ Skeleton joint access (shared by Torso, Gyroscope) ~~~~~~~~~~~~~~~~
		// Resolve a named skeleton node to its live Joint* via the PUBLIC segment
		// path (GetSegment -> segment joint index -> JointSubsystem::GetJoint);
		// the protected EntitySegment::jointPointer is never read.  The Torso ctor
		// (@004b6b0c) and Gyroscope ctor (@004b3778) inline this exact sequence
		// (binary FUN_00424b60); hoisted here as the single reuse point.
		Joint*	ResolveJoint(const char *joint_name);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Construction and Destruction
	//
	public:
		typedef Mech__ModelResource	ModelResource;
		typedef Mech__UpdateRecord	UpdateRecord;
		typedef Mech__MakeMessage	MakeMessage;
		typedef Mech__DamageZone	DamageZone;

		static Mech*
			Make(MakeMessage *creation_message);		// @004a2d48

		Mech(
			MakeMessage	*creation_message,
			SharedData	&shared_data = DefaultData
		);												// @004a1674
		~Mech();										// @004a452c (vtable slot 0)

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Resource authoring (offline tools)
	//
	public:
		static ResourceDescription::ResourceID
			CreateModelResource(				// @004a2da8
				ResourceFile				*resource_file,
				const char					*model_name,
				NotationFile				*model_file,
				const ResourceDirectories	*directories,
				ModelResource				*model = 0
			);

		static Logical
			CreateModelResourceStub(			// @004a2d78 (best-effort)
				ModelResource				*model
			);									// stamps model->classID = MechClassID

		typedef Logical (*FindNameFunction)(
			const char *control_name, ControlsMapping *mapping);

		static ResourceDescription::ResourceID
			CreateControlMappingStream(			// @004a3794
				const char					*mapping_name,
				NotationFile				*mapping_file,
				FindNameFunction			find_name,
				ResourceFile				*resource_file,
				const char					*model_name,
				NotationFile				*model_file,
				const ResourceDirectories	*directories,
				PlatformTool				*current_tool
			);

		static ResourceDescription::ResourceID
			CreateDamageZoneStream(				// @004a474c
				ResourceFile				*resource_file,
				const char					*model_name,
				NotationFile				*model_file,
				const ResourceDirectories	*directories
			);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Simulation / replication
	//
	protected:
		void
			ReadUpdateRecord(					// @004a1232 (vtable slot 6)
				Simulation::UpdateRecord *message
			);
		// WriteUpdateRecord / per-frame Simulate live in the uncaptured
		// mech.cpp gap (slot 7 @004a0c2c) and mech2.cpp.  [mech2]

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Instance-flag helper
	//
	protected:
		void
			SetInstanceFlags(Word flags);		// @004a4c54

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Subsystem roster (host pattern -- cf. VTV / CLASSMAP shared base)
	//
	//	this[0x47] segmentCount / this[0x48] segment[]   (JointedMover base)
	//	this[0x49] subsystemCount / this[0x4a] subsystem[] (Mech-owned table)
	//	The capability sub-rosters below are ChainOf<> views built in the ctor
	//	by IsDerivedFrom() tests against the streamed subsystem classes.
	//
	protected:
		// NOTE: subsystemCount (this[0x49]) and the subsystem table (this[0x4a])
		// are the INHERITED Entity base members `subsystemCount` / `subsystemArray`
		// (Entity reads them in GetSimulation/GetSubsystem).  They are NOT
		// re-declared here -- doing so shadowed the base fields, so the engine's
		// control-mapping / per-frame code never saw the streamed roster.  The
		// ctor streams into the inherited subsystemArray/subsystemCount directly.

		// NOTE: damageZoneCount (this[0x47]) and the zone table (this[0x48]) are the
		// INHERITED Entity base members `damageZoneCount` / `damageZones` -- the engine
		// Entity ctor (ENTITY.cpp:961-987 == binary FUN_0041ff38) reads the count from
		// the type-0x14 resource and allocates damageZones[count] (entries left
		// uninitialised); the Mech ctor populates THAT inherited array.  They are NOT
		// re-declared here -- doing so shadowed the base fields (count read 0xCDCDCDCD,
		// the populate loop never ran, and the engine damage router read the empty
		// base array).  Same lesson as subsystemCount/subsystemArray above.  Use Zone(i)
		// for typed (Mech__DamageZone*) access to the engine-stored DamageZone* entries.

		ReconChain<Subsystem*>	controllableSubsystems;	// @0x65c  this[0x197] (FUN_00427768)
		ReconChain<Subsystem*>	watchedSubsystems;		// @0x6bc  this[0x1af] (FUN_00427768)
		ReconChain<Subsystem*>	heatableSubsystems;		// @0x7ac  this[0x1eb] (class 0x51155c)
		ReconChain<Subsystem*>	weaponRoster;			// @0x7bc  this[0x1ef] (class 0x511830 =
														//   MechWeapon::ClassDerivations -- the
														//   reticle AddWeapon loop @part_014.c:5386
														//   asserts weapon attrs on every member.
														//   Was mislabeled "poweredSubsystems";
														//   PoweredSubsystem is 0x50f4bc.)
		ReconChain<Subsystem*>	damageableSubsystems;	// @0x7cc  this[499]   (class 0x50e4fc)

		int			weaponCount;				// @0x448  this[0x112]
		Subsystem	*sensorSubsystem;			// @0x7dc  this[0x1f7]  (0xBBE)
		Subsystem	*gyroSubsystem;				// @0x528  this[0x14a]  (0xBC4)
	public:
		// Cache accessors for the controls mapper (@004afd10 reads mech+0x438/+0x5b4).
		Subsystem	*GetTorsoSubsystem()	{ return sinkSourceSubsystem; }
		Subsystem	*GetHudSubsystem()		{ return hudSubsystem; }
		// Reachable horizontal firing half-arc (radians) the mech's torso can bring
		// its guns to bear -- the wider torso twist limit, or 0 for a fixed torso.
		// Defined in mechmppr.cpp (which knows the full Torso type). Used by the
		// weapon fire path (mech4.cpp) to derive the real, per-mech firing arc.
		Scalar		GetHorizontalFiringReach();
	protected:
		Subsystem	*sinkSourceSubsystem;		// @0x438  this[0x10e]  (0xBC5 -> real Torso)
		// The @0x5b4 cache holds the HUD, NOT the MechTech: raw factory (part_012.c:
		// 10155-10164) case 0xbd6 (alloc 0x2a4, HUD ctor @004b7f94) writes param_1[0x16d];
		// case 0xbdc (MechTech, alloc 0x104) stores NO cache.  Also required by the
		// controls mapper (@004afd10 writes cockpit+0x28c -- past MechTech's alloc,
		// inside the HUD's).  Was misnamed mechTechSubsystem.
		Subsystem	*hudSubsystem;				// @0x5b4  this[0x16d]  (0xBD6 -> real HUD)
		MechControlsMapper *controlsMapper;		// @0x434  this[0x10d]  (0xBD3)

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Pose / kinematics  (subset touched by the recovered slice)
	//
	protected:
		Matrix34	orientation;				// @0xd0   this[0x34] (FUN_0040ab44)
		Quaternion	bodyRotation;				// @0x100  this[0x40]
		BTVal		torsoRotation;				// @0x130  this[0x4b] (proxy: .Mul)
		Vector3D	worldPosition;				// @0x138  this[0x4e]
		BTVal		turretBase;					// @0x1c4  this[0x71] (proxy: .SetIdentity)
		Quaternion	headPitch;					// @0x1cc  this[0x73] (look)
		Quaternion	torsoTwist;					// @0x1dc  this[0x77]
		Vector3D	legAngle;					// @0x1f4  this[0x7d]
		Vector3D	hipAngle;					// @0x200  this[0x80]
		BTVal		torsoAimCurrent;			// @0x298  this[0xa6] (proxy: .Mul/.SetIdentity)
		BTVal		torsoAimTarget;				// @0x2c8  this[0xb2] (proxy: .SetIdentity)
		EulerAngles	netOrientation;				// @0x2d4  this[0xb5] (from update record)
	// SHADOW JOINT (task #20): the model record's ShadowJointName (record+0xB4,
	// 'jointshadow' on the BLH) resolved to the live skeleton Joint that tilts
	// the flat *_tshd shadow proxy to the terrain angle.  Binary: part_012.c:
	// 10285 (record+0xb4 -> CString -> GetSegment -> GetJointIndex -> GetJoint
	// -> this[0x10b] @0x42c).  Accessed BY NAME only; declared after the locked
	// fields so it does not shift them.
public:
	Joint		*shadowJointNode;			// binary @0x42c  this[0x10b]
	void		UpdateShadowJoint(const Vector3D &ground_normal);
	// AUTHENTIC GROUND MODEL ctor products (task #15, ground-model-decode;
	// binary part_012.c:9938-9940 + 9974-9975).  By-name access only; declared
	// after the layout-locked fields so nothing shifts.
	Scalar		standingTemplateMaxY;		// binary @0x518  collisionTemplate->maxY at ctor
	Scalar		duckedTemplateMaxY;			// binary @0x51c  0.6 x standing (duck preset)
	Scalar		templateBottomLift;			// binary @0x4b8  0.05 x (volume maxX-minX)

	// AUTHENTIC TARGETING (task #36): the engine Reticle struct (MUNGA/RETICLE.h
	// [T0]) -- the mech's "TargetReticle" attribute (id 0x1d) binds to it, per the
	// RP VTV analog (VTV.h: `Reticle targetReticle` + TargetReticleAttributeID).
	// The pilot slews reticlePosition; the pick ray through it fills
	// rayIntersection / targetEntity / targetDamageZone; the mech's engine-Entity
	// target slots (+0x37c/+0x388/+0x38c) are fed from this each frame (the
	// binary's writer is in an un-exported gap -- semantics per RETICLE.h).
	// By-name access only; declared after the layout-locked fields.
	Reticle		targetReticle;

	// Ray pick against this mech (the Reticle "pick point intersection"):
	// world ray -> local frame -> slab test vs the collision template's
	// ExtentBox (BoundingBox::HitBy clips the Line at the entry distance)
	// -> world hit point.  Defined in mech.cpp.
	Logical		PickRayHit(const Point3D &start, const Vector3D &dir,
						   Scalar max_range, Point3D *hit_world);
protected:

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Alarms / indicators
	//
	protected:
		AlarmIndicator	masterAlarm;			// @0x39c  this[0xe7] (33 levels)
		AlarmIndicator	heatAlarm;				// @0x450  this[0x114] (3 levels)
		AlarmIndicator	stabilityAlarm;			// @0x4c4  this[0x131] (2 levels)
		AlarmIndicator	statusAlarm;			// @0x714  this[0x1c5] (33 levels)

		FilteredScalar	telemetryFilter[5];		// @0x7e0  this[0x1f8..0x204] (15-sample)

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Misc recovered state (names best-effort)
	//
	protected:
		BTVal		mechName;					// @0x360  this[0xd8] (proxy: .Copy)
		Scalar		maxSpeed;					// @0x400  this[0x100] = FLT_MAX
		int			stateFlags;					// @0x410  this[0x104]
		int			throttleState;				// @0x4a4  this[0x129] = 2 (initial)
		int			heatLevel;					// @0x518  this[0x146]
		int			heatCapacity;				// @0x51c  this[0x147] = 0.6 * heatLevel
		Time		creationTime;				// @0x778  this[0x1de]
		// Cylinder hit-location table (STEP 6) -- DamageLookupTable* cached by the
		// ctor from the type-0x1d resource (FUN_0049ea48 = the table ctor), read by
		// Mech::TakeDamageMessageHandler to resolve an unaimed (-1) hit's zone from
		// its impact point.  MUST be a real named member: the old home Wword(0x111)
		// is the recon ABSORBER bank (BTVal stores nothing, reads 0) -> the cached
		// table silently vanished and every unaimed hit no-op'd.  (Was mislabeled
		// "ammoExpended"; binary slot this[0x111] is this pointer.)
		int			damageLookupTable;			// @0x444  this[0x111] (FUN_0049ea48)
		int			deathHandler;				// @0x850  this[0x214] (FUN_0042a984)
		// Wreck-smoke cadence (port addition, [T3]): re-fires the death/rubble
		// smoke plume (psfx 1, DDTHSMK) every 10s (its authored emission window)
		// while the mech is a destroyed wreck, so a dead mech keeps smoking.
		Scalar		wreckSmokeTimer;
		// The cockpit eye-slew angles (the "EyepointRotation" attribute the
		// binary's DPLEyeRenderable composes into the view every frame).  Was
		// bound to the shared junk attrPad -> the cockpit camera got rotated by
		// garbage (the canted-horizon / black-screen views).  Zero until the
		// eye-slew system (HUD freeAimSlew / gyro eye chain) writes it.
		EulerAngles	eyepointRotation;

		// Three ref-counted creation-name objects (badge/color/insignia).
		void		*resourceNameA;				// @0x844  this[0x211]
		void		*resourceNameB;				// @0x848  this[0x212]
		void		*resourceNameC;				// @0x84c  this[0x213]

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Reconstruction additions -- members & methods whose bodies live in the
	// mech2 / mech3 / mech4 slices (declared here so the whole class is visible).
	// Offsets cite the mech2/mech3 offset maps; types inferred from usage.
	//
	public:
		static Receiver::MessageHandlerSet	MessageHandlers;	// SharedData ctor operand
		// Mech's OWN handler table (overrides Entity's TakeDamage with the
		// cylinder hit-location resolver); chained to Entity::GetMessageHandlers().
		static const Receiver::HandlerEntry	MessageHandlerEntries[];
		enum { StateCount = 0x21 };

		//
		// Attribute Support -- the named attributes the cockpit gauges bind to.
		// The config (content/GAUGE/L4GAUGE.CFG) binds the bare names LinearSpeed /
		// MaxRunSpeed / DuckState / RadarRange / RadarLinearPosition /
		// RadarAngularPosition, resolved by Simulation::GetAttributePointer ->
		// activeAttributeIndex->Find(name).  The reconstruction shipped an EMPTY,
		// unchained Mech::AttributeIndex, so EVERY bare Mech gauge (and even the
		// inherited attributes) resolved to NullAttribute.
		//
		// The full binary id set (0x15..0x38, table VA 0x50be84) is declared so the
		// published table can be extended without renumbering.  The chain starts at
		// JointedMover::NextAttributeID (0x15); ids MUST stay contiguous, because the
		// built index is a dense array sized to max(id) and AttributeIndexSet::Find
		// strcmps EVERY slot -- an unfilled slot holds a garbage entryName -> AV.
		//
		enum {
			MaxAccelerationAttributeID = JointedMover::NextAttributeID,	// 0x15
			CollisionStateAttributeID,				// 0x16
			CollisionNormalAttributeID,				// 0x17
			CollisionSpeedAttributeID,				// 0x18
			CollisionMaterialTypeAttributeID,		// 0x19
			CurrentSpeedAttributeID,				// 0x1a
			MaxRunSpeedAttributeID,					// 0x1b
			EyepointRotationAttributeID,			// 0x1c
			TargetReticleAttributeID,				// 0x1d
			FootStepAttributeID,					// 0x1e
			AnimationStateAttributeID,				// 0x1f
			ReplicantAnimationStateAttributeID,		// 0x20
			LinearSpeedAttributeID,					// 0x21
			ClimbRateAttributeID,					// 0x22
			AccelerationLastFrameAttributeID,		// 0x23
			AngularSpeedAttributeID,				// 0x24
			ArmorDamageLevelAttributeID,			// 0x25
			SubsystemDamageLevelAttributeID,		// 0x26
			MyomerDamageLevelAttributeID,			// 0x27
			TestButton1AttributeID,					// 0x28
			TestButton2AttributeID,					// 0x29
			TestButton3AttributeID,					// 0x2a
			TestButton4AttributeID,					// 0x2b
			TestButton5AttributeID,					// 0x2c
			TestButton6AttributeID,					// 0x2d
			ReduceButtonAttributeID,				// 0x2e
			RadarRangeAttributeID,					// 0x2f
			RadarLinearPositionAttributeID,			// 0x30
			RadarAngularPositionAttributeID,		// 0x31
			RearFiringAttributeID,					// 0x32
			RequestDuckAnimationAttributeID,		// 0x33
			UnstablePercentageAttributeID,			// 0x34
			SuperStopAttributeID,					// 0x35
			IncomingLockAttributeID,				// 0x36
			DuckStateAttributeID,					// 0x37
			DistanceToMissileAttributeID,			// 0x38
			NextAttributeID							// 0x39
		};
		static const IndexEntry		AttributePointers[];
		static AttributeIndexSet&	GetAttributeIndex();

		// Cockpit-gauge attribute backing members (APPENDED to Mech's own region --
		// never at a locked base/gait offset).  attrPad is a shared read-only 0 for
		// the dense-prefix ids the BLH cockpit does not bind; linearSpeed is the live
		// forward ground speed the speed readout / arc / radar consume (binary @0x81c).
		Scalar		attrPad;
		Scalar		linearSpeed;
		// Radar/map gauge attributes (binary @0x404/0x408/0x40c/0x3f8).  The map
		// widget reads position/angle as POINTERS into the mech's live origin, so
		// radarLinearPosition/radarAngularPosition point at localOrigin.{linear,
		// angular}Position (set in the ctor); radarRange is the radar scale.
		Scalar		radarRange;				// 0x2f  RadarRange (scale/max)
		Point3D		*radarLinearPosition;	// 0x30  RadarLinearPosition
		Quaternion	*radarAngularPosition;	// 0x31  RadarAngularPosition
		int			duckState;				// 0x37  DuckState (crouch posture)

		// --- raw / engine-shim members touched by the ctor slice -------------
		void			*vtable;				// installed Mech vtable ptr
		Word			instanceFlags;			// entity instance flags (this+0x18 region)
		ReconFiltered	mechNameFilter;			// @0xdb  name filter
		ReconSlot		voltageBus;				// resolve-able power-bus slot
		void			*controlSource;			// @0x128 controls subsystem handle

		// --- locomotion / gait animation state (mech2/mech3 maps) ------------
		Word			actionRequestFlags;		// @0x18  pending action bits
		int				movementMode;			// @0x40  gait/death selector
		int				movementFlags;
		int				motionEventArmed;		// @0x5a4
		int				motionEventPending;
		int				airborneSelect;
		Scalar			forwardCycleRate;		// @0x344
		Scalar			legCycleSpeed;			// @0x348
		Scalar			reverseStrideLength;	// @0x34c
		Scalar			gimpStrideLength;		// @0x350
		AlarmIndicator	legStateAlarm;			// @0x39c
		int				legAnimationState;		// @0x3b0
		Scalar			gimpSpeedMax;			// @0x52c
		Scalar			standSpeed;				// @0x530
		Scalar			walkStrideLength;		// @0x534
		Scalar			reverseSpeedMax;		// @0x538
		Scalar			jumpRunSpeedMax;		// @0x53c
		Scalar			jumpWalkSpeedMax;		// @0x540
		Scalar			jumpRunStrideLength;	// @0x544
		Scalar			jumpWalkStrideLength;	// @0x548
		Scalar			groundCycleRate;		// @0x5b8
		Scalar			airborneCycleRate;		// @0x5bc
		// Forward-throttle scale read by the controls mapper (@004afd10:
		// speedDemand = topSpeed@0x34c * throttle * THIS).  No writer found in the
		// decomp windows (likely model/status-driven); ctor inits 1.0 (neutral).
		Scalar			forwardThrottleScale;	// @0x5c0
		int				clipLoadGuard;			// @0x5c4
		Scalar			globalTimeScale;		// @0x5a8
		Scalar			idleStrideScale;		// @0x5ac
		Scalar			gimpCycleRate;			// @0x5b0
		int				jumpCapable;			// @0x580
		int				hasReverseGimpSet;		// @0x584
		int				hasCrashSet;			// @0x588
		int				deathAnimationLatched;	// @0x650
		int				legResetLatch;			// @0x654
		int				bodyResetLatch;			// @0x658
		ReconSeq		legAnimation;			// @0x65c  channel-A controller
		ReconSeq		bodyAnimation;			// @0x6bc  channel-B controller
		AlarmIndicator	bodyStateAlarm;			// @0x714
		int				bodyAnimationState;		// @0x728
		Scalar			bodyTargetSpeed;		// @0x6b4
		Scalar			bodyCycleSpeed;			// @0x6b8
		Scalar			reverseSpeedMax2;		// @0x7a0
		Scalar			arrivalTime;
		Scalar			simTime;
		Scalar			spinRate;
		ResourceDescription::ResourceID	animationClips[40];	// @0x5cc  gait clips
		// namedClip is NOT a separate array in the binary: namedClip@0x5e0 == &animationClips[5]
		// (0x5cc + 5*4).  LoadLocomotionClips writes namedClip[i]; SetBodyAnimation/MeasureClipStride
		// read animationClips[i+5] -- SAME storage.  Aliased via this pointer (set in the ctor) so the
		// array syntax in mech3.cpp keeps working AND the writes land where the state machine reads.
		ResourceDescription::ResourceID	*namedClip;			// -> &animationClips[5]
		ResourceDescription::ResourceID	crashClipA, crashClipB, crashClipC;	// @0x5d8/0x5d4/0x5dc
		ResourceDescription::ResourceID	gimpBaseClip;		// @0x64c
		CString			motionEventName;		// @0x598  cleared to "" on fall/reset

		// --- gait helpers (mech2) -------------------------------------------
		void	SetLegAnimation(int state);					// @004a7fc4
		void	SetBodyAnimation(int state);				// @004a800c
		void	RequestActionFlags(Word bits);				// @004a4c54
		// P3 STEP-7 body finished-callback (the real PTR_LAB_0050d6fc == FUN_004a6d8c):
		// called by SequenceController::Advance at end-of-clip -- picks the next gait state
		// from commanded speed vs the loaded caps, re-arms via SetBodyAnimation, recursively
		// advances the carryover.  Static (the cb is a plain fn ptr, owner Mech is arg1).
		// BodyTransition = the shared tail (SetBodyAnimation(next) + bodyAnimation.Advance).
		// LoopBodyClip = bring-up loop for the inline cutover path.
		static Scalar	BodyClipFinished(Mech *m, unsigned a2, Scalar carryover, int move_joints);	// FUN_004a6d8c
		static Scalar	LoopBodyClip(Mech *m, unsigned a2, Scalar carryover, int move_joints);
		Scalar			BodyTransition(int next_state, Scalar adv_time, int move_joints);
		// LEG-channel finished-callback (the real PTR_LAB_0050d6f0 == FUN_004a6928):
		// same transition machine keyed on legAnimationState@0x3b0, but compares the
		// LIVE mapper speedDemand (subsystemArray[0]+0x128) and slews legCycleSpeed.
		static Scalar	LegClipFinished(Mech *m, unsigned a2, Scalar carryover, int move_joints);	// FUN_004a6928
		Scalar			LegTransition(int next_state, Scalar adv_time, int move_joints);
		Scalar	AdvanceLegAnimation(Scalar time_slice);				// @004a5028
		Scalar	AdvanceBodyAnimation(Scalar time_slice, int loop);
		Scalar	AdvanceBodyAnimationAirborne(Scalar time_slice, int loop);
		Scalar	AdvanceLegAnimationAirborne(Scalar time_slice);
		Logical	IsDisabled();								// FUN_0049fb54
		// --- death sequence (the un-exported master-perf death branch, rebuilt
		//     from its exported consumers + the RP VTV::DeathShutdown analog) ---
		Logical	IsMechDestroyed();		// damage-side death flag: graphicAlarm level >= 9
		void	UpdateDeathState(Scalar dt);	// death freeze + subsystem DeathShutdown + wreck smoke

		// --- clip loaders (mech3) -------------------------------------------
		ResourceDescription::ResourceID *
				ResolveAnimationClip(const char *prefix, const char *suffix);	// @004a7f50

		// Body-animation looping callback (bring-up gait).  Non-virtual so adding
		// it does NOT change object layout; passed to AnimationInstance::SetAnimation
		// (reinterpret_cast to JointedMover::AnimationCallback) so the clip re-arms at
		// frame 0 when it ends instead of dereferencing a NULL finishedCallback.
		Scalar	OnBodyAnimFinished(ResourceDescription::ResourceID animation_number,
						Scalar carryover, Logical animate_joints);
		void	MeasureClipStride(int slot, Scalar *total, Scalar *lastKey);		// @004a8054
		void	LoadLocomotionClips(Mech__ModelResource *model);				// @004a80d4
		void	LoadLocomotionClipsExt(Mech__ModelResource *model);				// @004a86c8

		static int
				CreateSubsystemStream(
					int subsystem_index, const char *type_name, int pass,
					NotationFile *model_file, const char *model_name,
					const char *subsystem_name, SubsystemResourceBlob *out_blob,
					NotationFile *subsystem_file,
					const ResourceDirectories *directories);
		static ResourceDescription::ResourceID
				CreateSubsystemsStream(
					NotationFile *model_file, const char *model_name,
					NotationFile *model_notation,
					const ResourceDirectories *directories);
		static SharedData *
				SubsystemDefaultData(const char *type_name);

		// --- simulation / damage (mech4) ------------------------------------
		void	DeadReckonPose(Scalar fraction);
		Logical	IntegrateMotion(Scalar time_slice, int loop);
		void	Simulate(Scalar time_slice);

		// --- BRING-UP locomotion (Tier 2: drivable player mech) -------------
		//  Overrides the engine per-frame Simulation::PerformAndWatch (virtual;
		//  the simulation director calls it every frame on every entity).  The
		//  shipped game's per-frame tick (Simulate via activePerformance) is the
		//  uncaptured/reconstructed-but-unsafe chain (raw this+0xNN offset bugs,
		//  stubbed subsystems), so for the drivable bring-up we override
		//  PerformAndWatch directly and integrate the player's localOrigin /
		//  localToWorld from throttle+turn input.  localToWorld is the matrix the
		//  render tree + chase camera (btl4vid.cpp) are bound to, so the mech
		//  walks and the camera follows.  See mech4.cpp.  MUST match the base
		//  signature exactly or it hides instead of overriding.
		void	PerformAndWatch(const Time& till, MemoryStream *update_stream);
		// THE REAL per-contact collision responder (task #15, ground-model-decode):
		// binary FUN_004abb40, Mech vtable slot +0x3c -- the override of the engine
		// protected virtual Mover::ProcessCollision (MOVER.h:359-365, signature
		// exact).  The earlier draft misread this function as a weapon sweep
		// ("ResolveWeaponImpact") -- it is the mech-vs-world collision policy:
		// BoxedSolid resolver -> StaticBounce -> owner classification (Mover /
		// CulturalIcon crush sentinel 0.00123f).  Gated: falls through to the
		// engine base when GroundReal() is off.
		virtual void ProcessCollision(Scalar time_slice, BoxedSolidCollision &collision,
					const Point3D &old_position, Damage *damage);
		// The authentic per-frame ground block (binary FUN_004a9b5c @4aa630-4aab5f):
		// MoveCollisionVolume -> ground probe/snap -> collisions -> response policy.
		// old_position = the START-OF-FRAME position (the blocking-hit restore target);
		// must NOT alias localOrigin.linearPosition.
		void	AuthenticGroundAndCollide(Scalar dt, const Point3D &old_position);
		void	FeedHeatCapacityGauge();
		void	FeedHeatLevelGauge();
		static Logical
				LookupDamageState(const char *keyword, int *out);
		void	RaiseStatusAlarm(int level);	// subsystem -> mech status alarm

		// --- damage-table support (dmgtable) --------------------------------
		Point3D	WorldToLocal(const Point3D &world);		// FUN_00408bf8 (owner+0xd0)
		void	*TorsoOrientationSource();				// *(this+0x438)
		// Height reference the cylinder table normalises impact height against --
		// the binary's *(mech+0x2ec)+0xc, a stance height set from mech+0x518
		// (standing) / +0x51c (ducked).  We return the standing value
		// (collisionTemplate->maxY captured at ctor).
		Scalar	CylinderReferenceHeight();
		// Live torso twist (yaw) in radians -- torso+0x1d8, via the roster torso
		// (sinkSourceSubsystem @0x438).  0 for a fixed-torso mech.
		Scalar	TorsoHeading();
		// Mech override of Entity::TakeDamageMessageHandler: resolve an unaimed
		// (invalidDamageZone) hit's zone via the cylinder table, then base-route.
		void	TakeDamageMessageHandler(TakeDamageMessage *message);

		// --- damage-routing support (mechdmg / mech4) -----------------------
		// Typed access to the inherited Entity::damageZones[] (engine stores DamageZone*;
		// our entries are Mech__DamageZone, populated by the Mech ctor).  Defined in
		// mech.cpp where Mech__DamageZone is a complete type.
		Mech__DamageZone *Zone(int i) const;
		// First VITAL damage zone index (0 if none) -- for concentrated fire that kills.
		// Defined in mech4.cpp (Mech__DamageZone::vitalDamageZone is protected; Mech has access).
		int				FirstVitalZone() const;
		int				flags;					// entity flags word (this+0x28 region)
		int				stance;					// posture state
		int				ammoState;				// @0x44c  0 none / 1 leaking / 2 dry
		AlarmIndicator	graphicAlarm;			// body graphic-state alarm
		EntityID		lastInflictingID;		// @0x43c  last attacker (read by DamageZone routing)
		Logical			IsAirborne();

		// --- gait world-step accumulators (P3 STEP-6 base-region reconciliation) ---
		// RELOCATED out of the engine base: IntegrateMotion used raw 1995 binary
		// offsets that, in the 2007 engine layout, stomp live engine fields
		// (0x260->projectedOrigin, 0x26c->previousOrigin, 0x298->projectedVelocity,
		// 0x100->localOrigin, 0x12c->updateOrigin).  Declared here in the Mech's OWN
		// region (appended -- shifts no locked offset) and accessed BY NAME.  See
		// btbuild/P3_LOCOMOTION.md "BASE-REGION RECONCILIATION".
		Quaternion		motionDelta;		// was raw @0x260 (orientation delta; FromQuat src)
		Quaternion		worldPose;			// was raw @0x26c (integrated world pose)
		Quaternion		worldPoseBase;		// was raw @0x138 (dead-reckon base pose; DeadReckonPose)
		Quaternion		angularAccum;		// was raw @0x298 (per-frame angular accumulator)
		Vector3D		motionSourceA;		// was raw @0x100 (motion-event source A)
		Vector3D		motionSourceB;		// was raw @0x12c (motion-event source B)
		Vector3D		motionEventVector;	// was raw @0x598 (motion-event delta = B - A)
		Quaternion		aimRate;			// was raw @0x1dc (aim/torso angular-rate telemetry,
											// cleared each frame; stomped localAcceleration)

	//
	// ---  Everything past here is owned by mech2.cpp .. mech4.cpp  ---
	//   [mech2] MoveAndCollide / Simulate / WriteUpdateRecord / damage routing
	//   [mech3] PrintState / status reporting / cockpit display feed
	//   [mech4] AI / pathing / scoring hooks
	// The remaining members up to sizeof==0x854 are declared by those slices.
	//
	};

#endif
