Files
BT412/game/reconstructed/mislanch.hpp
T
arcattackandClaude Fable 5 267059ab88 Damage economy RECONCILED: 3 stand-ins fixed, 3 paths confirmed authentic (task #60)
Full 5-path audit of the damage economy vs the decomp (5-finder +
adversarial-verify workflow), resolving the KB self-contradiction the
binary-coverage audit flagged.

CONFIRMED AUTHENTIC as-is (no change needed):
- Energy beam (emitter.cpp): damagePortion = authored DamageAmount x
  (charge/seekV)^2; the ctor x1e7 and fire x1e-7 cancel (_DAT_004bafbc
  dumped from the exe = x87 80-bit 1e-7 exactly).
- Autocannon (projweap.cpp): full authored DamageAmount from resource
  +0x19C delivered unmodified; the 0.0625 at :667 is the shooter's own
  gyro recoil, not the round.
- Zone-armor BASE model: damageLevel += amount x damageScale[type]
  (engine DAMAGE.cpp:379, called mechdmg.cpp:427), legs x0.5, 1.0=dead.

3 STAND-INS FIXED (all byte-verified against the decomp):
- A. mechdmg.cpp:451 -- read the phantom `stance` member (no binary
  offset, zero writers -> perma-0), so the leg-shot-out -> fall/death
  branch was DEAD. Now MovementMode() (mech+0x40, @part_012.c:6910).
- B. mechdmg.cpp:458 -- guarded the leg partial-failure graphic on the
  always-0 IsAirborne() stub where the binary calls IsDisabled()
  (@0049fb54 = movementMode 2||9). On a wreck the binary SUPPRESSES the
  write; the stub let it corrupt graphicAlarm 9->4/3 -- the task-#52
  wreck-graphic bug, now fixed AT SOURCE (was only masked by the
  IsMechDestroyed latch).
- C. mech4.cpp:1551 -- flat kShotDamage=12 fed as the kill-score
  damageAmount (the KB self-contradiction: task #8 claimed it retired,
  but it was live). The score handler @0x4c02e4 derives the whole kill
  award from it, so every kill scored identically regardless of weapon.
  Now lastInflictingDamage -- the real killing-blow magnitude, latched
  in TakeDamageMessageHandler (mech.cpp:624), mirroring the per-hit path
  (mech4.cpp:1207). The phantom `int stance` slot is reused for the new
  Scalar member (size-neutral, no layout shift); init 0 in the ctor.

DEFERRED (task #60-D, documented): the missile CLUSTER model -- the port
fires N flying rounds (net armor total authentic) vs the binary's ONE
missile with a random burstCount cluster roll (loses cluster variance +
single-zone concentration). Blocked on an OPEN decomp semantic (does
burstCount multiply armor or only the gyro kick? settle at FUN_004bef78
-> FUN_004be078 -> EXPLODE.cpp:209-210).

VERIFIED live: clean build; 2-node fight -> clean center-mass kill (no
crash, kills 0->1); [zone-armor] dump confirms per-zone armor 50-140 +
legs x0.5. NB the displayed POINTS score still reads 0 -- a SEPARATE
open gap (scoreAward + role/team/tonnage multipliers unwired); fix C
corrected the damage INPUT to that formula.

KB swept: open-questions.md (self-contradiction resolved + task #60
summary + deferred missile item), combat-damage.md (damageScale is
type-indexed not even/odd; task-#52 source fix; kill-score section),
RECONCILE.md (missile = ONE spawn not N), stale comments in mechweap.cpp
(SendDamageMessage is LIVE), mislanch.hpp, mechdmg.cpp (FUN_0049fb54 =
IsDisabled).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:38:04 -05:00

196 lines
7.8 KiB
C++

//============================================================================//
// File: mislanch.hpp //
// Project: BattleTech //
// Contents: Weapon Which Launches a missile //
// Derived From Projectile Weapon //
//----------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ------------------------------------------------------------//
// 04/13/95 JM Initial coding. //
//----------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
// PROPRIETARY AND CONFIDENTIAL //
//============================================================================//
//
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, module cluster
// @004bcff0-@004bd08c in part_013.c) cross-referenced HEAVILY against the
// SURVIVING MISLANCH.HPP and the reconstructed mechweap.hpp base. The class
// declaration below is the surviving header; the only additions are the byte
// offsets observed in the decompiled object (see mislanch.cpp for @ADDRs).
//
// Inheritance chain established from the decomp:
// MechWeapon (mechweap.cpp, vtable @00511d2c)
// -> ProjectileWeapon (ctor @004bc3fc, vtable @0051242c) ammo/jam/tracer
// -> MissileLauncher (ctor @004bcff0, vtable @00512570)
//
// The MissileLauncher ctor (@004bcff0) chains ProjectileWeapon::ProjectileWeapon
// (@004bc3fc), installs PTR_FUN_00512570, then reads the two launcher-specific
// resource fields MissileCount (+0x1d0) and MuzzleVelocity (+0x1d4, Vector3D)
// and divides the configured damage by missileCount (per-missile damage).
//
#if !defined (MISLANCH_HPP)
# define MISLANCH_HPP
# if !defined(PROJWEAP_HPP)
# include <projweap.hpp>
# endif
class Missile;
//################# MissileLauncher SubsystemResource ###################
//
// Extends the ProjectileWeapon resource. Parsed by CreateStreamedSubsystem
// @004bd08c (which chains ProjectileWeapon::CreateStreamedSubsystem @004bc7cc).
// Record offsets are relative to the streamed subsystem record:
// +0x1d0 "MissileCount" int
// +0x1d4 "MuzzleVelocity" Vector3D (3 floats)
//
struct MissileLauncher__SubsystemResource:
public ProjectileWeapon::SubsystemResource
{
int missileCount; // +0x1d0 "MissileCount"
Vector3D
muzzleVelocity; // +0x1d4 "MuzzleVelocity"
};
//###########################################################################
//################# CLASS -- MissileLauncher ##########################
//###########################################################################
//
// (vtable @00512570, ctor @004bcff0, dtor @004bd060.)
//
class MissileLauncher:
public ProjectileWeapon
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Messaging Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation ClassDerivations;
static Receiver::MessageHandlerSet& GetMessageHandlers(); // inherits ProjectileWeapon's (task #6)
static AttributeIndexSet AttributeIndex;
static SharedData DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Damage Support
//
public:
void TakeDamage(Damage &damage); // inherits MechWeapon::TakeDamage (vtable slot 6, @004b964c)
void DeathReset(Logical full_reset); // best-effort -- chains ProjectileWeapon reset @004bbaf8
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Replication Support (PORT record extension -- missile-visibility wave)
//
// In the 1995 binary a fired Missile was a REPLICATED WORLD ENTITY (its
// own update records, tag 0x78), so the peer pod rendered the round from
// entity replication and the launcher's record stayed the MechWeapon
// base. The port's flying round is a LOCAL tracked projectile
// (BTPushProjectile) -- invisible to peers -- so the launcher's record
// carries the SALVO EVENT instead: a fire counter + the aim point + the
// round count. The replicant-side reader edge-detects the counter and
// launches a VISUAL-ONLY mirror salvo (damage 0; the master's own rounds
// deliver damage cross-pod through TakeDamage). Both nodes run this
// port, so the extended wire format is self-consistent. Salvo STATE
// lives in a port-side table (mislanch.cpp) -- the object layout is
// byte-locked to the 0x44C factory alloc and must not grow.
//
public:
struct MissileLauncher__UpdateRecord:
public MechWeapon::UpdateRecord
{
int salvoCounter; // fires since spawn (edge-detected by peers)
int salvoRounds; // missiles in the salvo (authored missileCount)
Point3D salvoTarget; // the aim point the salvo flew at
};
typedef MissileLauncher__UpdateRecord UpdateRecord;
void WriteUpdateRecord(Simulation__UpdateRecord *message, int update_model); // base-typed param (task #51 override rule)
void ReadUpdateRecord(Simulation__UpdateRecord *message);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Model Support
//
public:
typedef void
(MissileLauncher::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
typedef MissileLauncher__SubsystemResource SubsystemResource;
MissileLauncher( // @004bcff0
Mech *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &default_data = DefaultData
);
~MissileLauncher(); // @004bd060
Logical
TestInstance() const;
static Logical
CreateStreamedSubsystem( // @004bd08c
ResourceFile *resource_file,
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes =1
);
// local stuff
protected:
// @004bcc60 (ProjectileWeapon spawn path) -- builds the streamed-entity
// spawn descriptor for ONE Missile (entity ClassID 0xBD0), seeds it with
// muzzleVelocity + the per-missile Damage (authored/missileCount) and the
// burstCount cluster-hit roll, and hands it to the entity manager.
// (task #60 correction: ONE spawn per trigger pull, NOT missileCount
// spawns -- burstCount carries the cluster count to the single missile;
// the PORT re-expresses this as N flying rounds, see mislanch.cpp.)
void
FireWeapon();
// The ONLY own field: missileCount@0x448 (ctor param_1[0x112]). The former
// salvoCount@0x3d4 was a SHADOW of the inherited MechWeapon damageData.burstCount
// (@0x3A8+0x2C), and muzzleVelocity@0x410 an ALIAS of the inherited ProjectileWeapon
// launchVelocity -- both re-declared as own members put them at wrong (appended)
// offsets; removed. Object ends 0x44C.
int missileCount; // @0x448 (this[0x112]) -- how many missiles per salvo (LAST field)
friend struct MissileLauncherLayoutCheck;
};
struct MissileLauncherLayoutCheck {
static_assert(offsetof(MissileLauncher, missileCount) == 0x448, "MissileLauncher::missileCount @0x448 (ProjectileWeapon must end 0x448)");
static_assert(sizeof(MissileLauncher) == 0x44C, "sizeof(MissileLauncher) 0x44C (== factory alloc)");
};
#endif