BT410 Phase 5.3.7: weapon view-fire gating + build header-stamp hardening
Completes the weapon-side half of the look commit: - MECHWEAP: real rearFiring/viewFireEnable members (binary @0x334/@0x3E0) + accessors. Ctor resolves REAR-FIRING authentically (@004b99a8 tail): the mount SEGMENT site name is tested for the 'b' (back) marker -- only the back gun ports (sitelbgunport/siterbgunport) carry it. Spawn = forward-armed. - MECH CommitLookState: re-arms every roster weapon per view (forward = non-rear, LOOK-BACK = rear-mounted, side/down = none) and flips the HUD reticle pip group with the authentic Reticle::Front/RearFiringWeaponsOn flags (BT411's raw "bits 1/2" resolved to their 1995 names). - MECHWEAP: defined MechWeapon::MessageHandlers (0-entry set inheriting the Subsystem chain) -- it was declared-but-undefined, and the surviving CODE PPC.CPP binds the inherited name into PPC::DefaultData; the zero-filled common block was a latent NULL deref (same trap as Emitter::AttributeIndex). Discovery: the TEST.EGG mech genuinely mounts TWO REAR LASERS (ERMLaser_2/3 on the b-ports). Verified on a full-clean baseline (engine 181 + bt 42, 0 fail): look-behind arms exactly the rear lasers, disarms the other five, pipMask=0xfffffffe; neutral run holds forward-armed; zero Fail. build410.sh HARDENED against the stale-obj layout-skew trap this wave exposed: cc() skipped recompiles on obj-vs-.cpp timestamps only, so the MECHWEAP.HPP member additions left emitter.obj on the OLD MechWeapon layout -- its chargeLevel=0.0f write landed exactly on the new rearFiring (a silent cross-TU struct skew, heisenbug-grade). cc() now honors header stamps: a source410 engine-header edit invalidates every bucket, a BT/BT_L4 header edit additionally invalidates the game buckets. CODE/ is immutable, no stamp needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -19,16 +19,39 @@
|
||||
# include <mech.hpp>
|
||||
#endif
|
||||
|
||||
#if !defined(SEGMENT_HPP)
|
||||
# include <segment.hpp>
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
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 << "<none>";
|
||||
}
|
||||
DEBUG_STREAM << " rear=" << (int)rearFiring << endl << flush;
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
@@ -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 <dir...> -> 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 <bucket> <src>
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user