#84: missiles ACCELERATE like the binary -- the pool flew at constant
rack-eject speed; the authored MissileThruster was never applied The binary Missile hosts a MissileThruster subsystem: authored acceleration for BurnTime seconds after the MuzzleVelocity eject. Authored values decoded from BTL4.RES (type-15 missile model records, reached from the AmmoBin's ammoModelFile via the type-1 MODELLIST indirection): SRM 2.5s @ 600 u/s^2, LRM 10s @ 300 (climb 50), Streak 3s @ 300 (turn 360deg/s), NARC 10s @ 300; splash 30 all (port constant was already right); authored drag ~0.001 = negligible. The port pool flew every round at CONSTANT |MuzzleVelocity| -- SRMs 100 u/s, LRMs 30 u/s (10x slower than authored) -- the entirety of the night-7 "missiles are very slow" report, and the driver of the "explosion before the missiles arrive" perception (the salvo-lead detonates while the slow spread rounds straggle in). Fix: BTMissileThrustOf (mech4.cpp) lazily parses + caches the RES thruster table (ModelList ids aliased to their type-15 member's burn/accel); both launch paths (master FireWeapon + the replicant salvo mirror -- peers see the same speed) resolve through the launcher's bin and pass burn/accel into the pool; the advance integrates speed += accel*dt while burn remains, heading preserved. Ballistic rounds (autocannon/gauss) pass 0/0 -- unchanged. Impact log now prints v=/burnLeft= evidence. Verified live (solo, BT_SPAWN_ENEMY + BT_AF_MISSILE): thrust table 8 entries cached; Black Hawk Streak resolve burn=3 accel=300; impacts at v=277 u/s (was a constant 100). Fun authenticity note: the Black Hawk's "SRM6" fires strk (STREAK) ammo per its authored bin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Zh7PTkFy4KwTzVighLR9J
This commit is contained in:
co-authored by
Claude Fable 5
parent
ac7c46b87a
commit
4704ab3d41
@@ -843,6 +843,8 @@ struct BTProjectile {
|
||||
// (matches the ONE arcade cluster missile). 0 on every
|
||||
// other round (a straight tracer / non-lead volley round)
|
||||
// so splash is NOT re-applied + floored-at-1 per round.
|
||||
Scalar accel; // #84: authored ThrusterAcceleration (u/s^2; 0 = unpowered round)
|
||||
Scalar burnLeft; // #84: authored BurnTime remaining (s); accel applies while > 0
|
||||
int active;
|
||||
};
|
||||
static BTProjectile gProjectiles[64];
|
||||
@@ -882,6 +884,105 @@ void BTProjectilesClearAll(void)
|
||||
gProjectiles[i].active = 0;
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// #84: the missile-model thruster table (BTL4.RES type-15 "GameModel" 0x54
|
||||
// records; the same records the binary's Missile ctor copies +0x40..0x4C
|
||||
// from). The launcher's AmmoBin carries the model's RESOURCE ID
|
||||
// (ammoModelFile); resolve burn/accel from the RES directory, parsed once
|
||||
// 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)
|
||||
{
|
||||
enum { kMax = 32 };
|
||||
static int s_loaded = 0, s_n = 0;
|
||||
static int s_id[kMax];
|
||||
static float s_burn[kMax], s_accel[kMax];
|
||||
if (!s_loaded)
|
||||
{
|
||||
s_loaded = 1;
|
||||
FILE *f = fopen("BTL4.RES", "rb"); // cwd == content\ at runtime
|
||||
if (f != 0)
|
||||
{
|
||||
int hdr[3];
|
||||
if (fread(hdr, 4, 3, f) == 3 && hdr[2] > 0 && hdr[2] < 65536)
|
||||
{
|
||||
int maxID = hdr[2];
|
||||
unsigned *dir = (unsigned *)malloc(maxID * 4);
|
||||
if (dir != 0 && (int)fread(dir, 4, maxID, f) == maxID)
|
||||
{
|
||||
// pass 1: the type-15 GameModel 0x54 records (the missile models)
|
||||
for (int i = 0; i < maxID && s_n < kMax; ++i)
|
||||
{
|
||||
if (dir[i] == 0) continue;
|
||||
int rh[2]; // rid, rtype
|
||||
unsigned meta[4]; // prio, flags, off, len
|
||||
if (fseek(f, dir[i], SEEK_SET) != 0) continue;
|
||||
if (fread(rh, 4, 2, f) != 2) continue;
|
||||
if (rh[1] != 15) continue; // GameModel records only
|
||||
if (fseek(f, dir[i] + 40, SEEK_SET) != 0) continue;
|
||||
if (fread(meta, 4, 4, f) != 4) continue;
|
||||
if (meta[3] != 0x54) continue; // the missile model record
|
||||
float tail[5]; // +0x40: rot,burn,accel,climb,splash
|
||||
if (fseek(f, meta[2] + 0x40, SEEK_SET) != 0) continue;
|
||||
if (fread(tail, 4, 5, f) != 5) continue;
|
||||
s_id[s_n] = rh[0];
|
||||
s_burn[s_n] = tail[1];
|
||||
s_accel[s_n] = tail[2];
|
||||
++s_n;
|
||||
}
|
||||
// pass 2: the AmmoBin's ammoModelFile references the TYPE-1
|
||||
// MODELLIST ('srm'/'lrm'/'strk'/'nrk'), which redirects to the
|
||||
// model family (type 10/3/15/18 members). Alias each list id
|
||||
// to its type-15 member's thruster values.
|
||||
const int nModels = s_n;
|
||||
for (int i = 0; i < maxID && s_n < kMax; ++i)
|
||||
{
|
||||
if (dir[i] == 0) continue;
|
||||
int rh[2];
|
||||
unsigned meta[4];
|
||||
if (fseek(f, dir[i], SEEK_SET) != 0) continue;
|
||||
if (fread(rh, 4, 2, f) != 2) continue;
|
||||
if (rh[1] != 1) continue; // ModelList only
|
||||
if (fseek(f, dir[i] + 40, SEEK_SET) != 0) continue;
|
||||
if (fread(meta, 4, 4, f) != 4) continue;
|
||||
int lst[9]; // count + up to 8 member ids
|
||||
if (meta[3] < 8 || meta[3] > 36) continue;
|
||||
if (fseek(f, meta[2], SEEK_SET) != 0) continue;
|
||||
int cnt = (int)(meta[3] / 4) - 1;
|
||||
if (fread(lst, 4, cnt + 1, f) != (size_t)(cnt + 1)) continue;
|
||||
if (lst[0] < 1 || lst[0] < cnt) cnt = lst[0] < cnt ? lst[0] : cnt;
|
||||
for (int m = 1; m <= cnt; ++m)
|
||||
for (int k = 0; k < nModels; ++k)
|
||||
if (s_id[k] == lst[m])
|
||||
{
|
||||
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_n;
|
||||
m = cnt + 1; // one alias per list
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dir != 0) free(dir);
|
||||
}
|
||||
fclose(f);
|
||||
DEBUG_STREAM << "[projectile] thrust table: " << s_n
|
||||
<< " missile model(s) cached from BTL4.RES\n" << std::flush;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < s_n; ++i)
|
||||
{
|
||||
if (s_id[i] == model_id)
|
||||
{
|
||||
*out_burn = s_burn[i]; *out_accel = s_accel[i];
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float);
|
||||
|
||||
//###########################################################################
|
||||
@@ -1207,7 +1308,8 @@ void
|
||||
void
|
||||
BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, const Point3D &targetPos,
|
||||
Scalar speed, Scalar damage, const Vector3D *launch_velocity, int guided,
|
||||
int weapon_subsys, int splash_burst, int muzzle_seg, int damage_type)
|
||||
int weapon_subsys, int splash_burst, int muzzle_seg, int damage_type,
|
||||
Scalar thrust_accel, Scalar thrust_burn)
|
||||
{
|
||||
// MUZZLE (muzzle wave, 2026-07-12): the passed muzzle is now the weapon's
|
||||
// AUTHENTIC mount segment (GetMuzzlePoint reads the real segmentIndex --
|
||||
@@ -1336,6 +1438,8 @@ void
|
||||
p.shooter = (Entity *)shooter;
|
||||
p.weaponSubsys = weapon_subsys;
|
||||
p.splashBurst = splash_burst; // >0 only on a salvo-lead round
|
||||
p.accel = (thrust_accel > 0.0f) ? thrust_accel : 0.0f; // #84 thruster
|
||||
p.burnLeft = (thrust_burn > 0.0f) ? thrust_burn : 0.0f;
|
||||
|
||||
// RACK-TUBE SPREAD [T3, physically grounded]: the binary's Missile
|
||||
// entities each launch from their own rack tube (per-tube authored
|
||||
@@ -1435,6 +1539,29 @@ static void
|
||||
}
|
||||
}
|
||||
|
||||
// #84 THRUSTER BURN: the binary Missile hosts a MissileThruster whose
|
||||
// authored acceleration drives the round while BurnTime remains
|
||||
// (MISTHRST: acceleration = (0,0,-thrusterAccel) in the missile frame,
|
||||
// integrated by Missile::MoveAndCollide @4bef78). The pool flew at
|
||||
// CONSTANT |MuzzleVelocity| -- the rack-eject speed (SRM 100, LRM 30
|
||||
// u/s) -- which is why field missiles crawled (Oracle/Rajel, #84).
|
||||
// Authored (BTL4.RES type-15 missile models, +0x44/+0x48): SRM
|
||||
// burn 2.5s @ 600 u/s^2, LRM 10s @ 300, Streak 3s @ 300. Drag is
|
||||
// authored ~0.001 (negligible at combat ranges) -- integrate accel
|
||||
// along the current heading, un-damped, range-capped as before. [T1
|
||||
// values / T2 integration]
|
||||
if (p.burnLeft > 0.0f && p.accel > 0.0f)
|
||||
{
|
||||
Scalar burn_dt = (dt < p.burnLeft) ? dt : p.burnLeft;
|
||||
p.burnLeft -= burn_dt;
|
||||
Scalar ns = p.speed + p.accel * burn_dt;
|
||||
if (p.speed > 0.01f)
|
||||
{
|
||||
Scalar k = ns / p.speed;
|
||||
p.vel.x *= k; p.vel.y *= k; p.vel.z *= k;
|
||||
}
|
||||
p.speed = ns;
|
||||
}
|
||||
p.pos.x += p.vel.x*dt; p.pos.y += p.vel.y*dt; p.pos.z += p.vel.z*dt;
|
||||
p.traveled += p.speed * dt;
|
||||
|
||||
@@ -1590,6 +1717,7 @@ static void
|
||||
|
||||
DEBUG_STREAM << "[projectile] IMPACT damage=" << p.damage
|
||||
<< " subsys=" << p.weaponSubsys
|
||||
<< " v=" << p.speed << " burnLeft=" << p.burnLeft // #84 thrust evidence
|
||||
<< (mgr ? " (msgmgr bundled)" : " (direct)")
|
||||
<< " (zone cyl-resolved)\n" << std::flush;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user