PARALLAX (#16): the pick/fire ray was anchored at mech.y+5.0 (a port improvisation) while the sight line ran from the eyepoint (y=6.23) -- two parallel rays whose constant offset grew into the reported low-miss as range closed (measured ry +0.072 @50u -> +1.54 @2.7u). The decomp's sight and pick share the eye origin (HudSimulation @4b7830 chain). Fix: the viewpoint mech's cockpit eye owns the aim-camera publish in BOTH views, origin = its own eye translation; leveling + deliberate elevation untouched; chase view now converges to cockpit ballistics (V cannot change where shots land). After: pick pinned to the crosshair (ry <= 5e-6) from 50u to point-blank; 26 laser + 8 missile center-mass hits at 3/4-screen. Awaiting the reporters' approach-test. VERDICT INSTRUMENTATION (#4 closed authentic, #5 verdict posted): BT_RANGE_LOG per-frame pick tracing + BTGroundRayHitExact analytic cross-check (0/8000 arena fall-throughs; cavern 6/8400 single-frame grazes -- the 'crazy sliding' is the authentic world-pick + 500 m/s slide over depth discontinuities); BT_AUD_TAIL StopNote/fade timing + BT_FIRE_PULSE single-shot driver (the energy 'buzz' is the AUTHORED charging loop: crescendo through recharge, 1.309s authored release, measured within one frame). CAVERN.EGG: solo cavern test egg. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d7158f1f82
commit
70eea6c1a4
@@ -17,6 +17,19 @@
|
||||
// per frame from AudioHead::Execute; a fade finishing (or being reclaimed by a
|
||||
// restart) stops + rewinds the source so the next SetupPatch sees AL_INITIAL.
|
||||
//
|
||||
|
||||
// [aud-tail] (Gitea #5 verdict harness, additive): BT_AUD_TAIL=1 gates
|
||||
// timestamped play/stop/fade tracing so the audible tail past beam-off can be
|
||||
// measured against the authored releaseSec. clock() is CRT wall-clock ms and
|
||||
// matches the [aud-tail] emitter state-transition logs (emitter.cpp).
|
||||
#include <ctime>
|
||||
static bool AudTailLog()
|
||||
{
|
||||
static int s_on = -1;
|
||||
if (s_on < 0) s_on = (getenv("BT_AUD_TAIL") != 0) ? 1 : 0;
|
||||
return s_on != 0;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct ReleaseFade
|
||||
@@ -32,6 +45,10 @@ namespace
|
||||
|
||||
void FinishFade(ReleaseFade &fade)
|
||||
{
|
||||
if (AudTailLog())
|
||||
DEBUG_STREAM << "[aud-tail] t=" << (long)clock() << "ms fade-END src="
|
||||
<< fade.source << " after " << fade.elapsed << "/" << fade.duration
|
||||
<< "s\n" << std::flush;
|
||||
alSourceStop(fade.source);
|
||||
alSourceRewind(fade.source);
|
||||
alSourcef(fade.source, AL_GAIN, fade.gain0);
|
||||
@@ -57,6 +74,10 @@ namespace
|
||||
fade.duration = duration;
|
||||
fade.elapsed = 0.0f;
|
||||
fade.active = true;
|
||||
if (AudTailLog())
|
||||
DEBUG_STREAM << "[aud-tail] t=" << (long)clock() << "ms fade-START src="
|
||||
<< source << " dur=" << duration << "s gain0=" << fade.gain0
|
||||
<< "\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -315,6 +336,15 @@ void PatchLevelOfDetail::PlayNote(SourceSet sourceSet, int note)
|
||||
{
|
||||
alSourcePlay(sourceSet.sources[i]);
|
||||
{ static int s_p=0; if (getenv("BT_AUDIO_LOG") && s_p++<30) DEBUG_STREAM << "[audio] PlayNote alSourcePlay src=" << sourceSet.sources[i] << " note=" << note << "\n" << std::flush; }
|
||||
if (AudTailLog())
|
||||
{
|
||||
SAMPLEINFO zinfo = PRESET_getSampleInfo(bankID, patchID, i);
|
||||
DEBUG_STREAM << "[aud-tail] t=" << (long)clock() << "ms PLAY src="
|
||||
<< sourceSet.sources[i] << " bank=" << (int)bankID << " patch="
|
||||
<< (int)patchID << " file=" << (zinfo.file ? zinfo.file : "?")
|
||||
<< " loop=" << (int)(zinfo.loop == LoopAtWill)
|
||||
<< " rel=" << zinfo.releaseSec << "s\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
ALenum error = alGetError();
|
||||
@@ -340,6 +370,12 @@ void PatchLevelOfDetail::StopNote(SourceSet sourceSet)
|
||||
SAMPLEINFO info = PRESET_getSampleInfo(bankID,patchID,i);
|
||||
ALenum state;
|
||||
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &state);
|
||||
if (AudTailLog() && state == AL_PLAYING)
|
||||
DEBUG_STREAM << "[aud-tail] t=" << (long)clock() << "ms STOP src="
|
||||
<< sourceSet.sources[i] << " bank=" << (int)bankID << " patch="
|
||||
<< (int)patchID << " file=" << (info.file ? info.file : "?")
|
||||
<< " rel=" << info.releaseSec << "s -> "
|
||||
<< ((info.releaseSec > 0.05f) ? "fade" : "cut") << "\n" << std::flush;
|
||||
if (state == AL_PLAYING && info.releaseSec > 0.05f)
|
||||
{
|
||||
StartReleaseFade(sourceSet.sources[i], info.releaseSec);
|
||||
|
||||
@@ -133,6 +133,31 @@ static float gBTAimVpW = 0.0f; // D3D viewport (backbuffer) size --
|
||||
static float gBTAimVpH = 0.0f; // the dpl2d frame's pixel space
|
||||
static int gBTAimCamValid = 0;
|
||||
|
||||
// issue #16 (boresight parallax) diagnostics: the RENDER camera pose -- the
|
||||
// frame the crosshair is actually drawn in (screen centre of the ACTIVE view).
|
||||
// Published unconditionally by the active eye each frame; BT_PARALLAX_LOG
|
||||
// projects the PICK point back into this view to measure how far the shot
|
||||
// lands from the crosshair. A healthy boresight keeps the pick point pinned
|
||||
// at reticle (0,0) in cockpit view from max range all the way in.
|
||||
static float gBTRenderCamPos[3] = {0, 0, 0};
|
||||
static float gBTRenderCamX[3] = {1, 0, 0}; // camera right
|
||||
static float gBTRenderCamY[3] = {0, 1, 0}; // camera up
|
||||
static float gBTRenderCamZ[3] = {0, 0, 1}; // camera BACK (view dir = -Z)
|
||||
static int gBTRenderCamValid = 0;
|
||||
|
||||
void BTSetRenderCamera(const float pos[3], const float xax[3],
|
||||
const float yax[3], const float zax[3])
|
||||
{
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
gBTRenderCamPos[i] = pos[i];
|
||||
gBTRenderCamX[i] = xax[i];
|
||||
gBTRenderCamY[i] = yax[i];
|
||||
gBTRenderCamZ[i] = zax[i];
|
||||
}
|
||||
gBTRenderCamValid = 1;
|
||||
}
|
||||
|
||||
void BTSetAimCamera(const float pos[3], const float xax[3],
|
||||
const float yax[3], const float zax[3])
|
||||
{
|
||||
@@ -263,6 +288,46 @@ int BTProjectToReticle(const float world[3], float *rx, float *ry)
|
||||
return 1;
|
||||
}
|
||||
|
||||
//
|
||||
// issue #16 (boresight parallax) diag: world point -> reticle coords through
|
||||
// the RENDER camera (the view the crosshair is drawn in), NOT the aim camera
|
||||
// (projecting the pick point through the aim camera is trivially (0,0) --
|
||||
// the pick point lies ON the aim ray). eyeOut receives the render eye
|
||||
// position so the caller can log the true sight-line range. Same reticle
|
||||
// frame as BTProjectToReticle (+y down; unit = half the viewport height).
|
||||
//
|
||||
int BTProjectToRenderView(const float world[3], float *rx, float *ry,
|
||||
float eyeOut[3])
|
||||
{
|
||||
if (!gBTRenderCamValid || gBTAimP11 <= 0.0f || gBTAimP22 <= 0.0f
|
||||
|| gBTAimVpH <= 0.0f)
|
||||
{
|
||||
*rx = 0.0f; *ry = 0.0f;
|
||||
return 0;
|
||||
}
|
||||
if (eyeOut != 0)
|
||||
for (int i = 0; i < 3; ++i)
|
||||
eyeOut[i] = gBTRenderCamPos[i];
|
||||
float rel[3];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
rel[i] = world[i] - gBTRenderCamPos[i];
|
||||
const float xc = rel[0]*gBTRenderCamX[0] + rel[1]*gBTRenderCamX[1] + rel[2]*gBTRenderCamX[2];
|
||||
const float yc = rel[0]*gBTRenderCamY[0] + rel[1]*gBTRenderCamY[1] + rel[2]*gBTRenderCamY[2];
|
||||
const float zc = rel[0]*gBTRenderCamZ[0] + rel[1]*gBTRenderCamZ[1] + rel[2]*gBTRenderCamZ[2];
|
||||
const float depth = -zc; // camera looks down -Z
|
||||
if (depth < 0.1f)
|
||||
{
|
||||
*rx = (xc >= 0.0f) ? 2.0f : -2.0f;
|
||||
*ry = 0.0f;
|
||||
return 0;
|
||||
}
|
||||
const float ndcX = (xc * gBTAimP11) / depth;
|
||||
const float ndcY = (yc * gBTAimP22) / depth;
|
||||
*rx = ndcX * BTGetPresentAspect();
|
||||
*ry = -ndcY;
|
||||
return 1;
|
||||
}
|
||||
|
||||
//
|
||||
// The target HOTBOX projection (the recovered reticle Execute @4cdff7-4ce0f9
|
||||
// [T1]): project the box hugging the target's extents -- x +-4 around the
|
||||
|
||||
@@ -16,6 +16,12 @@ float gBTEyeTwist = 0.0f;
|
||||
// pitch from the pick ray -- this is the pilot's DELIBERATE pitch, re-applied
|
||||
// after the leveling so aimed shots follow the R/F (stick-Y) elevation.
|
||||
float gBTEyeElev = 0.0f;
|
||||
// issue #16 (boresight parallax): non-zero while a BORESIGHT eye (the
|
||||
// viewpoint mech's cockpit siteeyepoint, marked by SetBoresightEye) is alive
|
||||
// and publishing the aim camera. While set, the active-camera fallback
|
||||
// publish is suppressed -- the cockpit eyepoint owns the pick/sight ray in
|
||||
// BOTH views, so toggling V cannot change ballistics.
|
||||
int gBTBoresightEyeLive = 0;
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4vidrnd.h"
|
||||
@@ -5492,6 +5498,7 @@ DPLEyeRenderable::DPLEyeRenderable(
|
||||
myEntity = This_Entity;
|
||||
// myParentDCS = Parent_DCS;
|
||||
myOrientationMatrix = Offset_Matrix;
|
||||
mIsBoresightEye = false; // issue #16: set via SetBoresightEye
|
||||
myEyepointRotation = eyepoint_rotation;
|
||||
if(myEyepointRotation)
|
||||
{
|
||||
@@ -5610,6 +5617,30 @@ DPLEyeRenderable::~DPLEyeRenderable()
|
||||
//// but Phil is making a patch to allow us to check that.
|
||||
//// dpl_RemoveDCSFromDCS(myParentDCS, myDCS);
|
||||
// dpl_DeleteDCS(myDCS);
|
||||
// issue #16: a deleted boresight eye releases the aim-camera publish so
|
||||
// the active camera's fallback path takes over (spectator / teardown)
|
||||
// instead of the aim ray going permanently stale.
|
||||
if (mIsBoresightEye)
|
||||
{
|
||||
extern int gBTBoresightEyeLive;
|
||||
gBTBoresightEyeLive = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// SetBoresightEye (issue #16 boresight parallax): mark this eye as the
|
||||
// VIEWPOINT mech's cockpit eyepoint -- the owner of the aim-camera (pick/
|
||||
// sight ray) publish in BOTH views. Tracks the process-wide latch that
|
||||
// gates the active-camera fallback publish (a viewpoint without a cockpit
|
||||
// eyepoint, e.g. a spectator camera).
|
||||
//
|
||||
void
|
||||
DPLEyeRenderable::SetBoresightEye(bool is_boresight)
|
||||
{
|
||||
extern int gBTBoresightEyeLive;
|
||||
mIsBoresightEye = is_boresight;
|
||||
if (is_boresight)
|
||||
gBTBoresightEyeLive = 1;
|
||||
}
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// TestInstance for the DPLEyeRenderable
|
||||
@@ -5805,13 +5836,42 @@ void
|
||||
{
|
||||
myDevice->SetTransform(D3DTS_VIEW, &view);
|
||||
|
||||
// issue #16 diag: publish the RENDER camera pose (the frame the
|
||||
// crosshair is drawn in) for BT_PARALLAX_LOG's pick-point
|
||||
// back-projection (BTProjectToRenderView). eyeW rows are
|
||||
// right/up/BACK + translation (view = inverse(eyeW)).
|
||||
{
|
||||
extern void BTSetRenderCamera(const float pos[3], const float xax[3],
|
||||
const float yax[3], const float zax[3]);
|
||||
const float rp[3] = { eyeW._41, eyeW._42, eyeW._43 };
|
||||
const float rxx[3] = { eyeW._11, eyeW._12, eyeW._13 };
|
||||
const float ryy[3] = { eyeW._21, eyeW._22, eyeW._23 };
|
||||
const float rzz[3] = { eyeW._31, eyeW._32, eyeW._33 };
|
||||
BTSetRenderCamera(rp, rxx, ryy, rzz);
|
||||
}
|
||||
|
||||
if (dbgEyeExec < 8)
|
||||
DEBUG_STREAM << "[EYE] ACTIVE eye " << (void *)this
|
||||
<< " (mCamera match) pos.y=" << pos.y << "\n" << std::flush;
|
||||
}
|
||||
|
||||
// aim-ray feed (task #36): publish this eye's world pose --
|
||||
// the LookAtRH basis (zaxis = back, view direction = -zaxis)
|
||||
// -- for the reticle pick ray / designator projection.
|
||||
// aim-ray feed (task #36): publish this eye's world pose --
|
||||
// the LookAtRH basis (zaxis = back, view direction = -zaxis)
|
||||
// -- for the reticle pick ray / designator projection.
|
||||
//
|
||||
// OWNERSHIP (issue #16, boresight parallax): the publish is owned by
|
||||
// the BORESIGHT eye -- the viewpoint mech's cockpit siteeyepoint --
|
||||
// in BOTH views (it executes in the mech's draw traversal every
|
||||
// frame, active camera or not), so the chase view's ballistics are
|
||||
// IDENTICAL to the cockpit's and the V toggle cannot change where a
|
||||
// shot lands. Only when no boresight eye is alive (spectator /
|
||||
// teardown) does the ACTIVE camera publish, with the legacy entity
|
||||
// anchor as the origin.
|
||||
{
|
||||
extern int gBTBoresightEyeLive;
|
||||
const bool aimOwner = mIsBoresightEye
|
||||
|| (!gBTBoresightEyeLive && myRenderer->mCamera == this);
|
||||
if (aimOwner)
|
||||
{
|
||||
extern void BTSetAimCamera(const float pos[3], const float xax[3],
|
||||
const float yax[3], const float zax[3]);
|
||||
@@ -5865,16 +5925,31 @@ void
|
||||
D3DXVec3Normalize(&xax, &xax);
|
||||
D3DXVec3Cross(&yax, &zax, &xax);
|
||||
}
|
||||
// ORIGIN: the guns fire from the MECH's torso, not the render
|
||||
// eye. In CHASE view the eye sits behind+above the mech, so a
|
||||
// ray from `pos` would start over the shoulder (and, being level,
|
||||
// fly above a ground target). Anchor the boresight at the mech's
|
||||
// XZ + a torso/gun height so the pick works in BOTH views (chase
|
||||
// is the default; cockpit toggles with V). In cockpit view this
|
||||
// is ~the eyepoint anyway.
|
||||
float p[3] = { pos.x, pos.y, pos.z };
|
||||
if (myEntity != 0)
|
||||
// ORIGIN (issue #16, boresight parallax): the pick/sight ray
|
||||
// starts at the TRUE EYEPOINT -- this eye's own world
|
||||
// translation (eyeW row 4, the same origin the crosshair's
|
||||
// view projects from). The binary's sight math runs in the
|
||||
// eyepoint-segment frame: HudSimulation computes the
|
||||
// designator HotBoxVector by transforming the target position
|
||||
// with inverse(eyeSegToWorld @00424da8) o inverse(
|
||||
// R(EyepointRotation)) (@004b7830, part_013.c:5688-5702), the
|
||||
// VIEW is inverse(the live jointtorso->jointeye->siteeyepoint
|
||||
// chain) (FUN_004c22c4 @part_013.c:11742), and the fired beam
|
||||
// CONVERGES from the muzzle segment to the picked point
|
||||
// (@004bace8:7742-7752) -- so pick ray and sight line share
|
||||
// the eye origin. The old `mech.y + 5.0` anchor (a port
|
||||
// improvisation) sat BELOW the sight line: a constant
|
||||
// vertical offset whose angular error grew as range closed --
|
||||
// shots landing LOW under the target up close (Gitea #16).
|
||||
// Measured pre-fix: the pick point slid down the reticle
|
||||
// ry=+0.07 @ 50u -> +0.32 @ 10u -> +1.5 @ 2.7u.
|
||||
float p[3] = { eyeW._41, eyeW._42, eyeW._43 };
|
||||
if (!mIsBoresightEye && myEntity != 0)
|
||||
{
|
||||
// no cockpit eyepoint alive (spectator/camera director):
|
||||
// legacy entity anchor -- this camera sits behind/above,
|
||||
// so a ray from its own origin would start over the
|
||||
// shoulder.
|
||||
p[0] = myEntity->localToWorld(3,0);
|
||||
p[1] = myEntity->localToWorld(3,1) + 5.0f; // feet -> torso/gun height
|
||||
p[2] = myEntity->localToWorld(3,2);
|
||||
|
||||
@@ -1716,7 +1716,17 @@ public:
|
||||
virtual void
|
||||
Execute();
|
||||
|
||||
// issue #16 (boresight parallax): mark the VIEWPOINT mech's cockpit
|
||||
// eyepoint (siteeyepoint) as the BORESIGHT eye -- it publishes the aim
|
||||
// camera (pick/sight ray) every frame in BOTH views, so the V toggle
|
||||
// cannot change ballistics. Defined in L4VIDRND.cpp (tracks the
|
||||
// gBTBoresightEyeLive fallback latch).
|
||||
void
|
||||
SetBoresightEye(bool is_boresight);
|
||||
|
||||
protected:
|
||||
bool mIsBoresightEye; // issue #16: this eye owns the aim-camera publish
|
||||
|
||||
dpl_DCS
|
||||
*myDCS,
|
||||
*myParentDCS;
|
||||
|
||||
Reference in New Issue
Block a user