//===========================================================================//
// File:	gyro.hpp                                                            //
// Project: BattleTech           Brick: Entity Manager                        //
// Contents: Gyroscope subsystem -- balance / orientation / tip-over model    //
//---------------------------------------------------------------------------//
//   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 for this class; every declaration below
// is inferred from the decompiled bodies and -- crucially -- from the
// resource field-name string pool parsed by Gyroscope::CreateStreamedSubsystem
// (@004b3eb4).  Those "missing <Name>!" diagnostics are GROUND TRUTH for the
// resource layout; the in-object member names are best-effort (flagged).
//
// Recovered binary facts (see gyro.cpp for per-method @ADDR evidence):
//   - Gyroscope IS-A PowerWatcher (powersub.hpp); the ctor @004b3778 chains to
//     the PowerWatcher base ctor (FUN_004b18a4) passing &Gyroscope::DefaultData
//     (&DAT_0050fdb0).  The full chain is therefore
//        Gyroscope : PowerWatcher : HeatWatcher : MechSubsystem : Subsystem
//        (NOT HeatableSubsystem -- the Watcher branch, re-based; see CLAUDE.md §10d)
//   - vtable @00510abc   ctor @004b3778   dtor @004b3e88
//   - DefaultData @0050fdb0
//   - RegisteredClass classID 0x0BC4 (resource +0x20), streamed model size
//     0x21C (resource +0x24) -- both stamped by CreateStreamedSubsystem.
//
// WHAT THE GYROSCOPE DOES (from the recovered simulation @004b275c + helpers):
//   It is the mech's balance/orientation model.  It carries a damage-dependent
//   idle "sway" (the PercentageOn{Normal,Degradation,Destruction,Failure}
//   targets, ramped at RotationPerSecond and clamped to the [MinAnimationNoise,
//   MaxAnimationNoise] band), a full pitch/yaw/roll spring-damper that tips the
//   body when off balance, a separate spring-damper that drives the cockpit
//   "eye" joint, and a set of per-damage-type response curves (Trans / PitchRoll
//   / Yaw / Vibration) that perturb the gyro when the mech is hit.  The result
//   is written into two skeleton joints (the "EyeJoint" and the "MechJoint").
//   When the watched power source is dead (PowerWatcher), the heat model is off,
//   or the subsystem is in the Failure heat state, it snaps to the destruction
//   target instead of the normal one (-> visible tip / collapse).
//
#if !defined(GYRO_HPP)
#	define GYRO_HPP

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

//#####################    Forward Class Declarations  #######################
class Mech;
class Joint;			// engine skeleton node (JOINT.h); eye/mech joint targets

