collision-pricing audit: restore the binary's crash economy -- two fixes

A. KNOCKDOWN PACING: the 0.4s gBlockCooldown contact-hysteresis is gone.
It suppressed the crash knockdown for as long as contact was held, letting
the kinematic drive re-slam the obstacle at full commanded speed EVERY
FRAME -- continuous full-price crash self-damage, a healthy 65-tonner dead
in ~2-3s of grinding (the night-7 SAURON ram death, 110 priced frames at
|v|~34.7).  The binary needs no guard (crash threshold @0x4ab178 read from
the exe = 0.0, dispatch per blocked frame is authentic): the bmp clip
zeroes the drive, and a pressed mech re-triggers the knockdown at v>6.3
long BEFORE its grind can price anything -- the type-0 divert's 0.5-pt
free floor needs v>~18.5 at 65t.  Wall grinding is free taps + paced
staggers; only genuine fast arrivals pay.

B. RAW RAM DISPATCH: the *0.001 "ram economy normalization" (2026-07-12)
predated the #83 type-0 divert and became a double-normalization -- the
victim's divert scale (~4e-5) compounded with it, so NO physically
possible ram could clear the 0.5-pt free floor: rams were a no-op against
the victim while the rammer's own un-scaled crash path rattled for real.
The binary dispatches raw [T1 @part_012:15324]; the divert IS the
normalizer.  Also un-starves cultural-icon crunches (trucks now crush on
contact per their authored armor, not after ~19 bumps).

Bench (scratchpad/night7/mp_rampricing.sh):
- wall leg: 150s full-throttle wall push = 125 paced knockdown binds
  (~1.2s cadence, iv2 40-43), 4767 crash frames ALL under the free floor,
  0 rattle, 0 deaths (pre-fix: dead in seconds).
- ram leg: BT_GOTO=enemy + BT_GOTO_STOP=2 rams the peer; raw amount
  arrives byte-identical on the victim (66239.1 tx == rx), victim prices
  2.55 pts internal rattle (first ram damage to a victim in the port's
  history), pressed follow-ups free, 0 deaths both nodes, 108 knockdowns
  -> exactly 108 type-5 records on the observer (no skating regression).

