//===========================================================================//
// File:	sensor.cpp                                                          //
// Project: BattleTech                                                       //
// Contents: Sensor subsystem -- radar/targeting effectiveness model         //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
//  8/10/95  GDU Initial coding.                                             //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, recovered shards
// part_012.c / part_013.c) cross-referenced with the surviving SENSOR.HPP and
// the sibling subsystem sources (powersub.hpp/.cpp, heat.hpp/.cpp).  Each
// non-trivial method cites the originating @ADDR.  Hex float constants have
// been converted to decimal:
//      0x3f800000 = 1.0f   0x3f000000 = 0.5f
//
// ---------------------------------------------------------------------------
// HOW THE SENSOR WAS LOCATED (and a corrected class identification)
// ---------------------------------------------------------------------------
// The Mech ctor (mech.cpp @004a1674) streams one Subsystem per segment via a
// switch over the streamed ClassID (part_012.c @~0x9978).  The slot the prior
// mech.cpp reconstruction named "sensorSubsystem" (this[0x1f7]) is filled by
// case 0xBBE -> ctor FUN_004ae8d0, allocation 0x1E4.  That object is far too
// small to be a PoweredSubsystem-derived Sensor (PoweredSubsystem alone is
// 0x31C bytes), so 0xBBE / 4ae8d0 is NOT the SENSOR.HPP Sensor -- that label
// in mech.cpp is a mis-guess (the slot caches some smaller HeatSink-family
// subsystem).
//
// The real Sensor is case 0xBC3 -> ctor FUN_004b1d18, allocation 0x328
// (= PoweredSubsystem 0x31C + three Scalar/Logical members at 0x31C/0x320/
// 0x324).  Proof:
//   * The shipped string pool @0050fae0 reads "Sensor\0RadarPercent\0
//     SelfTest\0BadVoltage\0".
//   * The AttributePointers IndexEntry table @0050fa50 binds those three
//     names to attribute IDs 0x12/0x13/0x14 at object offsets 0x31C/0x320/
//     0x324 -- exactly SENSOR.HPP's radarPercent / selfTest / badVoltage and
//     its RadarPercent/SelfTest/BadVoltage AttributeIDs.
//   * CreateStreamedSubsystem @004b1dcc stamps classID 0xBC3 and model size
//     0x190 and parses no extra fields -- matching the empty
//     Sensor__SubsystemResource in SENSOR.HPP.
//
// The earlier powersub.cpp/CLASSMAP work labelled this same class "Myomers"
// (vtable 0050fb0c, ctor 4b1d18) with INFERRED members
// outputVoltage/powered/voltageAvailable -- there is no "Myomers"/
// "OutputVoltage" string evidence for those.  The recovered strings settle
// it: vtable 0050fb0c / ctor 4b1d18 / classID 0xBC3 is Sensor.
//
// Helper-function name mapping (engine internals referenced by the decomp):
//      FUN_004b0f74  PoweredSubsystem base constructor   (powersub.cpp)
//      FUN_004b0bd0  PoweredSubsystem::PoweredSubsystemSimulation
//      FUN_004b0e6c  PoweredSubsystem::ResetToInitialState(powered)
//      FUN_004b0efc  PoweredSubsystem::HandleMessage
//      FUN_004b115c  PoweredSubsystem destructor
//      FUN_0041b9ec  AlarmIndicator(levels)
//      FUN_0041bbd8  AlarmIndicator::SetLevel(n)
//      FUN_0041a1a4  IsDerivedFrom(classDerivations)
//      FUN_004022d0  operator delete / global free
//      FUN_00402298  Memory::Allocate
//
#include <bt.hpp>
#pragma hdrstop

#if !defined(SENSOR_HPP)
#	include <sensor.hpp>
#endif
#if !defined(APP_HPP)
#	include <app.hpp>
#endif
#if !defined(TESTBT_HPP)
#	include <testbt.hpp>
#endif

//
// Tuning constants observed as read-only float globals adjacent to the
// SensorSimulation body (.rdata @004b1d10/@004b1d14, recovered from
// section_dump.txt).
//
static const Scalar RadarBaseline       = 1.0f;	// _DAT_004b1d10  (0x3f800000)
static const Scalar HeatDegradationScale = 0.5f;	// _DAT_004b1d14  (0x3f000000)


//###########################################################################
//###########################################################################
//					Sensor
//###########################################################################
//###########################################################################

//#############################################################################
// Shared Data Support
//
// DefaultData @0050fa1c, ClassDerivations @0050fa2c.
//
Derivation
	Sensor::ClassDerivations(
		PoweredSubsystem::GetClassDerivations(),
		"Sensor"
	);

Receiver::MessageHandlerSet
	Sensor::MessageHandlers;

Sensor::AttributeIndexSet
	Sensor::AttributeIndex;

Sensor::SharedData
	Sensor::DefaultData(
		&Sensor::ClassDerivations,
		Sensor::MessageHandlers,
		Sensor::AttributeIndex,
		Sensor::StateCount
	);


