diff --git a/context/combat-damage.md b/context/combat-damage.md index 13e40d9..22a6082 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -1012,15 +1012,21 @@ code involved; grammar = the binary ctors @0x49ea48/@0x49e5e4). Results: "Right*", W1/W2 = "Front*" -- with theta = atan2(z,x) from +X toward +Z, the mech-local FRONT is the **+Z arc (theta 45-135 deg)** and dead-ahead is the W1|W2 SEAM. (Corrects the earlier #92 comment that put frontal hits in W6.) -* **⚠ PORT FRAME ADAPTER (#124, fixed 2026-08-03)**: that +Z-forward is the - **1995 resolver frame**. OUR engine's entity frame carries forward along - **−Z** (bench-measured: a victim facing its attacker took frontal beams at - local z=−4..−10, θ≈270°, resolving the REAR-named cells — which was the whole - night-10 field table: rear panels unhittable, Owens flanks CROSSED). The - frames differ by a **π rotation about Y** (rotation, not reflection — the - flank observations cross rather than persist), so `ResolveHit` negates - local x and z after `WorldToLocal` before the band/wedge math. A/B verified: - frontal fire θ≈1.5 → utorso/dtorso/ltorso/rtorso, zero rear zones. The +* **⚠ PORT FRAME ADAPTER (#124, fixed 2026-08-03; corrected same day by the + MUZZLE-ANCHOR probe)**: that +Z-forward (and +X-right, from the W0/W7 + "Right*" names) is the **1995 resolver frame**. OUR engine frame, measured + with the mech's own asymmetric geometry as the anchor (`BT_ASPECT_TEST`: + four world-cardinal self-impacts + one at each weapon's physical muzzle): + **RIGHT = +X, same as 1995; FORWARD = −Z, flipped** — the LEFT missile pod + sits at raw local x=−2.32, the RIGHT at +2.32, and `WorldToLocal` is a clean + R(yaw) with no hidden terms (verified at yaws −30°/150°/40°). The frames + differ by a **Z-NEGATION ONLY — a reflection** (the first commit's π + rotation CROSSED the pods: the left muzzle resolved rtorso). `ResolveHit` + negates local z; and because a reflection reverses angular direction, the + torso-twist term in `SelectSlice` enters with the OPPOSITE sign (twist was 0 + in every probe — the twisted-torso verify is the tracked follow-up). + Anchor acceptance: left pod→ltorso, right pod→right wedges, left/right arm + muzzles→larm/rarm, nose→front cells, tail→rear cells, at a third yaw. The separate "one band LOW" Y-signature is #16 (pick-ray parallax), not this. Frontal unaimed foot-band hits therefore straddle the RightFoot/LeftFoot cells by impact-x sign, and the authored cross-bleed (20% opposite foot, diff --git a/game/reconstructed/dmgtable.cpp b/game/reconstructed/dmgtable.cpp index f8ce4b0..a61ec11 100644 --- a/game/reconstructed/dmgtable.cpp +++ b/game/reconstructed/dmgtable.cpp @@ -177,7 +177,11 @@ DamageZonePercentTable * if (rotateWithTorso) // binary this[3] != 0 { - theta += owner->TorsoHeading(); // torso+0x1d8 + // #124 frame adapter, angular half: theta arrives in the 1995 frame + // (ResolveHit's z-reflection), so the twist -- measured in OUR frame + // -- enters with the OPPOSITE sign (a reflection reverses angular + // direction). Binary form: theta += torso+0x1d8. + theta -= owner->TorsoHeading(); // torso+0x1d8 (sign: see adapter) if (theta >= TwoPi) theta -= TwoPi; if (theta < 0.0f) theta += TwoPi; } @@ -277,20 +281,22 @@ int Scalar heightRef = owner->CylinderReferenceHeight(); // owner+0x2ec[+0xc] if (heightRef <= 0.0f) return -1; - // PORT FRAME ADAPTER (#124, measured 2026-08-03). The 1995 resolver frame - // carries the mech's FORWARD along +Z: the authored wedge ring puts the - // FrontChest-family cells in the +Z arc (slice-name audit [T1]) -- frontal - // hits MUST read z > 0 for the shipped tables to mean what they say. Our - // engine's entity frame carries forward along -Z (bench 2026-08-03: the - // dummy facing its attacker takes frontal beams at local z = -4..-10, - // theta ~270 deg, resolving the REAR-named cells; the night-10 field table - // -- rear panels unhittable, Owens left torso -> RIGHT pod -- is this same - // pi ROTATION at every aspect: rotation, not reflection, because the - // left/right flanks CROSS rather than persist). Express the impact in the - // cylinder's frame: rotate pi about Y. y (the band axis) is untouched, - // and the frames share handedness, so the torso-twist add in SelectSlice - // keeps its sign. - local.x = -local.x; + // PORT FRAME ADAPTER (#124, corrected 2026-08-03 by the MUZZLE-ANCHOR + // probe). The 1995 resolver frame: FORWARD = +Z (the authored ring puts + // Front* cells in the +Z arc) and RIGHT = +X (W0/W7 = "Right*") [T1 + // slice-name data]. Our engine frame, measured with the mech's own + // asymmetric geometry as the anchor (BT_ASPECT_TEST muzzle probes -- the + // LEFT missile pod LRM15_1 sits at raw local x=-2.32, the RIGHT pod + // LRM15_2 at x=+2.32): RIGHT = +X (SAME as 1995) but FORWARD = -Z + // (drive-code convention, "forward = -Z at heading 0"). The frames + // therefore differ by a Z-NEGATION ONLY -- a REFLECTION, not the pi + // rotation first committed (that crossed the pods: left muzzle resolved + // rtorso). y (the band axis) untouched. + // + // A reflection reverses the angular walk direction, so the OTHER angular + // input -- the torso-twist term SelectSlice adds -- must flip sign with + // it (applied there, same adapter). Twist was 0 in every probe; the + // twisted-torso verify is the follow-up on #124. local.z = -local.z; // --- height -> layer (penetration depth) --- diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 295d8b2..4e62b97 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -6218,6 +6218,74 @@ void } } + // #124 PROBE (BT_ASPECT_TEST=1): the zero-premise frame probe. At frame + // ~700, dispatch four self TakeDamage messages whose impact points sit + // at KNOWN WORLD BEARINGS (+X/-X/+Z/-Z, 8 u out, torso height) around + // our OWN mech, logging our yaw + each resolve. With the mech's + // heading known and torso twist zero, the theta each bearing produces + // measures the WorldToLocal frame convention directly -- no facing + // premises, no shooter geometry. + if ((Entity *)this == application->GetViewpointEntity() + && getenv("BT_ASPECT_TEST")) + { + static int s_apFrame = 0; + if (++s_apFrame == 700) + { + YawPitchRoll ypr; ypr = localOrigin.angularPosition; + DEBUG_STREAM << "[aspect] mech yaw=" << (float)ypr.yaw + << " twist=" << (float)TorsoHeading() + << " pos=(" << localOrigin.linearPosition.x << "," + << localOrigin.linearPosition.z << ")\n" << std::flush; + static const struct { const char *tag; float dx, dz; } kProbe[4] = + { {"+X", 8, 0}, {"-X", -8, 0}, {"+Z", 0, 8}, {"-Z", 0, -8} }; + for (int pi = 0; pi < 4; ++pi) + { + Damage dmg; + dmg.damageType = (Enumeration)3; // Laser + dmg.damageAmount = 0.01f; // negligible + dmg.burstCount = 1; + dmg.impactPoint = localOrigin.linearPosition; + dmg.impactPoint.x += kProbe[pi].dx; + dmg.impactPoint.y += 7.0f; // torso height + dmg.impactPoint.z += kProbe[pi].dz; + DEBUG_STREAM << "[aspect] probe " << kProbe[pi].tag + << " ->\n" << std::flush; + Entity::TakeDamageMessage td( + Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage), + GetEntityID(), -1, dmg, -1); + Dispatch(&td); + } + // SELF-ANCHORED L/R probe: impact each weapon's PHYSICAL muzzle + // position -- the pod's own asymmetric geometry is the anchor. + // A right-side launcher's muzzle impact must resolve a + // right-family zone; which flip (z-only reflection vs full pi + // rotation) achieves that decides the adapter's x term. + { + extern int BTWeaponMuzzleWorld(Subsystem *w, Point3D *out); // mechweap TU + for (int wi = 2; wi < GetSubsystemCount(); ++wi) + { + Subsystem *w = GetSubsystem(wi); + Point3D mzp; + if (w == 0 || !BTWeaponMuzzleWorld(w, &mzp)) + continue; + Damage dmg; + dmg.damageType = (Enumeration)3; + dmg.damageAmount = 0.01f; + dmg.burstCount = 1; + dmg.impactPoint = mzp; + DEBUG_STREAM << "[aspect] muzzle probe '" + << (w->GetName() ? w->GetName() : "?") + << "' at (" << mzp.x << "," << mzp.y << "," << mzp.z + << ") ->\n" << std::flush; + Entity::TakeDamageMessage td( + Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage), + GetEntityID(), -1, dmg, -1); + Dispatch(&td); + } + } + } + } + // #86 BENCH (BT_KILL_SUBSYS=): force the AUTHENTIC destruction of // one named subsystem -- MechSubsystem::ForceCriticalFailure, the exact // call the zone crit-cascade (SendSubsystemDamage) makes, and the thing diff --git a/game/reconstructed/mechweap.cpp b/game/reconstructed/mechweap.cpp index 0ee67e1..64f6f64 100644 --- a/game/reconstructed/mechweap.cpp +++ b/game/reconstructed/mechweap.cpp @@ -997,3 +997,13 @@ void BTWeaponSetViewFireEnable(Subsystem *sub, int enable) if (sub != 0 && sub->IsDerivedFrom(MechWeapon::ClassDerivations)) ((MechWeapon *)sub)->SetViewFireEnable(enable); } + +// #124 aspect probe: a weapon's physical muzzle WORLD position (the pod's own +// asymmetric geometry as a left/right anchor). -1-family returns 0. +int BTWeaponMuzzleWorld(Subsystem *sub, Point3D *out) +{ + if (sub == 0 || out == 0 || !sub->IsDerivedFrom(MechWeapon::ClassDerivations)) + return 0; + ((MechWeapon *)sub)->MuzzlePoint(*out); // the public beam-render alias + return 1; +}