#87: mech armour PANELS now darken with damage -- the .DZM material system was loaded and unused
Players: "the actual enemy mech in external view is not showing darkened armor panels". We swapped destroyed LIMB meshes but never darkened a panel. The 1995 game darkens armour through the MATERIALS. MakeMechRenderables (FUN_004cef28) built, per (damage zone, material), a watcher FUN_004573e4(material, &zone->damageLevel, 0.1f) that snapshotted the materials
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# #87 PIXEL A/B: render the SAME standing scene twice -- every armour zone pinned
|
||||
# to level 0 (pristine) and level 1 (fully damaged) via BT_ARMOR_FORCE -- so the
|
||||
# only difference between the two frame sets is the darkening itself. No damage,
|
||||
# no smoke, no motion, no death to confound the comparison.
|
||||
set -x
|
||||
. /c/git/bt411/scratchpad/night6/bench_common.sh
|
||||
cd /c/git/bt411/content || exit 1
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||
sleep 2
|
||||
sed "s/^map=.*/map=grass/; s/^time=.*/time=day/" MP.EGG > ARMOR.EGG
|
||||
|
||||
run () { # run <level> <prefix>
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1; sleep 2
|
||||
rm -f ${2}_*.png
|
||||
( bt_player_env
|
||||
unset BT_START_INSIDE # presence-only gate -- external view
|
||||
export BT_LOG=armor_ab_$2.log BT_AFFINITY=0x03
|
||||
export BT_ARMOR_FORCE=$1 BT_ARMOR_LOG=1
|
||||
export BT_SHOT_EVERY=400 BT_SHOT_PREFIX=$2
|
||||
"$BT_EXE" -egg ARMOR.EGG & )
|
||||
sleep 45
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||
sleep 2
|
||||
}
|
||||
|
||||
run 0.0 abpristine
|
||||
run 1.0 abdamaged
|
||||
ls abpristine_*.png abdamaged_*.png 2>/dev/null | head
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
# #87 bench: MECH ARMOUR DARKENING.
|
||||
#
|
||||
# The 1995 renderer lerped every material a damage zone paints from its authored
|
||||
# colour toward 0.1x as that zone's damageLevel ran 0->1 (FUN_004573e4 built one
|
||||
# watcher per (zone, material) from the mech's .DZM list; FUN_00457784 re-pushed
|
||||
# the blend on every change). Our renderer swapped destroyed LIMB meshes but
|
||||
# never darkened a panel -- "the actual enemy mech in external view is not
|
||||
# showing darkened armor panels".
|
||||
#
|
||||
# Two legs:
|
||||
# self -- self-damage one named zone on the player's own mech; the cleanest
|
||||
# read of the level->brightness chain (deterministic, one zone).
|
||||
# enemy -- walk up to a spawned target and shoot it: the REPORTED case, an
|
||||
# enemy hull darkening under fire, with screenshots to pixel-verify.
|
||||
#
|
||||
# Verdict lines:
|
||||
# [armor] zone N '<dzname>' -> M draw op(s) .DZM list resolved onto geometry
|
||||
# [armor] '<mat>' zone N level a -> b (brightness kx) level reaching the ops
|
||||
#
|
||||
# usage: armor_darken.sh [self|enemy] [zone]
|
||||
set -x
|
||||
. /c/git/bt411/scratchpad/night6/bench_common.sh
|
||||
cd /c/git/bt411/content || exit 1
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||
sleep 2
|
||||
|
||||
LEG="${1:-self}"
|
||||
ZONE="${2:-dz_ltorso}"
|
||||
LOG=armor_${LEG}.log
|
||||
rm -f "$LOG" armorshot_*.tga
|
||||
|
||||
sed "s/^map=.*/map=grass/; s/^time=.*/time=day/" MP.EGG > ARMOR.EGG
|
||||
|
||||
if [ "$LEG" = "enemy" ]; then
|
||||
# The reported case: an ENEMY hull taking fire. Beeline to the spawned target
|
||||
# and hold the trigger; BT_SHOT_EVERY dumps backbuffer frames so the panels can
|
||||
# be compared pixel-wise before/after.
|
||||
BT_ARMOR_LOG=1 BT_DMG_LOG=1 \
|
||||
BT_SPAWN_ENEMY=1 BT_GOTO=enemy BT_GOTO_STOP=2 BT_AUTOFIRE=1 \
|
||||
BT_SHOT_EVERY=300 BT_SHOT_PREFIX=armorshot \
|
||||
bt_launch "$LOG" ARMOR.EGG 0x03
|
||||
elif [ "$LEG" = "ext" ]; then
|
||||
# PIXEL leg. Same single-zone ramp, but in the EXTERNAL chase camera so the
|
||||
# hull fills the frame and one zone can be compared before/after.
|
||||
#
|
||||
# NOTE: this deliberately overrides the shipped player env's BT_START_INSIDE=1
|
||||
# (bench_common) -- players start in the cockpit and cannot see their own hull.
|
||||
# This leg exists ONLY to photograph the panels; the `self` and `enemy` legs
|
||||
# are the player-authentic ones.
|
||||
( bt_player_env
|
||||
# presence-only gate (btl4vid.cpp:657) -- "=0" still reads as set, so UNSET it
|
||||
unset BT_START_INSIDE
|
||||
export BT_LOG="$LOG" BT_AFFINITY=0x03
|
||||
export BT_ARMOR_LOG=1 BT_DMG_LOG=1
|
||||
export BT_SELF_DAMAGE=3 BT_SELF_DAMAGE_ZONE="$ZONE"
|
||||
export BT_SHOT_EVERY=600 BT_SHOT_PREFIX=armorext
|
||||
"$BT_EXE" -egg ARMOR.EGG & )
|
||||
else
|
||||
# Deterministic single-zone ramp on our own mech. Small dose so the level
|
||||
# climbs in steps instead of saturating in one tick.
|
||||
BT_ARMOR_LOG=1 BT_DMG_LOG=1 \
|
||||
BT_SELF_DAMAGE=3 BT_SELF_DAMAGE_ZONE="$ZONE" \
|
||||
bt_launch "$LOG" ARMOR.EGG 0x03
|
||||
fi
|
||||
|
||||
sleep 90
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||
sleep 2
|
||||
|
||||
echo "=== per-entity binding totals ==="
|
||||
grep -E "^\[armor\] entity" "$LOG" | sort -u
|
||||
echo "=== level changes ==="
|
||||
grep -E "^\[armor\] '" "$LOG" | head -30
|
||||
echo "=== distinct materials darkened ==="
|
||||
grep -E "^\[armor\] '" "$LOG" | sed "s/.*\['armor'\] //" | awk '{print $2}' | sort -u | head -20
|
||||
echo "=== shots ==="
|
||||
ls armorshot_*.tga 2>/dev/null | head
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Raw-image xref sweep (gotcha #21: the decomp export has gaps).
|
||||
|
||||
Finds every CALL/JMP rel32 in .text whose target is one of the VAs given on the
|
||||
command line, plus any absolute immediate referencing them.
|
||||
|
||||
python xref.py 0x4902b0 0x490308
|
||||
"""
|
||||
import struct, sys
|
||||
|
||||
PATH = r"C:\git\bt411\content\BTL4OPT.EXE"
|
||||
data = open(PATH, "rb").read()
|
||||
e_lfanew = struct.unpack_from("<I", data, 0x3C)[0]
|
||||
coff = e_lfanew + 4
|
||||
num_sec = struct.unpack_from("<H", data, coff + 2)[0]
|
||||
opt_size = struct.unpack_from("<H", data, coff + 16)[0]
|
||||
opt = coff + 20
|
||||
image_base = struct.unpack_from("<I", data, opt + 28)[0]
|
||||
sec_tbl = opt + opt_size
|
||||
secs = []
|
||||
for i in range(num_sec):
|
||||
off = sec_tbl + i * 40
|
||||
name = data[off:off + 8].rstrip(b"\0").decode("latin1")
|
||||
vsize, va, rawsize, rawptr = struct.unpack_from("<IIII", data, off + 8)
|
||||
secs.append((name, va, vsize, rawptr, rawsize))
|
||||
|
||||
|
||||
def off_to_va(off):
|
||||
for name, va, vsize, rawptr, rawsize in secs:
|
||||
if rawptr <= off < rawptr + rawsize:
|
||||
return image_base + va + (off - rawptr)
|
||||
return None
|
||||
|
||||
|
||||
targets = set(int(a, 16) for a in sys.argv[1:])
|
||||
hits = []
|
||||
|
||||
for name, va, vsize, rawptr, rawsize in secs:
|
||||
blob = data[rawptr:rawptr + rawsize]
|
||||
base_va = image_base + va
|
||||
# rel32 CALL (E8) / JMP (E9)
|
||||
for i in range(len(blob) - 5):
|
||||
op = blob[i]
|
||||
if op not in (0xE8, 0xE9):
|
||||
continue
|
||||
rel = struct.unpack_from("<i", blob, i + 1)[0]
|
||||
tgt = base_va + i + 5 + rel
|
||||
if tgt in targets:
|
||||
hits.append((base_va + i, "call" if op == 0xE8 else "jmp", tgt, name))
|
||||
# absolute immediates (vtable slots, push offset)
|
||||
for i in range(len(blob) - 4):
|
||||
v = struct.unpack_from("<I", blob, i)[0]
|
||||
if v in targets:
|
||||
hits.append((base_va + i, "abs32", v, name))
|
||||
|
||||
for addr, kind, tgt, sec in sorted(hits):
|
||||
print("%08x %-6s -> %08x [%s]" % (addr, kind, tgt, sec))
|
||||
print("\n%d xrefs" % len(hits))
|
||||
Reference in New Issue
Block a user