//#############################################################################
// Construction / Destruction
//
// @004b1d18 -- chains to the PoweredSubsystem ctor (FUN_004b0f74) passing
// Sensor::DefaultData (&DAT_0050fa1c), installs the Sensor vtable
// (PTR_FUN_0050fb0c), primes the three sensor members, marks the subsystem as
// carrying a per-frame Performance (instance flag bit 0x8 at this[0x28]) and
// -- unless this is a damaged copy segment (segment flags & 0xC == 4) --
// registers SensorSimulation as the active Performance (this[7..9], the
// 12-byte member-function pointer at PTR_FUN_0050fa94).
//
Sensor::Sensor(
	Mech				*owner,
	int					subsystem_ID,
	SubsystemResource	*subsystem_resource
):
	PoweredSubsystem(owner, subsystem_ID, subsystem_resource, DefaultData)
{
	Check(owner);
	Check_Pointer(subsystem_resource);

	// (WAVE 4 de-shim) the 5 cross-family shim fields are gone; the accessors
	// now read the inherited base state directly.  Only the 3 own fields init here.
	radarPercent = RadarBaseline;	// @0x31C  = 1.0f
	selfTest     = False;			// @0x320  = 0
	badVoltage   = False;			// @0x324  = 0

	// this[0x28] |= 0x8  -- flag "has an active per-frame Performance".
	SetHasPerformanceFlag();		// param_1[10] |= 8

	// INTEGRATION (gate reconcile): read OWNER simulationFlags (param_2+0x28) —
	// the oracle-verified authoritative source — not the local segment shim.
	// Sensor installs its per-frame Performance unless it is a damaged copy
	// segment (owner flags & 0xC == 4); a live master (== 0) arms it.
	if ((owner->simulationFlags & SegmentCopyMask) != 4)	// (owner flags & 0xC) != 4
	{
		SetPerformance(&Sensor::SensorSimulation);			// this[7..9] = &SensorSimulation
	}

	Check_Fpu();
}

//
// @004b1d90 -- reinstalls the vtable, clears the owning Mech's cached sensor
// back-reference ( *(owner + 0x374) = 0 ), then chains to ~PoweredSubsystem
// (FUN_004b115c) and frees the block when the deleting-dtor bit is set.
//
Sensor::~Sensor()
{
	Check(this);
	// *(this->owner + 0x374) = 0;	-- drop Mech's cached sensor pointer
	Check_Fpu();
}

//###########################################################################
//					TestInstance -- Sensor
//
// @004b1e18 -> FUN_0041a1a4(**this[3], 0x50fa2c)  (Sensor::ClassDerivations)
//
Logical
	Sensor::TestInstance() const
{
	return IsDerivedFrom(ClassDerivations);
}

//###########################################################################
//					TestClass -- Sensor
//
// Standard subsystem TestClass (cf. HeatSink/PoweredSubsystem).  BEST-EFFORT:
// no distinct Sensor body was captured; the family convention returns True.
//
Logical
	Sensor::TestClass(Mech &)
{
	return True;
}

//###########################################################################
//					ResetToInitialState -- Sensor
//
// @004b1c18 (vtable slot 10).  When (re)powering, re-primes the three sensor
// members to their ctor defaults, then chains to
// PoweredSubsystem::ResetToInitialState (FUN_004b0e6c).
//
// NOTE: the captured override takes the inherited `powered` flag
// (PoweredSubsystem::ResetToInitialState(Logical powered=True)); SENSOR.HPP
// declares it argument-less, so the default is shown here.
//
void
	Sensor::ResetToInitialState()
{
	const Logical powered = True;

	if (powered)
	{
		radarPercent = RadarBaseline;	// 1.0f
		selfTest     = False;
		badVoltage   = False;
	}

	PoweredSubsystem::ResetToInitialState(powered);		// FUN_004b0e6c
}


//#############################################################################
// Per-frame simulation
//

