diff --git a/restoration/source410/BT/MISLANCH.CPP b/restoration/source410/BT/MISLANCH.CPP index daa11cf8..ca42cd04 100644 --- a/restoration/source410/BT/MISLANCH.CPP +++ b/restoration/source410/BT/MISLANCH.CPP @@ -145,14 +145,13 @@ void // if (targetWithinRange && owner != NULL && owner->GetTargetEntity() != NULL - && getenv("BT_MISSILE_FLIGHT") == NULL) + && getenv("BT_MISSILE_INSTANT") != NULL) { // - // DEFAULT delivery (the 5.3.18a arcade economy): the instant-hit - // cluster message. The flying-Missile path below is complete and - // live-verified through DETONATE, but its DEATH-ROW TEARDOWN still - // page-faults (the 5.3.18 frontier, MISSILE.NOTES.md) -- opt in - // with BT_MISSILE_FLIGHT=1 until the dtor chain is closed. + // DEV opt-out (BT_MISSILE_INSTANT=1): the 5.3.18a instant-hit + // cluster message, kept for A/B damage-economy checks. The flying + // Missile below is the DEFAULT since 5.3.19 (teardown closed: the + // Projectile ctor NULLs the inherited zone slots). // SendDamage(owner->GetTargetEntity(), damageData); } diff --git a/restoration/source410/BT/MISSILE.NOTES.md b/restoration/source410/BT/MISSILE.NOTES.md index 291d7c9d..3993d508 100644 --- a/restoration/source410/BT/MISSILE.NOTES.md +++ b/restoration/source410/BT/MISSILE.NOTES.md @@ -1,73 +1,74 @@ -# MISSILE.CPP / SEEKER.CPP / MISTHRST.CPP / PROJTILE.CPP — the flying rounds - -**Status: PARTIAL (5.3.18) — the guided flight loop is LIVE-VERIFIED through -DETONATE (spawn → fly → home → proximity fuse → cluster damage lands once), -but the death-row TEARDOWN page-faults, so the spawn path is opt-in -(`BT_MISSILE_FLIGHT=1`); the default delivery remains the 5.3.18a instant-hit -cluster message.** - -## Interfaces - -- `SEEKER.HPP` + `MISTHRST.HPP` are AUTHENTIC survivors (CODE/BT/BT/). The - Subsystem base chain is the NAME ctor (binary FUN_0041c52c) with the - shipped strings "Seeker" @00512e1e and **"MissleThruster"** @00512e25 (the - 1995 misspelling is authentic — keep it). -- `MISSILE.HPP` / `PROJTILE.HPP` are STAGED (only MISSILE.TCP / PROJTILE.TCP - survive). Binary chain: Entity → Projectile (@004be1bc, 0x340) → Missile - (@004bf5b4, vtable @00512f2c, 0x368). Our staged chain matches: - Projectile : Mover (make-message path), Missile : Projectile. - -## What is verified live (BT_MISSILE_FLIGHT=1 fight run) - -ONE cluster Missile entity per SRM salvo, spawned through the real Mover make -machinery mid-weapon-sim (the Entity ctor self-registers — the engine -tolerates mid-roster-walk creation, as the binary did); flight telemetry -shows thruster acceleration (150→153 u/s) and closing range; the proximity -fuse (rangeToTarget <= 20u staged) delivers `damageData` (salvo-split amount, -burstCount=missileCount) exactly once via TakeDamageMessage with the LAUNCHER -as inflictor; lifetime fizzle retires unexploded rounds. Guidance constants -are binary-decoded (steer deadband 1e-4, turn gain 4.0, seeker loft -200/50/300/0.1 — .rdata @004bec18/@004bf594). - -## THE OPEN CRASH (the 5.3.18 frontier) - -After the first detonated round's `~Missile` completes (dtor log proves it), -the frame page-faults with EIP IN THE HEAP (0x9FEAEF-style) — something -calls through freed missile memory or a base-dtor releases a structure -wrongly. The Seeker/MissileThruster are the FIRST SUBSYSTEMS and the Missile -the FIRST ENTITY ever DESTROYED in the reconstructed sim — the whole -~Entity/~Mover/~Subsystem teardown path is virgin. Checked already: NOT -AlwaysExecute (flag-only), NOT interestZoneID (message base NULLs it), NOT -the subsystemArray idiom (matches ~Entity's expectations exactly), Sensor -holds no pointers. Latent bug FIXED on the way: the Subsystem NAME ctor left -`damageZone` UNINITIALIZED (the resource ctor NULLs it) — garbage for every -name-built subsystem. Next suspects: ~Mover's collision/interest release -(collisionLists array delete), the executive's per-frame entity chain vs -mid-frame delete (FryDeathRow one-per-frame), the host-manager update-record -writer touching a condemned entity. - -## Engine truths learned (load-bearing) - -- `ResourceFile::SearchList(id, type)` CRASHES (@0x414938) when `id` is not - in the top-level directory — it derefs its inner FindResourceDescription - unconditionally. `FindResourceDescription(int)` is the NULL-safe probe. -- Resource IDs in MakeMessages must be FAMILY ids: the Mover base ctor runs - its own `SearchList(resourceID, GameModelResourceType)`, and member - resources resolve only through their family head. -- The streamed `explosionModelFile` is a small INDEX (SRM6 reads 17), a - model-list indirection like voltageSourceIndex/ammoBinIndex — decoding it - into the real projectile family is the projectile-model brick. Until then - the round spawns on the OWNER's family (GameModel member provably present; - our staged flight reads none of the Mover mass/drag). - -## Staged / deferred - -- Muzzle = mech origin; the authored MuzzleVelocity arc through the MOUNT - segment world frame (fire builder @0x4bcc60:8758-64, z NEGATED + mech - velocity) is the muzzle wave. -- Flight envelope defaults (lifetime 10s, burn 2s @120 u/s², launch 150 u/s, - fuse 20u) pending the missile GameModel record decode (model +0x44 - lifetime, +0x48 thrust, +0x50 detonation mode). -- Cluster SPLASH + explosion entity (ClassID 0x5C @004be078), world-geometry - collision (@0042291c), target-velocity intercept lead, Missile - WriteUpdateRecord (tag 0x78) for MP replication. +# MISSILE.CPP / SEEKER.CPP / MISTHRST.CPP / PROJTILE.CPP — the flying rounds + +**Status: LIVE and DEFAULT (5.3.19) — the full entity lifecycle is verified: +spawn → guided flight → proximity detonation → death-row teardown, 20/20/20 +over a 110s mutual fight with zero faults. `BT_MISSILE_INSTANT=1` keeps the +5.3.18a instant-hit delivery as an A/B dev opt-out.** + +## Interfaces + +- `SEEKER.HPP` + `MISTHRST.HPP` are AUTHENTIC survivors (CODE/BT/BT/). The + Subsystem base chain is the NAME ctor (binary FUN_0041c52c) with the + shipped strings "Seeker" @00512e1e and **"MissleThruster"** @00512e25 (the + 1995 misspelling is authentic — keep it). +- `MISSILE.HPP` / `PROJTILE.HPP` are STAGED (only MISSILE.TCP / PROJTILE.TCP + survive). Binary chain: Entity → Projectile (@004be1bc, 0x340) → Missile + (@004bf5b4, vtable @00512f2c, 0x368). Our staged chain matches: + Projectile : Mover (make-message path), Missile : Projectile. + +## What is verified live (BT_MISSILE_FLIGHT=1 fight run) + +ONE cluster Missile entity per SRM salvo, spawned through the real Mover make +machinery mid-weapon-sim (the Entity ctor self-registers — the engine +tolerates mid-roster-walk creation, as the binary did); flight telemetry +shows thruster acceleration (150→153 u/s) and closing range; the proximity +fuse (rangeToTarget <= 20u staged) delivers `damageData` (salvo-split amount, +burstCount=missileCount) exactly once via TakeDamageMessage with the LAUNCHER +as inflictor; lifetime fizzle retires unexploded rounds. Guidance constants +are binary-decoded (steer deadband 1e-4, turn gain 4.0, seeker loft +200/50/300/0.1 — .rdata @004bec18/@004bf594). + +## The teardown crash — SOLVED (5.3.19) + +The dtor-chain bisect (logs in ~Missile/~Projectile/~Seeker/~MissileThruster) +showed the whole chain COMPLETING, then the fault. The killer was the third +strike of the uninitialized-zone-array trap: the staged spawn uses the MECH +FAMILY's resourceID, whose DamageZoneStream makes the Entity base ctor +allocate the 20-pointer `damageZones[]` array (entity.cpp:1032, slots +UNINITIALIZED) — the Missile never fills it, and `~Entity`'s teardown loop +`if (damageZones[i]) delete damageZones[i]` called 20 virtual dtors through +trash vtables (EIP-in-heap AFTER the subsystem dtors — exactly the bisect +signature). FIX: the Projectile ctor NULLs every inherited zone slot. +**RULE (three crashes now): any entity subclass that does not FILL the +allocated zone array must NULL it — the Entity base never initializes the +slots, on construction OR destruction.** Also fixed en route: the Subsystem +NAME ctor left `damageZone` uninitialized (the resource ctor NULLs it). +Ruled out along the way: AlwaysExecute flags, interestZoneID (message base +NULLs it), the subsystemArray idiom, Sensor caching, FryDeathRow timing (an +app task, one delete per frame, not mid-walk). + +## Engine truths learned (load-bearing) + +- `ResourceFile::SearchList(id, type)` CRASHES (@0x414938) when `id` is not + in the top-level directory — it derefs its inner FindResourceDescription + unconditionally. `FindResourceDescription(int)` is the NULL-safe probe. +- Resource IDs in MakeMessages must be FAMILY ids: the Mover base ctor runs + its own `SearchList(resourceID, GameModelResourceType)`, and member + resources resolve only through their family head. +- The streamed `explosionModelFile` is a small INDEX (SRM6 reads 17), a + model-list indirection like voltageSourceIndex/ammoBinIndex — decoding it + into the real projectile family is the projectile-model brick. Until then + the round spawns on the OWNER's family (GameModel member provably present; + our staged flight reads none of the Mover mass/drag). + +## Staged / deferred + +- Muzzle = mech origin; the authored MuzzleVelocity arc through the MOUNT + segment world frame (fire builder @0x4bcc60:8758-64, z NEGATED + mech + velocity) is the muzzle wave. +- Flight envelope defaults (lifetime 10s, burn 2s @120 u/s², launch 150 u/s, + fuse 20u) pending the missile GameModel record decode (model +0x44 + lifetime, +0x48 thrust, +0x50 detonation mode). +- Cluster SPLASH + explosion entity (ClassID 0x5C @004be078), world-geometry + collision (@0042291c), target-velocity intercept lead, Missile + WriteUpdateRecord (tag 0x78) for MP replication. diff --git a/restoration/source410/BT/MISTHRST.CPP b/restoration/source410/BT/MISTHRST.CPP index a87dfdd2..22502abf 100644 --- a/restoration/source410/BT/MISTHRST.CPP +++ b/restoration/source410/BT/MISTHRST.CPP @@ -1,135 +1,135 @@ -//===========================================================================// -// File: misthrst.cpp // -// Project: BattleTech Brick: Mech weapons // -// Contents: MissileThruster -- the missile's propulsion subsystem // -//---------------------------------------------------------------------------// -// Copyright (C) 1995, Virtual World Entertainment, Inc. // -// All Rights reserved worldwide // -// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // -//===========================================================================// -// -// The class interface is the AUTHENTIC surviving CODE/BT/BT/MISTHRST.HPP. -// Bodies reconstructed via the BT411 decomp (ctor @004be7c4). The Subsystem -// base chain is the NAME ctor with the shipped string "MissleThruster" -// @00512e25 (the 1995 misspelling is AUTHENTIC -- keep it). No streamed -// resource in the pod content: the flight envelope defaults below are -// STAGED until the missile GameModel record decode (see MISSILE.NOTES.md). -// - -#include -#pragma hdrstop - -#if !defined(MISTHRST_HPP) -# include -#endif - -#if !defined(MISSILE_HPP) -# include -#endif - -// -// STAGED-DEFAULT burn envelope (a believable SRM: short hard burn). -// -static const Scalar DefaultBurnTime = 2.0f; -static const Scalar DefaultThrusterAcceleration = 120.0f; // u/s^2 -static const Scalar DefaultThrusterClimb = 0.0f; - -Derivation - MissileThruster::ClassDerivations( - Subsystem::ClassDerivations, - "MissileThruster" - ); - -const MissileThruster::IndexEntry - MissileThruster::AttributePointers[] = -{ - ATTRIBUTE_ENTRY(MissileThruster, BurnTimeRemaining, burnTimeRemaining), - ATTRIBUTE_ENTRY(MissileThruster, MaxThrusterRotationRate, maxThrusterRotationRate), - ATTRIBUTE_ENTRY(MissileThruster, ThrusterAcceleration, thrusterAcceleration) -}; - -MissileThruster::AttributeIndexSet - MissileThruster::AttributeIndex( - ELEMENTS(MissileThruster::AttributePointers), - MissileThruster::AttributePointers, - Subsystem::AttributeIndex - ); - -MissileThruster::SharedData - MissileThruster::DefaultData( - MissileThruster::ClassDerivations, - Subsystem::MessageHandlers, - MissileThruster::AttributeIndex, - MissileThruster::StateCount - ); - -// -//############################################################################# -// Construction (binary @004be7c4). The const burn/thrust envelope streams -// from the missile resource in the binary; staged defaults here. -//############################################################################# -// -MissileThruster::MissileThruster( - Missile *owner, - int subsystem_ID, - SubsystemResource *subsystem_resource -): - Subsystem( - owner, - subsystem_ID, - "MissleThruster", - (RegisteredClass::ClassID)ThrusterClassID, - DefaultData - ), - thrusterAcceleration( - (subsystem_resource != NULL) - ? subsystem_resource->thrusterAcceleration - : DefaultThrusterAcceleration - ), - thrusterClimb( - (subsystem_resource != NULL) - ? subsystem_resource->thrusterClimb - : DefaultThrusterClimb - ) -{ - Check(owner); - - burnTimeRemaining = (subsystem_resource != NULL) - ? subsystem_resource->burnTime - : DefaultBurnTime; - maxThrusterRotationRate = 0.0f; - - Check_Fpu(); -} - -MissileThruster::~MissileThruster() -{ -} - -Logical - MissileThruster::TestInstance() const -{ - return IsDerivedFrom(ClassDerivations); -} - -// -//############################################################################# -// MissileThrusterSimulation -- bleed the burn. (Driven from the Missile's -// MoveAndCollide integrator, not a roster Performance -- the binary folds -// the thruster work into the integrate pass.) -//############################################################################# -// -void - MissileThruster::MissileThrusterSimulation(Scalar time_slice) -{ - Check(this); - - if (burnTimeRemaining > 0.0f) - { - burnTimeRemaining -= time_slice; - if (burnTimeRemaining < 0.0f) - { - burnTimeRemaining = 0.0f; - } - } -} +//===========================================================================// +// File: misthrst.cpp // +// Project: BattleTech Brick: Mech weapons // +// Contents: MissileThruster -- the missile's propulsion subsystem // +//---------------------------------------------------------------------------// +// Copyright (C) 1995, Virtual World Entertainment, Inc. // +// All Rights reserved worldwide // +// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // +//===========================================================================// +// +// The class interface is the AUTHENTIC surviving CODE/BT/BT/MISTHRST.HPP. +// Bodies reconstructed via the BT411 decomp (ctor @004be7c4). The Subsystem +// base chain is the NAME ctor with the shipped string "MissleThruster" +// @00512e25 (the 1995 misspelling is AUTHENTIC -- keep it). No streamed +// resource in the pod content: the flight envelope defaults below are +// STAGED until the missile GameModel record decode (see MISSILE.NOTES.md). +// + +#include +#pragma hdrstop + +#if !defined(MISTHRST_HPP) +# include +#endif + +#if !defined(MISSILE_HPP) +# include +#endif + +// +// STAGED-DEFAULT burn envelope (a believable SRM: short hard burn). +// +static const Scalar DefaultBurnTime = 2.0f; +static const Scalar DefaultThrusterAcceleration = 120.0f; // u/s^2 +static const Scalar DefaultThrusterClimb = 0.0f; + +Derivation + MissileThruster::ClassDerivations( + Subsystem::ClassDerivations, + "MissileThruster" + ); + +const MissileThruster::IndexEntry + MissileThruster::AttributePointers[] = +{ + ATTRIBUTE_ENTRY(MissileThruster, BurnTimeRemaining, burnTimeRemaining), + ATTRIBUTE_ENTRY(MissileThruster, MaxThrusterRotationRate, maxThrusterRotationRate), + ATTRIBUTE_ENTRY(MissileThruster, ThrusterAcceleration, thrusterAcceleration) +}; + +MissileThruster::AttributeIndexSet + MissileThruster::AttributeIndex( + ELEMENTS(MissileThruster::AttributePointers), + MissileThruster::AttributePointers, + Subsystem::AttributeIndex + ); + +MissileThruster::SharedData + MissileThruster::DefaultData( + MissileThruster::ClassDerivations, + Subsystem::MessageHandlers, + MissileThruster::AttributeIndex, + MissileThruster::StateCount + ); + +// +//############################################################################# +// Construction (binary @004be7c4). The const burn/thrust envelope streams +// from the missile resource in the binary; staged defaults here. +//############################################################################# +// +MissileThruster::MissileThruster( + Missile *owner, + int subsystem_ID, + SubsystemResource *subsystem_resource +): + Subsystem( + owner, + subsystem_ID, + "MissleThruster", + (RegisteredClass::ClassID)ThrusterClassID, + DefaultData + ), + thrusterAcceleration( + (subsystem_resource != NULL) + ? subsystem_resource->thrusterAcceleration + : DefaultThrusterAcceleration + ), + thrusterClimb( + (subsystem_resource != NULL) + ? subsystem_resource->thrusterClimb + : DefaultThrusterClimb + ) +{ + Check(owner); + + burnTimeRemaining = (subsystem_resource != NULL) + ? subsystem_resource->burnTime + : DefaultBurnTime; + maxThrusterRotationRate = 0.0f; + + Check_Fpu(); +} + +MissileThruster::~MissileThruster() +{ +} + +Logical + MissileThruster::TestInstance() const +{ + return IsDerivedFrom(ClassDerivations); +} + +// +//############################################################################# +// MissileThrusterSimulation -- bleed the burn. (Driven from the Missile's +// MoveAndCollide integrator, not a roster Performance -- the binary folds +// the thruster work into the integrate pass.) +//############################################################################# +// +void + MissileThruster::MissileThrusterSimulation(Scalar time_slice) +{ + Check(this); + + if (burnTimeRemaining > 0.0f) + { + burnTimeRemaining -= time_slice; + if (burnTimeRemaining < 0.0f) + { + burnTimeRemaining = 0.0f; + } + } +} diff --git a/restoration/source410/BT/PROJTILE.CPP b/restoration/source410/BT/PROJTILE.CPP index 92e09898..7441e925 100644 --- a/restoration/source410/BT/PROJTILE.CPP +++ b/restoration/source410/BT/PROJTILE.CPP @@ -1,65 +1,84 @@ -//===========================================================================// -// File: projtile.cpp // -// Project: BattleTech Brick: Mech weapons // -// Contents: Projectile -- the flying-round entity base (Missile derives) // -//---------------------------------------------------------------------------// -// Copyright (C) 1995, Virtual World Entertainment, Inc. // -// All Rights reserved worldwide // -// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // -//===========================================================================// -// -// PARTIAL (binary ctor @004be1bc, vtable @00512a5c, 0x340 bytes): the base -// chains the Mover make path -- the MakeMessage's resourceID is the -// projectile GameModel, so the Mover ctor streams the authored mass / drag / -// inertia. The tracer/ballistic body (the unguided MoveAndCollide, smoke -// trail, PROJTILE.TCP surface) joins the autocannon wave; the Missile -// subclass carries the only flying rounds in the 4.10 pod content. -// - -#include -#pragma hdrstop - -#if !defined(PROJTILE_HPP) -# include -#endif - -Derivation - Projectile::ClassDerivations( - Mover::ClassDerivations, - "Projectile" - ); - -Projectile::SharedData - Projectile::DefaultData( - Projectile::ClassDerivations, - Mover::MessageHandlers, - Mover::AttributeIndex, - 1, - (Entity::MakeHandler)Projectile::Make - ); - -Projectile* - Projectile::Make(MakeMessage *creation_message) -{ - return new Projectile(creation_message); -} - -Projectile::Projectile( - MakeMessage *creation_message, - SharedData &shared_data -): - Mover(creation_message, shared_data) -{ - Check(creation_message); - Check_Fpu(); -} - -Projectile::~Projectile() -{ -} - -Logical - Projectile::TestInstance() const -{ - return IsDerivedFrom(ClassDerivations); -} +//===========================================================================// +// File: projtile.cpp // +// Project: BattleTech Brick: Mech weapons // +// Contents: Projectile -- the flying-round entity base (Missile derives) // +//---------------------------------------------------------------------------// +// Copyright (C) 1995, Virtual World Entertainment, Inc. // +// All Rights reserved worldwide // +// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // +//===========================================================================// +// +// PARTIAL (binary ctor @004be1bc, vtable @00512a5c, 0x340 bytes): the base +// chains the Mover make path -- the MakeMessage's resourceID is the +// projectile GameModel, so the Mover ctor streams the authored mass / drag / +// inertia. The tracer/ballistic body (the unguided MoveAndCollide, smoke +// trail, PROJTILE.TCP surface) joins the autocannon wave; the Missile +// subclass carries the only flying rounds in the 4.10 pod content. +// + +#include +#pragma hdrstop + +#if !defined(PROJTILE_HPP) +# include +#endif + +Derivation + Projectile::ClassDerivations( + Mover::ClassDerivations, + "Projectile" + ); + +Projectile::SharedData + Projectile::DefaultData( + Projectile::ClassDerivations, + Mover::MessageHandlers, + Mover::AttributeIndex, + 1, + (Entity::MakeHandler)Projectile::Make + ); + +Projectile* + Projectile::Make(MakeMessage *creation_message) +{ + return new Projectile(creation_message); +} + +Projectile::Projectile( + MakeMessage *creation_message, + SharedData &shared_data +): + Mover(creation_message, shared_data) +{ + Check(creation_message); + + // + // LOAD-BEARING: the Entity base ctor ALLOCATES the raw damage-zone + // pointer array whenever the resource family carries a DamageZoneStream + // (entity.cpp:1032) and leaves the slots UNINITIALIZED -- filling them + // is the derived entity's job (the Mech's Pass 3). A projectile has no + // zones, so NULL every slot; otherwise ~Entity's teardown walks the + // array and deletes 20 garbage pointers (virtual dtor calls through + // trash vtables -- the 5.3.18 death-row page fault, EIP-in-heap after + // the subsystem dtors completed). + // + if (damageZones != NULL) + { + for (int dz = 0; dz < damageZoneCount; ++dz) + { + damageZones[dz] = NULL; + } + } + + Check_Fpu(); +} + +Projectile::~Projectile() +{ +} + +Logical + Projectile::TestInstance() const +{ + return IsDerivedFrom(ClassDerivations); +} diff --git a/restoration/source410/BT/SEEKER.CPP b/restoration/source410/BT/SEEKER.CPP index 663a6b91..6c37e344 100644 --- a/restoration/source410/BT/SEEKER.CPP +++ b/restoration/source410/BT/SEEKER.CPP @@ -1,195 +1,195 @@ -//===========================================================================// -// File: seeker.cpp // -// Project: BattleTech Brick: Mech weapons // -// Contents: Seeker -- the missile's homing-guidance subsystem // -//---------------------------------------------------------------------------// -// Copyright (C) 1995, Virtual World Entertainment, Inc. // -// All Rights reserved worldwide // -// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // -//===========================================================================// -// -// The class interface is the AUTHENTIC surviving CODE/BT/BT/SEEKER.HPP -// (07/12/95 GDU). Bodies reconstructed from the binary via the BT411 -// decomp: ctor @004bec34, LeadTarget @004beae4 (constants .rdata -// @004bec18..28). The Subsystem base chain is the NAME ctor (binary -// FUN_0041c52c) with the shipped string "Seeker" @00512e1e. -// - -#include -#pragma hdrstop - -#if !defined(SEEKER_HPP) -# include -#endif - -#if !defined(MISSILE_HPP) -# include -#endif - -// -// Lead/guidance tuning (binary .rdata, BT411-decoded). -// -static const Scalar SeekerLeadMinRange = 200.0f; // _DAT_004bec18 -static const Scalar SeekerLeadFloor = 50.0f; // _DAT_004bec20 -static const Scalar SeekerLeadMaxClamp = 300.0f; // _DAT_004bec24 -static const Scalar SeekerLeadCoef = 0.1f; // _DAT_004bec28 (loft gain) - -Derivation - Seeker::ClassDerivations( - Subsystem::ClassDerivations, - "Seeker" - ); - -const Seeker::IndexEntry - Seeker::AttributePointers[] = -{ - ATTRIBUTE_ENTRY(Seeker, TargetPosition, targetPosition), - ATTRIBUTE_ENTRY(Seeker, RangeToTarget, rangeToTarget), - ATTRIBUTE_ENTRY(Seeker, RangeFromCreation, rangeFromCreation) -}; - -Seeker::AttributeIndexSet - Seeker::AttributeIndex( - ELEMENTS(Seeker::AttributePointers), - Seeker::AttributePointers, - Subsystem::AttributeIndex - ); - -Seeker::SharedData - Seeker::DefaultData( - Seeker::ClassDerivations, - Subsystem::MessageHandlers, - Seeker::AttributeIndex, - Seeker::StateCount - ); - -// -//############################################################################# -// Construction (binary @004bec34): snapshot the firing geometry -- the -// creation point, the homing target + aim offset, and the initial ranges. -//############################################################################# -// -Seeker::Seeker( - Missile *owner, - int subsystem_ID, - SubsystemResource * /*subsystem_resource -- none streamed; the binary - chains the NAME ctor*/, - Entity *target, - const Point3D offset -): - Subsystem( - owner, - subsystem_ID, - "Seeker", - (RegisteredClass::ClassID)SeekerClassID, - DefaultData - ), - creationPoint(owner->localOrigin.linearPosition), - targetOffset(offset) -{ - Check(owner); - - targetEntity = target; - - if (targetEntity == NULL) - { - targetPosition = targetOffset; - } - else - { - targetPosition.x = - targetOffset.x + targetEntity->localOrigin.linearPosition.x; - targetPosition.y = - targetOffset.y + targetEntity->localOrigin.linearPosition.y; - targetPosition.z = - targetOffset.z + targetEntity->localOrigin.linearPosition.z; - } - - Vector3D delta; - delta.Subtract(targetPosition, creationPoint); - rangeToTarget = delta.Length(); - startingRangeToTarget = rangeToTarget; - rangeFromCreation = 0.0f; - - Check_Fpu(); -} - -Seeker::~Seeker() -{ -} - -Logical - Seeker::TestInstance() const -{ - return IsDerivedFrom(ClassDerivations); -} - -// -//############################################################################# -// LeadTarget (binary @004beae4): re-aim at the (moving) target each slice -- -// refresh the aim point from the target's live position + offset, then past -// SeekerLeadMinRange loft the aim upward by SeekerLeadCoef x the clamped -// excess (the authored missile arc). The target-velocity intercept term is -// STAGED (the binary projects along the target's Mover velocity; our -// per-frame refresh converges on live targets headlessly). -//############################################################################# -// -void - Seeker::LeadTarget() -{ - Check(this); - - if (targetEntity == NULL) - { - return; - } - - targetPosition.x = - targetOffset.x + targetEntity->localOrigin.linearPosition.x; - targetPosition.y = - targetOffset.y + targetEntity->localOrigin.linearPosition.y; - targetPosition.z = - targetOffset.z + targetEntity->localOrigin.linearPosition.z; - - Vector3D toMissile; - toMissile.Subtract( - targetPosition, - GetEntity()->localOrigin.linearPosition - ); - Scalar range = toMissile.Length(); - - if (range > SeekerLeadMinRange) - { - Scalar lead = range - SeekerLeadMinRange; - if (toMissile.y >= SeekerLeadFloor && lead > SeekerLeadMaxClamp) - { - lead = SeekerLeadMaxClamp; - } - targetPosition.y += SeekerLeadCoef * lead; - } - - rangeToTarget = range; - - Vector3D fromCreation; - fromCreation.Subtract( - GetEntity()->localOrigin.linearPosition, - creationPoint - ); - rangeFromCreation = fromCreation.Length(); -} - -void - Seeker::FindTarget(Scalar /*time_slice*/) -{ - Check(this); - LeadTarget(); -} - -void - Seeker::ResetToInitialState() -{ - Check(this); - rangeToTarget = 0.0f; - rangeFromCreation = 0.0f; - startingRangeToTarget = 0.0f; -} +//===========================================================================// +// File: seeker.cpp // +// Project: BattleTech Brick: Mech weapons // +// Contents: Seeker -- the missile's homing-guidance subsystem // +//---------------------------------------------------------------------------// +// Copyright (C) 1995, Virtual World Entertainment, Inc. // +// All Rights reserved worldwide // +// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // +//===========================================================================// +// +// The class interface is the AUTHENTIC surviving CODE/BT/BT/SEEKER.HPP +// (07/12/95 GDU). Bodies reconstructed from the binary via the BT411 +// decomp: ctor @004bec34, LeadTarget @004beae4 (constants .rdata +// @004bec18..28). The Subsystem base chain is the NAME ctor (binary +// FUN_0041c52c) with the shipped string "Seeker" @00512e1e. +// + +#include +#pragma hdrstop + +#if !defined(SEEKER_HPP) +# include +#endif + +#if !defined(MISSILE_HPP) +# include +#endif + +// +// Lead/guidance tuning (binary .rdata, BT411-decoded). +// +static const Scalar SeekerLeadMinRange = 200.0f; // _DAT_004bec18 +static const Scalar SeekerLeadFloor = 50.0f; // _DAT_004bec20 +static const Scalar SeekerLeadMaxClamp = 300.0f; // _DAT_004bec24 +static const Scalar SeekerLeadCoef = 0.1f; // _DAT_004bec28 (loft gain) + +Derivation + Seeker::ClassDerivations( + Subsystem::ClassDerivations, + "Seeker" + ); + +const Seeker::IndexEntry + Seeker::AttributePointers[] = +{ + ATTRIBUTE_ENTRY(Seeker, TargetPosition, targetPosition), + ATTRIBUTE_ENTRY(Seeker, RangeToTarget, rangeToTarget), + ATTRIBUTE_ENTRY(Seeker, RangeFromCreation, rangeFromCreation) +}; + +Seeker::AttributeIndexSet + Seeker::AttributeIndex( + ELEMENTS(Seeker::AttributePointers), + Seeker::AttributePointers, + Subsystem::AttributeIndex + ); + +Seeker::SharedData + Seeker::DefaultData( + Seeker::ClassDerivations, + Subsystem::MessageHandlers, + Seeker::AttributeIndex, + Seeker::StateCount + ); + +// +//############################################################################# +// Construction (binary @004bec34): snapshot the firing geometry -- the +// creation point, the homing target + aim offset, and the initial ranges. +//############################################################################# +// +Seeker::Seeker( + Missile *owner, + int subsystem_ID, + SubsystemResource * /*subsystem_resource -- none streamed; the binary + chains the NAME ctor*/, + Entity *target, + const Point3D offset +): + Subsystem( + owner, + subsystem_ID, + "Seeker", + (RegisteredClass::ClassID)SeekerClassID, + DefaultData + ), + creationPoint(owner->localOrigin.linearPosition), + targetOffset(offset) +{ + Check(owner); + + targetEntity = target; + + if (targetEntity == NULL) + { + targetPosition = targetOffset; + } + else + { + targetPosition.x = + targetOffset.x + targetEntity->localOrigin.linearPosition.x; + targetPosition.y = + targetOffset.y + targetEntity->localOrigin.linearPosition.y; + targetPosition.z = + targetOffset.z + targetEntity->localOrigin.linearPosition.z; + } + + Vector3D delta; + delta.Subtract(targetPosition, creationPoint); + rangeToTarget = delta.Length(); + startingRangeToTarget = rangeToTarget; + rangeFromCreation = 0.0f; + + Check_Fpu(); +} + +Seeker::~Seeker() +{ +} + +Logical + Seeker::TestInstance() const +{ + return IsDerivedFrom(ClassDerivations); +} + +// +//############################################################################# +// LeadTarget (binary @004beae4): re-aim at the (moving) target each slice -- +// refresh the aim point from the target's live position + offset, then past +// SeekerLeadMinRange loft the aim upward by SeekerLeadCoef x the clamped +// excess (the authored missile arc). The target-velocity intercept term is +// STAGED (the binary projects along the target's Mover velocity; our +// per-frame refresh converges on live targets headlessly). +//############################################################################# +// +void + Seeker::LeadTarget() +{ + Check(this); + + if (targetEntity == NULL) + { + return; + } + + targetPosition.x = + targetOffset.x + targetEntity->localOrigin.linearPosition.x; + targetPosition.y = + targetOffset.y + targetEntity->localOrigin.linearPosition.y; + targetPosition.z = + targetOffset.z + targetEntity->localOrigin.linearPosition.z; + + Vector3D toMissile; + toMissile.Subtract( + targetPosition, + GetEntity()->localOrigin.linearPosition + ); + Scalar range = toMissile.Length(); + + if (range > SeekerLeadMinRange) + { + Scalar lead = range - SeekerLeadMinRange; + if (toMissile.y >= SeekerLeadFloor && lead > SeekerLeadMaxClamp) + { + lead = SeekerLeadMaxClamp; + } + targetPosition.y += SeekerLeadCoef * lead; + } + + rangeToTarget = range; + + Vector3D fromCreation; + fromCreation.Subtract( + GetEntity()->localOrigin.linearPosition, + creationPoint + ); + rangeFromCreation = fromCreation.Length(); +} + +void + Seeker::FindTarget(Scalar /*time_slice*/) +{ + Check(this); + LeadTarget(); +} + +void + Seeker::ResetToInitialState() +{ + Check(this); + rangeToTarget = 0.0f; + rangeFromCreation = 0.0f; + startingRangeToTarget = 0.0f; +}