//===========================================================================//
// File:	torso.hpp                                                           //
// Project: BattleTech           Brick: Entity Manager                        //
// Contents: Torso subsystem -- torso twist (yaw) and elevation (pitch) aim    //
//---------------------------------------------------------------------------//
//   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, recovered shard
// part_013.c).  NO header survived; declarations are inferred from the
// decompiled bodies and from the resource field-name string pool parsed by
// Torso::CreateStreamedSubsystem (@004b6fec) -- the "missing <Name>!"
// diagnostics are GROUND TRUTH for the resource layout.  In-object member names
// are best-effort (flagged in torso.cpp).
//
// Recovered binary facts (see torso.cpp for per-method @ADDR evidence):
//   - Torso IS-A PowerWatcher (powersub.hpp); the ctor @004b6b0c chains to the
//     PowerWatcher base ctor (FUN_004b18a4) passing &Torso::DefaultData
//     (&DAT_00510af8).  Full chain:
//        Torso : PowerWatcher : HeatWatcher : MechSubsystem : Subsystem
//        (NOT HeatableSubsystem -- the Watcher branch is separate from the
//         HeatSink/PoweredSubsystem heat leaves; they share only MechSubsystem.)
//   - vtable @0051103c   ctor @004b6b0c   dtor @004b6fc0
//   - DefaultData @00510af8
//   - RegisteredClass classID 0x0BC5 (resource +0x20), streamed model size
//     0x158 (resource +0x24).
//
// WHAT THE TORSO DOES (from the recovered simulation @004b5cf0 + helpers):
//   It models torso twist (horizontal / yaw) and torso elevation (vertical /
//   pitch) aim.  Player turn/elevate commands ramp an angular rate (with a
//   button-acceleration curve from a start value up to a cap), integrate into
//   horizontal/vertical aim angles that are clamped to per-axis software limits
//   (HorizontalLimitLeft/Right, VerticalLimitTop/Bottom), auto-recenter the
//   twist when released, and write the result into two skeleton joints
//   (TorsoHorizontalJoint and its TorsoHorizontalShadowJoint).  All angular
//   resource values are stored in radians (deg * PI/180).  When the watched
//   power source is dead, not Ready, or in the Failure heat state, the effective
//   rate is zeroed (the torso freezes).
//
#if !defined(TORSO_HPP)
#	define TORSO_HPP

#if !defined(POWERSUB_HPP)
#	include "powersub.hpp"
#endif

//#####################    Forward Class Declarations  #######################
class Mech;
class Joint;			// engine skeleton node (JOINT.h); the twist target

//###########################################################################
//#################    Torso Model Resource    ##############################
//###########################################################################
//
// Extends the PowerWatcher resource (MinVoltagePercent @+0xF0).  Field offsets
// and names are exactly those read by Torso::CreateStreamedSubsystem @004b6fec.
// The angular Scalars are stored in radians (the ctor multiplies the parsed
// degree values by PI/180); they are primed to -1.0f on pass 1 and mandatory.
//
	struct Torso__SubsystemResource:
		public PowerWatcher::SubsystemResource
	{
		Scalar	horizontalRotationPerSecond;	// +0xF4  "HorizontalRotationPerSecond"
		Scalar	verticalRotationPerSecond;		// +0xF8  "VerticalRotationPerSecond"
		Scalar	horizontalLimitRight;			// +0xFC  "HorizontalLimitRight"
		Scalar	horizontalLimitLeft;			// +0x100 "HorizontalLimitLeft"
		Scalar	verticalLimitTop;				// +0x104 "VerticalLimitTop"
		Scalar	verticalLimitBottom;			// +0x108 "VerticalLimitBottom"

		char	torsoHorizontalJoint[32];		// +0x10C "TorsoHorizontalJoint"
		char	torsoHorizontalShadowJoint[32];	// +0x12C "TorsoHorizontalShadowJoint"

		Logical	torsoHorizontalEnabled;			// +0x14C "TorsoHorizontalEnabled" (default True)
		Scalar	buttonAccelerationPerSecond;	// +0x150 "ButtonAccelerationPerSecond"
		Scalar	buttonAccelerationStartValue;	// +0x154 "ButtonAccelerationStartValue"
	};											// sizeof == 0x158 (model size stamp)
	static_assert(offsetof(Torso__SubsystemResource, horizontalRotationPerSecond) == 0xF4,  "Torso horizontalRotationPerSecond must be at 0xF4");
	static_assert(offsetof(Torso__SubsystemResource, buttonAccelerationStartValue)== 0x154, "Torso buttonAccelerationStartValue must be at 0x154");
	static_assert(sizeof(Torso__SubsystemResource)                                == 0x158, "Torso record must be 0x158");

