diff --git a/context/combat-damage.md b/context/combat-damage.md index d691ce5..0daa9dd 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -56,17 +56,29 @@ projected through the live per-axis projection; **0 = dead-centre on the fixed-t `BT_AIM="x y"` pins it for headless tests) → `BTGetAimRay` (the ACTIVE eye publishes pos+LookAtRH basis via `BTSetAimCamera`, the render loop publishes proj._11/_22 + backbuffer dims) → `Mech::PickRayHit` (world ray → local frame → `BoundingBox::HitBy` slab test vs the collision -template's ExtentBox → world hit point) → the entity target slots. The slots MIRROR the pick EACH -FRAME: locked while the boresight is ON the mech, no target otherwise — **steer to keep the enemy -under the centred crosshair; that IS the gunnery** (verified: face-to-face spawn → HOT lock; -`BT_FORCE_TURN` circling → 31 no-target vs 1 HOT). (⚠ Two earlier cuts were wrong and are -corrected: a STICKY designation — locked forever after first hover, made aim irrelevant; and a -MOUSE cursor slew — mis-sourced from the RP-shared struct, gave a free cursor the pod never had. -Both removed.) While locked the impact point = the PICKED hull point → STEP-6 resolves the zone -under the crosshair (verified: center-aim → head-band zone 13 dominant). The HUD draws the aim -group at the boresight ([0x9a] translate), the designator hotbox at the target's projected point -(subB9 hot / subB8 designated, `BTProjectHotBox`/`BTProjectToReticle`), and the edge arrows when -off-screen/behind. `BT_FIRE_ARC` is now an EXPLICIT OPT-IN presentation clamp (unset = authentic +template's ExtentBox → world hit point). + +**FIRING NEEDS A TARGET BUT NOT A MANUAL LOCK / PINPOINT AIM (task #40, binary-verified [T1]).** +The weapon fire path is DOUBLY gated on `mech+0x388 != 0`: `Emitter::EmitterSimulation` +(`FUN_004baa88` :7689) only calls FireWeapon with a target, AND `Emitter::FireWeapon` +(`FUN_004bace8` :7727) wraps its ENTIRE body (incl. `beamFlag`=+0x46c=1 :7749) in the same check — +so **no target ⇒ no beam, ever** (you cannot fire into truly empty space). BUT a capstone scan of +the whole CODE section finds **11 reads of `+0x388` and ZERO direct stores** — the target is +written INDIRECTLY (a message/selector via a `this`-pointer), i.e. the game **AUTO-ACQUIRES** the +target for you; there is no manual "lock to fire." The spinning-ring **LOCK** (HudSimulation +5619-5634) is a SEPARATE, stricter state layered on top (host-zone < 0.75 damage AND targeted-zone +< 1.0). **Port model:** `mech+0x388` = the enemy whenever it is ALIVE (`!IsMechDestroyed()`) — the +pod's auto-target — so firing works freely with the enemy merely present, NO pinpoint needed +(verified: `BT_AIM="0.5 0.2"` off-hull → fires + lands damage; before this fix off-hull dealt 0). +A pinpoint hull hit (`PickRayHit` → `hotTarget`) UPGRADES the shot: `mech+0x37c` = the picked hull +point → STEP-6 resolves the zone under the crosshair (aimed fire; center-aim → head-band zone 13), +and lights the lock ring. Off-hull, `0x37c` = the target's centre mass (body hits, near-centre +zone). (⚠ THREE earlier cuts were wrong and are corrected: a STICKY designation — locked forever; +a MOUSE cursor slew — a free cursor the pod never had; and a PINPOINT-required fire gate — you had +to boresight the hull exactly to fire at all. All from over-reading the RP-shared `Reticle` struct; +removed.) The HUD draws the aim group at the boresight ([0x9a] translate), the designator hotbox at +the target's projected point (subB9 hot / subB8 designated, `BTProjectHotBox`/`BTProjectToReticle`), +and the edge arrows when off-screen/behind. `BT_FIRE_ARC` is now an EXPLICIT OPT-IN presentation clamp (unset = authentic no-arc). The old hardwired lock, the ±30°-default cone, and the projectile path's gEnemyMech fallback are all REMOVED. LMB fires lasers / RMB missiles (with SPACE/CTRL). ⚠ A view-selection bug was fixed en route: every diff --git a/context/open-questions.md b/context/open-questions.md index e7a469a..9049553 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -55,9 +55,12 @@ authentic path scoped. pick-ray chain is LIVE (see [[combat-damage]] Targeting for the full port map): the crosshair = **torso boresight** (NO free-aim mouse — the pod stick twisted the torso; you steer to aim) → pick ray → per-frame lock (boresight ON the mech = locked; off = none) → aimed zone damage + - designator hotbox; `BT_FIRE_ARC` is opt-in-only now. Residue: (a) the binary's own per-frame - reticle→mech copy is still in an un-exported gap (our writer follows RETICLE.h + the - HudSimulation pose derivation [T3 dynamics]); (b) our cockpit view is body-mounted, so on a + designator hotbox; `BT_FIRE_ARC` is opt-in-only now. Firing needs a target but NOT a manual + lock/pinpoint — the target is AUTO-acquired (task #40, binary-verified: FireWeapon doubly-gated + on mech+0x388, but 0x388 has 11 reads / 0 direct stores across CODE → set indirectly). Residue: + (a) the binary's own per-frame reticle→mech copy + the AUTO-TARGET SELECTOR are still in an + un-exported gap (a message/selector; our port auto-targets the single living enemy — for MP, + reconstruct the real multi-target selection: nearest/most-aligned/cycle); (b) our cockpit view is body-mounted, so on a TWIST-CAPABLE mech the crosshair should deflect with the torso twist (`BTTwistToReticleX` is wired for it) AND the torso should visibly lead the legs — verify on a twist mech when one is in play (the BLH is fixed-torso, boresight always centred); (c) pre-burial, the pick still diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 1073a41..566c0f7 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -2330,24 +2330,38 @@ void if (hotTarget != 0) targetReticle.rayIntersection = hotPoint; - // The target slots MIRROR the pick each frame: you are locked while - // the crosshair is ON the mech, and not otherwise. (A first cut - // made the designation STICKY -- once hovered, locked forever -- but - // that made aim irrelevant after the first acquisition, and the - // binary's own code treats no-target as a FREQUENT live state: the - // fire path re-checks 0x388!=0 at every step (part_013.c:7689/7727) - // and the HUD range feed has a permanent no-target default of 1200 - // (part_013.c:5636). Keeping the crosshair on the enemy IS the - // gunnery.) - if (hotTarget != 0) + // TARGET (mech+0x388) is AUTO-ACQUIRED, not manually locked (task #40, + // binary-confirmed): the weapon's fire path (Emitter FUN_004baa88 :7689 + // + FireWeapon FUN_004bace8 :7727) is DOUBLY gated on mech+0x388 != 0 -- + // no target, NO beam, ever -- but 0x388 is WRITTEN INDIRECTLY (a + // disasm scan of the whole CODE section finds 11 reads and ZERO direct + // stores), i.e. the game AUTO-SELECTS the target for you; you never + // manually "lock" to fire. The spinning-ring LOCK (HudSimulation + // 5619-5634) is a SEPARATE, stricter state layered on top. So: 0x388 + // = the enemy whenever it is alive (the pod's auto-target); firing + // needs only that, NOT a pinpoint boresight hit. A pinpoint hull hit + // (hotTarget) UPGRADES the shot to aimed-zone damage + the lock ring. + Entity *autoTarget = + (gEnemyMech != 0 && !((Mech *)gEnemyMech)->IsMechDestroyed()) + ? gEnemyMech : 0; + if (autoTarget != 0) { - MECH_TARGET_ENTITY(this) = hotTarget; + MECH_TARGET_ENTITY(this) = autoTarget; MECH_TARGET_SUBIDX(this) = -1; - MECH_TARGET_POS(this) = hotPoint; // the aimed hull point + if (hotTarget != 0) + MECH_TARGET_POS(this) = hotPoint; // aimed: the picked hull point + else + { + // no pinpoint pick -> converge on the target's centre mass + // (the beam still fires; the zone resolves near-centre). + Point3D cm = ((Mech *)autoTarget)->localOrigin.linearPosition; + cm.y += kMuzzleHeight; + MECH_TARGET_POS(this) = cm; + } } else { - MECH_TARGET_ENTITY(this) = 0; // crosshair off -> no lock + MECH_TARGET_ENTITY(this) = 0; // no living enemy -> no target MECH_TARGET_SUBIDX(this) = -1; } } @@ -2424,12 +2438,12 @@ void { gTargetLogAccum = 0.0f; DEBUG_STREAM << "[target] aim=(" << gBTAimX << "," << gBTAimY << ")" - << (hotTarget != 0 ? " HOT (crosshair on target)" - : (designated ? " designated (off-crosshair)" : " no target")) + << (hotTarget != 0 ? " HOT-aimed (zone under crosshair)" + : (designated ? " auto-target (fire freely, body hits)" : " no living target")) << " range=" << range << (range <= kWeaponRange ? " IN RANGE" : "") - << " [hits=" << gAimHits << " noRay=" << gAimNoRay - << " noPick=" << gAimNoPick << "]" + << " [aimedHits=" << gAimHits << " noRay=" << gAimNoRay + << " offHull=" << gAimNoPick << "]" << "\n" << std::flush; gAimHits = 0; gAimNoRay = 0; gAimNoPick = 0; }