scoring: restore the type-0 INTERCEPTOR -- per-hit inflicted credit was live all along

Players reported scoring and K/D going screwy on 4.11.817.  Cause: build 787
(#45/#134) retired the port's per-hit inflicted crediting as an "invention",
on the strength of a KB claim that the type-0 score handler was dead code.
That claim was wrong.

BTPlayer overrides Dispatch -- vtable @00513300 slot 3 = FUN_004bffa0 -- and
splits type 0 off BEFORE base dispatch:

    if (msg->id == 0x16 && msg->type == 0)  FUN_004c0200(...);   // ScoreInflicted
    else                                    base dispatch;

@004c0200 names itself in its own Verify string
("BTPlayer::ScoreInflictedMessageHandler") and computes
CalcInflicted(basis) -> negate if target==self -> x (targetTonnage/ownTonnage)
-> accumulate into +0x278.  ScoreMessageHandler's type-0 arm Verify-rejects
precisely BECAUSE this interceptor guarantees type 0 never reaches it.

The port had the handler, faithfully reconstructed, and no interceptor -- so
Block B's inflicted reports all landed in the rejecting arm and banked 0.
Per-hit damage credit was silently deleted.

Independently corroborated by the ORIGINAL MANUAL'S SCORING CHART (filed as
reference/manual/scoring_chart.webp, from Lynx): "+1 each damage point scored
on opponent's armor" and "-1 each self-inflicted point of armor damage" -- the
negate-if-target-is-self arm exactly.  Without that chart the dead-code note
would probably have stood.

Verified (scratchpad/night13/scoreverify.sh, cross-node kill, 2 nodes):
  type-0 Verify rejections   0   (was firing on every non-lethal hit)
  inflicted score rows      83   awards 0.98..25.00, all positive, tracking damage
  kill path un-regressed    type=2 award=4.88 kills=1, victim respawn x1

KB: combat-damage.md report B and the score-model paragraph rewritten, with
the full chart and THREE unreconciled rows flagged [T4] -- flat +500 kill vs
the benched 4.88, +1000 at game start, and -1000 eject / -500 ammo (which
would live in ScenarioRole::specialCaseDeathPenalty @role+0x20, read by the
port but authored nowhere in shipped content).

KNOWN, NOT FIXED HERE: in MP the running total does not persist -- currentScore
is flushed to the operator console and ZEROED (btplayer.cpp ~1219) because the
binary treats it as a console DELTA.  Restoring the credit makes that very
visible (bench: totals climb to ~35 then reset).  Needs its own decision; the
chart's "+1000 starting the game" implies a persistent total lives somewhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ
This commit is contained in:
Joe DiPrima
2026-08-07 08:15:22 -05:00
co-authored by Claude Opus 5
parent 4642129e76
commit 27721754da
6 changed files with 292 additions and 10 deletions
+49 -3
View File
@@ -731,6 +731,47 @@ void
suppressConsole = 0; // this+0x258
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Dispatch (@004bffa0, vtable @00513300 slot 3)
//
// THE TYPE-0 INTERCEPTOR. Restored 2026-08-07 -- it was missing, and its
// absence silently deleted per-hit inflicted scoring:
//
// * the binary carries all three score reports under ONE id (0x16) and
// splits type 0 off here, BEFORE base dispatch, straight into
// ScoreInflictedMessageHandler (@004c0200 -- which names itself in its own
// Verify string, "BTPlayer::ScoreInflictedMessageHandler");
// * ScoreMessageHandler's type-0 arm Verify-rejects ON PURPOSE, because this
// interceptor guarantees type 0 never gets that far;
// * the port had the handler, faithfully reconstructed, and NO interceptor.
// Block B sends its inflicted report under Player::ScoreMessageID, so every
// one of them landed in the rejecting arm and banked 0.
//
// The KB previously recorded @004c0200 as "in NO table entry: dead code" and
// concluded 1995 folded an uninitialised stack float into the shooter's score
// on every non-lethal hit -- and the port's per-hit crediting was retired as an
// "invention" on that basis (#45/#134, build 787, the build players report
// scoring regressed in). That reading was wrong: the handler is live through
// THIS vtable slot, and the original manual's SCORING CHART independently
// corroborates what it computes -- "+1 each damage point scored on opponent's
// armor" and "-1 each self-inflicted point of armor damage", which is exactly
// this handler's negate-if-target-is-self arm. combat-damage.md is corrected.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
BTPlayer::Dispatch(Receiver::Message *what)
{
if (what != 0
&& what->messageID == Player::ScoreMessageID
&& ((BTPlayer::ScoreMessage *)what)->scoreType
== BTPlayer::ScoreMessage::DamageInflictedScore)
{
ScoreInflictedMessageHandler((BTPlayer::ScoreMessage *)what);
return;
}
Player::Dispatch(what);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ScoreInflictedMessageHandler
//
@@ -2351,9 +2392,14 @@ void BTMechPostCombatReports(
else if (damage_tally != 0.0f && shooter_player != 0)
{
//
// Block B: the plain inflicted report. Wire fidelity only -- the 0x16
// handler Verify-rejects type 0 and banks award 0 (1995 banked an
// uninitialized stack float; see the handler's type-0 arm note).
// Block B: the inflicted report -- "+1 each damage point scored on
// opponent's armor" (original manual scoring chart). Goes out under
// Player::ScoreMessageID with type 0, exactly as the binary does; the
// Dispatch override (@004bffa0) intercepts it into
// ScoreInflictedMessageHandler. It is NOT wire-fidelity-only -- the
// old note here claimed the handler banks 0 because @004c0200 was
// "dead code", which was a misreading of the vtable; corrected
// 2026-08-07 and the interceptor restored.
//
BTPlayer::ScoreMessage inflicted(
Player::ScoreMessageID,