Cockpit: the CONFIG-MODE weapon-regrouping session + the live-play fire fixes (tasks #6+#11)
TASK #6 -- the pod's in-cockpit weapon regrouping, fully reconstructed and live-verified (hold-configure -> tap fire button -> toggle membership -> release-commit): - MechWeapon handlers id 9 ConfigureMappables @004b9550 / id 10 ChooseButton @004b95b8 (binary table @0x511860; the old "Myomers fns" mislabel swept) + the GetMessageHandlers() accessor chain through Emitter/ProjectileWeapon/ MissileLauncher/GAUSS/PPC (empty per-class sets swallowed dispatch). - Mapper vtable truth: +0x38 EnterConfiguration / +0x3C ExitConfiguration / +0x40,+0x44 AddOrErase evt/dir. No "secondary vtable @0050f498", no "CreateTemporaryEventMappings" virtual (RP-name drift; swept incl CLASSMAP). L4 Enter/Exit rebuilt complete: StartMappableButtonsConfigure (the NonMapping 0x10000 <-> Mapping 0x8000 mode flip + the gauge's active-weapon latch) + the held-button re-arm + the 4 fire-button temp maps; only the RIO mapper implements the toggle (Thrustmaster no-ops = can't regroup). - MechSubsystem +0xE8/+0xEC corrected to controlDestination/controlMessageID (ex hostEntity/subsystemId2 mislabel); MechWeapon ctor defaults the destination to &fireImpulse (@004b99a8). - ConfigMapGauge state loop reconstructed (PE-recovered DAT_00518eb4 table; buttonGroup GetMapState sampler -- the "needs ModeManager" guard rationale was wrong). Finding [T1]: the shipped binary NEVER enables this gauge (no SetColor caller) -- authentically dormant; BT_CONFIGMAP=1 is the dev enable. - Dev harness: HOLD 'G' opens the session (BT_CONFIG_SLOT picks the weapon); BT_CONFIG_TEST scripts a headless verify. @004afbc4 corrected to its real Fail-trap body (the guessed AddOrErase(NULL) body would corrupt groups). - Bonus [T1]: the binary MechWeapon ATTRIBUTE table has ELEVEN entries (PercentDone..WeaponState) -- "TriggerState is the only one" was wrong. TASK #11 -- the user-reported live-play regressions (both real bugs): - PHANTOM FIRE: the mech4 bring-up shot block painted an explosion at the victim on its OWN 0.3s cadence whenever fire was held -- desynced once the authentic recharges landed. Retired (the real impact visual flows from each discharge via the messmgr SubmitExplosion). - WEAPON BRICKING (the "cuts out" bug): the Loading->Loaded snap window (+-0.01 around seekV) assumes the pod's LOCKED 60 fps; one port dt-spike jumps it, the byte-verified >1.0 clamp (_DAT_004ba830=0) zeroes readiness, and the weapon sticks in Loading at level ~10000 forever (observed live). Fixed with pod-frame (1/60s) sub-stepping of the binary's own Loading tick -- also fixes the I^2R integral over-heating generators on big steps. NEW GOTCHA CLASS recorded: reconstruction-gotchas #12 (frame-pacing trap). - Duty-cycle measured (max-rate autofire): generators equilibrate in the degradation band (recharge stretch ~3.5-4.4x), the FailureHeat breaker never trips solo, PPCs sustain ~11.8-damage full-charge fire. The slow kill pacing and recharge gaps are the AUTHENTIC heat economy; the pilot's counters (this regrouping UI, generator reassignment, coolant valves) are the reconstruction queue. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4e63a7b6c3
commit
092408041c
+116
-71
@@ -644,6 +644,7 @@ int gBTMissileTrigger = 0; // channel 3: missiles (key 3 / CTRL)
|
||||
static int gBTLaserKey = 0; // raw key states (set by the keyboard poll)
|
||||
static int gBTPPCKey = 0;
|
||||
static int gBTMissileKey = 0;
|
||||
static int gBTConfigKey = 0; // task #6: HOLD 'G' = the weapon-configure button
|
||||
|
||||
// Damage: each shot dispatches a REAL Entity::TakeDamageMessage to the target. Now
|
||||
// that the damage zones are constructed (mech.cpp Pass-3 zone build), the engine base
|
||||
@@ -1524,6 +1525,10 @@ void
|
||||
gBTLaserKey = focused && ((pAsync('1') | pAsync(0x20 /*VK_SPACE*/)) & dn) ? 1 : 0;
|
||||
gBTPPCKey = focused && (pAsync('2') & dn) ? 1 : 0;
|
||||
gBTMissileKey = focused && ((pAsync('3') | pAsync(0x11 /*VK_CONTROL*/)) & dn) ? 1 : 0;
|
||||
// task #6: HOLD 'G' opens the config session on the selected
|
||||
// weapon (BT_CONFIG_SLOT, default: the first weapon); while
|
||||
// held, the fire keys TOGGLE its group membership.
|
||||
gBTConfigKey = focused && (pAsync('G') & dn) ? 1 : 0;
|
||||
// gBTDrive.fire = "any weapon trigger down" (feeds the bring-up
|
||||
// damage dispatcher + the beam-visual keepalive)
|
||||
gBTDrive.fire = (gBTLaserKey || gBTPPCKey || gBTMissileKey) ? 1 : 0;
|
||||
@@ -3001,6 +3006,34 @@ void
|
||||
gBTHudPrimary = ((int)targetReticle.reticleElementMask & 0x20) != 0;
|
||||
}
|
||||
|
||||
// task #6 ORDER FIX: the scripted block must run BEFORE the fire-push
|
||||
// block below -- it writes gBTLaserKey/gBTConfigKey, which the earlier
|
||||
// keyboard poll zeroes each frame while the window is unfocused; the
|
||||
// old placement AFTER the push block meant the push never saw the
|
||||
// scripted press.
|
||||
// task #6 scripted verify (BT_CONFIG_TEST=1, headless): drives the same
|
||||
// gBTConfigKey/gBTLaserKey states the keyboard would --
|
||||
// t=[8,11) hold the configure button (session open)
|
||||
// t=[9.5,9.7) one Trigger press edge -> ChooseButton toggle
|
||||
// t>=13 0.2s Trigger pulses -> does the weapon fire now?
|
||||
if ((Entity *)this == application->GetViewpointEntity()
|
||||
&& getenv("BT_CONFIG_TEST"))
|
||||
{
|
||||
// frame-count driven (this env throttles background windows to a
|
||||
// few fps -- wall/sim seconds are useless for scripting)
|
||||
static int s_cfgFrame = 0;
|
||||
++s_cfgFrame;
|
||||
(void)dt;
|
||||
// Schedule LATE (frame 2000+): the sim ticks many frames per second
|
||||
// and the first-run schedule (30-120) fired before the enemy spawn
|
||||
// completed -- targetInArc false -> the fire press never pushed.
|
||||
gBTConfigKey = (s_cfgFrame >= 2000 && s_cfgFrame < 2600) ? 1 : 0;
|
||||
int fireOn = (s_cfgFrame >= 2300 && s_cfgFrame < 2320) ? 1 : 0; // ONE press edge
|
||||
if (s_cfgFrame >= 3000)
|
||||
fireOn = ((s_cfgFrame / 60) & 1); // fire pulses after exit
|
||||
gBTLaserKey = fireOn;
|
||||
}
|
||||
|
||||
// E8: pulse the three fire channels per frame (1,0,1,0...) so each
|
||||
// weapon sim's CheckFireEdge sees clean rising edges. UNCONDITIONAL --
|
||||
// NOT inside the enemy block: firing needs only the world PICK (task
|
||||
@@ -3067,6 +3100,77 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// task #6 dev harness: the config-session BRACKET. 'G' edge ->
|
||||
// dispatch ConfigureMappables (id 9) press/release to the selected
|
||||
// weapon -- the exact ReceiverDataMessageOf<ControlsButton> payload
|
||||
// (+-(element+1)) the pod's streamed aux-panel EventMapping delivers.
|
||||
// Everything downstream is the AUTHENTIC chain: EnterConfiguration
|
||||
// flips the global mode to Mapping(0x8000) (normal fire mappings go
|
||||
// dormant for the hold), the fire keys deliver ChooseButton (id 10)
|
||||
// through the temp 0x8000 event mappings pushed by the SAME live-mode
|
||||
// button path above, and the release restores NonMapping(0x10000).
|
||||
if ((Entity *)this == application->GetViewpointEntity())
|
||||
{
|
||||
static int s_prevCfg = 0;
|
||||
if (gBTConfigKey != s_prevCfg)
|
||||
{
|
||||
s_prevCfg = gBTConfigKey;
|
||||
Subsystem *weapon = 0;
|
||||
const char *slotEnv = getenv("BT_CONFIG_SLOT");
|
||||
int wantSlot = (slotEnv != 0) ? atoi(slotEnv) : -1;
|
||||
for (int s = 1; s < GetSubsystemCount(); ++s) // slot 0 = the MAPPER, never a weapon
|
||||
{
|
||||
Subsystem *sub = GetSubsystem(s);
|
||||
if (sub == 0)
|
||||
continue;
|
||||
// Pick by WEAPON classID (0xBC8 Emitter / 0xBCA ProjectileWeapon /
|
||||
// 0xBCE GaussRifle / 0xBD0 MissileLauncher / 0xBD4 PPC). The old
|
||||
// (simulationFlags & 0x8) heuristic matched the MAPPER at slot 0:
|
||||
// its MakeViewpointEntity resource is stack-built with only
|
||||
// name/classID/size set, so its flags are STACK GARBAGE -- and a
|
||||
// config message dispatched to the mapper lands in the mapper's
|
||||
// OWN id space (the aux-preset Fail trap) = the abort popup.
|
||||
int cid = (int)sub->GetClassID();
|
||||
if (wantSlot >= 0 ? (s == wantSlot)
|
||||
: (cid == 0xBC8 || cid == 0xBCA || cid == 0xBCE
|
||||
|| cid == 0xBD0 || cid == 0xBD4))
|
||||
{
|
||||
weapon = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (weapon == 0 && getenv("BT_FIRE_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[config] NO WEAPON matched (count="
|
||||
<< GetSubsystemCount() << ")" << std::endl;
|
||||
for (int s2 = 0; s2 < GetSubsystemCount() && s2 < 33; ++s2)
|
||||
{
|
||||
Subsystem *p = GetSubsystem(s2);
|
||||
if (p != 0)
|
||||
DEBUG_STREAM << " [cfg-scan] " << s2 << " " << p->GetName()
|
||||
<< " flags=0x" << std::hex << (int)p->simulationFlags
|
||||
<< std::dec << std::endl;
|
||||
}
|
||||
}
|
||||
if (weapon != 0)
|
||||
{
|
||||
const int kElem = 0x0e; // an aux-panel element (as authored)
|
||||
ReceiverDataMessageOf<ControlsButton> msg(
|
||||
9 /*MechWeapon::ConfigureMappablesMessageID (mechweap.hpp)*/,
|
||||
sizeof(ReceiverDataMessageOf<ControlsButton>),
|
||||
(ControlsButton)(gBTConfigKey ? (kElem + 1) : -(kElem + 1)));
|
||||
weapon->Dispatch(&msg);
|
||||
if (getenv("BT_FIRE_LOG"))
|
||||
DEBUG_STREAM << "[config] " << (gBTConfigKey ? "ENTER" : "EXIT")
|
||||
<< " session on " << weapon->GetName()
|
||||
<< " mode now=0x" << std::hex
|
||||
<< (int)application->GetModeManager()->GetModeMask()
|
||||
<< std::dec << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// The VICTIM under the boresight (task #46: any peer mech, not just
|
||||
// the solo gEnemyMech). Range/log/fire all key off THIS mech.
|
||||
@@ -3115,11 +3219,6 @@ void
|
||||
gAimHits = 0; gAimNoRay = 0; gAimGround = 0;
|
||||
}
|
||||
|
||||
if (gFireCooldown > 0.0f)
|
||||
gFireCooldown -= dt;
|
||||
|
||||
const int fireWanted = gBTDrive.fireForced || gBTDrive.fire;
|
||||
|
||||
// Resolve the "explode" effect resource once.
|
||||
if (gExplodeReady == 0)
|
||||
{
|
||||
@@ -3143,72 +3242,18 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
// FIRE + DAMAGE: only vs a MECH under the boresight (terrain/sky picks
|
||||
// draw beams but deal no damage). Dispatch routes to the picked victim
|
||||
// -- for a REPLICANT this is a cross-pod hit: Entity::Dispatch reroutes
|
||||
// the TakeDamageMessage to the owning master over the wire (task #46).
|
||||
if (fireWanted && victim != 0 && targetInArc && gFireCooldown <= 0.0f
|
||||
&& anyWeaponInRange && gExplodeReady == 1)
|
||||
{
|
||||
gFireCooldown = kFireCooldown;
|
||||
++gShotCount;
|
||||
|
||||
// Impact = the PICKED HULL POINT -> the STEP-6 cylinder lookup
|
||||
// resolves the zone under the boresight (aimed fire).
|
||||
Point3D impact = hotPoint;
|
||||
|
||||
Origin exp_origin = ((Mech *)victim)->localOrigin;
|
||||
exp_origin.linearPosition = impact; // at the hit point
|
||||
// IMPACT FRAME: -Z aimed from the victim at the shooter (the .PFX
|
||||
// hit spray convention; yaw = atan2(ddx, ddz)). [T0]
|
||||
exp_origin.angularPosition =
|
||||
EulerAngles(0.0f, (Scalar)atan2((double)ddx, (double)ddz), 0.0f);
|
||||
|
||||
Explosion::MakeMessage exp_message(
|
||||
Explosion::MakeMessageID,
|
||||
sizeof(Explosion::MakeMessage),
|
||||
(Entity::ClassID)RegisteredClass::ExplosionClassID,
|
||||
EntityID::Null,
|
||||
gExplodeRes,
|
||||
Explosion::DefaultFlags,
|
||||
exp_origin,
|
||||
victim->GetEntityID(), // entity hit
|
||||
GetEntityID()); // shooter (this mech)
|
||||
|
||||
Explosion *shot = Explosion::Make(&exp_message);
|
||||
if (shot)
|
||||
{
|
||||
Register_Object(shot);
|
||||
DEBUG_STREAM << "[fire] SHOT #" << gShotCount
|
||||
<< " -> explosion at target (range=" << range << ")\n" << std::flush;
|
||||
}
|
||||
|
||||
// --- DAMAGE (real, STEP 6): dispatch UNAIMED (zone == -1) with the
|
||||
// beam's world entry point. Mech::TakeDamageMessageHandler
|
||||
// resolves the zone from the cylinder table; the base handler
|
||||
// routes it to Mech__DamageZone::TakeDamage. Dispatch on a
|
||||
// REPLICANT is rerouted by Entity::Dispatch to the owning
|
||||
// master over the wire -- the cross-pod damage path.
|
||||
// DAMAGE DELIVERY MOVED (task #8): the AUTHENTIC submitter is
|
||||
// each firing weapon's own SendDamageMessage (@004b9728, called
|
||||
// from Emitter::FireWeapon with the authored per-shot
|
||||
// damagePortion + damageForce + impact) into the
|
||||
// SubsystemMessageManager. This block keeps only the
|
||||
// presentation roles: the boresight pick feeds the target
|
||||
// slots the weapons read (mech+0x388/0x37C/0x38C), the [fire]
|
||||
// beam-entry explosion visual above, and the shot log. The
|
||||
// flat kShotDamage build + shooter-side score moved with the
|
||||
// submission (mechweap.cpp).
|
||||
if (getenv("BT_FIRE_LOG") && ((Mech *)victim)->damageZoneCount > 0)
|
||||
{
|
||||
DEBUG_STREAM << "[damage] beam hit " << (void*)victim
|
||||
<< (((Mech *)victim)->GetInstance() == ReplicantInstance
|
||||
? " (REPLICANT -> cross-pod via the weapon submit)" : "")
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
// DEATH effects fire at the VICTIM's own death transition
|
||||
// (UpdateDeathState, task #42) -- MP-correct: the master runs it.
|
||||
}
|
||||
// THE PHANTOM-SHOT BLOCK IS RETIRED (task #11, user-reported): this
|
||||
// bring-up stand-in spawned an explosion at the victim on its OWN
|
||||
// 0.3s cadence whenever the fire key was down -- matched the old
|
||||
// one-frame recharge, but once the AUTHENTIC electrical model
|
||||
// landed (task #10: 2-5s recharges + the generator thermal breaker)
|
||||
// it kept painting hits while no weapon was discharging ("enemy
|
||||
// shows fire even though I'm not producing lasers"). The authentic
|
||||
// impact visual flows from each REAL discharge: Emitter::FireWeapon
|
||||
// -> SendDamageMessage -> SubsystemMessageManager explosion
|
||||
// bundling -> SubmitExplosion (messmgr.cpp). This block now keeps
|
||||
// only its presentation roles above (boresight pick -> the target
|
||||
// slots the weapons read, and the [target] log).
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user