//###########################################################################
//#################    Gyroscope Model Resource    ##########################
//###########################################################################
//
// Extends the PowerWatcher resource (which adds MinVoltagePercent @+0xF0).
// Field offsets and names are exactly those read by
// Gyroscope::CreateStreamedSubsystem @004b3eb4.  Every Scalar below is primed
// to -1.0f on pass 1 and is mandatory (the parser fails with
// "<subsystem> missing <Name>!" if absent).  The Rotation*Spring* angular
// fields are additionally multiplied by PI/180 (deg->rad, _DAT_004b5b24).
//
	// Per-damage-type gyro response curve (4 named Scalars, +0x10 each).  The
	// recon first modelled this as a Vector4D; the engine Vector4D is incomplete
	// here and the fields are addressed by name (Trans/PitchRoll/Yaw/Vibration),
	// so a dedicated POD captures the layout exactly.
	struct Gyroscope__DamageResponse
	{
		Scalar	trans;		// +0x0
		Scalar	pitchRoll;	// +0x4
		Scalar	yaw;		// +0x8
		Scalar	vibration;	// +0xC
	};

	struct Gyroscope__SubsystemResource:
		public PowerWatcher::SubsystemResource	// ends 0xF4 after RESOURCE_AUDIT.md fix B
	{
		// +0xF4: a 4-byte record slot between the PowerWatcher base and "Exageration".
		// The Gyroscope parser/ctor do not read it (verified: ctor reads 0xF8/0xFC/0x100/
		// 0x104), but the record reserves it -- omitting it slid every field 4 bytes low
		// (exageration -> 0xF4 instead of 0xF8).  Keep the slot to match the binary.
		Scalar	_reserved_f4;				// +0xF4  (unread record slot)
		Scalar	exageration;				// +0xF8  "Exageration"
		Scalar	maxAnimationNoise;			// +0xFC  "MaxAnimationNoise"
		Scalar	minAnimationNoise;			// +0x100 "MinAnimationNoise"
		Scalar	rotationPerSecond;			// +0x104 "RotationPerSecond"
		Scalar	percentageOnNormal;			// +0x108 "PercentageOnNormal"
		Scalar	percentageOnDestruction;	// +0x10C "PercentageOnDestruction"
		Scalar	percentageOnDegradation;	// +0x110 "PercentageOnDegradation"
		Scalar	percentageOnFailure;		// +0x114 "PercentageOnFailure"

		Vector3D	springConstant;			// +0x118 "SpringConstantX/Y/Z"
		Vector3D	dampingConstant;		// +0x124 "DampingConstantX/Y/Z"
		Vector3D	posSpring;				// +0x130 "PosSpringX/Y/Z"
		Vector3D	negSpring;				// +0x13C "NegSpringX/Y/Z"

		// angular spring/damping, ordered Roll(+0x148), Yaw(+0x14C), Pitch(+0x150)
		Vector3D	rotationSpringConstant;	// +0x148 "RotationSpringConstantRoll/Yaw/Pitch"
		Vector3D	rotationDampingConstant;// +0x154 "RotationDampingConstantRoll/Yaw/Pitch"

		// rotation limits, ordered Pitch(.x) / Yaw(.y) / Roll(.z), each *PI/180.
		// RECON NOTE: originally guessed Point4D (quaternion-ish); the parser only
		// reads three named components (Pitch/Yaw/Roll) so a Vector3D is exact.
		Vector3D	rotationPosSpring;		// +0x160 "RotationPosSpringPitch/Yaw/Roll"
		Vector3D	rotationNegSpring;		// +0x16C "RotationNegSpringPitch/Yaw/Roll"

		char		eyeJoint[32];			// +0x178 "EyeJoint"   (skeleton node name)
		char		mechJoint[32];			// +0x198 "MechJoint"  (skeleton node name)

		// per-damage-type response: scalar multiplier + {Trans,PitchRoll,Yaw,Vibration}
		Scalar	collisionDamageMultiplier;	// +0x1B8 "CollisionDamageMultiplier"
		Scalar	ballisticDamageMultiplier;	// +0x1BC "BallisticDamageMultiplier"
		Scalar	explosiveDamageMultiplier;	// +0x1C0 "ExplosiveDamageMultiplier"
		Scalar	laserDamageMultiplier;		// +0x1C4 "LaserDamageMultiplier"
		Scalar	energyDamageMultiplier;		// +0x1C8 "EnergyDamageMultiplier"

		Gyroscope__DamageResponse	collisionDamageResponse;// +0x1CC Trans/PitchRoll/Yaw/Vibration
		Gyroscope__DamageResponse	ballisticDamageResponse;// +0x1DC
		Gyroscope__DamageResponse	explosiveDamageResponse;// +0x1EC
		Gyroscope__DamageResponse	laserDamageResponse;	// +0x1FC
		Gyroscope__DamageResponse	energyDamageResponse;	// +0x20C
	};											// sizeof == 0x21C (model size stamp)
	static_assert(offsetof(Gyroscope__SubsystemResource, exageration)         == 0xF8,  "Gyroscope exageration must be at 0xF8");
	static_assert(offsetof(Gyroscope__SubsystemResource, energyDamageResponse)== 0x20C, "Gyroscope energyDamageResponse must be at 0x20C");
	static_assert(sizeof(Gyroscope__SubsystemResource)                        == 0x21C, "Gyroscope record must be 0x21C");