//
// @004b1c4c -- the registered Performance.  Runs the base powered-subsystem
// electrical update first, then derives the three sensor readouts from the
// thermal + electrical state:
//
//   radarPercent =  RadarBaseline - master->heatEnergy        (this[0x38]->+0x158)
//
//   * if the linked thermal master reports "heat model off" (this[0x10]==1):
//         radarPercent = 0,  selfTest = 0
//     else
//         selfTest = 1
//
//   * if the electrical state alarm (PoweredSubsystem electricalStateAlarm
//     level @0x278, this[0x9e]) is Ready(4):
//         badVoltage = 0
//     else
//         badVoltage = 1,  radarPercent = 0      (no power -> blind)
//
//   * finally the heat-state alarm level (this[0x61] @0x184) trims the radar:
//         NormalHeat(0)      -> selfTest = 1
//         DegradationHeat(1) -> radarPercent *= 0.5f,  selfTest = 1
//         FailureHeat(2)     -> radarPercent = 0,      selfTest = 0
//
void
	Sensor::SensorSimulation(Scalar time_slice)
{
	Check(this);

	PoweredSubsystem::PoweredSubsystemSimulation(time_slice);	// FUN_004b0bd0

	// this[0x38] is the resolved thermal master sink; +0x158 == heatEnergy.
	radarPercent = RadarBaseline - HeatMasterEnergy();

	if (HeatModelOff())					// this[0x10] == 1
	{
		radarPercent = 0.0f;
		selfTest     = False;
	}
	else
	{
		selfTest = True;
	}

	if (ElectricalStateLevel() == Ready)		// this[0x9e] (@0x278) == 4
	{
		badVoltage = False;
	}
	else
	{
		badVoltage   = True;
		radarPercent = 0.0f;
	}

	switch (HeatStateLevel())					// this[0x61] (@0x184)
	{
	case NormalHeat:		// 0
		selfTest = True;
		break;

	case DegradationHeat:	// 1
		radarPercent *= HeatDegradationScale;	// *= 0.5f
		selfTest      = True;
		break;

	case FailureHeat:		// 2
		radarPercent = 0.0f;
		selfTest     = False;
		break;

	default:
		break;
	}

	Check_Fpu();
}


//#############################################################################
// Death and Damage Support
//
// BEST-EFFORT.  No Sensor-specific TakeDamage / DeathReset bodies were
// captured; the Sensor vtable (@0050fb0c) carries the inherited
// PoweredSubsystem / HeatSink addresses in the damage/death slots.  The
// bodies below simply chain to the base so the SENSOR.HPP interface compiles;
// a human should verify whether sensor.cpp originally added behaviour here.
//
void
	Sensor::TakeDamage(Damage &damage)
{
	PoweredSubsystem::TakeDamage(damage);		// TODO: verify (inherited slot)
}

void
	Sensor::DeathReset(Logical full_recovery)
{
	PoweredSubsystem::DeathReset(full_recovery);	// TODO: verify (inherited slot)
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- Sensor
//
// @004b1dcc -- chains to PoweredSubsystem::CreateStreamedSubsystem
// (FUN_004b13ac) then stamps the resource:
//     resource->classID            = 0x0BC3   (resource +0x20)
//     resource->subsystemModelSize = 0x190    (resource +0x24)
// No Sensor-specific resource fields are read (the Sensor resource record is
// an empty extension of the PoweredSubsystem record).
//
int
	Sensor::CreateStreamedSubsystem(
		NotationFile				*model_file,
		const char					*model_name,
		const char					*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile				*subsystem_file,
		const ResourceDirectories	*directories,
		int							passes
	)
{
	if (
		!PoweredSubsystem::CreateStreamedSubsystem(		// FUN_004b13ac
			model_file, model_name, subsystem_name,
			subsystem_resource, subsystem_file, directories, passes
		)
	)
	{
		return False;
	}

	subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);	// 0x190
	subsystem_resource->classID            = RegisteredClass::SensorClassID;	// 0x0BC3

	Check_Fpu();
	return True;
}


//===========================================================================//
// WAVE 4 -- compile-time OVERFLOW lock (the property that matters for the
//   placement-new into the 0x328 factory alloc).
//   NOTE: the reconstructed HEAT-LEAF base chain (PoweredSubsystem : HeatSink :
//   HeatableSubsystem) is NOT byte-exact to the binary (unlike the re-based
//   WATCHER branch Torso sits on), so radarPercent does NOT land at the binary's
//   0x31C.  That is fine here: SensorSimulation reads every field through NAMED
//   members (compiled-consistent), and NOTHING reads Sensor at a raw binary
//   offset yet.  The one raw-offset consumer is the attribute table
//   (RadarPercent -> 0x31C), used only by the (un-drawn) cockpit gauge -- so the
//   exact-offset re-base of the heat-leaf branch is a Phase-4 HUD follow-up.
//   Until then, no-overflow (sizeof <= alloc) is the correct, sufficient lock.
//===========================================================================//
struct SensorLayoutCheck
{
	static_assert(sizeof(Sensor) <= 0x328, "sizeof(Sensor) must fit the factory Memory::Allocate(0x328)");
};


//===========================================================================//
// WAVE 4 factory bridge -- Sensor (factory case 0xBC3, "Myomers" mislabel).
//   The real class at 0xBC3 (ctor @004b1d18) is Sensor (string pool @0050fae0 +
//   surviving SENSOR.HPP); MyomersClassID is a factory-enum mislabel (the real
//   Myomers is 0xBC6/@4b8fec).  Object is 0x328 == the factory alloc (zero
//   headroom; locked by SensorLayoutCheck above).  Sensor's ctor is 3-arg
//   (DefaultData applied internally) -- no shared_data argument.
//===========================================================================//
Subsystem *CreateSensorSubsystem(Mech *owner, int id, void *seg)
{
	return (Subsystem *) new (Memory::Allocate(0x328))
		Sensor(owner, id, (Sensor::SubsystemResource *)seg);
}
