Files
BT411/scratchpad/night13/scoreverify.sh
T
Joe DiPrimaandClaude Opus 5 a4bfb64ace scoring: BIND the scenario role -- one commented-out line zeroed the whole chart
BTPlayer::scenarioRole was never assigned.  The lookup sat commented out with
"the BT role registry (BTMission::GetRoleRegistry()->Lookup) has no WinTesla
analog, so the scenarioRole set by the base Player ctor stands" -- and the base
ctor sets it to NULL (PLAYER.cpp:680).  So it stood NULL forever.

Every scoring value the game has hangs off that pointer, and the shipped
content authors them correctly.  New ungated receipt in the ScenarioRole ctor
prints what a real mission loads:

  [role] 'Role::Default' model='dfltrole' killBonus=500 deathPenalty=500
         dmgInf=1 dmgRcv=0 bias=1 ff=1 return=1000

That IS the original manual's scoring chart -- +500 a kill, -500 a special-case
death, +1 per damage point.  With the pointer NULL every award multiplied
against zero: kills scored the damage tally alone (4.88), the eject charge read
0 (the field log's "PUNCH-OUT: charge=0 (role killBonus)" = #134's missing
penalty), and the death-cost block was skipped.

The analog DOES exist: Mission::GetScenarioRole(name) (MISSION.h:162) walks
scenarioRoleChain -- the same dictionary BTL4Mission fills via AddScenarioRole()
when it parses the role pages, whose own comment says the WinTesla base exposes
it.  Same lookup, same key.  Falls back to Role::Default when a creation
message names an unknown role (shipped content authors exactly one page), and
logs BOUND/NULL so this cannot fail silently again.

Benched cross-node:
  role binding    player 2:1 BOUND, player 3:1 BOUND
  KILL AWARD      505.88  (was 4.88)   <- chart's +500, verified
  death cost      victim total -500.00 <- chart's -500
  inflicted       still tracking, killer total 1017.32 kills=1

The -500 on an ORDINARY combat death is AUTHENTIC, not a bug: the binary's gate
is advancedDamageOn alone (@004c05c4 tail: `if (player+0x264 != 0) { -role+0x20 }`),
verified in the decomp.  It only shows now because the role finally binds.  It
also reconciles the chart's two death rows: an EJECT costs -500 (death) plus its
self-kill negating its own ~500 award = -1000, and an ammo death costs -500.

CORRECTION to my own earlier note: returnFromDeath=1000 is NOT the chart's
"+1000 starting the game" -- role+0x28 is a lives/return gate (`if (< 1)` ->
mission review, else respawn).  The 1000 is coincidence.  That row is still
unlocated and is most likely console-side.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ
2026-08-07 10:21:15 -05:00

96 lines
4.0 KiB
Bash

#!/usr/bin/env bash
# =========================================================================
# SCORING verify -- the type-0 interceptor (@004bffa0) restored.
#
# Composition lifted from night12/scorekill.sh, which is known to produce a
# clean cross-node kill: A (madcat, shooter) zone-walk-hammers B (loki,
# spinner) at 90u until B dies.
#
# THE A/B IS READABLE IN ONE RUN, because the old behaviour left a receipt:
# BEFORE every inflicted report hit ScoreMessageHandler's type-0 arm and
# tripped Verify "ScoreMessageHandler should not be given
# DamageInflictedScoreMessages!" -- night12's bench listed those
# Verify prints as an expected PASS signal.
# AFTER the interceptor routes type 0 to ScoreInflictedMessageHandler, so
# those Verify prints must be GONE and matchlog SCORE type=0 rows
# with non-zero awards must appear instead.
#
# Chart cross-check (original manual, via Lynx): "+1 each damage point scored
# on opponent's armor". The handler scales by tonnage ratio and the role's
# damageInflictedModifier, so award != damage exactly -- but it must TRACK
# damage, not sit at zero, and must be NEGATIVE for self-damage.
# =========================================================================
set -x
. /c/git/bt411/scratchpad/night6/bench_common.sh
cd /c/git/bt411/content || exit 1
bt_assert_player_env
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 2
rm -f sv_a.log sv_b.log sv_relay.log matchlog_*.txt
bt_expert_egg MP.EGG SV.EGG
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/" SV.EGG
python - << 'EOF'
lines = open('SV.EGG').read().splitlines(True)
n = 0
for i, l in enumerate(lines):
if l.startswith('vehicle='):
n += 1
lines[i] = 'vehicle=madcat\n' if n == 1 else 'vehicle=loki\n'
open('SV.EGG', 'w').writelines(lines)
print('vehicles set:', n)
EOF
( export BT_DMG_LOG=1 BT_DEATH_LOG=1 BT_MP_LOG=1 BT_MATCHLOG=1 BT_SCORE_LOG=1
bt_launch sv_b.log SV.EGG 0x0C -net 1601 )
sleep 2
( export BT_ZONE_WALK=8 BT_WALK_ZONES=dz_ldleg
export BT_GOTO=enemy BT_GOTO_STOP=90 BT_KEY_NOFOCUS=1
export BT_DMG_LOG=1 BT_DEATH_LOG=1 BT_MP_LOG=1 BT_MATCHLOG=1 BT_SCORE_LOG=1
bt_launch sv_a.log SV.EGG 0x03 -net 1501 )
sleep 5
python ../tools/btconsole.py SV.EGG 127.0.0.1:1501 127.0.0.1:1601 > sv_relay.log 2>&1 &
RELAY=$!
sleep 400
kill $RELAY 2>/dev/null
sleep 3
bt_kill_ours
sleep 2
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 3
echo "=================== SCORING VERIFY ==================="
echo "--- 0. did combat happen at all? (if 0 hits the run is VOID) ---"
echo -n "damage rows on victim B : "; grep -ac "dmghit\|DMG" sv_b.log
echo
echo "--- 1. THE OLD SYMPTOM: type-0 rejections (must be ZERO now) ---"
echo -n "'should not be given DamageInflictedScore' Verify prints: "
cat sv_a.log sv_b.log | grep -ac "should not be given DamageInflictedScore"
echo
echo "--- 2. THE FIX: inflicted score rows (matchlog SCORE type=0) ---"
echo -n "type=0 rows: "; cat matchlog_*.txt 2>/dev/null | grep -ac "type=0"
cat matchlog_*.txt 2>/dev/null | grep -a "type=0" | head -8
echo
echo "--- 3. award vs damage: does the credit TRACK damage? ---"
python - << 'EOF'
import glob, re
aw = []
for fn in glob.glob('matchlog_*.txt'):
for line in open(fn, errors='replace'):
m = re.search(r'type=0 award=(-?[\d.]+) total=(-?[\d.]+)', line)
if m:
aw.append((float(m.group(1)), float(m.group(2))))
if not aw:
print(' NO type=0 rows -- interceptor did not fire')
else:
pos = [a for a, t in aw if a > 0]
neg = [a for a, t in aw if a < 0]
print(' rows=%d positive=%d negative(self)=%d' % (len(aw), len(pos), len(neg)))
print(' award range: %.2f .. %.2f running total ends at %.2f'
% (min(a for a, t in aw), max(a for a, t in aw), aw[-1][1]))
EOF
echo
echo "--- 4. kill path un-regressed (type=2) + respawn ---"
echo -n "type=2 kill rows: "; cat matchlog_*.txt 2>/dev/null | grep -ac "type=2"
cat matchlog_*.txt 2>/dev/null | grep -a "type=2" | head -3
echo -n "victim death cycles: "; grep -ac "death cycle START" sv_b.log