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:
co-authored by
Claude Opus 5
parent
4642129e76
commit
27721754da
@@ -606,10 +606,21 @@ btplayer.hpp, static_assert-locked; `scoreAward`=APPLIED tally in all three):
|
||||
the VICTIM role's `killBonus` (role+0x1c). senderMechID = the victim. Suicide (eject charge,
|
||||
self-damage) IS dispatched — the handler negates the award (@0x4c03ab `fchs`) and skips
|
||||
`killCount++`: **the #134 panic penalty, live** (bench: `type=2 award=-39.00 kills=0`).
|
||||
- **B @0x4a05d9, not newly killed && tally≠0** → type 0 to the shooter. The ONLY registered 0x16
|
||||
receiver Verify-rejects type 0 → 1995 folded an UNINITIALIZED stack float into the shooter's
|
||||
score on every non-lethal hit (real 1995 bug — @0x4c0200, the handler that accepts type 0, is
|
||||
in NO table entry: dead code). Port sends it for wire fidelity, banks award 0.
|
||||
- **B @0x4a05d9, not newly killed && tally≠0** → type 0 to the shooter: the **per-hit INFLICTED
|
||||
credit**, and it is LIVE. ⚠ **CORRECTED 2026-08-07** — this entry previously read "the ONLY
|
||||
registered 0x16 receiver Verify-rejects type 0 → 1995 folded an UNINITIALIZED stack float into
|
||||
the shooter's score on every non-lethal hit (real 1995 bug — @0x4c0200 … is in NO table entry:
|
||||
dead code)". **That was wrong**, and build 787 retired the port's per-hit crediting on the
|
||||
strength of it (the scoring regression players reported on 4.11.817). `BTPlayer` overrides
|
||||
**`Dispatch` — vtable `@00513300` slot 3 = `FUN_004bffa0`** — and splits type 0 off BEFORE base
|
||||
dispatch: `if (id==0x16 && type==0) FUN_004c0200(...); else base;`. `@004c0200` names itself in
|
||||
its own Verify string (`BTPlayer::ScoreInflictedMessageHandler`) and computes
|
||||
`CalcInflicted(basis) → negate if target==self → × (targetTonnage/ownTonnage) → += +0x278`.
|
||||
`ScoreMessageHandler`'s type-0 arm Verify-rejects precisely BECAUSE the interceptor guarantees
|
||||
type 0 never reaches it. Independently corroborated by the original manual's SCORING CHART
|
||||
(`reference/manual/scoring_chart.webp`, via Lynx): "+1 each damage point scored on opponent's armor" and "-1 each
|
||||
self-inflicted point of armor damage" — the negate-if-self arm exactly. Interceptor restored;
|
||||
benched 83 inflicted rows, awards 0.98–25.00, zero type-0 Verify rejections. [T1]
|
||||
- **C @0x4a06c0, tally>0 (kills included)** → type 1 DamageReceivedScore to the VICTIM's player.
|
||||
Basis = INTENDED damage (burstCount×amount). senderMechID = the INFLICTOR. Feeds the received
|
||||
penalty (`CalcDamageReceivedScore` returns the NEGATIVE) + the operator-console VTVDamaged line
|
||||
@@ -617,9 +628,31 @@ btplayer.hpp, static_assert-locked; `scoreAward`=APPLIED tally in all three):
|
||||
Reports carry the LOOP-ENTRY zone (msg+0x24, never rewritten mid-loop), the vital-wreck flag, and
|
||||
`inflictingSubsystemID` (msg+0x5c, engine T0 name).
|
||||
|
||||
**Score model consequence** [T1]: 1995 pod scoring = **kill awards + received-damage penalties +
|
||||
death costs. No per-hit inflicted credit** (the port's old per-hit crediting — and the #95 salvo
|
||||
fix on top of it — were inventions riding the dead @0x4c0200 channel; both retired).
|
||||
**Score model consequence** [T1, ⚠ REWRITTEN 2026-08-07]: 1995 pod scoring = **per-hit inflicted
|
||||
credit + kill awards + received-damage penalties + death costs**. The previous text here claimed
|
||||
"No per-hit inflicted credit … inventions riding the dead @0x4c0200 channel; both retired" — that
|
||||
followed from the dead-code misreading corrected in report B above, and retiring the credit IS the
|
||||
scoring regression reported on 4.11.817. The **original manual's SCORING CHART** (`reference/manual/scoring_chart.webp`)
|
||||
is the cross-check for every row and should be consulted before touching this path again:
|
||||
|
||||
| Points | Event |
|
||||
|---|---|
|
||||
| +1000 | Starting the game |
|
||||
| +1 | Each damage point scored on opponent's armor |
|
||||
| +10..+30 | Destroying an opponent's internal system |
|
||||
| +500 | Destroying an opponent's 'Mech |
|
||||
| −1 | Each self-inflicted point of armor damage |
|
||||
| −10..−30 | Knocking out one of your own internal systems |
|
||||
| −500 | Destroying your own 'Mech by an ammo explosion |
|
||||
| −1000 | Destroying your own 'Mech by ejecting |
|
||||
|
||||
⚠ **Three chart rows are NOT yet reconciled with the reconstruction** — treat as open [T4]:
|
||||
(a) a kill benches at `award=4.88`, two orders off the chart's flat **+500**; (b) **+1000 at
|
||||
game start** has no known implementation; (c) **−1000 eject / −500 ammo** would live in
|
||||
`ScenarioRole::specialCaseDeathPenalty` (role+0x20), which the port reads
|
||||
(`GetSpecialCaseDeathPenalty`, the death-cost block) but which **shipped content authors
|
||||
nowhere**, so it is 0 in the field — the #134 symptom by another route. Do not "fix" these by
|
||||
hard-coding chart numbers; find where the binary sources them.
|
||||
Kill award = `(victimKillBonus + tally) × killerRole.damageInflictedModifier ×
|
||||
(victimAvgZoneDamage@0x354 × damageBias + 1.0) × (victimTonnage/killerTonnage)`; same-team kill in
|
||||
a non-FFA game = `-friendlyFirePenalty` basis (inline strcmp of `teamName@0x20c`, gate
|
||||
|
||||
Reference in New Issue
Block a user