//###########################################################################
//#############################   Torso   ###################################
//###########################################################################

	class Torso:
		public PowerWatcher
	{
		friend struct TorsoLayoutCheck;		// compile-time offset locks (torso.cpp STEP 3)
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Shared Data Support   (DefaultData @00510af8, vtable @0051103c)
	//
	public:
		static Derivation ClassDerivations;
		static SharedData DefaultData;
		static Receiver::MessageHandlerSet MessageHandlers;
		static AttributeIndexSet AttributeIndex;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// BT segment / base-state / joint compatibility shims.
	//
	// CROSS-FAMILY: these accessors, the segment-flag constants, the joint
	// helpers and the PowerWatcher base-method forwards logically belong on the
	// shared bases (Mech::GetSegmentFlags / Mech::ResolveJoint, HeatSink::
	// HeatStateLevel / HeatModelOff, PoweredSubsystem::ElectricalStateLevel, the
	// skeleton-node writer, the Simulation dirty bit + creation/current time
	// accessors, and PowerWatcher::ResetToInitialState / HandleDeathMessage /
	// UpdateWatch).  Until those families expose them they are backed locally so
	// this module compiles; see report "CROSS-FAMILY NEEDS".
	//
#if !defined(BT_SEGMENT_FLAG_CONSTS)
#	define BT_SEGMENT_FLAG_CONSTS
	enum {
		SegmentCopyMask		= 0x0C,		// (flags & 0xC): 0 == primary, 4 == damaged copy
		SegmentLiveFlag		= 0x01,
		MasterHeatSinkFlag	= 0x0100
	};
#endif
	public:
		// BASE-CHAIN RE-BASE: the 7 shim BACKING FIELDS are removed so the object
		// matches the binary 0x280 layout (currentTwist@0x1D8); these accessors now
		// read the REAL inherited base state:
		//   HeatStateLevel   <- HeatWatcher::heatAlarm level        (this+0x140)
		//   ElectricalState  <- PowerWatcher::watchdogAlarm level    (this+0x198)
		//   HeatModelOff     <- MechSubsystem::simulationState==Destroyed (this+0x40)
		//   Creation/Current <- engine Simulation::lastPerformance   (this+0x14/0x10)
		//                        the net/copy timing is dormant in single-player;
		//                        equal reads give a 0 window == the shipped master path.
		//   SetMovedFlag     -> engine Simulation::ForceUpdate() dirty bit (this+0x18)
		// (GetSegmentFlags removed -- unused; the ctor reads owner->simulationFlags.)
		HeatSink::HeatState					HeatStateLevel() const		{ return (HeatSink::HeatState)heatAlarm.GetLevel(); }
		Logical								HeatModelOff() const		{ return simulationState == 1; /* Destroyed */ }
		PoweredSubsystem::ElectricalState	ElectricalStateLevel() const{ return (PoweredSubsystem::ElectricalState)watchdogAlarm.GetLevel(); }
		LWord								GetCreationTime() const		{ return (LWord)(long)lastPerformance; }
		LWord								GetCurrentTime() const		{ return (LWord)(long)lastPerformance; }
	protected:
		// Resolve a named skeleton node to its live Joint* via the owning Mech
		// (the ctor @004b6b0c inlines Mech::ResolveJoint == FUN_00424b60).  Defined
		// out-of-line in torso.cpp -- needs the complete Mech type.
		Joint*	ResolveJoint(const char *joint_name);
		// Write one twist (yaw) scalar into a resolved skeleton node, preserving its
		// other DOF -- the @004b66b4 inner block (hinge -> SetRotation(Radian);
		// ball -> SetRotation(EulerAngles) with yaw <- twist).
		void	PushTwist(Joint *node, Scalar value);
		// Simulation dirty / replicate bit -> real engine Simulation::ForceUpdate().
		void	SetMovedFlag()									{ ForceUpdate(); }
		// Local helper: "is value pinned at this software limit?".
		Logical	AtLimit(Scalar value, Scalar limit) const		{ return fabsf(value - limit) <= 0.0001f; }
		// CROSS-FAMILY delegate shims -- still no-ops (no layout impact; STEP-4 will
		// chain them to the real PowerWatcher/HeatWatcher base once the Torso is live).
		void	WatcherResetToInitialState()					{}
		Logical	WatcherHandleDeathMessage(Message & /*m*/)		{ return False; }
		void	WatcherUpdateWatch()							{}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Test Class Support
	//
	public:
		static Logical	TestClass(Mech&);
		Logical			TestInstance() const;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Command interface (set by MechControlsMapper each frame -- inputs @0x1F8)
	//
	public:
		// Command roles verified against the raw decomp @004b5cf0 (part_013.c
		// 4522-4571): @0x1F8/@0x1FC drive ELEVATION (0x1e4, clamp vertTop/Bottom);
		// @0x200/@0x204 drive TWIST (0x1d8, clamp horizLimitLeft/Right); @0x208
		// recenters twist.  (The earlier draft had elevation/twist swapped.)
		enum {
			ElevateUpCommand,		// @0x1F8  elevation +  (clamp verticalLimitTop)
			ElevateDownCommand,		// @0x1FC  elevation -  (clamp verticalLimitBottom)
			TwistLeftCommand,		// @0x200  twist +      (clamp horizontalLimitLeft)
			TwistRightCommand,		// @0x204  twist -      (clamp horizontalLimitRight)
			CenterCommand			// @0x208  recenter twist
		};

		// Analog (RIO/stick) aim demand written by MechControlsMapper::Interpret-
		// Controls (@0x1F0 twist, @0x1F4 elevation); proportional, no button ramp.
		void	SetAnalogTwistAxis(Scalar v)		{ analogTwistAxis     = v; }
		void	SetAnalogElevationAxis(Scalar v)	{ analogElevationAxis = v; }
		Logical	GetHorizontalEnabled() const		{ return horizontalEnabled; }	// @0x250 (mapper free-aim gate @004afd10)

		// Reachable horizontal (yaw) half-arc the guns can be brought to bear by
		// twisting the torso: the wider of the two software limits (radians), or 0
		// when horizontal twist is disabled (e.g. the Blackhawk, TorsoHorizontalEnabled=0).
		// This is the AUTHENTIC, data-driven weapon traverse -- weapons carry no arc
		// field; the torso mount is what lets a mech point its guns off dead-ahead.
		Scalar	GetHorizontalReach() const
		{
			if (!horizontalEnabled) return 0.0f;
			Scalar l = fabsf(horizontalLimitLeft), r = fabsf(horizontalLimitRight);
			return (l > r) ? l : r;
		}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Simulation Support
	//
	public:
		typedef void
			(Torso::*Performance)(Scalar time_slice);

		void
			SetPerformance(Performance performance)
			{
				Check(this);
				activePerformance = (Simulation::Performance)performance;
			}

		void	TorsoSimulation(Scalar time_slice);		// @004b5cf0  (master, PTR @00510c10)
		void	TorsoCopySimulation(Scalar time_slice);	// @004b65f8  (damaged copy, PTR @00510c1c)

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Subsystem virtual overrides (slots on vtable @0051103c)
	//
	public:
		void	WriteUpdateRecord(UpdateRecord *message, int update_model);	// slot 6, @004b6a78
		Logical	HandleDeathMessage(Message &message);	// slot  9, @004b5be0 -> @004b179c
		void	ResetToInitialState();					// slot 10, @004b5bf8

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Construction and Destruction
	//
	public:
		typedef Torso__SubsystemResource SubsystemResource;

		Torso(											// @004b6b0c
			Mech				*owner,
			int					subsystem_ID,
			SubsystemResource	*subsystem_resource,
			SharedData			&shared_data = DefaultData
		);
		~Torso();										// @004b6fc0

		static int
			CreateStreamedSubsystem(					// @004b6fec
				NotationFile				*model_file,
				const char					*model_name,
				const char					*subsystem_name,
				SubsystemResource			*subsystem_resource,
				NotationFile				*subsystem_file,
				const ResourceDirectories	*directories,
				int							passes = 1
			);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Internal model helpers
	//
	protected:
		Logical	ComputeTargetTwist();					// @004b6510 (returns "still slewing")
		void	WriteJoints(Scalar &verticalOut);		// @004b66b4
		void	UpdateJoints();							// @004b67ec
		Logical	Recenter(Scalar time_slice);			// @004b6918 (returns "still off-centre")

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Local data.  Byte offsets are those observed in the shipped object
	// (this[n] == @0x(4n)); names are best-effort.  Angular values are radians.
	// PowerWatcher/HeatWatcher/MechSubsystem base data occupies the low
	// offsets and is NOT redeclared here.
	//
	protected:
		Scalar	currentTwist;				// @0x1D8  current horizontal (yaw) aim
		Scalar	horizontalLimitRight;		// @0x1DC  resource +0xFC  (rad)
		Scalar	horizontalLimitLeft;		// @0x1E0  resource +0x100 (rad)
		Scalar	currentElevation;			// @0x1E4  current vertical (pitch) aim
		Scalar	twistVelocity;				// @0x1E8  d(currentTwist)/dt
		Scalar	elevationVelocity;			// @0x1EC  d(currentElevation)/dt
		Scalar	analogTwistAxis;			// @0x1F0  RIO/stick twist demand
		Scalar	analogElevationAxis;		// @0x1F4  RIO/stick elevation demand

		int		elevateUpCommand;			// @0x1F8  digital elevation-up   (>0 = active)
		int		elevateDownCommand;			// @0x1FC  digital elevation-down
		int		twistLeftCommand;			// @0x200  digital twist-left
		int		twistRightCommand;			// @0x204  digital twist-right
		int		centerCommand;				// @0x208  digital recenter
		int		statusFlags;				// @0x20C  0/1/2 (limit/active state)

		Scalar	buttonAccelerationPerSecond;// @0x210  resource +0x150
		Scalar	buttonAccelerationStart;	// @0x214  resource +0x154

		Scalar	targetTwist;				// @0x218  ComputeTargetTwist output
		Scalar	twistAtUpdate;				// @0x21C  twist snapshot at last net update
		Scalar	verticalLimitTop;			// @0x220  resource +0x104 (rad)
		Scalar	verticalLimitBottom;		// @0x224  resource +0x108 (rad)
		Scalar	elevationCenter;			// @0x228  derived limit copy
		Scalar	elevationHalfBottom;		// @0x22C  = verticalLimitBottom * 0.5f
		Scalar	twistCenterHigh;			// @0x230  derived limit copy
		Scalar	twistCenterLow;				// @0x234  derived limit copy

		Scalar	twistRate;					// @0x238  net-synced angular rate
		Scalar	baseTwistRate;				// @0x23C  resource +0xF4 (rad/s)
		Scalar	baseElevationRate;			// @0x240  resource +0xF8 (rad/s)
		Scalar	effectiveTwistRate;			// @0x244  baseTwistRate, gated by power/heat
		Scalar	effectiveElevationRate;		// @0x248  baseElevationRate, gated by power/heat

		int		isDamagedCopy;				// @0x24C  0 = live master, 1 = copy
		Logical	horizontalEnabled;			// @0x250  resource +0x14C
		LWord	lastUpdateTime;				// @0x254  ms timestamp of last command sample

		int		hitElevTop;					// @0x258  elevation-top    limit latch (vs verticalLimitTop)
		int		hitElevBottom;				// @0x25C  elevation-bottom limit latch (vs verticalLimitBottom)
		int		hitTwistLeft;				// @0x260  twist-left       limit latch (vs horizontalLimitLeft)
		int		hitTwistRight;				// @0x264  twist-right      limit latch (vs horizontalLimitRight)
		int		buttonRampActive;			// @0x268
		Scalar	buttonRamp;					// @0x26C  current button-accel multiplier
		int		_reserved0x270;				// @0x270  binary reserves a 4-byte slot here
											//         (cleared by ResetToInitialState @004b5bf8);
											//         present so recenterActive lands at 0x274.
		int		recenterActive;				// @0x274

		Joint	*horizontalJointNode;		// @0x278  resolved TorsoHorizontalJoint
		Joint	*horizontalShadowJointNode;	// @0x27C  resolved TorsoHorizontalShadowJoint
	};	// sizeof == 0x280 (own block 0x1D8..0x280); locked by static_asserts below.

	//
	// BASE-CHAIN RE-BASE: the 7 CROSS-FAMILY shim backing fields that used to sit
	// here (segmentFlags/heatModelOff/heatStateLevel/electricalState/creationTime/
	// currentTime/movedFlag) are DELETED -- they over-sized the object by 28 bytes
	// and duplicated engine-base state.  Their accessors now read the real inherited
	// members (HeatWatcher::heatAlarm, PowerWatcher::watchdogAlarm, MechSubsystem::
	// simulationState, engine Simulation::lastPerformance/ForceUpdate).  See above.
	//

#endif
