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:
co-authored by
Claude Fable 5
parent
054c3f2eee
commit
37dd7f9663
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user