From 4ace2571d9f35bb2c9710417a8e07f17e0643b44 Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 3 Aug 2026 13:55:47 -0500 Subject: [PATCH] BT410 5.3.99: walls are real -- frame rejection, crash self-damage, the knockdown stagger, and a pricing question left honestly open The response half of the ground model (binary @4aa6cf-4aab0b), after the 5.3.98 snap: WALLS BLOCK BY FULL FRAME REJECTION, never slide or climb: any blocking contact restores the start-of-frame position and zeroes the velocity. THE CRASH HURTS: the accumulated collision Damage is dispatched at our own mech, zone -1 -- through the same cylinder lottery as enemy fire -- which is what makes wall-grinding self-limiting in the original. A HARD HIT STAGGERS: past the binary's |v|^2 > 40 threshold (@0x4ab184), both gait channels bind the bump clip (slot 0x20, the reason the clip array outgrows the name table) and recover to Standing at its end. Both channels together, per the donor's foot-slip finding: staggering only the leg channel desyncs the two cycles permanently. LIVE, one arena run, no fault -- and the run turned into an unplanned full-system exercise: 8 wall contacts (three at knockdown force, iv2 745-932; one low-speed bump correctly blocked without stagger), and the self-damage cascaded through vital zones to MECH KILL, pilot notification, respawn HEAL-and-place, four times over. The death and respawn machinery all held. OPEN, stated rather than tuned away: THE PRICE IS TOO HOT. Wall crashes land 14k-24k damage points against a game where a PPC is ~12 and an upper torso carries ~124 -- a hard crash one-shots vital zones through the lottery. BT411's economy audit hit the identical symptom ("tapped a wall and died instantly") and traced part of it to compounded StaticBounce reflections across multi-solid frames. The fix lives in the Mech::ProcessCollision override (with the crushable-icon sentinel and the gyro crunch), which is the NEXT increment -- the pricing stays as the engine computes it until that override is reconstructed from the binary, because guessing a divisor would be tuning, not archaeology. The [crash] log line prints amount + iv2 under BT_MECH_LOG as the audit trail. Co-Authored-By: Claude Fable 5 --- restoration/source410/BT/MECH.CPP | 91 +++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index fbe89f5b..3583acf4 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -1523,6 +1523,9 @@ void // Integrate position and commit the Origin to the world transform. //----------------------------------------------------------------------- // + Point3D + frame_start_position = localOrigin.linearPosition; + localOrigin.linearPosition.AddScaled( localOrigin.linearPosition, worldLinearVelocity, @@ -1590,6 +1593,94 @@ void } } + // + //----------------------------------------------------------------------- + // COLLISIONS -- the response half of the master performance's ground + // model (binary @4aa6cf-4aab0b). Walls block by FULL FRAME REJECTION, + // never by slide or climb: any blocking contact restores the + // start-of-frame position and zeroes the velocity. The crash itself + // HURTS -- the accumulated collision Damage is dispatched at OUR OWN + // mech, zone -1 (the cylinder lottery), which is what makes wall- + // grinding self-limiting. And a hard enough hit (|v|^2 > 40, the + // binary's @0x4ab184 threshold) staggers the mech: both gait channels + // bind the bump clip (slot 0x20) and recover to Standing at its end. + // + // Guarded on the collision assistant: the engine's GetCurrentCollisions + // walks it unchecked, and only the viewpoint mech gets one + // (StartCollisionAssistant, BTL4APP.CPP:409). + // + // STAGED deltas, named: the crushable-CulturalIcon sentinel (0.00123f + // -- the move stands, gyro crunch) needs the Mech::ProcessCollision + // override, a later increment; the gyro crunch feed itself is with it; + // and the collisionState audio push waits on the same override's + // contact accumulation. + //----------------------------------------------------------------------- + // + if ( + collisionAssistant != NULL && + GetCollisionVolumeCount() > 0 && + collisionVolume != NULL + ) + { + Vector3D + impact_velocity = worldLinearVelocity; + BoxedSolidCollisionList + *collisions = GetCurrentCollisions(); + Damage + collision_damage; + + if (collisions != NULL) + { + ProcessCollisionList( + collisions, time_slice, frame_start_position, + &collision_damage); + } + + if (collision_damage.damageAmount > 0.0f) + { + worldLinearVelocity = Vector3D(0.0f, 0.0f, 0.0f); + localOrigin.linearPosition = frame_start_position; + localToWorld = localOrigin; + MoveCollisionVolume(); + + Entity::TakeDamageMessage + crash( + Entity::TakeDamageMessageID, + sizeof(Entity::TakeDamageMessage), + GetEntityID(), + -1, + collision_damage + ); + Dispatch(&crash); + + Scalar + impact_squared = + impact_velocity.x * impact_velocity.x + + impact_velocity.y * impact_velocity.y + + impact_velocity.z * impact_velocity.z; + if ( + impact_squared > 40.0f && + animationClips[0x20] != ResourceDescription::NullResourceID && + legStateAlarm.GetLevel() != 0x20 + ) + { + SetLegAnimation(0x20); + SetBodyAnimation(0x20); + ForceUpdate(1); + ForceUpdate(0x20); + } + + if (getenv("BT_MECH_LOG")) + { + DEBUG_STREAM << "[crash] BLOCK dmg=" + << collision_damage.damageAmount + << " iv2=" << impact_squared + << (impact_squared > 40.0f ? " KNOCKDOWN" : "") + << endl << flush; + } + } + } + if (getenv("BT_MECH_LOG")) { static Scalar reportAccum = 0.0f;