Files
TeslaRel410/restoration/source410/BT/MECHWEAP.NOTES.md
T
CydandClaude Fable 5 f2424e9fdc BT410 Phase 5.3.18a: salvo economy corrected -- one cluster hit per trigger
BT411 task-#62 truth: the arcade fires ONE cluster Missile per salvo and its
zone damage lands EXACTLY ONCE (burstCount feeds only the gyro bounce +
splash falloff, never the zone armor formula). The 5.3.16 per-missile
delivery loop was ~6x too lethal. MISLANCH now sends a single salvo-split
damage message (verified 1:1 FIRED-to-damage in the mutual fight; zero
exceptions). The cluster splash joins the Missile entity wave.

Also: BT_FORCE_ZONE=n dev hook (pin every hit -- the cascade verification
knob) landed with 5.3.17's runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:19:27 -05:00

163 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
## Fire path (2026-07-21, Phase 5.3.8)
- **The authentic attribute table IS published** (binary @0x511890, IDs pinned):
PercentDone 0x12 → rechargeLevel, **TriggerState 0x13 → fireImpulse** (the
streamed per-mech fire-button mappings bind this ID — the real trigger
wiring), DistanceToTarget 0x14, TargetWithinRange 0x15, WeaponRange 0x16 →
effectiveRange, EstimatedReadyTime 0x1A, RearFiring 0x1B, WeaponState 0x1C →
weaponAlarm. Pads bridge our chain gap 2..0x11 (parents publish nothing yet;
they shrink to the authentic 0x0F..0x11 when the mechsub/heat/powersub
attribute waves land — SENSOR.HPP pins PoweredSubsystem::NextAttributeID at
0x0F). **Pads are LOAD-BEARING**: AttributeIndexSet::Build leaves uncovered
gap slots as UNINITIALIZED GARBAGE (`new IndexEntry[entryCount]`, only
inherited + listed entries written), so every ID up to the max must be
covered or Find() on a gap ID returns a wild pointer. Pip* (0x17-0x19) are
pads → rechargeLevel until the pip members are reconstructed.
- **Fire state machine members**: fireImpulse/previousFireImpulse (the trigger
sample pair), rechargeLevel, rangeToTarget, effectiveRange,
estimatedReadyTime, recoil, weaponAlarm (the state carrier: 0 Firing /
2 Loaded / 3 Loading / 4 trigger-during-load). Spawn = charged + Loaded.
- **CheckFireEdge** (@004b9608): rising-edge on fireImpulse comparing BIT
PATTERNS as signed ints — TriggerState carries raw ControlsButton ints
(+(button+1) press / (button+1) release); the release bit-pattern is a
negative NaN as float, and an IEEE float compare would latch the detector
shut after the first release (BT411 task #8). Int compare reproduces the
binary's x87 semantics exactly.
- **ComputeOutputVoltage** (@004b9c9c): `rechargeLevel = (rechargeRate
recoil) / rechargeRate` — the recharge dial.
- **Emitter::EmitterSimulation** (PARTIAL, @004baa88) installed on every
non-replicant energy weapon: Firing → discharge countdown →
ResetFiringState(Loading); Loaded + trigger edge → viewFireEnable gate →
FireWeapon; Loading → recoil decays over the authored RechargeRate seconds →
Loaded. Deferred within it: the leading PoweredSubsystemSimulation electrical
step, the destroyed/heat-failure/dead-mech hard gates, the TrackSeekVoltage
generator charge integration (the partial uses the authored seconds
directly), and the HasActiveTarget gate (targeting wave).
- **Emitter::FireWeapon** (PARTIAL, @004bace8): discharge bookkeeping (beam
countdown re-arm, charge spent, recoil loaded). Deferred: the 0.5·V²·EC
energy/damage/heat closed forms (need the electrical model), the heat dump
(heat wave), the beam build + Damage submission (targeting + renderer).
- Emitter's index re-chained to MechWeapon's (ChargeLevel at the authentic
0x1D past the table end); Emitter::DefaultData now binds
MechWeapon::MessageHandlers.
- DEV hook `BT_FORCE_FIRE=1`: pulses the trigger whenever a weapon is Loaded →
every armed weapon auto-fires at its authored cadence.
**VERIFIED headlessly**: forward view — PPC_1/2 + ERMLaser_1 cycle
FIRED→LOADED→FIRED at the authored 0.6 s discharge / 1 s recharge, rear lasers
silent; `BT_FORCE_LOOK=3` (look-behind) — ONLY ERMLaser_2/3 fire. The full
view-fire × fire-FSM interlock holds in both directions. Zero Fail. (SRM6s are
MissileLaunchers — the ballistic ProjectileWeaponSimulation is a later
increment.)
## Ballistic fire path (2026-07-21, Phase 5.3.9)
- **ProjectileWeaponSimulation** (PARTIAL, binary @004bbd04 — FULLY RECOVERED
in the BT411 RE) installed on every non-replicant ballistic weapon. The
authentic FSM shape: one trigger-edge sample before the gates; gate 2 (the
linked AmmoBin dry → pin NoAmmo(7) every frame); Loaded(2): edge →
viewFireEnable gate → **the ammo pull happens in the CALLER** (a failed pull
stays Loaded silently; a denied shot does the 4→2 DENIAL BLIP with no ammo
pulled — early-returning gates from FireWeapon while the caller cycles the
alarm is exactly the 1995 "denied shot fakes a full firing cycle" defect);
granted → Firing(0) → FireWeapon → Loading(3) (or NoAmmo if the pull emptied
the bin), recoil = rechargeRate; Loading(3): recoil bleeds → 0 → Loaded when
a round is chambered, ComputeOutputVoltage animates the dial; Jammed(5) held
at full recoil; **NoAmmo(7) is the roach-motel** (re-asserts unconditionally;
only a mission reset leaves it). Deferred inside: the leading electrical
step, gate 1 (destroyed/FailureHeat/mech-disabled), magazine eject, the
electrical-Ready recoil gate, the heat-scaled jam roll, HasActiveTarget.
- **AmmoBin feed interface**: `GetAmmoState()` (1 round-ready / 2 EMPTY),
`FeedAmmo()` (pull one round; 0 when dry). Feed pacing + cook-off join the
heat wave.
- **ammoBinLink wiring**: the launcher ctor resolves the bin from the
resource's `ammoBinIndex` (the bin's roster slot) via the owner's subsystem
table — WIRE-VERIFIED: the raw stream carries 27/29, exactly the
AmmoBinSRM6_1/2 slots.
- **Resource alignment (wire-verified)**: a raw-stream dump showed the
ProjectileWeapon field block sits +3 ints past our staged struct — the
MechWeapon resource ancestry runs three ints short (the pip-family fields,
not yet broken out). `resourceAlignPad[3]` re-aligns it; the phantom
muzzleVelocity/missileCount tail fields were dropped (BT411's verified
overlay ends at minJamChance; missileCount belongs to MissileLauncher's own
extension and now reads its true 6). The full resource-overlay verification
(every ancestor struct byte-checked) is its own wave.
- MissileLauncher::FireWeapon PARTIAL (@004bcc60: authentically heat + the
missileCount-salvo Missile spawn ONLY — spawn needs the entity/targeting
waves).
**VERIFIED headlessly** (BT_FORCE_FIRE): bins resolve with the authored 24
rounds; SRM6s fire 6-missile salvos and reload at the authored 1 s cadence,
rounds counting 24→0; after the 24th salvo the launcher goes DRY and stays
silent across ~3,000 subsequent fire events while the energy weapons keep
cycling — the NoAmmo roach-motel holds. Zero Fail.
## Still staged
- The Missile entity spawn/seeker, `SendDamage` (damage/networking),
`CreateStreamedSubsystem` (tool-side model build), the jam roll
(CheckForJam), magazine eject.
- ~~`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).
## 5.3.16 — targeting + damage delivery
- `HasActiveTarget()` = owner's target slot non-NULL (binary owner+0x388);
`UpdateTargeting()` (@004b9bdc) refreshes rangeToTarget/targetWithinRange
from `target->localOrigin` every FSM tick (both weapon families call it
right after the trigger sample). The Loaded→Firing gate is now the authentic
`viewFireEnable && HasActiveTarget()` — a denied pull is the one-frame blip.
- `SendDamage(target, damage)`: builds the engine 5-arg
`Entity::TakeDamageMessage` (inflictor = our mech's EntityID) and Dispatches
AT the target; the engine handler routes `damageZones[zone]->TakeDamage`.
INTERIM zone pick = `Random(target->damageZoneCount)` — the authentic
resolvers are the reticle aimed hit and the cylinder hit-location table
(unaimed, zone 1 + the Mech TakeDamage override), both later waves.
- Salvo semantics (BT411-verified): **burstCount is NOT in the zone armor
formula** — one message = one amount×scale application. MissileLauncher
therefore dispatches ONE message PER MISSILE (each carrying the ctor's
salvo-split amount, each on its own random zone); a single split message
would under-deliver the salvo by the round count. Interim all-rounds-hit
until the Missile entity flight/seeker wave.
- Live-verified (two-mech fight): PPC 11.77/hit (12×chargeRatio², type 4
Energy), ER-M 3.43 (type 3 Laser), SRM6 5.83×6 (type 2 Explosive); repeat
hits climb linearly; zones reach 1.0 Burning/Destroyed; no-target runs hold
fire (0 FIRED across a 65s force-fire run without a target).
## 5.3.18a — salvo economy CORRECTED (BT411 task #62)
The 5.3.16 "one message per missile" loop was ~missileCount-times too lethal.
The arcade fires ONE cluster Missile per trigger and its zone damage lands
EXACTLY ONCE: damageData carries the ctor's salvo-split per-missile amount
with burstCount=missileCount (burstCount feeds only gyro bounce + splash
falloff — never the zone formula). MISLANCH now dispatches a single
SendDamage per salvo (verified 1:1 FIRED:[dmg]). The cluster SPLASH component
arrives with the Missile entity wave (proximity-fuse detonation).