//###########################################################################
//#############################   Gyroscope   ###############################
//###########################################################################

	class Gyroscope:
		public PowerWatcher
	{
		friend struct GyroLayoutCheck;		// compile-time offset lock (gyro.cpp)
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Shared Data Support   (DefaultData @0050fdb0, vtable @00510abc)
	//
	public:
		static Derivation ClassDerivations;
		static Receiver::MessageHandlerSet MessageHandlers;
		static AttributeIndexSet AttributeIndex;
		static SharedData DefaultData;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// BT segment / base-state compatibility shims.
	//
	// CROSS-FAMILY: these accessors and segment-flag constants logically belong
	// on the shared bases (Mech/Subsystem::GetSegmentFlags, HeatSink::HeatStateLevel
	// / HeatModelOff, PoweredSubsystem::ElectricalStateLevel).  Gyroscope derives
	// from PowerWatcher (-> Subsystem), which in these headers exposes none of
	// them, so they are backed locally here so this module compiles; the backing
	// fields are written by the (cross-family) base simulation once the real
	// accessors land.  See report "CROSS-FAMILY NEEDS".
	//
#if !defined(BT_SEGMENT_FLAG_CONSTS)
#	define BT_SEGMENT_FLAG_CONSTS
		enum {
			SegmentCopyMask		= 0x0C,		// (flags & 0xC): 0 == master, 4 == damaged copy
			SegmentLiveFlag		= 0x01,
			MasterHeatSinkFlag	= 0x0100
		};
#endif
	public:
		// BASE-CHAIN RE-BASE: the 4 shim BACKING FIELDS are removed (they over-sized
		// the object + duplicated engine-base state); these accessors now read the
		// REAL inherited base state, exactly like Torso (see CLAUDE.md §10d).
		// (GetSegmentFlags removed -- unused; the ctor reads owner->simulationFlags.)
		HeatSink::HeatState
			HeatStateLevel() const			{ return (HeatSink::HeatState)heatAlarm.GetLevel(); }		// inherited HeatWatcher
		Logical
			HeatModelOff() const			{ return simulationState == 1; /* Destroyed */ }			// inherited MechSubsystem
		PoweredSubsystem::ElectricalState
			ElectricalStateLevel() const	{ return (PoweredSubsystem::ElectricalState)watchdogAlarm.GetLevel(); }	// inherited PowerWatcher

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

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

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

		// @004b275c -- the registered Performance (PTR @0050fe08).  Advances the
		// idle sway toward the damage-state target then runs the two integrators.
		void
			GyroscopeSimulation(Scalar time_slice);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Damage / impulse hooks (called by the Mech damage path to kick the gyro)
	//
	public:
		void	ApplyDamageImpulse(						// @004b2d8c
					Scalar x, Scalar y, Scalar z, Scalar magnitude);
		void	ApplyDamageTorque(						// @004b2de4
					Scalar x, Scalar y, Scalar z, Scalar magnitude);
		void	ApplyVerticalImpulse(					// @004b2e50
					Scalar pitch, Scalar /*y*/, Scalar /*z*/, Scalar magnitude);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Subsystem virtual overrides (slots on vtable @00510abc)
	//
	public:
		Logical	HandleDeathMessage(Message &message);	// slot  9, @004b2660 -> @004b179c
		void	ResetToInitialState();					// slot 10, @004b2678

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

		Gyroscope(										// @004b3778
			Mech				*owner,
			int					subsystem_ID,
			SubsystemResource	*subsystem_resource,
			SharedData			&shared_data = DefaultData
		);
		~Gyroscope();									// @004b3e88

		static int
			CreateStreamedSubsystem(					// @004b3eb4
				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:
		void	IntegrateEyeJoint(Scalar time_slice);	// @004b2ec0
		void	IntegrateBody(Scalar time_slice);		// @004b30ec
		void	WriteEyeJoint();						// @004b33e0  (@004b2eac thunks here)
		void	WriteMechJoint();						// @004b34ec
		Logical	UpdateAnimationNoise(Scalar time_slice);// @004b357c

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Local data.  Byte offsets are those observed in the shipped object
	// (this[n] == @0x(4n)).  Names are best-effort from usage; the resource
	// provenance (where a field is copied straight out of the resource) is
	// noted.  PowerWatcher/HeatWatcher/HeatableSubsystem base data occupies the
	// low offsets and is NOT redeclared here.
	//
	protected:
		Scalar		exageration;			// @0x1D8  resource +0xF8 (impulse scale)

		Vector3D	eyeOrientation;			// @0x1DC  integrated cockpit "eye" angles
		Vector3D	eyeVelocity;			// @0x1E8  d(eyeOrientation)/dt
		Vector3D	eyeLimitLow;			// @0x200  = posSpring  (clamp lo, +0x200/204/208)
		Vector3D	eyeLimitHigh;			// @0x20C  = negSpring  (clamp hi, +0x20C/210/214)
		Vector3D	eyePosSpring;			// @0x218  resource posSpring  (+0x130)
		Vector3D	eyeNegSpring;			// @0x224  resource negSpring  (+0x13C)
		Vector3D	eyeSpringConstant;		// @0x1F4? eyeSpring work (see ctor 0408744)
		Vector3D	eyeAccel;				// @0x230  spring-damper accumulator
		Vector3D	eyeForce;				// @0x23C  scaled by time_slice
		Vector3D	eyeWork;				// @0x248  scratch (cross/normalise)

		Vector3D	bodyOrientation;		// @0x2B4  integrated body tip angles
		Vector3D	rotationSpringConstant;	// @0x2C0  resource +0x148  (roll/yaw/pitch)
		Vector3D	rotationDampingConstant;// @0x2CC  resource +0x154
		Vector3D	bodyLimitLow;			// @0x2D8  = rotationPosSpring
		Vector3D	bodyLimitHigh;			// @0x2E4  = rotationNegSpring
		Vector3D	bodyPosSpring;			// @0x2F0  resource rotationPosSpring (+0x160)
		Vector3D	bodyNegSpring;			// @0x2FC  resource rotationNegSpring (+0x16C)
		Vector3D	bodyAccel;				// @0x308  spring-damper accumulator
		Vector3D	bodyForce;				// @0x314  scaled by time_slice
		Vector3D	bodyWork;				// @0x320  scratch

		Scalar		animationOffset;		// @0x390  (this[0xE4]) idle-noise phase, init 0
		Scalar		animationScale;			// @0x394  (this[0xE5]) init 1.0f
		Scalar		animationPhase;			// @0x398  (this[0xE6]) init 0

		Scalar		maxAnimationNoise;		// @0x39C  resource +0xFC
		Scalar		minAnimationNoise;		// @0x3A0  resource +0x100
		Scalar		rotationPerSecond;		// @0x3A4  resource +0x104
		Scalar		swayBias;				// @0x3A8  (this[0xEA]) added to target, init 0
		Scalar		percentageOnNormal;		// @0x3AC  resource +0x108
		Scalar		percentageOnDestruction;// @0x3B0  resource +0x10C
		Scalar		percentageOnDegradation;// @0x3B4  resource +0x110
		Scalar		percentageOnFailure;	// @0x3B8  resource +0x114

		Scalar		swayAngle;				// @0x3BC  (this[0xEF]) current idle sway value
		Scalar		swayVelocity;			// @0x3C0  (this[0xF0]) noise ramp timer
		int			swayActive;				// @0x3C4  (this[0xF1]) noise state flag

		Joint		*eyeJointNode;			// @0x3C8  (this[0xF2]) resolved EyeJoint
		Joint		*mechJointNode;			// @0x3CC  (this[0xF3]) resolved MechJoint
	};

#endif
