From 02a41f165f2354f9176ffa953f498191fc0d3372 Mon Sep 17 00:00:00 2001 From: arcattack Date: Thu, 23 Jul 2026 01:09:13 -0500 Subject: [PATCH] Issue #27: projectiles carry the weapon's real damage type (ballistic) Playtest matchlog forensics: across 2,591 applied-damage events in two rounds, Ballistic (type 1) damage NEVER appeared -- Collision/Explosive/ Laser/Energy only -- despite 136 projectile impacts and autocannon/Gauss mechs. The weapon parses its authentic damageData.damageType correctly (mechweap.cpp:316 maps "BallisticDamage" -> 1), but BTPushProjectile never carried the type and every projectile-impact site in mech4.cpp hardcoded ExplosiveDamageType. So autocannon/Gauss rounds were scaled against armor by damageScale[Explosive] instead of damageScale[Ballistic] (the model indexes a distinct scale cell per damage type) -- every ballistic weapon did the wrong damage all night. Lasers/PPCs were unaffected (their SendDamageMessage path carries the weapon's own damageData); missiles happen to BE Explosive so only autocannon & Gauss were mis-scaled. BTProjectile gains a damageType field, threaded from the firing weapon (damageData.damageType) at launch through to the impact dispatch; missiles resolve Explosive from their own streamed type, autocannon/ Gauss now resolve Ballistic. The missile-splash template stays Explosive. Enum is anonymous so the field casts to Enumeration. Co-Authored-By: Claude Opus 4.8 (1M context) --- context/combat-damage.md | 13 +++++++++++++ game/reconstructed/mech4.cpp | 11 ++++++++--- game/reconstructed/mislanch.cpp | 6 ++++-- game/reconstructed/projweap.cpp | 7 +++++-- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/context/combat-damage.md b/context/combat-damage.md index 09b1b21..6020f45 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -258,6 +258,19 @@ Band effects: `MechDeathHandler::Tick` fires the CURRENT band descriptor on any (the binary's changed-flag semantics, workflow-verified) — a damaged mech under fire smokes/ burns per hit; the earlier crossing-only gate was over-tight (the metronome was SHKWAVE). +## Ballistic damage type -- FIXED (2026-07-23, issue #27) [T2] +Playtest matchlog forensics (2,591 applied-damage events over 2 rounds): the damageType +histogram had Collision/Explosive/Laser/Energy but **Ballistic (type 1) NEVER appeared**, +despite 136 projectile impacts + autocannon/Gauss mechs. The weapon parses its authentic +`damageData.damageType` right (mechweap.cpp:316, "BallisticDamage"->1), but `BTPushProjectile` +dropped it and every mech4.cpp projectile-impact site hardcoded `ExplosiveDamageType` -- so +autocannon/Gauss scaled against armor via `damageScale[Explosive]` not `damageScale[Ballistic]` +(distinct cells: `damageLevel += amount * damageScale[type]`). Lasers/PPCs were correct (their +`SendDamageMessage` carries the weapon damageData); only the projectile path flattened. FIX: +`BTProjectile` gains a `damageType` field, threaded from the weapon at launch through to impact +(missiles still resolve Explosive from their own type; autocannon/Gauss now Ballistic). The +missile-SPLASH template stays Explosive. [T2 verify: an autocannon mech produces type=1 DMG.] + ## Damage delivery + the real damage model `Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)` → `target->Dispatch`. **Base handler IGNORES zone==−1** (`Entity::TakeDamageMessageHandler`, ENTITY.cpp:878 — returns on diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 7aee087..23af7ae 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -805,6 +805,10 @@ struct BTProjectile { Scalar damage; // <= 0 -> VISUAL-ONLY round (replicant-side salvo mirror) int guided; // 1 = missile (seeker loft + steering); 0 = ballistic (straight) Entity *shooter; // the firing mech (messmgr explosion bundling at impact) + int damageType; // the FIRING WEAPON's authentic damageData.damageType + // (issue: was hardcoded Explosive at impact, so + // autocannon/Gauss Ballistic rounds scaled wrong -- + // Ballistic type never reached the armor model). int weaponSubsys; // firing weapon's roster index (-1 = unthreaded) -- // resolves the weapon's ExplosionModelFile (mslhit/ // acanhit) in the messmgr, task #7 bundling @@ -1143,7 +1147,7 @@ void void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, const Point3D &targetPos, Scalar speed, Scalar damage, const Vector3D *launch_velocity, int guided, - int weapon_subsys, int splash_burst, int muzzle_seg) + int weapon_subsys, int splash_burst, int muzzle_seg, int damage_type) { // MUZZLE (muzzle wave, 2026-07-12): the passed muzzle is now the weapon's // AUTHENTIC mount segment (GetMuzzlePoint reads the real segmentIndex -- @@ -1267,6 +1271,7 @@ void p.aimOffsetY = (target != 0 && BTIsRegisteredMech((Entity *)target)) ? (tpos.y - ((Mech *)target)->localOrigin.linearPosition.y) : 0.0f; p.damage = damage; + p.damageType = damage_type; // weapon's authentic type (issue fix) p.guided = guided; // autocannon shells fly straight (no seeker in the binary's plain Projectile) p.shooter = (Entity *)shooter; p.weaponSubsys = weapon_subsys; @@ -1471,7 +1476,7 @@ static void // zone from the cylinder hit-location table. (Previously this // aimed the internal vital zone directly -> invisible insta-kill.) Damage dmg; - dmg.damageType = Damage::ExplosiveDamageType; + dmg.damageType = (Enumeration)p.damageType; // issue: was hardcoded Explosive dmg.damageAmount = p.damage; dmg.burstCount = 1; dmg.impactPoint = p.pos; @@ -1557,7 +1562,7 @@ static void // The icon's own handler maps -1 -> 0 and runs the death // transition (-> the FULL 'trkdead' explosion, res 32). Damage dmg; - dmg.damageType = Damage::ExplosiveDamageType; + dmg.damageType = (Enumeration)p.damageType; // issue: was hardcoded Explosive dmg.damageAmount = p.damage; dmg.burstCount = 1; dmg.impactPoint = p.pos; diff --git a/game/reconstructed/mislanch.cpp b/game/reconstructed/mislanch.cpp index 39c95b2..db3742e 100644 --- a/game/reconstructed/mislanch.cpp +++ b/game/reconstructed/mislanch.cpp @@ -73,7 +73,8 @@ extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, const Point3D &targetPos, Scalar speed, Scalar damage, const Vector3D *launch_velocity = 0, int guided = 1, - int weapon_subsys = -1, int splash_burst = 0, int muzzle_seg = -1); + int weapon_subsys = -1, int splash_burst = 0, int muzzle_seg = -1, + int damage_type = 2 /*ExplosiveDamageType default*/); //########################################################################### // Port-side salvo replication state (missile-visibility wave). @@ -318,7 +319,8 @@ void MissileLauncher::FireWeapon() &launchVelocity, 1, subsystemID /*messmgr explosion bundling at impact (task #7)*/, (i == 0) ? nmiss : 0 /*salvo-lead: cluster splash baseBurst*/, - GetSegmentIndex() /*task #67: launch through the MOUNT frame (torso twist)*/); + GetSegmentIndex() /*task #67: launch through the MOUNT frame (torso twist)*/, + (int)damageData.damageType /*missile = Explosive (authentic)*/); // Salvo replication (missile-visibility wave): bump the fire counter + stamp // the aim point; the extended update record carries both to peer nodes. diff --git a/game/reconstructed/projweap.cpp b/game/reconstructed/projweap.cpp index d234bdc..aa40925 100644 --- a/game/reconstructed/projweap.cpp +++ b/game/reconstructed/projweap.cpp @@ -97,7 +97,8 @@ extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, const Point3D &targetPos, Scalar speed, Scalar damage, const Vector3D *launch_velocity = 0, int guided = 1, - int weapon_subsys = -1, int splash_burst = 0, int muzzle_seg = -1); // AC: no cluster splash (default 0) + int weapon_subsys = -1, int splash_burst = 0, int muzzle_seg = -1, + int damage_type = 2 /*ExplosiveDamageType default*/); // AC: no cluster splash (default 0) //############################################################################# @@ -890,7 +891,9 @@ void + launchVelocity.z*launchVelocity.z); BTPushProjectile(muzzle, o, target, targetPos, speed, damageData.damageAmount, 0 /*aim straight at the pick*/, 0 /*BALLISTIC: no seeker on a shell*/, - subsystemID /*messmgr explosion bundling at impact (task #7)*/); + subsystemID /*messmgr explosion bundling at impact (task #7)*/, + 0 /*splash_burst*/, -1 /*muzzle_seg*/, + (int)damageData.damageType /*issue: autocannon = Ballistic, was Explosive*/); // task #61: mark this shot for REPLICATION so the peer sees the enemy's // cannon. ++fireCounter; the CALLER's Loaded case makes the two