#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
@@ -359,6 +359,23 @@ Band effects: `MechDeathHandler::Tick` fires the CURRENT band descriptor on any
|
||||
(the binary's changed-flag semantics, workflow-verified) — a damaged mech under fire smokes/
|
||||
burns per hit; the earlier crossing-only gate was over-tight (the metronome was SHKWAVE).
|
||||
|
||||
## Missile THRUST -- FIXED (2026-07-31, issue #84) [T1 values / T2 verified]
|
||||
The binary Missile hosts a **MissileThruster** subsystem: the round leaves the rack at the
|
||||
launcher's authored MuzzleVelocity (SRM eject 100 u/s flat; LRM 30 u/s up-tilted) and then
|
||||
ACCELERATES while BurnTime remains. Authored values (BTL4.RES type-15 missile model records
|
||||
+0x44/+0x48, reached from the AmmoBin's `ammoModelFile` through the type-1 MODELLIST
|
||||
indirection): **SRM 2.5s @ 600 u/s² (turn 120°/s) · LRM 10s @ 300 (turn 60°/s, climb 50) ·
|
||||
Streak 3s @ 300 (turn 360°/s) · NARC 10s @ 300**; SplashRadius 30 for all (the port's constant
|
||||
was right); authored drag ~0.001 = negligible. The port pool flew at CONSTANT eject speed --
|
||||
LRMs 10× slower than authored -- which was the whole of the field "missiles are slow" report,
|
||||
and the "explosion before the missiles arrive" perception (the salvo-lead detonates while the
|
||||
slow spread rounds straggle). Fixed: `BTMissileThrustOf` (mech4.cpp) lazily caches the RES
|
||||
thruster table (ModelList ids aliased to their type-15 member); both launch paths (master fire
|
||||
+ the replicant salvo mirror) pass burn/accel into the pool, whose advance integrates
|
||||
`speed += accel·dt` while burning. Verified live: Streak impacts at 277 u/s (was 100).
|
||||
Note: the Black Hawk's "SRM6" fires **strk** (Streak) ammo per its bin -- the ammo model, not
|
||||
the launcher name, decides the flight profile.
|
||||
|
||||
## Ballistic damage type -- FIXED (2026-07-23, issue #27) [T2]
|
||||
Playtest matchlog forensics (2,591 applied-damage events over 2 rounds): the damageType
|
||||
histogram had Collision/Explosive/Laser/Energy but **Ballistic (type 1) NEVER appeared**,
|
||||
|
||||
@@ -631,3 +631,14 @@ Subsystem *CreateAmmoBinSubsystem(Mech *owner, int id, void *seg)
|
||||
return (Subsystem *) new (Memory::Allocate(0x22c))
|
||||
AmmoBin(owner, id, (AmmoBin::SubsystemResource *)seg);
|
||||
}
|
||||
|
||||
|
||||
//###########################################################################
|
||||
// #84 bridge -- the launcher resolves its round's MODEL RESOURCE ID through
|
||||
// its connected bin (the binary Missile ctor receives the same id via the
|
||||
// launch descriptor). Complete-AmmoBin TU accessor.
|
||||
//###########################################################################
|
||||
int BTAmmoBinModelFile(void *bin)
|
||||
{
|
||||
return (bin != 0) ? ((AmmoBin *)bin)->AmmoModelFileID() : -1;
|
||||
}
|
||||
|
||||
@@ -260,6 +260,12 @@
|
||||
// member. The 0x54 ammoAlarm (was an 8-byte HeatAlarm) lands ammoModelFile
|
||||
// at 0x1E8 -- exact binary layout, locked by AmmoBinLayoutCheck.
|
||||
int ammoModelFile; // @0x1E8 (word 0x7A) round model index
|
||||
public:
|
||||
// #84: the round's model RESOURCE ID (the type-15 record carrying the
|
||||
// MissileThruster tail the binary Missile ctor copies) -- read by the
|
||||
// launcher's fire path via the BTAmmoBinModelFile bridge.
|
||||
int AmmoModelFileID() const { return ammoModelFile; }
|
||||
protected:
|
||||
int explosionModelFile; // @0x1EC (word 0x7B) cook-off explosion model index
|
||||
|
||||
// @0x1F0..0x21C (words 0x7C..0x87): the cook-off DAMAGE record -- a real
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -74,7 +74,14 @@ extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target,
|
||||
const Point3D &targetPos, Scalar speed, Scalar damage,
|
||||
const Vector3D *launch_velocity = 0, int guided = 1,
|
||||
int weapon_subsys = -1, int splash_burst = 0, int muzzle_seg = -1,
|
||||
int damage_type = 2 /*ExplosiveDamageType default*/);
|
||||
int damage_type = 2 /*ExplosiveDamageType default*/,
|
||||
Scalar thrust_accel = 0.0f, Scalar thrust_burn = 0.0f /*#84 thruster*/);
|
||||
// #84: resolve this launcher's authored thruster (burn s, accel u/s^2) through
|
||||
// its connected bin's round-model resource. 0/0 when unresolvable (flies at
|
||||
// the old constant eject speed -- never worse than before).
|
||||
extern void *BTWeaponAmmoBin(void *weapon); // projweap.cpp
|
||||
extern int BTAmmoBinModelFile(void *bin); // ammobin.cpp
|
||||
extern int BTMissileThrustOf(int model_id, float *out_burn, float *out_accel); // mech4.cpp
|
||||
|
||||
//###########################################################################
|
||||
// Port-side salvo replication state (missile-visibility wave).
|
||||
@@ -305,6 +312,18 @@ void MissileLauncher::FireWeapon()
|
||||
// MuzzleVelocity VECTOR rides along (missile-arc wave): the round leaves the rack
|
||||
// on the authored up-tilt, then the seeker loft + steering arc it onto the target.
|
||||
int nmiss = (missileCount > 0 && missileCount < 40) ? missileCount : 1;
|
||||
float tBurn = 0.0f, tAccel = 0.0f; // #84 authored thruster
|
||||
{
|
||||
void *bin = BTWeaponAmmoBin(this);
|
||||
int mid = (bin != 0) ? BTAmmoBinModelFile(bin) : -1;
|
||||
int hit = 0;
|
||||
if (mid >= 0)
|
||||
hit = BTMissileThrustOf(mid, &tBurn, &tAccel);
|
||||
if (getenv("BT_PROJ_LOG"))
|
||||
DEBUG_STREAM << "[projectile] thrust resolve: bin=" << bin
|
||||
<< " modelID=" << mid << " hit=" << hit
|
||||
<< " burn=" << tBurn << " accel=" << tAccel << "\n" << std::flush;
|
||||
}
|
||||
for (int i = 0; i < nmiss; ++i)
|
||||
// DAMAGE + SPLASH ONCE PER SALVO (task #62 fix): the arcade fires ONE
|
||||
// cluster Missile per trigger, and its zone damage is applied EXACTLY ONCE
|
||||
@@ -320,7 +339,8 @@ void MissileLauncher::FireWeapon()
|
||||
subsystemID /*messmgr explosion bundling at impact (task #7)*/,
|
||||
(i == 0) ? nmiss : 0 /*salvo-lead: cluster splash baseBurst*/,
|
||||
GetSegmentIndex() /*task #67: launch through the MOUNT frame (torso twist)*/,
|
||||
(int)damageData.damageType /*missile = Explosive (authentic)*/);
|
||||
(int)damageData.damageType /*missile = Explosive (authentic)*/,
|
||||
tAccel, tBurn /*#84: the round ACCELERATES like the binary Missile*/);
|
||||
|
||||
// Salvo replication (missile-visibility wave): bump the fire counter + stamp
|
||||
// the aim point; the extended update record carries both to peer nodes.
|
||||
@@ -386,11 +406,19 @@ void
|
||||
int n = rec->salvoRounds;
|
||||
if (n < 1 || n > 40)
|
||||
n = (missileCount > 0 && missileCount < 40) ? missileCount : 1;
|
||||
float tBurn = 0.0f, tAccel = 0.0f; // #84: mirror the master's
|
||||
{ // thruster so peer missiles
|
||||
void *bin = BTWeaponAmmoBin(this); // fly at the same speed
|
||||
int mid = (bin != 0) ? BTAmmoBinModelFile(bin) : -1;
|
||||
if (mid >= 0)
|
||||
BTMissileThrustOf(mid, &tBurn, &tAccel);
|
||||
}
|
||||
for (int i = 0; i < n; ++i)
|
||||
BTPushProjectile(mz, owner, 0 /*no entity: aim point only*/,
|
||||
rec->salvoTarget, spd, 0.0f /*VISUAL*/, &launchVelocity, 1,
|
||||
subsystemID /*per-round detonation resolve on this node*/,
|
||||
0, GetSegmentIndex() /*task #67 mount frame*/);
|
||||
0, GetSegmentIndex() /*task #67 mount frame*/,
|
||||
2 /*Explosive*/, tAccel, tBurn);
|
||||
if (getenv("BT_PROJ_LOG"))
|
||||
DEBUG_STREAM << "[projectile] REPLICANT salvo x" << n
|
||||
<< " at(" << rec->salvoTarget.x << "," << rec->salvoTarget.y
|
||||
|
||||
@@ -98,7 +98,8 @@ extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target,
|
||||
const Point3D &targetPos, Scalar speed, Scalar damage,
|
||||
const Vector3D *launch_velocity = 0, int guided = 1,
|
||||
int weapon_subsys = -1, int splash_burst = 0, int muzzle_seg = -1,
|
||||
int damage_type = 2 /*ExplosiveDamageType default*/); // AC: no cluster splash (default 0)
|
||||
int damage_type = 2 /*ExplosiveDamageType default*/,
|
||||
Scalar thrust_accel = 0.0f, Scalar thrust_burn = 0.0f); // AC: unpowered (defaults)
|
||||
|
||||
|
||||
//#############################################################################
|
||||
|
||||
Reference in New Issue
Block a user