diff --git a/game/reconstructed/btl4vid.cpp b/game/reconstructed/btl4vid.cpp index 65634dd..48fe809 100644 --- a/game/reconstructed/btl4vid.cpp +++ b/game/reconstructed/btl4vid.cpp @@ -1621,6 +1621,58 @@ int hitAny = 1; } + // #92 probe: which spheres did the ray actually THREAD, and which won? + // "smallest radius wins" means a big sphere can never beat a small one that + // the ray also grazes -- so a foot can be unhittable behind its own knee. + if (getenv("BT_PICK_LOG")) + { + static int s_pc = 0; + if ((s_pc++ % 60) == 0) + { + // one-shot: EVERY sphere in world space, so the foot/knee geometry + // can be worked out analytically rather than by aim-hunting + static int s_dumped = 0; + if (!s_dumped) + { + s_dumped = 1; + DEBUG_STREAM << "[pickgeom] ray_start=(" << ray_start[0] << "," + << ray_start[1] << "," << ray_start[2] << ")" << std::endl; + std::map::iterator gp; + for (gp = it->second.segPick.begin(); gp != it->second.segPick.end(); ++gp) + { + d3d_OBJECT *o3 = gp->second.obj; + if (o3 == NULL || o3->mCullRadius <= 0.0f) continue; + D3DXMATRIX m3 = o3->GetLocalToWorld(); + D3DXVECTOR3 c3; + D3DXVec3TransformCoord(&c3, &o3->mCullCenter, &m3); + DEBUG_STREAM << "[pickgeom] zone=" << gp->second.zone + << " r=" << o3->mCullRadius + << " world=(" << c3.x << "," << c3.y << "," << c3.z << ")" + << std::endl; + } + } + DEBUG_STREAM << "[pickcand]"; + for (sp = it->second.segPick.begin(); sp != it->second.segPick.end(); ++sp) + { + d3d_OBJECT *o2 = sp->second.obj; + if (o2 == NULL || o2->mCullRadius <= 0.0f) continue; + D3DXMATRIX m2 = o2->GetLocalToWorld(); + D3DXVECTOR3 c2; + D3DXVec3TransformCoord(&c2, &o2->mCullCenter, &m2); + float ax = c2.x - ray_start[0], ay = c2.y - ray_start[1], az = c2.z - ray_start[2]; + float tc = ax*ray_dir[0] + ay*ray_dir[1] + az*ray_dir[2]; + float rr = o2->mCullRadius; + float a2 = ax*ax + ay*ay + az*az; + if (tc < 0.0f && a2 > rr*rr) continue; + float dd = a2 - tc*tc; + if (dd > rr*rr) continue; // not threaded + DEBUG_STREAM << " {z" << sp->second.zone << " r=" << rr + << " d=" << sqrtf(dd > 0.0f ? dd : 0.0f) << "}"; + } + DEBUG_STREAM << " -> WON z" << bestZone << " r=" << bestR << std::endl; + } + } + if (!hitAny) return 0; diff --git a/game/reconstructed/dmgtable.cpp b/game/reconstructed/dmgtable.cpp index f0a7b2c..fe637e8 100644 --- a/game/reconstructed/dmgtable.cpp +++ b/game/reconstructed/dmgtable.cpp @@ -211,6 +211,44 @@ DamageLookupTable::DamageLookupTable( { layers.push_back(new PieSlice(owner, stream)); } + + // #92 -- "unable to damage the foot panels on Loki ... could on Thor". + // Direct-fire damage does NOT use the mesh or the aim pick: the impact's + // HEIGHT picks a layer, its ANGLE picks a slice, and a weighted random roll + // picks the zone from that slice. So a zone that appears in NO slice of the + // lowest layer is simply unreachable by direct fire. Dump the whole table. + if (getenv("BT_DMGTABLE_LOG")) + DumpTable(); +} + +// +// #92 diagnostic: print every layer / slice / weighted zone entry. +// +void DamageLookupTable::DumpTable() const +{ + DEBUG_STREAM << "[dmgtable] layers=" << layerCount << std::endl; + for (int L = 0; L < (int)layers.size(); ++L) + { + const PieSlice *layer = layers[L]; + DEBUG_STREAM << "[dmgtable] layer " << L + << " rotateWithTorso=" << layer->DiagRotateWithTorso() + << " slices=" << layer->DiagSliceCount() << std::endl; + for (int S = 0; S < layer->DiagSliceCount(); ++S) + { + const DamageZonePercentTable *leaf = layer->DiagSlice(S); + if (leaf == 0) continue; + DEBUG_STREAM << "[dmgtable] slice " << S << ":"; + Scalar prev = 0.0f; + for (int E = 0; E < leaf->DiagEntryCount(); ++E) + { + Scalar cum = leaf->DiagCumulative(E); + DEBUG_STREAM << " z" << leaf->DiagZone(E) + << "=" << (int)((cum - prev) * 100.0f + 0.5f) << "%"; + prev = cum; + } + DEBUG_STREAM << std::endl; + } + } } DamageLookupTable::~DamageLookupTable() // @0x49eadc @@ -259,5 +297,20 @@ int } DamageZonePercentTable *leaf = layer->SelectSlice(theta); // FUN_0049e678 - return leaf ? leaf->SelectZone() : -1; // FUN_0049de14 + int resolved = leaf ? leaf->SelectZone() : -1; // FUN_0049de14 + + // #92: which LAYER does a given impact height land in? A foot shot that + // lands in a layer above the foot layer can never roll a foot zone. + if (getenv("BT_DMGTABLE_LOG")) + { + static int s_rh = 0; + if (s_rh++ < 400) + DEBUG_STREAM << "[dmgresolve] localY=" << local.y + << " heightRef=" << heightRef + << " frac=" << (depth / heightRef) + << " layer=" << layerIndex << "/" << layerCount + << " theta=" << theta + << " -> zone " << resolved << std::endl; + } + return resolved; } diff --git a/game/reconstructed/dmgtable.hpp b/game/reconstructed/dmgtable.hpp index 6922bf6..d354ccd 100644 --- a/game/reconstructed/dmgtable.hpp +++ b/game/reconstructed/dmgtable.hpp @@ -105,6 +105,11 @@ class Point3D; // content-build authoring path -- is not part of the runtime port; the // shipped .RES is pre-built. Omitted here.) + // #92 diagnostics (read-only) + int DiagEntryCount() const { return (int)entries.size(); } + Scalar DiagCumulative(int i) const { return entries[i].cumulative; } + int DiagZone(int i) const { return entries[i].zoneIndex; } + protected: Mech *owner; // @0x0c std::vector entries; // @0x14 (was ReconTable) -- sorted ascending @@ -138,6 +143,11 @@ class Point3D; // DamageZonePercentTable *SelectSlice(Scalar theta) const; + // #92 diagnostics (read-only) + int DiagSliceCount() const { return (int)slices.size(); } + const DamageZonePercentTable *DiagSlice(int i) const { return slices[i]; } + int DiagRotateWithTorso() const { return rotateWithTorso; } + protected: Mech *owner; // @0x0c std::vector @@ -172,6 +182,7 @@ class Point3D; // the table is empty or the roll falls through (treated as a miss). // int ResolveHit(const Point3D &impact) const; + void DumpTable() const; // #92 diagnostic int LayerCount() const { return layerCount; } // bring-up verify diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 5815696..036ad3b 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -949,6 +949,17 @@ void } DamageLookupTable *table = (DamageLookupTable *)damageLookupTable; // named member (Wword absorbs!) + // #92: when this gate fails the zone keeps whatever the message carried -- + // which for an unaimed weapon hit is 0, i.e. EVERY shot lands on zone 0. + if (getenv("BT_DMGTABLE_LOG")) + { + static int s_g = 0; + if (s_g++ < 40) + DEBUG_STREAM << "[cylgate] invalidZone=" << (int)message->invalidDamageZone + << " table=" << (void *)table + << " incomingZone=" << message->damageZone + << " type=" << (int)message->damageData.damageType << std::endl; + } if (message->invalidDamageZone && table != 0) { int zone = table->ResolveHit(message->damageData.impactPoint); diff --git a/scratchpad/night8/footpick.sh b/scratchpad/night8/footpick.sh new file mode 100644 index 0000000..277eed9 --- /dev/null +++ b/scratchpad/night8/footpick.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# #92: "Unable to damage the foot panels on Loki via direct fire ... was able to +# damage on Thor" (Oracle). The aimed per-part pick (MechSegmentPick, #73) is a +# BOUNDING-SPHERE test whose primary key is SMALLEST RADIUS WINS -- a larger +# sphere can never beat a smaller one, whatever the aim. So if a chassis's foot +# sphere is bigger than its lower-leg sphere, every ray that threads both is +# credited to the leg and the foot is unhittable. Dump the spheres per chassis. +set -x +. /c/git/bt411/scratchpad/night6/bench_common.sh +cd /c/git/bt411/content || exit 1 +V="$1" +taskkill //F //IM btl4.exe > /dev/null 2>&1 +sleep 2 +sed "s/^map=.*/map=grass/; s/^time=.*/time=day/; 0,/^vehicle=.*/s//vehicle=$V/" MP.EGG > FOOT.EGG +LOG=footpick_$V.log +rm -f "$LOG" +export BT_PICK_LOG=1 BT_DMGTABLE_LOG=1 +export BT_START_INSIDE=0 # external: the whole mech tree is built + shown +export BT_SPAWN_ENEMY=1 # target of the SAME chassis (uses the mission model) +export BT_GOTO=enemy BT_GOTO_STOP=25 # close in so the foot subtends a real angle +export BT_AIM="${2:-0 -0.35}" # aim LOW -- at the feet +export BT_AUTOFIRE=1 BT_DMG_LOG=1 # shoot it and log the resolved zone +bt_launch "$LOG" FOOT.EGG 0x03 +sleep 70 +taskkill //F //IM btl4.exe > /dev/null 2>&1 +sleep 2