diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index 6536d66d..e12f1445 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -560,10 +560,67 @@ void Radian(0.0f) ); + // + // Re-arm each weapon's view-fire enable: the forward view arms the + // non-rear-mounted weapons, LOOK-BACK arms the rear-mounted ones, and the + // side/down views arm none. + // + { + for (int id = 2; id < subsystemCount; ++id) + { + Subsystem *sub = subsystemArray[id]; + if (sub == NULL || !sub->IsDerivedFrom(MechWeapon::ClassDerivations)) + { + continue; + } + MechWeapon *weapon = (MechWeapon *)sub; + Logical arm; + if (look_state == MechControlsMapper::LookNone) + { + arm = (weapon->IsRearFiring() == False); + } + else if (look_state == MechControlsMapper::LookBehindState) + { + arm = weapon->IsRearFiring(); + } + else + { + arm = False; + } + weapon->SetViewFireEnable(arm); + + if (getenv("BT_MECH_LOG")) + { + DEBUG_STREAM << "[look] weapon '" << weapon->GetName() + << "' rear=" << (int)weapon->IsRearFiring() + << " armed=" << (int)arm << endl << flush; + } + } + } + + // + // The HUD reticle weapon-pip group: the forward view shows the FRONT pip + // group, look-back shows the REAR group; side/down views leave the mask. + // + if (look_state == MechControlsMapper::LookNone) + { + targetReticle.reticleElementMask = (Reticle::ReticleElements) + (((int)targetReticle.reticleElementMask | Reticle::FrontFiringWeaponsOn) + & ~Reticle::RearFiringWeaponsOn); + } + else if (look_state == MechControlsMapper::LookBehindState) + { + targetReticle.reticleElementMask = (Reticle::ReticleElements) + (((int)targetReticle.reticleElementMask | Reticle::RearFiringWeaponsOn) + & ~Reticle::FrontFiringWeaponsOn); + } + if (getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[look] state=" << look_state - << " yaw=" << yaw << " pitch=" << pitch << endl << flush; + << " yaw=" << yaw << " pitch=" << pitch + << " pipMask=0x" << hex << (int)targetReticle.reticleElementMask << dec + << endl << flush; } Check_Fpu(); diff --git a/restoration/source410/BT/MECH.NOTES.md b/restoration/source410/BT/MECH.NOTES.md index 218ac863..b7d22793 100644 --- a/restoration/source410/BT/MECH.NOTES.md +++ b/restoration/source410/BT/MECH.NOTES.md @@ -317,3 +317,41 @@ elevation). Zero Fail. Next: the gait leg-clip animation / terrain drop / weapon wave. These become VISIBLE only under the renderer; headlessly the composed angles are loggable (as here). + +## PHASE 5.3 INCREMENT 7: weapon view-fire gating (2026-07-21) + +Completes the weapon-side half of the look commit: + +- **MechWeapon** gains the real `rearFiring` / `viewFireEnable` members (out of + the 1995 binary's @0x334/@0x3E0) + accessors. The ctor resolves REAR-FIRING + authentically (binary ctor tail @004b99a8): the weapon's MOUNT SEGMENT site + name is tested for the 'b' (back) marker — the back gun ports + (`sitelbgunport`/`siterbgunport`) are the only port names carrying it. A + weapon spawns armed for the forward view (`viewFireEnable = !rearFiring`). +- **Mech::CommitLookState** now re-arms every roster weapon on a view change + (forward = non-rear weapons, LOOK-BACK = rear-mounted, side/down = none) and + flips the HUD reticle pip group with the authentic 1995 enum flags + (`Reticle::FrontFiringWeaponsOn`/`RearFiringWeaponsOn` — BT411's raw "bits + 1/2" resolved to their real names). + +**Discovery:** the TEST.EGG mech genuinely mounts TWO REAR LASERS — ERMLaser_2 +on `siterbgunport`, ERMLaser_3 on `sitelbgunport` (PPCs on the u-ports, the +third laser + SRM6s on the torso ports). + +**VERIFIED headlessly** (`BT_FORCE_LOOK=3`): the look-behind commit arms exactly +the two rear lasers (`rear=1 armed=1`) and disarms the five forward weapons; +`pipMask=0xfffffffe` (front pips off, rear on). Zero Fail. + +### The stale-obj layout-skew trap (build410.sh hardened) + +The first test runs showed the rear flags mysteriously zeroed after +construction. Root cause was NOT game code: `build410.sh cc()` skipped +recompiles purely on obj-vs-.cpp timestamps, so after MECHWEAP.HPP grew two +members, `emitter.obj` (+ ppc/gauss/projweap/...) kept the OLD MechWeapon +layout — their `Emitter::chargeLevel = 0.0f` write landed exactly on the new +`rearFiring` offset. A silent cross-TU struct-layout skew. Fix: +`build410.sh` now keeps header stamps (newest source410 engine-header mtime +invalidates every bucket; newest BT/BT_L4 header mtime additionally invalidates +the game buckets) so header edits force the dependent recompiles. CODE/ is +immutable and needs no stamp. When in doubt: purge `build410/obj/*` for a +clean baseline. diff --git a/restoration/source410/BT/MECHWEAP.CPP b/restoration/source410/BT/MECHWEAP.CPP index a4a4ff5b..108a6163 100644 --- a/restoration/source410/BT/MECHWEAP.CPP +++ b/restoration/source410/BT/MECHWEAP.CPP @@ -19,16 +19,39 @@ # include #endif +#if !defined(SEGMENT_HPP) +# include +#endif + +#include + Derivation MechWeapon::ClassDerivations( PoweredSubsystem::ClassDerivations, "MechWeapon" ); +// +//############################################################################# +// Message handlers. MechWeapon publishes no handlers of its own yet (the +// weapon fire/damage handler wave), but the set MUST be a real defined object: +// the surviving CODE PPC.CPP binds the inherited name (`PPC::MessageHandlers`) +// into PPC::DefaultData, and a declared-but-undefined static links as a +// zero-filled common block -- the same silent-NULL trap as the +// Emitter::AttributeIndex crash (see MECHWEAP.NOTES.md). +//############################################################################# +// +MechWeapon::MessageHandlerSet + MechWeapon::MessageHandlers( + 0, + NULL, + Subsystem::MessageHandlers + ); + MechWeapon::SharedData MechWeapon::DefaultData( MechWeapon::ClassDerivations, - Subsystem::MessageHandlers, + MechWeapon::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); @@ -54,6 +77,45 @@ MechWeapon::MechWeapon( damageData.damageType = damageType; damageData.damageAmount = damageAmount; + // + // REAR-FIRING resolve (the binary ctor tail @004b99a8): a weapon is + // rear-mounted when its MOUNT SEGMENT's site name carries the 'b' (back) + // marker -- the back gun ports (sitelbgunport / siterbgunport) are the only + // port names containing it. Rear-mounted weapons fire only into the + // LOOK-BACK view; everything else fires forward. The look-state commit + // (Mech::CommitLookState) re-arms viewFireEnable on every view change; a + // weapon spawns armed for the forward view. + // + rearFiring = False; + { + EntitySegment *mount = owner->GetSegment(GetSegmentIndex()); + if (mount != NULL) + { + const char *mount_name = mount->GetName(); + if (mount_name != NULL && strstr(mount_name, "b") != NULL) + { + rearFiring = True; + } + } + } + viewFireEnable = (rearFiring == False); + + if (getenv("BT_MECH_LOG")) + { + EntitySegment *mount = owner->GetSegment(GetSegmentIndex()); + DEBUG_STREAM << "[weap] '" << GetName() + << "' seg=" << GetSegmentIndex() << " mount="; + if (mount != NULL) + { + DEBUG_STREAM << mount->GetName(); + } + else + { + DEBUG_STREAM << ""; + } + DEBUG_STREAM << " rear=" << (int)rearFiring << endl << flush; + } + Check_Fpu(); } diff --git a/restoration/source410/BT/MECHWEAP.HPP b/restoration/source410/BT/MECHWEAP.HPP index 6b68392f..b04e8ecc 100644 --- a/restoration/source410/BT/MECHWEAP.HPP +++ b/restoration/source410/BT/MECHWEAP.HPP @@ -109,6 +109,18 @@ Damage &damage ); + // + // View-fire gating (the look-state commit re-arms these): a weapon + // mounted on a back gun port (rearFiring) fires only into the LOOK-BACK + // view; the rest fire only in the forward view. + // + Logical + IsRearFiring() const { Check(this); return rearFiring; } + Logical + GetViewFireEnable() const { Check(this); return viewFireEnable; } + void + SetViewFireEnable(Logical enable){ Check(this); viewFireEnable = enable; } + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local Data // @@ -120,6 +132,8 @@ Scalar heatCostToFire; int targetWithinRange; Damage damageData; + Logical rearFiring; + Logical viewFireEnable; }; #endif diff --git a/restoration/source410/BT/MECHWEAP.NOTES.md b/restoration/source410/BT/MECHWEAP.NOTES.md new file mode 100644 index 00000000..c19d3262 --- /dev/null +++ b/restoration/source410/BT/MECHWEAP.NOTES.md @@ -0,0 +1,37 @@ +# MECHWEAP.CPP / .HPP — reconstruction notes + +`MechWeapon` (: PoweredSubsystem) is the weapon base: PPC/GaussRifle bind it via +Emitter, the SRMs via ProjectileWeapon/MissileLauncher. The resource streams +rechargeRate / weaponRange / damage / heatCostToFire (+ pip fields). + +## Reconstructed + +- Statics (ClassDerivations, DefaultData on Subsystem::MessageHandlers/ + AttributeIndex), ctor streaming the tuning fields, damageData prime. +- **View-fire gating (2026-07-21, the look-commit wave):** real `rearFiring` / + `viewFireEnable` members (1995 binary @0x334/@0x3E0) + `IsRearFiring()` / + `GetViewFireEnable()` / `SetViewFireEnable()`. The ctor resolves REAR-FIRING + authentically (binary ctor tail @004b99a8): the mount SEGMENT site name + (`owner->GetSegment(GetSegmentIndex())`) is tested for the 'b' (back) marker — + only the back gun ports (`sitelbgunport`/`siterbgunport`) carry it. Spawn + state = armed for the forward view. `Mech::CommitLookState` re-arms on every + view change (see MECH.NOTES.md increment 7). Verified: the TEST.EGG mech's + two rear lasers (ERMLaser_2/3) arm only in LOOK-BACK. + +## Still staged + +- `FireWeapon` (abstract here; the concrete fire paths in Emitter/PPC/Gauss/ + MissileLauncher are the combat wave), `SendDamage` (damage/networking), + `CreateStreamedSubsystem` (tool-side model build). +- The 1995 MechWeapon attribute table (RearFiring @0x1B, EstimatedReadyTime, + ...) is not yet published — our DefaultData still uses the base + Subsystem::AttributeIndex. Comes with the weapon-gauge/console wave. +- ~~`MechWeapon::MessageHandlers` declared but not defined~~ — **FIXED + 2026-07-21**: it is now a real defined set (`(0, NULL, + Subsystem::MessageHandlers)` — no own handlers yet, inherits the chain), and + `MechWeapon::DefaultData` binds it. This mattered because the surviving CODE + PPC.CPP binds the inherited name (`PPC::MessageHandlers`) into + PPC::DefaultData, and a declared-but-undefined static links as a zero-filled + common block — the same silent-NULL trap as the Emitter::AttributeIndex + crash. Real weapon handlers (fire/damage messages) come with the combat wave + (grow the table in place). diff --git a/restoration/source410/build410.sh b/restoration/source410/build410.sh index b06c0754..6a7354fd 100644 --- a/restoration/source410/build410.sh +++ b/restoration/source410/build410.sh @@ -27,10 +27,33 @@ FLAGS="-c -DLBE4 -DDEBUG_LEVEL=0 -DDEBUG_STREAM=cout \ mkdir -p "$B"/obj/{munga,mungal4,bt,btl4} "$B"/lib -# compile one TU into a bucket; skip if the obj is newer than the source +# Header-dependency stamps: cc() has no per-TU include tracking, so a header +# edit must invalidate every obj that could embed its layout (a stale obj with +# an old struct layout SILENTLY corrupts adjacent members at runtime -- the +# 2026-07-21 MechWeapon::rearFiring stomp). CODE/ is the immutable archive; +# only source410/ headers change. Engine-level headers (MUNGA/shim/override) +# are included by everything; game headers (BT/BT_L4) only by the game buckets. +hstamp() { # hstamp -> newest header mtime (epoch secs, 0 if none) + local t + t=$(find "$@" -maxdepth 1 \( -iname '*.hpp' -o -iname '*.h' \) -printf '%T@\n' 2>/dev/null \ + | sort -n | tail -1) + echo "${t%%.*}" +} +HS_ENGINE=$(hstamp "$S/MUNGA" "$S/MUNGA_L4" "$S/shim" "$S/override") +HS_GAME=$(hstamp "$S/BT" "$S/BT_L4") +[ -z "$HS_ENGINE" ] && HS_ENGINE=0 +[ -z "$HS_GAME" ] && HS_GAME=0 + +# compile one TU into a bucket; skip only if the obj is newer than the source +# AND the relevant header stamps cc() { # cc local out="$B/obj/$1/$(basename "${2%.*}" | tr 'A-Z' 'a-z').obj" - [ -f "$out" ] && [ "$out" -nt "$2" ] && return 0 + if [ -f "$out" ] && [ "$out" -nt "$2" ]; then + local omt=$(stat -c %Y "$out") + local need=$HS_ENGINE + case "$1" in bt|btl4) [ "$HS_GAME" -gt "$need" ] && need=$HS_GAME;; esac + [ "$omt" -ge "$need" ] && return 0 + fi if bcc32 $FLAGS -I"$INC" -o"$(cygpath -w "$out" 2>/dev/null || echo $out)" "$2" \ > "$B/obj/$1/$(basename "${2%.*}").log" 2>&1; then return 0