Model record: the authoritative 200-byte overlay -- struct skew fixed, floor-25 era retired (task #4)
Two size errors (animationPrefix char[12] vs 4 bytes; a FICTITIOUS 64-byte skeletonName block -- the record has no skl name) skewed every member after word 0x10 by +2/+15 words and pushed the fieldXX block past the record end. Full writer map recovered (FUN_004a2da8 + Mover FUN_004238bc + Reticle FUN_00435ac8 chains) + runtime reader map (ctor pass @0x4a24d1-0x4a2968, capstone-verified). Struct rewritten as the exact overlay (static_assert- locked); all Pass-2 consumers renamed to real fields; retired: ctor + replicant + master-gait floor-25 hacks, the throttle guard, the fictitious classID stamp. Verified: accel=30 superStop=10 throttleAdj=1 (was ~1/OOB/0), deadbands byte-identical via named fields, full walk->run lifecycle clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
001ca16af9
commit
63206d7f19
+44
-42
@@ -1221,10 +1221,10 @@ Mech::Mech(
|
||||
ModelResource *model = (ModelResource *)modelResourceDesc->resourceAddress;
|
||||
Check_Pointer(model);
|
||||
|
||||
Wword(0x68) = model->base + 0xa8; // FUN_00408440(this+0x68,...)
|
||||
Wword(0x130) = model->field74;
|
||||
Wword(0x148) = model->field78;
|
||||
Wword(0x149) = model->field7c;
|
||||
Wword(0x68) = model->cameraOffset; // FUN_00408440(this+0x1a0, rec+0xA8)
|
||||
Wword(0x130) = model->deathEffectResourceID; // rec+0x74 -> mech+0x4c0
|
||||
Wword(0x148) = model->deathSplashDamage; // rec+0x78 -> mech+0x520
|
||||
Wword(0x149) = model->deathSplashRadius; // rec+0x7C -> mech+0x524
|
||||
Wword(0x195) = Wword(0x196) = 0;
|
||||
Wword(0xfd) = 0;
|
||||
masterAlarm.SetLevel(0); // FUN_0041bbd8(this+0xe7,0)
|
||||
@@ -1237,55 +1237,54 @@ Mech::Mech(
|
||||
// gimp/ground copies (ResetToInitialState restores 0x344 from 0x5b8 -- raw
|
||||
// part_012.c:9441); +0x48 -> airborneCycleRate; +0x4c -> forwardThrottleScale
|
||||
// (the ctor's "=1.0, writer not in decomp" note is resolved: THIS is the writer).
|
||||
forwardCycleRate = model->maxAcceleration; // @0x344 (this[0xd1] = +0x44)
|
||||
// FLOOR all cycle slew rates (the record field decodes to 1.0 -- see the
|
||||
// maxAcceleration note): an unfloored gimpCycleRate made REVERSE accelerate
|
||||
// at 1 u/s^2 (12+ seconds to back up -- the user's '10-20s to change
|
||||
// directions'). Same floor as the mech4 forward floor (25 u/s^2).
|
||||
if (!(forwardCycleRate >= 25.0f && forwardCycleRate < 10000.0f))
|
||||
forwardCycleRate = 25.0f;
|
||||
forwardCycleRate = model->maxAcceleration; // @0x344 (this[0xd1] = rec+0x44 = 30
|
||||
// for the madcat -- the struct skew
|
||||
// used to land this on
|
||||
// ThrottleAdjustment ~1.0)
|
||||
// (The "floor 25" era is over: the old struct skew landed this read on
|
||||
// ThrottleAdjustment ~1.0 -- the authentic MaxAcceleration (madcat: 30)
|
||||
// now reads directly. task #4)
|
||||
gimpCycleRate = forwardCycleRate; // @0x5b0 (this[0x16c])
|
||||
groundCycleRate = forwardCycleRate; // @0x5b8 (this[0x16e])
|
||||
airborneCycleRate = model->field48; // @0x5bc (this[0x16f])
|
||||
if (!(airborneCycleRate >= 25.0f && airborneCycleRate < 10000.0f))
|
||||
airborneCycleRate = 25.0f;
|
||||
// ⚠ forwardThrottleScale: the record field at +0x4c decodes to 0 for the
|
||||
// Blackhawk (same undecoded-ModelResource-layout problem as maxAcceleration
|
||||
// == 1.0 above) -- a literal read ZEROES the forward speed demand
|
||||
// (speedDemand = topSpeed * throttle * THIS) and the mech can never walk
|
||||
// forward. Guard until the record layout is decoded against the raw parser.
|
||||
forwardThrottleScale = model->field4c; // @0x5c0 (this[0x170])
|
||||
if (!(forwardThrottleScale > 0.01f && forwardThrottleScale < 100.0f))
|
||||
forwardThrottleScale = 1.0f;
|
||||
Wword(0x12f) = model->field70;
|
||||
Wword(0x1e1) = model->field80; Wword(0x1e2) = model->field84;
|
||||
Wword(0x1e3) = model->field88; Wword(0x1e4) = model->field8c;
|
||||
Wword(0x1e5) = model->field90; Wword(0x1e6) = model->field94;
|
||||
airborneCycleRate = model->superStopAcceleration; // @0x5bc (this[0x16f] = rec+0x48)
|
||||
forwardThrottleScale = model->throttleAdjustment; // @0x5c0 (this[0x170] = rec+0x4C;
|
||||
// the old "decodes to 0" was the
|
||||
// out-of-bounds struct read)
|
||||
if (getenv("BT_REPL_LOG"))
|
||||
DEBUG_STREAM << "[model] accel=" << forwardCycleRate
|
||||
<< " superStop=" << airborneCycleRate
|
||||
<< " throttleAdj=" << forwardThrottleScale << std::endl;
|
||||
Wword(0x12f) = model->relativeMechValue; // rec+0x70 -> mech+0x4bc
|
||||
Wword(0x1e1) = model->maxUnstableAcceleration; // rec+0x80-0x94 -> mech+0x784..0x798
|
||||
Wword(0x1e2) = model->unstableAccelerationEffect; // (the six Unstable* effects)
|
||||
Wword(0x1e3) = model->unstableGunTheEngineEffect;
|
||||
Wword(0x1e4) = model->unstableSuperStopEffect;
|
||||
Wword(0x1e5) = model->unstableHighVelocityEffect;
|
||||
Wword(0x1e6) = model->unstableStopedTurnEffect;
|
||||
|
||||
// Angle fields converted from degrees to radians.
|
||||
Wword(0x159) = (int)(model->lookLeftAngle * DegreesToRadians); // +0x50
|
||||
Wword(0x15a) = (int)(model->lookRightAngle * DegreesToRadians); // +0x54
|
||||
Wword(0x15b) = (int)(model->lookFrontAngle * DegreesToRadians); // +0x58
|
||||
Wword(0x15c) = (int)(model->lookBackAngle * DegreesToRadians); // +0x5c
|
||||
// The update-record deadbands, read at the RAW record offsets the binary
|
||||
// itself uses ([esi+0x98/0x9c/0xa0], disasm @0x4a26a7-0x4a26ef) -- the
|
||||
// Mech__ModelResource struct fieldXX members are LAYOUT-SKEWED (audit
|
||||
// task: struct field98 read -1.5 where raw word 0x26 holds 0.3). A
|
||||
// resource record is a byte blob, so word-indexed reads are the correct
|
||||
// idiom (not the object databinding trap).
|
||||
updateTurnAngleDeadband = ((Scalar *)model)[0x28] * DegreesToRadians; // @0x770 <- UpdateTurnDegreeDiffrence
|
||||
updateTurnVelocityDeadband = ((Scalar *)model)[0x27]; // @0x76c <- UpdateTurnVelocityDiffrence
|
||||
updatePositionDeadband = ((Scalar *)model)[0x26]; // @0x768 <- UpdatePositionDiffrence
|
||||
// The update-record deadbands (task #3; the struct is now the verified
|
||||
// 200-byte overlay, so the named fields ARE the raw offsets).
|
||||
updateTurnAngleDeadband = model->updateTurnDegreeDiffrence * DegreesToRadians; // @0x770
|
||||
updateTurnVelocityDeadband = model->updateTurnVelocityDiffrence; // @0x76c
|
||||
updatePositionDeadband = model->updatePositionDiffrence; // @0x768
|
||||
if (getenv("BT_REPL_LOG"))
|
||||
DEBUG_STREAM << "[deadband] pos=" << updatePositionDeadband
|
||||
<< " turnVel=" << updateTurnVelocityDeadband
|
||||
<< " turnAngle=" << updateTurnAngleDeadband << " (rad)" << std::endl;
|
||||
Wword(0x10c) = model->fieldA4;
|
||||
Wword(0x10c) = model->timeDelay; // rec+0xA4 -> mech+0x430
|
||||
// NOTE: the decomp writes word slots this[0x15d]/this[0x15e]. Routed to the
|
||||
// scratch bank (unnamed pose-angle fields) -- writing through (float*)(this +
|
||||
// 0x15d) would be typed-pointer arithmetic on Mech* (== this + 0x15d*sizeof(Mech)).
|
||||
Wword(0x15d) = (int)(model->field60 * DegreesToRadians);
|
||||
Wword(0x15e) = (int)(model->field64 * DegreesToRadians);
|
||||
// The AUTHENTIC per-mech turn rates (rec+0x60/0x64, deg->rad ->
|
||||
// mech+0x574/0x578) -- still absorbed pending the turn-code wiring
|
||||
// (locomotion polish item: the port turns at a bring-up constant rate).
|
||||
Wword(0x15d) = model->walkingTurnRate * DegreesToRadians;
|
||||
Wword(0x15e) = model->runningTurnRate * DegreesToRadians;
|
||||
|
||||
//
|
||||
// SHADOW JOINT resolve (task #20; CORRECTED -- the earlier draft misread
|
||||
@@ -1413,8 +1412,8 @@ Mech::Mech(
|
||||
|
||||
Wword(0xe5) = 0;
|
||||
Wword(0xdd) = Wword(0xde) = 1;
|
||||
Wword(0xdb) = model->field68;
|
||||
Wword(0xdc) = model->field6c;
|
||||
Wword(0xdb) = model->reticleX; // rec+0x68 -> mech+0x36c
|
||||
Wword(0xdc) = model->reticleY; // rec+0x6C -> mech+0x370
|
||||
|
||||
//
|
||||
// ---- Pass 3: build the per-zone Mech::DamageZone objects (resource type 0x14) ----
|
||||
@@ -2120,7 +2119,9 @@ ResourceDescription::ResourceID
|
||||
resource_file, model_name, model_file, directories, model) == -1
|
||||
|| ParseJointResource( // FUN_00435ac8
|
||||
resource_file, model_name, model_file, directories,
|
||||
model->skeletonName) == -1)
|
||||
(char *)&model->reticleX) == -1) // rec words 0x1A-0x1B (Reticle level;
|
||||
// the old 'skeletonName' block was
|
||||
// fictitious -- no skl name in this record)
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
@@ -2258,7 +2259,8 @@ Logical
|
||||
{
|
||||
return False;
|
||||
}
|
||||
model->classID = MechClassID; // model[7] = 0xBB9
|
||||
// (the 200-byte record has NO classID field -- the old struct appended a
|
||||
// fictitious one; the class identity rides the resource TYPE/segment.)
|
||||
return True;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user