gauge-complete P4c: Condenser dedup + RecomputeCondenserValves -> ValveSetting gauge reads authentic 1/N
Two coupled fixes so the condenser valve gauge (ValveSetting -> coolantFlowScale @0x15C) reads its authentic value instead of garbage/zero. STEP 7 (gating dedup): heat.cpp carried STUB Condenser ctor/dtor/TestClass/ TestInstance/CreateStreamedSubsystem that ODR-duplicated the REAL bodies in heatfamily_reslice.cpp and WON under /FORCE (heat.obj links first) -> the real ctor (which sets valveState=1) was shadowed, leaving valveState=0xCDCDCDCD. #if 0'd the heat.cpp stubs so the reslice ctor (@4ae568, byte-verified: valveState=1, coolantFlowScale=0, massScale=refrigerationFactor, condenserNumber from name) is the sole definition. DefaultData/GetClassDerivations/ResetToInitialState (not duplicated) stay in heat.cpp. STEP 9 (the real writer): FinishConstruction() at the Mech ctor tail was a no-op template stub in place of FUN_0049f788 = RecomputeCondenserValves -> coolantFlowScale was never written (stayed 0). Reconstructed it (byte-verified vs part_012.c:9264): distribute coolant flow across the mech's condensers, coolantFlowScale_i = valveState_i / sum(valveState), with the condenserAlarm@0x1DC change pulse (2-if-flow<=old-else-1, then 0). Walks the populated subsystem roster filtering Condensers via IsDerivedFrom (behaviorally identical to the binary's @mech+0x7cc condenser chain, GUID 0x50e4fc). Wired as BTRecomputeCondenserValves(this) at the Mech ctor post-init pass (binary @9457). _DAT_0049f850 fallback confirmed 0.0f (PE read). Verified: [valve] the Blackhawk's 6 condensers each read flow=0.166667 (=1/6, total=6) for both player + spawned enemy, no every-mech crash; combat TARGET DESTROYED, un-regressed. Diagnostic BT_VALVE_LOG added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
16f5f545ce
commit
b91057aadc
@@ -242,6 +242,13 @@ Derivation*
|
||||
return &classDerivations;
|
||||
}
|
||||
|
||||
// ~~~ Condenser ctor/dtor: the REAL bodies live in heatfamily_reslice.cpp
|
||||
// (@4ae568, which sets valveState=1 / refrigerationFactor / condenserNumber).
|
||||
// These STUBs were ODR-duplicates that WON under /FORCE (heat.obj links before
|
||||
// heatfamily_reslice.obj) -> valveState was left 0xCDCDCDCD (garbage valve gauge).
|
||||
// #if 0'd so the real ctor is the sole definition. DefaultData / GetClassDerivations
|
||||
// / ResetToInitialState below are NOT duplicated in the reslice TU -- kept here.
|
||||
#if 0
|
||||
Condenser::Condenser(
|
||||
Mech *owner,
|
||||
int subsystem_ID,
|
||||
@@ -260,6 +267,7 @@ Condenser::~Condenser()
|
||||
Check(this);
|
||||
Check_Fpu();
|
||||
}
|
||||
#endif
|
||||
|
||||
//###########################################################################
|
||||
// ResetToInitialState -- Condenser (HEAT.TCP)
|
||||
@@ -271,8 +279,13 @@ void
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// TestClass -- Condenser (HEAT.TCP)
|
||||
// TestClass / TestInstance / CreateStreamedSubsystem -- Condenser
|
||||
//
|
||||
// The REAL bodies live in heatfamily_reslice.cpp (@4ae63c / @4ae658, the latter
|
||||
// parsing "RefrigerationFactor"). These STUBs were ODR-duplicates -- #if 0'd so
|
||||
// the reslice definitions are the sole ones (see the ctor note above).
|
||||
//
|
||||
#if 0
|
||||
Logical
|
||||
Condenser::TestClass(Mech &)
|
||||
{
|
||||
@@ -310,6 +323,7 @@ int
|
||||
Check_Fpu();
|
||||
return True;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//###########################################################################
|
||||
|
||||
@@ -811,3 +811,53 @@ Subsystem *CreateHeatSinkBankSubsystem(Mech *owner, int id, void *seg)
|
||||
AggregateHeatSink(owner, id,
|
||||
(AggregateHeatSink::SubsystemResource *)seg, AggregateHeatSink::DefaultData);
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// @0049f788 -- RecomputeCondenserValves. Distribute coolant flow across the
|
||||
// mech's condensers so each one's ValveSetting gauge (coolantFlowScale@0x15C)
|
||||
// reads its share of the total valve opening: flow_i = valveState_i / sum(valveState).
|
||||
//
|
||||
// The binary iterates the condenser chain @mech+0x7cc (GUID 0x50e4fc == Condenser);
|
||||
// we walk the populated subsystem roster and filter for Condensers via IsDerivedFrom
|
||||
// -- behaviorally identical (the chain holds exactly the condensers) and independent
|
||||
// of whether the @0x7cc chain is wired. Called at the end of the Mech ctor (binary
|
||||
// @9457, the post-init pass) so the valve gauge shows the authentic 1/N per condenser
|
||||
// instead of 0 (the ctor leaves coolantFlowScale=0 by design; this is its first writer).
|
||||
//
|
||||
// The per-condenser condenserAlarm@0x1DC toggle (2 if flow<=old else 1, then 0) is the
|
||||
// binary's change-notification pulse; reproduced exactly (FUN_0041bbd8 == SetLevel).
|
||||
//===========================================================================//
|
||||
void BTRecomputeCondenserValves(Entity *owner)
|
||||
{
|
||||
Check(owner);
|
||||
Derivation &condClass = *Condenser::GetClassDerivations();
|
||||
int count = owner->GetSubsystemCount();
|
||||
|
||||
// pass 1: total valve opening across all condensers
|
||||
int total = 0;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
Subsystem *s = owner->GetSubsystem(i);
|
||||
if (s != 0 && s->IsDerivedFrom(condClass))
|
||||
total += ((Condenser *)s)->valveState; // iVar3 += *(int*)(iVar1+0x1d0)
|
||||
}
|
||||
|
||||
// pass 2: each condenser's flow = its share of the total
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
Subsystem *s = owner->GetSubsystem(i);
|
||||
if (s == 0 || !s->IsDerivedFrom(condClass))
|
||||
continue;
|
||||
Condenser *c = (Condenser *)s;
|
||||
Scalar flow = 0.0f; // _DAT_0049f850 (no-condenser fallback)
|
||||
if (total > 0)
|
||||
flow = (Scalar)c->valveState / (Scalar)total;
|
||||
c->condenserAlarm.SetLevel(flow <= c->coolantFlowScale ? 2 : 1); // @0x1dc change pulse
|
||||
c->coolantFlowScale = flow; // *(float*)(iVar1+0x15c) = local_8
|
||||
c->condenserAlarm.SetLevel(0);
|
||||
if (getenv("BT_VALVE_LOG"))
|
||||
DEBUG_STREAM << "[valve] condenser#" << c->condenserNumber
|
||||
<< " valveState=" << c->valveState << " flow=" << c->coolantFlowScale
|
||||
<< " (total=" << total << ")\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,7 @@ struct MechControlsMapper : ReconSubsystemStub { template<class...A> MechContro
|
||||
extern Subsystem *CreateCondenserSubsystem(Mech *, int, void *); // 0xBBD
|
||||
extern Subsystem *CreateHeatSinkBankSubsystem(Mech *, int, void *); // 0xBBE
|
||||
extern Subsystem *CreateReservoirSubsystem(Mech *, int, void *); // 0xBC0
|
||||
extern void BTRecomputeCondenserValves(Entity *); // @0049f788 (post-init valve distribution)
|
||||
extern Subsystem *CreateHUDSubsystem(Mech *, int, void *); // 0xBD6
|
||||
extern Subsystem *CreateMechTechSubsystem(Mech *, int, void *); // 0xBDC
|
||||
extern Subsystem *CreateGeneratorSubsystem(Mech *, int, void *); // 0xBC1 (WAVE 3a)
|
||||
@@ -1237,7 +1238,11 @@ Mech::Mech(
|
||||
}
|
||||
dir->Add(this); // (**(dir[4]+4))(dir+4,this)
|
||||
|
||||
FinishConstruction(); // FUN_0049f788 (post-init pass)
|
||||
// @0049f788 -- distribute coolant flow across the condensers (post-init pass).
|
||||
// The real RecomputeCondenserValves; sets each condenser's coolantFlowScale to
|
||||
// valveState/sum(valveState) so the ValveSetting gauge reads the authentic 1/N
|
||||
// (was a no-op stub -> the valve gauge showed 0).
|
||||
BTRecomputeCondenserValves(this);
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import struct,sys
|
||||
va=int(sys.argv[1],16)
|
||||
f=open('content/BTL4OPT.EXE','rb').read()
|
||||
# PE header
|
||||
pe=struct.unpack_from('<I',f,0x3c)[0]
|
||||
assert f[pe:pe+4]==b'PE\0\0'
|
||||
numsec=struct.unpack_from('<H',f,pe+6)[0]
|
||||
optsz=struct.unpack_from('<H',f,pe+20)[0]
|
||||
imgbase=struct.unpack_from('<I',f,pe+0x34)[0]
|
||||
sectab=pe+24+optsz
|
||||
for i in range(numsec):
|
||||
off=sectab+i*40
|
||||
name=f[off:off+8].rstrip(b'\0').decode(errors='replace')
|
||||
vsz,vaddr,rsz,raddr=struct.unpack_from('<IIII',f,off+8)
|
||||
if imgbase+vaddr <= va < imgbase+vaddr+max(vsz,rsz):
|
||||
fo=raddr+(va-imgbase-vaddr)
|
||||
raw=f[fo:fo+4]
|
||||
print(f"VA {va:#x} sec={name} bytes={raw.hex()} float={struct.unpack('<f',raw)[0]} int={struct.unpack('<i',raw)[0]}")
|
||||
break
|
||||
else:
|
||||
print("not found; imgbase",hex(imgbase))
|
||||
Reference in New Issue
Block a user