diff --git a/context/test-harness.md b/context/test-harness.md index 61f27d8..e5a528f 100644 --- a/context/test-harness.md +++ b/context/test-harness.md @@ -84,6 +84,12 @@ The vehicle sed must replace the WHOLE line (`s/^vehicle=.*/`), or you mint `vehicle=thr1bhk1` and the spawn FATALs. Chassis codes: ava1 bhk1 lok1/2 mad1/2 own1 snd1 thr1 vul1 (own1 has NO arm zones — rack zones instead). +**Weapon/combat benches need `map=grass time=day`.** `MP.EGG` authors +`map=cavern time=night`; a bench that copies it without the map sed (e.g. via +`bt_expert_egg` alone — mp_double.sh has this gap) parks the GOTO-driven mechs +against cavern rock and they shoot terrain for the whole window. Always sed the +map/time on the COPY, single-node and two-node alike. + ### Two-node bench skeleton (the MP pattern) ```bash bt_assert_player_env diff --git a/game/reconstructed/btplayer.cpp b/game/reconstructed/btplayer.cpp index 59cbd3c..18543ac 100644 --- a/game/reconstructed/btplayer.cpp +++ b/game/reconstructed/btplayer.cpp @@ -2162,7 +2162,7 @@ Logical BTResolveMessageBoard(Entity * /*tracked_mech*/, int *messageId, BitMap // local (crediting) player is application->GetMissionPlayer(). Bridges (not inline // in mech4.cpp) so the message construction lives in this complete-BTPlayer TU. // -void BTPostDamageScore(Entity *victim, Scalar damage) // Step 6: per-hit SCORE +void BTPostDamageScore(Entity *victim, Scalar damage, Entity *shooter) // Step 6: per-hit SCORE { if (application == 0 || victim == 0) { @@ -2173,6 +2173,17 @@ void BTPostDamageScore(Entity *victim, Scalar damage) // Step 6: per-hit SCORE { return; } + // The credit belongs to the SHOOTER, not the viewer: the binary reports the + // victim handler's damage tally to the INFLICTING player (the id-0x16 report + // tail @0x4a04da, deferred #45). This stand-in can only bank score on the + // LOCAL player, so refuse any post whose shooter is not the local vehicle -- + // an AI master's fire on the player (SP), or a dying mech's death-blast + // splash, must not credit the viewer. MP is unaffected: only local fire + // carries live damage on this node. + if (shooter != (Entity *)local_player->GetPlayerVehicle()) + { + return; + } // ScoreInflicted (scoreType 0): senderMechID = the mech that TOOK the damage. // -> ScoreInflictedMessageHandler: currentScore += tonnageRatio*CalcInflictedScore. BTPlayer::ScoreMessage message( diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 02b2423..dfdefb8 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -706,7 +706,7 @@ static int gEnemyDestroyed = 0; // mech locks onto it as its current target each frame (see targeting step below). extern Entity *gEnemyMech; // gauge scoring wave: producers (btplayer.cpp) -- feed the scoreboard from combat. -extern void BTPostDamageScore(Entity *victim, Scalar damage); // per-hit SCORE (ScoreInflicted) +extern void BTPostDamageScore(Entity *victim, Scalar damage, Entity *shooter); // per-hit SCORE (ScoreInflicted) extern void BTPostKillScore(Entity *victim, Scalar damage); // KILL (+ MP death) // Mech target slots (verified vs the binary's weapon/fire path, part_013.c): @@ -1295,6 +1295,13 @@ void shooter->GetEntityID(), -1 /*unaimed -> cylinder resolves*/, dmg); e->Dispatch(&td); + // SCORE (#95 follow-up): the binary's victim handler tallies EVERY + // TakeDamage -- splash included -- and reports it to the INFLICTING + // player (id-0x16, deferred #45), so a bystander blast is score too. + // The bridge itself refuses a shooter that is not the local vehicle + // (this function's death-blast caller passes the DYING mech). + BTPostDamageScore(e, dmg.damageAmount * (Scalar)bursts, shooter); + // MP MATCH FORENSICS (matchlog.hpp): shooter-side splash delivery // to a bystander (pairs with the victim's DMG burst>1 line). if (BTMatchLogActive()) @@ -1776,9 +1783,17 @@ static void (p.shooter != 0) ? p.shooter->GetEntityID() : EntityID::Null, -1 /*unaimed -> cylinder resolves*/, dmg, p.weaponSubsys); tgt->Dispatch(&take_damage); - // gauge scoring wave (Step 6): a projectile hit credits SCORE too - // (tgt == gEnemyMech here; local player is the viewpoint shooter). - BTPostDamageScore((Entity *)tgt, p.damage); + // gauge scoring wave (Step 6): a projectile hit credits SCORE with + // the DELIVERED amount -- per-missile damage x the rolled cluster + // count, the same figure the dispatch above hands the victim's + // handler (which applies it burstCount times, tallying the amount + // per burst @0x4a04da). Posting the bare per-missile amount here + // was the "LRM10 registered ~3 points to score" defect (#95): the + // armour took the full salvo while the scoreboard banked one + // missile. (Crit bonuses in the tally remain victim-side + // knowledge -- the deferred id-0x16 report, #45.) + BTPostDamageScore((Entity *)tgt, + p.damage * (Scalar)dmg.burstCount, p.shooter); // MP MATCH FORENSICS (matchlog.hpp): shooter-side // projectile impact delivery (pairs with the victim's diff --git a/game/reconstructed/mechweap.cpp b/game/reconstructed/mechweap.cpp index 65ef4df..0ee67e1 100644 --- a/game/reconstructed/mechweap.cpp +++ b/game/reconstructed/mechweap.cpp @@ -742,12 +742,15 @@ void // PORT bookkeeping (score lived in the retired mech4 fire block): credit // the delivered amount to the shooter-side score exactly as before. - extern void BTPostDamageScore(Entity *victim, Scalar damage); + extern void BTPostDamageScore(Entity *victim, Scalar damage, Entity *shooter); extern Logical BTIsRegisteredMech(Entity *e); if (BTIsRegisteredMech(target) && !((Mech *)target)->IsMechDestroyed()) { - BTPostDamageScore(target, damageData.damageAmount); + // Direct-fire damageData always authors burstCount = 1 (emitter.cpp:355), + // so the plain amount IS the delivered amount here. The shooter param + // lets the bridge refuse AI-master fire (score is the shooter's, #45). + BTPostDamageScore(target, damageData.damageAmount, (Entity *)mech); } // MP MATCH FORENSICS (matchlog.hpp): the shooter-side per-shot damage