#113 follow-on: missiles fly their AUTHORED agility -- the turn-rate cap is wired (model+0x40 deg/s, the field the #113 audit identified: pi/180-scaled into the binary thruster @004be7c4[0x3a], _DAT_004be8b8 byte-verified [T1]). The thrust table now reads tail[0] (it always fetched and dropped it); mislanch passes the rate via a salvo-scoped latch (BTPushSetTurnRate -- deliberately NOT a BTPushProjectile signature change: six call sites + three externs under /FORCE is the silent-AV churn class), master AND replicant mirror so peers see the same arcs; the steering keeps the field-verified exponential blend [T3] and CAPS this frame's heading change at turnDps*dt -- below the cap the old behavior is bit-identical, above it the round slews at the authored rate. BT_TURN_AUTH=0 restores uncapped; [projturn] CAP receipts under BT_PROJ_LOG. A/B bench (2-node standoff, scratchpad/night17/turn_bench.sh): AUTH runs 43/69 CAP receipts, ALL at 60dps (the blend wants <=~220 so 360 rounds are authentically unconstrained; LRMs slew ~3.6x slower during corrections -- the era 'sluggish 60 deg/sec' look); OFF runs ZERO CAP lines (negative control); impacts 25/9 vs 7/4 -- no accuracy collapse. Data bonus: mad2's 'LRM15'-labeled racks resolve 360dps (Narc-profile rounds, the guide's authored-mislabeling lore confirmed by receipts); madcat's are true 60dps LRMs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
This commit is contained in:
co-authored by
Claude Fable 5
parent
569b8d31b5
commit
cec64583db
@@ -875,6 +875,8 @@ struct BTProjectile {
|
||||
Scalar aimOffsetY; // vertical aim offset vs the target's origin (live re-lead)
|
||||
Scalar damage; // <= 0 -> VISUAL-ONLY round (replicant-side salvo mirror)
|
||||
int guided; // 1 = missile (seeker loft + steering); 0 = ballistic (straight)
|
||||
float turnDps; // (#113) authored turn-rate cap, deg/s (model+0x40 [T1]);
|
||||
// 0 = uncapped (legacy tuned steering only)
|
||||
Entity *shooter; // the firing mech (messmgr explosion bundling at impact)
|
||||
int damageType; // the FIRING WEAPON's authentic damageData.damageType
|
||||
// (issue: was hardcoded Explosive at impact, so
|
||||
@@ -950,12 +952,19 @@ void BTProjectilesClearAll(void)
|
||||
// and cached. Reading the shipped RES directly mirrors the binary's stream
|
||||
// (the engine-side in-memory path has no raw-record accessor in the port).
|
||||
//###########################################################################
|
||||
int BTMissileThrustOf(int model_id, float *out_burn, float *out_accel)
|
||||
int BTMissileThrustOf(int model_id, float *out_burn, float *out_accel,
|
||||
float *out_turn_dps)
|
||||
{
|
||||
enum { kMax = 32 };
|
||||
static int s_loaded = 0, s_n = 0;
|
||||
static int s_id[kMax];
|
||||
static float s_burn[kMax], s_accel[kMax];
|
||||
// (#113 audit, 2026-08-14) model+0x40 is the AUTHORED TURN RATE in deg/s
|
||||
// (60 lrm / 360 strk+nrk) -- the Missile ctor @004bf5b4 stack-builds the
|
||||
// thruster record from the model and @004be7c4 scales this field by
|
||||
// _DAT_004be8b8 = pi/180 into thruster[0x3a] rad/s [T1 raw bytes]. The
|
||||
// tail read below always fetched it as tail[0] ("rot") and dropped it.
|
||||
static float s_turn[kMax];
|
||||
if (!s_loaded)
|
||||
{
|
||||
s_loaded = 1;
|
||||
@@ -987,6 +996,7 @@ int BTMissileThrustOf(int model_id, float *out_burn, float *out_accel)
|
||||
s_id[s_n] = rh[0];
|
||||
s_burn[s_n] = tail[1];
|
||||
s_accel[s_n] = tail[2];
|
||||
s_turn[s_n] = tail[0]; // authored turn rate, deg/s
|
||||
++s_n;
|
||||
}
|
||||
// pass 2: the AmmoBin's ammoModelFile references the TYPE-1
|
||||
@@ -1017,6 +1027,7 @@ int BTMissileThrustOf(int model_id, float *out_burn, float *out_accel)
|
||||
s_id[s_n] = rh[0]; // the LIST id aliases the model
|
||||
s_burn[s_n] = s_burn[k];
|
||||
s_accel[s_n] = s_accel[k];
|
||||
s_turn[s_n] = s_turn[k];
|
||||
++s_n;
|
||||
m = cnt + 1; // one alias per list
|
||||
break;
|
||||
@@ -1035,12 +1046,24 @@ int BTMissileThrustOf(int model_id, float *out_burn, float *out_accel)
|
||||
if (s_id[i] == model_id)
|
||||
{
|
||||
*out_burn = s_burn[i]; *out_accel = s_accel[i];
|
||||
if (out_turn_dps != 0)
|
||||
*out_turn_dps = s_turn[i];
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// (#113) Salvo-scoped turn-rate hand-off: mislanch sets the authored rate
|
||||
// just before pushing a salvo (master AND replicant mirror), BTPushProjectile
|
||||
// consumes it for guided rounds. A latch instead of a BTPushProjectile
|
||||
// signature change ON PURPOSE: six call sites + three extern decls under
|
||||
// /FORCE is exactly the silent-AV churn the conventions warn about. Only
|
||||
// mislanch pushes guided rounds in production (the BT_JUMP test hook's
|
||||
// guided push inherits whatever was last set -- test-only, harmless).
|
||||
static float gPushTurnDps = 0.0f; // 0 = uncapped (legacy steering)
|
||||
void BTPushSetTurnRate(float dps) { gPushTurnDps = dps; }
|
||||
|
||||
extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float);
|
||||
|
||||
//###########################################################################
|
||||
@@ -1694,6 +1717,7 @@ void
|
||||
p.damage = damage;
|
||||
p.damageType = damage_type; // weapon's authentic type (issue fix)
|
||||
p.guided = guided; // autocannon shells fly straight (no seeker in the binary's plain Projectile)
|
||||
p.turnDps = (guided != 0 && gPushTurnDps > 0.0f) ? gPushTurnDps : 0.0f; // (#113) authored agility
|
||||
p.shooter = (Entity *)shooter;
|
||||
p.weaponSubsys = weapon_subsys;
|
||||
p.splashBurst = splash_burst; // >0 only on a salvo-lead round
|
||||
@@ -1825,7 +1849,8 @@ static void
|
||||
if (range > 0.001f)
|
||||
{
|
||||
Scalar inv = 1.0f / range;
|
||||
Scalar cx = p.vel.x / p.speed, cy = p.vel.y / p.speed, cz = p.vel.z / p.speed;
|
||||
Scalar ox = p.vel.x / p.speed, oy = p.vel.y / p.speed, oz = p.vel.z / p.speed;
|
||||
Scalar cx = ox, cy = oy, cz = oz;
|
||||
// MissileTurnGain (4.0, _DAT_004bf5a4) far out; DOUBLED inside
|
||||
// the no-loft radius so the dive converges onto the contact
|
||||
// sphere (the port shape of the binary's squared-error terminal
|
||||
@@ -1835,7 +1860,53 @@ static void
|
||||
Scalar cl = (Scalar)sqrtf(cx*cx + cy*cy + cz*cz);
|
||||
if (cl > 0.001f)
|
||||
{
|
||||
p.vel.x = cx/cl*p.speed; p.vel.y = cy/cl*p.speed; p.vel.z = cz/cl*p.speed;
|
||||
cx /= cl; cy /= cl; cz /= cl;
|
||||
// (#113, 2026-08-14) THE AUTHORED TURN-RATE CAP. The binary
|
||||
// flies each round at its authored agility: model+0x40 deg/s
|
||||
// (lrm 60, strk/nrk 360), scaled pi/180 into the thruster
|
||||
// (@004bf5b4 stack record -> @004be7c4 [0x3a], _DAT_004be8b8
|
||||
// byte-verified) [T1]. The exponential blend above is the
|
||||
// field-verified port shape [T3]; the CAP is where the
|
||||
// per-round personality lives -- limit this frame's heading
|
||||
// change to turnDps*dt. Below the cap the old behavior is
|
||||
// bit-identical; above it the round slews at the authored
|
||||
// rate (the LRM's slow arc-over, the Streak/Narc "angry
|
||||
// bees"). BT_TURN_AUTH=0 restores uncapped (A/B lever).
|
||||
{
|
||||
static int s_turnAuth = -1;
|
||||
if (s_turnAuth < 0)
|
||||
{
|
||||
const char *tv = getenv("BT_TURN_AUTH");
|
||||
s_turnAuth = (tv != 0 && *tv == '0') ? 0 : 1;
|
||||
}
|
||||
if (s_turnAuth && p.turnDps > 0.0f)
|
||||
{
|
||||
Scalar dot = ox*cx + oy*cy + oz*cz;
|
||||
if (dot > 1.0f) dot = 1.0f;
|
||||
if (dot < -1.0f) dot = -1.0f;
|
||||
Scalar ang = (Scalar)acosf((float)dot);
|
||||
Scalar lim = p.turnDps * 0.01745329f * dt;
|
||||
if (ang > lim && ang > 1e-4f)
|
||||
{
|
||||
if (getenv("BT_PROJ_LOG"))
|
||||
{
|
||||
static int s_tc = 0;
|
||||
if ((s_tc++ % 61) == 0) // 61: coprime sampler
|
||||
DEBUG_STREAM << "[projturn] CAP "
|
||||
<< p.turnDps << " dps (wanted "
|
||||
<< (float)(ang / dt * 57.29578f)
|
||||
<< ") dmg=" << p.damage << "\n" << std::flush;
|
||||
}
|
||||
Scalar t = lim / ang; // partial slew (small
|
||||
cx = ox + (cx - ox) * t; // angles: lerp+renorm
|
||||
cy = oy + (cy - oy) * t; // ~= slerp)
|
||||
cz = oz + (cz - oz) * t;
|
||||
cl = (Scalar)sqrtf(cx*cx + cy*cy + cz*cz);
|
||||
if (cl > 0.001f) { cx /= cl; cy /= cl; cz /= cl; }
|
||||
}
|
||||
}
|
||||
}
|
||||
p.vel.x = cx*p.speed; p.vel.y = cy*p.speed; p.vel.z = cz*p.speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+536
-530
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user