KB swept: combat-damage.md (the 1.3e6 moverMass mis-attribution corrected
to the measured 60-90k tonnage scale), locomotion.md, rendering.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Zh7PTkFy4KwTzVighLR9J
This commit is contained in:
Joe DiPrima
2026-07-31 02:48:35 -05:00
co-authored by Claude Fable 5
parent 054c3f2eee
commit 37dd7f9663
5 changed files with 131 additions and 56 deletions
+31 -42
View File
@@ -641,20 +641,17 @@ static int gShotCount = 0;
// = {0,0,-adv/dt} is ZEROED while crashed) so a knocked-down mech cannot creep back
// into the obstacle and re-trigger every frame. We reproduce that: while the crash
// clip (legState 0x20) plays, hold the velocity at zero.
// The knockdown must also only fire on a FRESH impact -- the frame the mech first
// strikes something -- NOT every frame it grinds against an obstacle it is already
// pressed into. Otherwise holding throttle into a wall/mech re-knocks-down every
// recovery (the stuttery leg flip-flop the user sees). gWasBlocked tracks last
// frame's contact so a continuous press just BLOCKS (stand/walk-in-place) and only a
// separate-then-re-approach produces a new knockdown.
// Block hysteresis: a knockdown fires only when the mech has been OUT of contact
// for kBlockHysteresis seconds (a genuine fresh approach). A 1-frame edge is too
// brittle -- a glancing / sliding contact alternates blocked/not-blocked (the frame
// rejection pushes the mech out each blocked frame), which would flicker the edge
// back to "fresh" and re-knockdown. The window bridges those gaps so any sustained
// OR intermittent press just BLOCKS; only separating for real re-arms the knockdown.
static Scalar gBlockCooldown = 0.0f; // seconds since the last blocking contact (counts down)
static const Scalar kBlockHysteresis = 0.4f;
// (The former gBlockCooldown "fresh strike" hysteresis is GONE -- collision-pricing
// audit, 2026-07-31. It suppressed the knockdown for as long as contact was held,
// which let the drive re-slam the obstacle at full commanded speed EVERY FRAME:
// continuous full-price crash self-damage, a healthy mech dead in ~2-3s of grinding
// (night-7 field death, SAURON's Loki vs the Sunder wreck). The binary's own pacing
// needs no guard: the knockdown zeroes the drive, and a pressed mech re-accelerating
// from stand re-triggers the knockdown at v>6.3 (iv2>40) long BEFORE its grind can
// price anything (the divert's 0.5-pt free floor needs v>~18.5 at 65t) -- so wall
// grinding is free taps + periodic staggers, and only genuine fast ARRIVALS pay.
// The mid-clip rebind guard below provides the flicker protection the hysteresis
// was really buying.)
// E8 weapon triggers (bring-up): pulsed per player frame; read by the weapon
// sims so CheckFireEdge sees rising edges. Non-static (the weapons extern
@@ -6790,16 +6787,6 @@ void
Point3D savedPos = localOrigin.linearPosition;
BoxedSolidCollisionList *cols = GetCurrentCollisions();
Vector3D impactVel = localVelocity.linearMotion;
// CONTACT EDGE (player only): a knockdown fires only on a FRESH strike (not blocked
// last frame). While the mech stays pressed against the obstacle it just BLOCKS --
// no repeated knockdowns. gWasBlocked is a single global, so gate it to the
// viewpoint mech; other masters (the stationary dummy, MP replicant-driven masters)
// keep the original per-fresh-block behavior and never touch the shared state.
const bool isPlayer = (application != 0
&& (Entity *)this == application->GetViewpointEntity());
const int freshBlock = isPlayer ? (gBlockCooldown <= 0.0f ? 1 : 0) : 1;
if (isPlayer && gBlockCooldown > 0.0f)
gBlockCooldown -= dt; // decay the out-of-contact window
// Per-frame collision-state reset (@4aa741): the walk below re-accumulates it
// per resolved contact, then it's pushed into the collisionState StateIndicator.
collisionTemporaryState = 0;
@@ -6857,8 +6844,6 @@ void
worldLinearVelocity = Vector3D(0.0f, 0.0f, 0.0f);
localOrigin.linearPosition = old_position;
MoveCollisionVolume();
if (isPlayer)
gBlockCooldown = kBlockHysteresis; // (re)arm the in-contact window
//
// CRASH BOOKKEEPING + SELF-DAMAGE (binary @4aa89f-4aaab4; raw disasm
// scratchpad/night6/gap_4a9770.txt -- the export gap). This block was
@@ -6929,11 +6914,14 @@ void
// (adv=0 measured), so iv2 collapses during the clip and re-binds
// are naturally spaced. What actually bounds the stagger LOOP in
// the binary is the crash SELF-DAMAGE above -- grinding costs
// armor. This guard + the freshBlock hysteresis stay as cheap
// belt-and-braces port additions.)
// armor. The mid-clip guard stays as a cheap belt-and-braces
// port addition; the freshBlock HYSTERESIS is GONE (pricing audit
// 2026-07-31): it held the knockdown off during sustained contact,
// letting the drive pay full-speed crash price every frame -- the
// 2-3s grind death. See the pacing note at gBlockCooldown's old
// site (~line 640).
if (animationClips[0x20] != 0
&& legStateAlarm.GetLevel() != 0x20
&& freshBlock) // only on a FRESH strike, not continuous grinding
&& legStateAlarm.GetLevel() != 0x20) // not mid-clip (natural spacing)
{
SetLegAnimation(0x20); // FUN_004a7fc4(this, 0x20)
// SYNC THE DISPLAY CHANNEL (fix the persistent post-bump foot-slip): the
@@ -7060,18 +7048,19 @@ static void
{
Damage dmg;
dmg.damageType = Damage::CollisionDamageType;
// RAM ECONOMY NORMALIZATION [T3, evidence-forced 2026-07-12]: StaticBounce
// prices the kinetic loss with the AUTHORED moverMass (~1.3e6 units) --
// ~59,000 "points" for a 10 m/s ram, against zones whose armor absorbs
// 1 point per point (scale = 1/armorPoints, armor 50-140: the [zone-armor]
// dump). The BINARY dispatches this raw number [T1 @part_012:15324] --
// but on the pod network it only ever hit the local REPLICANT of the
// victim, where it evaporated (the master never heard it; MECH.CPP:986
// merely warns). Our MP forwards replicant damage (task #47, required
// for weapons), which surfaced the dormant 59K as a one-shot ram-kill.
// Normalize by 1e-3 (the mass-unit/armor-point scale): a walking bump =
// a few points, a full charge = tens -- the armor-table economy.
dmg.damageAmount = resolved->damageAmount * 0.001f;
// RAW DISPATCH RESTORED [T1 @part_012:15324] -- collision-pricing audit
// 2026-07-31. The old *0.001 "ram economy normalization" (2026-07-12)
// predated the #83 type-0 divert: back then this amount hit the victim's
// ARMOR zones directly, and the raw kinetic number one-shot people. Since
// 14ff351 the victim's TakeDamage hub diverts type-0 into
// DistributeCollisionDamage, whose authentic (2000/mass)/(100km/h)^2 scale
// (~4.5e-5 at 65t) IS the normalizer -- with the 0.001 still applied the
// two compounded and NO physically possible ram could clear the divert's
// 0.5-pt free-tap floor: rams were a no-op against the victim while the
// rammer's own (un-scaled) crash path rattled for real. Raw through the
// divert prices a full-speed 65t ram at ~1.7 pts of internal rattle --
// small-but-real, the manual's "collision damage" technician setting.
dmg.damageAmount = resolved->damageAmount;
dmg.surfaceNormal = resolved->surfaceNormal;
dmg.impactPoint = Point3D(
(collision.collisionSlice.minX + collision.collisionSlice.maxX) * 0.5f,