Impact-FX FORENSICS wave: the i860 specialfx engine, per-round detonations, the ram economy closed
The "we're off the rails" reinvestigation -- a 5-thread evidence workflow
(logs / PFX data / RES model lists / decomp / port audit) + the i860 firmware
decode, then surgical fixes. Every claim tiered; the wrong turns are on the
record in the KB alongside the corrections.
THE OLD-STYLE SPECIALFX ENGINE [T1, firmware-decoded from VREND.MNG]:
rebuilt scratchpad/i860dis.py (binutils opcode table); mapped the dispatch
(data+0xdd0c; sfx trigger 0xf040cda0, install 0xf040cdc0, step ~0xf0413698,
instance init 0xf04128d8); decoded the heat model EXACTLY: per 30Hz board
frame h *= cool_a; RGB_ch = h_old*(h_new*cook_ch - 0.25) + 0.25 (K=0.25 =
the ember floor @VA 0xF080; kill at h <= 1e-4 @0xF0A0); alpha fades cool_b
x RAW dt (PER-SECOND -- the x30 scaling made laser hits invisible); 7s cap;
y_off = the kill plane; "variance" is DEAD DATA (binary reads "variance",
INI authors "varience"). The 13 descriptors (PPCHit/LaserHit/MissileHit/
Chunks/Sparks/Fireball...) parse from BTDPL.INI and render via the BTPfx
layer -- heat bursts draw the FIERY sheet (brightness over fire), .PFX keeps
GRAYSCALE (authored colours: DNBOOM orange, DDAM gray).
HIT-PACKAGE CENSUS [T1, RES byte-verified]: ppchit=[8] lzrhit=[9] mghit=[7,11]
canhit=[11] mslhit=[10,12,1023] explode=[6]; all 8 mech death lists identical.
Effect routing corrected in BOTH consumers: <100 = specialfx, >=1000 = psfx.
"SILVER MIST x5" ROOT CAUSE: SHKWAVE.PFX (mslhit's 1023) authors maxIssue=5
relPeriod=0.2 rate=1 -- the ONE file where rate contradicts the window; the
emitter trusted rate -> 5 shells at exactly 1Hz. Emission rate now always
maxIssue/releasePeriod.
PER-ROUND DETONATIONS [T1 @004bef78]: every missile round spawns its own
ExplosionModelFile at ITS impact (hull + terrain) -- a volley ripples 12
fireballs like the demos; rack-tube launch spread [T3] (GUIDED rounds only --
the slot-0 deflection sent every AFC100 shell 3.4deg left/2.5deg down: the
phantom "4th gold beam"); replicant salvos detonate too (launcher index rides
the visual push). Missile damage bundles through the shooter's messmgr with
the launcher's subsystemID (mslhit fires at the consolidated point; the
binary's dedup CONFIRMED [T1 @0049b784] -- a workflow agent's per-record
claim REFUTED by direct decomp read).
DAMAGE-BAND SEMANTICS [T1]: MechDeathHandler fires the CURRENT band effect on
any damage rise (the binary's changed-flag coalescing) -- a mauled mech under
fire smokes/burns per hit; bands 3/4 are authored fire plumes.
THE RAM ECONOMY -- CLOSED (3 layers, measured live):
1. armor = POINTS (every zone: damageScale = 1/armorPoints, armor 50-140;
the [zone-armor] BT_DMG_LOG dump);
2. StaticBounce prices rams with authored moverMass ~1.3e6 -> ~59,000 pts
@10m/s; the binary dispatches it RAW but pod MP dropped it on the local
replicant (MECH.CPP:986 warns) -- ram damage was NETWORK-INERT; our
task-#47 replicant forwarding surfaced it as a one-shot. Port
normalizes x1e-3 to the point economy [T3];
3. contact EDGE (ramLastVictim/ramContactLinger): the binary's bounce made
separation implicit; our gait-derived velocity re-priced full rams at
60Hz (the respawn explode-loop). One bump = one hit; pressed = BLOCK.
PLUS: sfx size x0.5 + 1.25m visibility floor + hot-phase occlusion [T3];
particle pool 2048->8192 (missile traffic starved laser bursts -- the
"intermittent effects"); replicant beam aging (a lost beam-END record pinned
a stale beam on forever); UV-variant noise stamps (mask-safe mirror/swap);
no scroll on particle stamps (material-path only); BT_FX_TEST/fxshot.py
self-verification harness ([beam-draw]/[zone-armor]/[collide-tx] telemetry).
KB: rendering.md (specialfx engine + census + closeout), combat-damage.md
(ram economy + band semantics).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
48c9c8444f
commit
065c114590
+185
-27
@@ -153,6 +153,7 @@
|
||||
#include <cultural.hpp> // CulturalIcon::IsStoppingCollisionVolume / GetClassDerivations
|
||||
#include <hostmgr.hpp> // HostManager::GetEntityPointer (band-effect attacker resolve)
|
||||
#include <messmgr.hpp> // SubsystemMessageManager (task #7 consolidated delivery)
|
||||
#include <mechweap.hpp> // MechWeapon::GetExplosionResourceID (per-round detonation)
|
||||
#if !defined(PLAYER_HPP)
|
||||
# include <player.hpp> // Player::VehicleDeadMessage -- the death->respawn notification (task #52)
|
||||
#endif
|
||||
@@ -774,18 +775,60 @@ struct BTProjectile {
|
||||
Scalar aimOffsetY; // vertical aim offset vs the target's origin (live re-lead)
|
||||
Scalar damage; // <= 0 -> VISUAL-ONLY round (replicant-side salvo mirror)
|
||||
int guided; // 1 = missile (seeker loft + steering); 0 = ballistic (straight)
|
||||
Entity *shooter; // the firing mech (messmgr explosion bundling at impact)
|
||||
int weaponSubsys; // firing weapon's roster index (-1 = unthreaded) --
|
||||
// resolves the weapon's ExplosionModelFile (mslhit/
|
||||
// acanhit) in the messmgr, task #7 bundling
|
||||
int active;
|
||||
};
|
||||
static BTProjectile gProjectiles[64];
|
||||
|
||||
extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float);
|
||||
|
||||
//###########################################################################
|
||||
// PER-ROUND DETONATION (the binary's Missile::MoveAndCollide @004bef78: every
|
||||
// round spawns ITS OWN ExplosionModelFile at its impact point, resource
|
||||
// missile+0x33c -- a volley RIPPLES fireballs across its arrival frames, the
|
||||
// demo look). Resolved from the firing weapon's roster entry (weapon+0x3E4);
|
||||
// runs on BOTH nodes (the replicant visual salvo passes its launcher's index
|
||||
// too). The messmgr's one bundled explosion remains (direct-fire semantics +
|
||||
// the cross-pod damage stream); among a rippled volley it is invisible.
|
||||
//###########################################################################
|
||||
static void
|
||||
BTSpawnRoundDetonation(Entity *shooter, int weapon_subsys, const Point3D &at)
|
||||
{
|
||||
extern int BTIsRegisteredMech(Entity *e);
|
||||
if (shooter == 0 || weapon_subsys < 0 || !BTIsRegisteredMech(shooter))
|
||||
return;
|
||||
Mech *sm = (Mech *)shooter;
|
||||
if (weapon_subsys >= sm->GetSubsystemCount())
|
||||
return;
|
||||
Subsystem *w = sm->GetSubsystem(weapon_subsys);
|
||||
if (w == 0 || !w->IsDerivedFrom(*MechWeapon::GetClassDerivations()))
|
||||
return;
|
||||
ResourceDescription::ResourceID res = ((MechWeapon *)w)->GetExplosionResourceID();
|
||||
if (res <= 0)
|
||||
return;
|
||||
Origin o;
|
||||
o.linearPosition = at;
|
||||
o.angularPosition = EulerAngles(Radian(0.0f), Radian(0.0f), Radian(0.0f));
|
||||
Explosion::MakeMessage m(
|
||||
Explosion::MakeMessageID, sizeof(Explosion::MakeMessage),
|
||||
(Entity::ClassID)RegisteredClass::ExplosionClassID, EntityID::Null,
|
||||
res, Explosion::DefaultFlags, o,
|
||||
sm->GetEntityID(), sm->GetEntityID());
|
||||
Explosion *e = Explosion::Make(&m);
|
||||
if (e)
|
||||
Register_Object(e);
|
||||
}
|
||||
|
||||
// Called from ProjectileWeapon / MissileLauncher::FireWeapon (via the extern below)
|
||||
// with the live muzzle, the SHOOTER mech (to resolve the real launch port), the owner's
|
||||
// locked target entity + point, the launch speed (|muzzleVelocity|), and per-shot damage.
|
||||
void
|
||||
BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, const Point3D &targetPos,
|
||||
Scalar speed, Scalar damage, const Vector3D *launch_velocity, int guided)
|
||||
Scalar speed, Scalar damage, const Vector3D *launch_velocity, int guided,
|
||||
int weapon_subsys)
|
||||
{
|
||||
// MUZZLE (muzzle wave, 2026-07-12): the passed muzzle is now the weapon's
|
||||
// AUTHENTIC mount segment (GetMuzzlePoint reads the real segmentIndex --
|
||||
@@ -880,6 +923,34 @@ void
|
||||
? (tpos.y - ((Mech *)target)->localOrigin.linearPosition.y) : 0.0f;
|
||||
p.damage = damage;
|
||||
p.guided = guided; // autocannon shells fly straight (no seeker in the binary's plain Projectile)
|
||||
p.shooter = (Entity *)shooter;
|
||||
p.weaponSubsys = weapon_subsys;
|
||||
|
||||
// RACK-TUBE SPREAD [T3, physically grounded]: the binary's Missile
|
||||
// entities each launch from their own rack tube (per-tube authored
|
||||
// MuzzlePosition); our single muzzle superimposed a whole salvo onto
|
||||
// ONE trajectory -- 12 rounds read as one round, and their 12
|
||||
// detonations stacked into one frame. Approximate the tube offsets
|
||||
// with a small deterministic per-slot cone (+-~2.5 deg) + lateral
|
||||
// muzzle offset; the seeker re-converges them onto the target so the
|
||||
// volley arrives as the demo's RIPPLE of impacts.
|
||||
// GUIDED ROUNDS ONLY: a ballistic shell has no seeker to re-converge
|
||||
// it -- the slot-0 pattern deflected every AFC100 shell a fixed
|
||||
// 3.4deg left / 2.5deg down (the phantom "4th gold beam" burying its
|
||||
// tracer beside the target, user-reported). The binary's plain
|
||||
// Projectile flies straight at the pick.
|
||||
if (guided)
|
||||
{
|
||||
float ja = ((i % 5) - 2) * 0.030f; // yaw +-0.06 rad
|
||||
float jb = (((i / 5) % 5) - 2) * 0.022f; // pitch +-0.044 rad
|
||||
float vx = p.vel.x, vy = p.vel.y, vz = p.vel.z;
|
||||
float ca = cosf(ja), sa = sinf(ja);
|
||||
p.vel.x = vx * ca - vz * sa;
|
||||
p.vel.z = vx * sa + vz * ca;
|
||||
p.vel.y = vy + jb * p.speed;
|
||||
p.pos.x += ((i % 3) - 1) * 0.6f;
|
||||
p.pos.z += (((i / 3) % 3) - 1) * 0.6f;
|
||||
}
|
||||
p.active = 1;
|
||||
if (getenv("BT_PROJ_LOG"))
|
||||
DEBUG_STREAM << "[projectile] vel=(" << p.vel.x << "," << p.vel.y
|
||||
@@ -888,7 +959,7 @@ void
|
||||
? " authored=(" : " none=(")
|
||||
<< (launch_velocity ? launch_velocity->x : 0.0f) << ","
|
||||
<< (launch_velocity ? launch_velocity->y : 0.0f) << ","
|
||||
<< (launch_velocity ? launch_velocity->z : 0.0f) << ")\n" << std::flush;
|
||||
<< (launch_velocity ? launch_velocity->z : 0.0f) << ")" << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -973,13 +1044,19 @@ static void
|
||||
if (BTGroundRayHit(prev.x, prev.y, prev.z, rd.x, rd.y, rd.z,
|
||||
step + 1.0f, &hx, &hy, &hz))
|
||||
{
|
||||
// burst on the rock: a tight puff cluster at the hit, no damage
|
||||
// burst on the rock: the round's own DETONATION (the binary
|
||||
// missile detonates on ANY geometry, @004bef78) + a tight
|
||||
// puff cluster, no damage
|
||||
{
|
||||
Point3D hp; hp.x = hx; hp.y = hy; hp.z = hz;
|
||||
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, hp);
|
||||
}
|
||||
extern void BTPfxTrailPuff(int, float, float, float, float, float, float, int);
|
||||
for (int pf = 0; pf < 4; ++pf)
|
||||
BTPfxTrailPuff(0, hx, hy, hz, -rd.x, -rd.y, -rd.z, 2);
|
||||
if (getenv("BT_PROJ_LOG"))
|
||||
DEBUG_STREAM << "[projectile] WORLD burst at(" << hx << ","
|
||||
<< hy << "," << hz << ")\n" << std::flush;
|
||||
<< hy << "," << hz << ")" << std::endl;
|
||||
p.active = 0;
|
||||
continue;
|
||||
}
|
||||
@@ -1014,6 +1091,7 @@ static void
|
||||
}
|
||||
if (contact)
|
||||
{
|
||||
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, p.pos);
|
||||
Entity *tgt = p.target;
|
||||
// Deliver to the projectile's target mech -- the launcher set p.target
|
||||
// from the shooter's 0x388 slot (the picked victim; any peer mech in
|
||||
@@ -1033,15 +1111,38 @@ static void
|
||||
dmg.damageAmount = p.damage;
|
||||
dmg.burstCount = 1;
|
||||
dmg.impactPoint = p.pos;
|
||||
Entity::TakeDamageMessage take_damage(
|
||||
Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
|
||||
0 /*inflictor id: bring-up*/, -1 /*unaimed -> cylinder resolves*/, dmg);
|
||||
tgt->Dispatch(&take_damage);
|
||||
// Route through the SHOOTER's SubsystemMessageManager with the
|
||||
// firing launcher's roster index (task #7 bundling): the
|
||||
// consolidation resolves roster[id]+0x3E4 = the weapon's
|
||||
// ExplosionModelFile (mslhit/acanhit) and fires it AT the
|
||||
// impact point -- the missing missile-hit explosion (the
|
||||
// laser path always did this via SendDamageMessage; the
|
||||
// projectile path bypassed the manager entirely).
|
||||
SubsystemMessageManager *mgr = 0;
|
||||
if (p.shooter != 0 && p.weaponSubsys >= 0 && BTIsRegisteredMech(p.shooter))
|
||||
mgr = (SubsystemMessageManager *)((Mech *)p.shooter)->GetMessageManager();
|
||||
if (mgr != 0)
|
||||
{
|
||||
Entity::TakeDamageMessage take_damage(
|
||||
Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
|
||||
p.shooter->GetEntityID(), -1 /*unaimed -> cylinder resolves*/,
|
||||
dmg, p.weaponSubsys);
|
||||
mgr->AddDamageMessage(tgt, &take_damage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity::TakeDamageMessage take_damage(
|
||||
Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
|
||||
0 /*inflictor id: bring-up*/, -1 /*unaimed -> cylinder resolves*/, dmg);
|
||||
tgt->Dispatch(&take_damage);
|
||||
}
|
||||
// gauge scoring wave (Step 6): a projectile hit credits SCORE too
|
||||
// (tgt == gEnemyMech here; local player is the viewpoint shooter).
|
||||
BTPostDamageScore((Entity *)tgt, p.damage);
|
||||
DEBUG_STREAM << "[projectile] IMPACT damage=" << p.damage
|
||||
<< " zone=" << take_damage.damageZone << " (cyl-resolved)\n" << std::flush;
|
||||
<< " subsys=" << p.weaponSubsys
|
||||
<< (mgr ? " (msgmgr bundled)" : " (direct)")
|
||||
<< " (zone cyl-resolved)\n" << std::flush;
|
||||
}
|
||||
}
|
||||
p.active = 0;
|
||||
@@ -1196,6 +1297,8 @@ void
|
||||
worldLinearVelocity = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
localVelocity = Motion::Identity;
|
||||
frameEntryWorldVelocity = Vector3D(0.0f, 0.0f, 0.0f);
|
||||
ramLastVictim = 0;
|
||||
ramContactLinger = 0.0f;
|
||||
|
||||
// --- our RELOCATED gait/motion accumulators -> identity (the 1995 offsets
|
||||
// the decomp zeroes map to these named members in our layout) ---
|
||||
@@ -1363,7 +1466,7 @@ void
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[death] mech " << GetEntityID()
|
||||
<< " destroyed -> subsystem shutdown + frozen wreck (IsDisabled="
|
||||
<< (int)IsDisabled() << ")\n" << std::flush;
|
||||
<< (int)IsDisabled() << ")" << std::endl;
|
||||
|
||||
// --- DEATH EFFECTS (task #42): dispatched HERE, at the VICTIM's own
|
||||
// once-per-death transition, so they fire regardless of WHAT killed it
|
||||
@@ -1516,7 +1619,7 @@ void
|
||||
DEBUG_STREAM << "[mech-exec] entity " << GetEntityID()
|
||||
<< " instance=" << (int)GetInstance()
|
||||
<< " pos=(" << localOrigin.linearPosition.x << ", "
|
||||
<< localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
<< localOrigin.linearPosition.z << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2023,7 +2126,7 @@ void
|
||||
<< " err=" << err << " turn=" << gBTGotoTurn
|
||||
<< " thr=" << gBTGotoThrottle << " arr=" << arrived
|
||||
<< " fwd=(" << fwdX << "," << fwdZ << ")"
|
||||
<< " tgt=(" << s_gx << "," << s_gz << ")\n" << std::flush; }
|
||||
<< " tgt=(" << s_gx << "," << s_gz << ")" << std::endl; }
|
||||
}
|
||||
// SELF-DRIVE (task #48): a "beeline" harness must supply its OWN
|
||||
// forward throttle, not only steering -- otherwise it just turns
|
||||
@@ -2357,7 +2460,7 @@ void
|
||||
gCurrentGait = wantGait;
|
||||
DEBUG_STREAM << "[gait] SequenceController -> blh" << suffix
|
||||
<< " id=" << (long)*clip << " (" << (wantGait ? "run" : "walk")
|
||||
<< ")\n" << std::flush;
|
||||
<< ")" << std::endl;
|
||||
}
|
||||
}
|
||||
adv = bodyAnimation.Advance(dt, 1); // inline clip-select path
|
||||
@@ -2427,7 +2530,7 @@ void
|
||||
DEBUG_STREAM << "[gait] adv=" << travelAdv << " (proj=" << adv
|
||||
<< ") pos=("
|
||||
<< localOrigin.linearPosition.x << ", "
|
||||
<< localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
<< localOrigin.linearPosition.z << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2446,7 +2549,7 @@ void
|
||||
gCurrentGait = wantGait;
|
||||
gBodyAnimReady = 1;
|
||||
DEBUG_STREAM << "[anim] gait -> blh" << suffix << " id=" << (long)*clip
|
||||
<< " (" << (wantGait ? "run" : "walk") << ")\n" << std::flush;
|
||||
<< " (" << (wantGait ? "run" : "walk") << ")" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2481,7 +2584,7 @@ void
|
||||
<< (dir < 0 ? " (reverse)" : "") << " adv=" << adv
|
||||
<< " loops=" << gBodyAnimLoops
|
||||
<< " pos=(" << localOrigin.linearPosition.x << ", "
|
||||
<< localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
<< localOrigin.linearPosition.z << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
} // close the BT_GAIT_CUTOVER else (STEP 1-2 path)
|
||||
@@ -2591,7 +2694,7 @@ void
|
||||
DEBUG_STREAM << "[collide] pushed out by (" << dx << ", "
|
||||
<< (localOrigin.linearPosition.y - before.y) << ", " << dz
|
||||
<< ") pos=(" << localOrigin.linearPosition.x << ", "
|
||||
<< localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
<< localOrigin.linearPosition.z << ")" << std::endl;
|
||||
}
|
||||
// Floor clamp: never sink below the base ground (safety on solid-less maps).
|
||||
if (localOrigin.linearPosition.y < s_groundY)
|
||||
@@ -2716,7 +2819,7 @@ void
|
||||
DEBUG_STREAM << "[visgnd] lift=" << vLift
|
||||
<< " at (" << localOrigin.linearPosition.x << ", "
|
||||
<< localOrigin.linearPosition.y << ", "
|
||||
<< localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
<< localOrigin.linearPosition.z << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3804,6 +3907,14 @@ void
|
||||
// and a multi-solid frame compounds those reflections into a garbage
|
||||
// velocity that priced mech-vs-mech damage 4x-40x too high (mp_a.log:32651).
|
||||
frameEntryWorldVelocity = savedWorldVel;
|
||||
// ram contact-edge decay: once contact with the last victim has been
|
||||
// broken for the linger window, re-arm the one-bump-one-hit gate
|
||||
if (ramContactLinger > 0.0f)
|
||||
{
|
||||
ramContactLinger -= dt;
|
||||
if (ramContactLinger <= 0.0f)
|
||||
ramLastVictim = 0;
|
||||
}
|
||||
Damage dmg; // ctor zeroes damageAmount
|
||||
if (cols != 0)
|
||||
ProcessCollisionList(cols, dt, old_position, &dmg);
|
||||
@@ -3831,7 +3942,7 @@ void
|
||||
}
|
||||
if (GroundLog())
|
||||
DEBUG_STREAM << "[ground] CRUNCH (crushable icon) at ("
|
||||
<< savedPos.x << ", " << savedPos.z << ")\n" << std::flush;
|
||||
<< savedPos.x << ", " << savedPos.z << ")" << std::endl;
|
||||
}
|
||||
else if (dmg.damageAmount > 0.0f) // blocking hit: FULL FRAME REJECTION
|
||||
{
|
||||
@@ -3893,12 +4004,12 @@ void
|
||||
}
|
||||
if (GroundLog())
|
||||
DEBUG_STREAM << "[ground] BLOCK + CRASH (iv2=" << iv2
|
||||
<< " clip=" << animationClips[0x20] << ")\n" << std::flush;
|
||||
<< " clip=" << animationClips[0x20] << ")" << std::endl;
|
||||
}
|
||||
else if (GroundLog())
|
||||
DEBUG_STREAM << "[ground] BLOCK dmg=" << dmg.damageAmount
|
||||
<< " at (" << old_position.x << ", " << old_position.z
|
||||
<< ")\n" << std::flush;
|
||||
<< ")" << std::endl;
|
||||
}
|
||||
|
||||
// 1-Hz telemetry (verification: h / y / hit-miss); INSIDE-SOLID detector:
|
||||
@@ -3915,7 +4026,7 @@ void
|
||||
DEBUG_STREAM << "[ground] INSIDE-SOLID streak at ("
|
||||
<< localOrigin.linearPosition.x << ", "
|
||||
<< localOrigin.linearPosition.y << ", "
|
||||
<< localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
<< localOrigin.linearPosition.z << ")" << std::endl;
|
||||
}
|
||||
else
|
||||
s_zeroStreak = 0;
|
||||
@@ -3928,7 +4039,7 @@ void
|
||||
<< " lift=" << collisionTemplate->minY
|
||||
<< " pos=(" << localOrigin.linearPosition.x << ", "
|
||||
<< localOrigin.linearPosition.y << ", "
|
||||
<< localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
<< localOrigin.linearPosition.z << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3989,7 +4100,18 @@ static void
|
||||
{
|
||||
Damage dmg;
|
||||
dmg.damageType = Damage::CollisionDamageType;
|
||||
dmg.damageAmount = resolved->damageAmount;
|
||||
// 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;
|
||||
dmg.surfaceNormal = resolved->surfaceNormal;
|
||||
dmg.impactPoint = Point3D(
|
||||
(collision.collisionSlice.minX + collision.collisionSlice.maxX) * 0.5f,
|
||||
@@ -4016,9 +4138,11 @@ static void
|
||||
DEBUG_STREAM << "[collide-tx] " << inflictor->GetEntityID()
|
||||
<< " rams " << victim->GetEntityID()
|
||||
<< " dmg=" << resolved->damageAmount
|
||||
<< " mass=" << inflictor->moverMass
|
||||
<< " e=" << inflictor->elasticityCoefficient
|
||||
<< " |v|=" << (Scalar)sqrtf(v.x*v.x + v.y*v.y + v.z*v.z)
|
||||
<< " at(" << dmg.impactPoint.x << "," << dmg.impactPoint.y
|
||||
<< "," << dmg.impactPoint.z << ")\n" << std::flush;
|
||||
<< "," << dmg.impactPoint.z << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4100,7 +4224,21 @@ void
|
||||
// is UNBLOCKED (was deferred: zone==-1 used to be dropped).
|
||||
if (owner->IsDerivedFrom(*Mech::GetClassDerivations()))
|
||||
{
|
||||
BTDispatchCollisionDamage(this, owner, damage, collision);
|
||||
// RAM CONTACT EDGE (see mech.hpp): dispatch damage only on a FRESH
|
||||
// contact with this victim; while pressed, refresh the linger so
|
||||
// sustained contact (held stick / respawn overlap) stays a BLOCK,
|
||||
// not a 60Hz damage grinder -- the binary's bounce-separation made
|
||||
// this edge implicit; our gait-derived velocity needs it explicit.
|
||||
if (owner == ramLastVictim && ramContactLinger > 0.0f)
|
||||
{
|
||||
ramContactLinger = 0.35f; // still pressed: refresh, no damage
|
||||
}
|
||||
else
|
||||
{
|
||||
ramLastVictim = owner;
|
||||
ramContactLinger = 0.35f;
|
||||
BTDispatchCollisionDamage(this, owner, damage, collision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4234,6 +4372,11 @@ void
|
||||
continue;
|
||||
++energyOrdinal;
|
||||
Emitter *em = (Emitter *)ws;
|
||||
// replicant beams: age the replicated discharge locally (a lost
|
||||
// beam-END record otherwise pins the beam on forever -- see
|
||||
// Emitter::ReplicantServiceBeam)
|
||||
if (GetInstance() == Entity::ReplicantInstance)
|
||||
em->ReplicantServiceBeam(ttl1);
|
||||
if (!em->BeamOn())
|
||||
continue;
|
||||
Point3D mz;
|
||||
@@ -4293,6 +4436,21 @@ void
|
||||
(((unsigned)(40.0f + r * 215.0f) & 0xFF) << 16) |
|
||||
(((unsigned)(40.0f + g * 215.0f) & 0xFF) << 8) |
|
||||
((unsigned)(40.0f + b * 215.0f) & 0xFF);
|
||||
// PHANTOM-BEAM FORENSICS (BT_FIRE_LOG): identify every drawn beam --
|
||||
// owner/instance, roster slot, class, discharge state, colour, ends.
|
||||
if (getenv("BT_FIRE_LOG"))
|
||||
{
|
||||
static int s_bd = 0;
|
||||
if ((++s_bd % 30) == 1) // ~2Hz per sustained beam
|
||||
DEBUG_STREAM << "[beam-draw] mech=" << GetEntityID()
|
||||
<< " inst=" << (int)GetInstance()
|
||||
<< " slot=" << wi << " cid=" << wcid
|
||||
<< " timer=" << em->DischargeTimer()
|
||||
<< " pip=(" << r << "," << g << "," << b << ")"
|
||||
<< " mz=(" << mz.x << "," << mz.y << "," << mz.z << ")"
|
||||
<< " end=(" << bend.x << "," << bend.y << "," << bend.z
|
||||
<< ")" << std::endl;
|
||||
}
|
||||
BTPushBeamKind(mz.x, mz.y, mz.z, bend.x, bend.y, bend.z,
|
||||
tint, ttl1, 1.0f /* natural model width */,
|
||||
isPPC ? 1 : 0 /* ppc.bgf : ermlaser.bgf */);
|
||||
@@ -4300,6 +4458,6 @@ void
|
||||
DEBUG_STREAM << "[beam] " << (isPPC ? "PPC" : "laser") << " #" << wi
|
||||
<< " mz=(" << mz.x << "," << mz.y << "," << mz.z
|
||||
<< ") end=(" << bend.x << "," << bend.y << "," << bend.z
|
||||
<< ") rgb=(" << r << "," << g << "," << b << ")\n" << std::flush;
|
||||
<< ") rgb=(" << r << "," << g << "," << b << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user