MP live-play wave: collision economy, missiles, radar transform, panel polarity, comm ticker
The interactive 2-node playtest wave -- every fix decomp-grounded and live-verified: COLLISION ECONOMY (the ram one-shot): StaticBounce mutates worldLinearVelocity per contact and ProcessCollisionList walks EVERY touched solid per frame; with 2007 terrain-as-solids the reflections compounded x4-x40 within one frame and a walking bump one-shot a pristine mech for 112,375 pts (62-pt authentic economy). Fix: frameEntryWorldVelocity restore per contact (damage always priced at the real approach speed -- all the binary's physics ever saw); Mech::Reset zeroes the mover motion (respawn = teleport); [collide-tx]/[mp-hdlr] telemetry. Gotcha #16 (engine-facility drift class). MISSILES: peer-visible salvos (the launcher record extension carries a salvo counter + aim point; ForceUpdate actually enqueues it -- the dirty flag alone never serialized), the authentic arc (authored MuzzleVelocity vector + the Seeker's 200m/0.1/300 loft + gain-4 steering, decoded from @004beae4/@004bef78), world-impact bursts (rounds detonate on cave geometry instead of phasing through), contact-only damage (flight-cap expiry = fizzle, no more teleport damage), live re-lead, and ballistic (unguided) shells for autocannons. projweap's stale BTPushProjectile extern (the /FORCE signature trap, gotcha #6 corollary) crashed the Avatar's first AFC100 shot -- fixed + sweep rule recorded. RADAR: two transcription bugs made the scope permanently empty -- FUN_0040b244 is the affine INVERSE (not a copy) and FUN_0040adec writes ONLY the 3x3 rotation (never the translation); worldToView now Invert(view) built rotation-first. CulturalIcons sorted out of the moving grid (the phantom red pips were map props), visible-radius culls on all three draw passes, live pip verified at |delta| x ppm px. Gotcha #17 (verify the FUN_ body, not its call shape). WEAPON PANELS (the frozen-dial hunt): the binary's *(subsystem+0x40) means FAILED -- the recon's 'operating' name was backwards, inverting the destroyed-X lamps, the panel look, the children enable and the ready-lamp gate (which had NEVER executed). Polarity chain corrected end-to-end (failedState, fed by real damage saturation). Root cause of the freezes: MFD page-mode gating -- the dev composite shows ALL pages at once, so off-page dials legitimately stopped; under BT_DEV_GAUGES the 15 page-plane bits stay active (the exclusive secondary trio untouched). The SEH gauge guard now names its kills; repaint-heal resets the incremental arc after panel repaints; [panel]/[arc] probes added. COMM/SCORE: MessageBoard LIVE (the engine already shipped the whole Player__StatusMessage queue; wired the binary's one producer -- the kill branch, victim's name, 6s -- plus the consumer bridge and a lazy source bind); MP DEATHS counted via the observed-death tally (each node scores every pilot from locally observed events, the same model as the KILLS credit) and the -2/-1 engine seed clamped for display. DEV UX: node-tagged window titles (-net port), gauge panel reworked (1320x480, true 4:3 MFD cells, the portrait secondary UNROTATED upright, linear filtering, BT_GAUGE_SCALE), fixed close spawns via BT_SPAWN_XZ, Boreas flies an Avatar (first second-chassis live outing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
dd27238ceb
commit
bb795e2805
@@ -1310,7 +1310,7 @@ SubsystemCluster::SubsystemCluster(
|
||||
*(int *)((char *)subsystem_in + 0x150) /*third*/, 3 /*frames*/,
|
||||
(Scalar *)coolantLeak, "LeakGauge"); // BEST-EFFORT raw (subsys+0x150)
|
||||
|
||||
operating = False; // @0xC4 this[0x31]
|
||||
failedState = False; // @0xC4 this[0x31] (1 = destroyed)
|
||||
subsystem = subsystem_in; // @0xC0 this[0x30]
|
||||
previousDrawState = 0; // @0xC8 this[0x32]
|
||||
}
|
||||
@@ -1362,10 +1362,12 @@ void SubsystemCluster::DrawPanelFrame(int color)
|
||||
//
|
||||
void SubsystemCluster::SetChildrenEnable(int enable)
|
||||
{
|
||||
// FUN_00444650 forwards visibility; enable==0 (offline) => Disable(True).
|
||||
if (temperatureBar) temperatureBar->Disable(enable == 0);
|
||||
if (coolingLoopA) coolingLoopA->Disable(enable == 0);
|
||||
if (powerSourceA) powerSourceA->Disable(enable == 0);
|
||||
// POLARITY (PPC-dial fix, take 3): the passed value is the DRAW STATE --
|
||||
// 0 = alive (children run), nonzero = dead panel (children off). The old
|
||||
// reading (`enable==0 => Disable`) had it backwards.
|
||||
if (temperatureBar) temperatureBar->Disable(enable != 0);
|
||||
if (coolingLoopA) coolingLoopA->Disable(enable != 0);
|
||||
if (powerSourceA) powerSourceA->Disable(enable != 0);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1374,11 +1376,13 @@ void SubsystemCluster::SetChildrenEnable(int enable)
|
||||
//
|
||||
int SubsystemCluster::GetDrawState()
|
||||
{
|
||||
if (operating != 0)
|
||||
return 1;
|
||||
if (*(int *)((char *)subsystem + 0x278) != 4) // BEST-EFFORT raw offset
|
||||
return 1;
|
||||
return 0;
|
||||
// POLARITY (PPC-dial fix, take 3): state 1 = the DEAD-panel presentation
|
||||
// (black fill + bright frame + X lamps lit); state 0 = ALIVE (frame 0 +
|
||||
// background art; the WeaponCluster lamp gate runs ONLY in state 0). The
|
||||
// binary's secondary condition (*(subsystem+0x278) != 4 -> 1) remains
|
||||
// unidentified [T4] -- a healthy subsystem reads ALIVE here until that
|
||||
// field is decoded (open-questions).
|
||||
return failedState ? 1 : 0;
|
||||
}
|
||||
|
||||
Logical SubsystemCluster::TestInstance() const
|
||||
@@ -1394,7 +1398,18 @@ Logical SubsystemCluster::TestInstance() const
|
||||
//
|
||||
void SubsystemCluster::Execute()
|
||||
{
|
||||
operating = (*(int *)((char *)subsystem + 0x40) == 1); // BEST-EFFORT raw offset
|
||||
// PPC-dial fix (2026-07-12, take 3 -- POLARITY): the binary's
|
||||
// *(subsystem+0x40)==1 means FAILED (it lights the X TwoStates, which the
|
||||
// engine draws bright on NONZERO, and selects the black-fill dead-panel
|
||||
// look). The recon's old name `operating` was backwards, which inverted
|
||||
// the X lamps, the panel look, the children enable AND the weapon lamp
|
||||
// gate (which runs only in draw-state 0 = alive). Truth source: the
|
||||
// subsystem's own damage saturation (DistributeCriticalHit destroys at
|
||||
// >= 1.0, mechdmg.cpp).
|
||||
{
|
||||
extern Scalar BTSubsystemDamageLevelOf(void *subsystem_in);
|
||||
failedState = (BTSubsystemDamageLevelOf(subsystem) >= 1.0f);
|
||||
}
|
||||
|
||||
int state = GetDrawState();
|
||||
if (state != previousDrawState)
|
||||
@@ -1450,7 +1465,7 @@ HeatSinkCluster::HeatSinkCluster(
|
||||
int engPort = renderer_in->FindGraphicsPort(eng_port_name);
|
||||
|
||||
heatFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, // @004739d8
|
||||
engPort, 200, 0x110, fail_lamp_image, 0, 0xff, (int *)&operating, "TwoState");
|
||||
engPort, 200, 0x110, fail_lamp_image, 0, 0xff, (int *)&failedState, "TwoState"); // bright when FAILED
|
||||
|
||||
failSubsystem = hud; // @0x33
|
||||
failFlag = 0; // @0x34
|
||||
@@ -1587,6 +1602,12 @@ WeaponCluster::WeaponCluster(
|
||||
warningState = 0; // @0x39
|
||||
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &percentDone, // @00474855
|
||||
(Scalar *)percentDoneAttr));
|
||||
|
||||
// PANEL DIAGNOSTIC (PPC-dial investigation 2026-07-12): remember the
|
||||
// weapon's name so the Execute probe can attribute its readings; tag the
|
||||
// recharge arc with the same name for its own [arc] probe.
|
||||
diagWeaponName = (subsystem_in != 0) ? subsystem_in->GetName() : "?";
|
||||
((SegmentArc270 *)rechargeArc)->diagName = diagWeaponName;
|
||||
}
|
||||
|
||||
WeaponCluster::~WeaponCluster()
|
||||
@@ -1630,7 +1651,33 @@ void WeaponCluster::BecameActive()
|
||||
//
|
||||
void WeaponCluster::Execute()
|
||||
{
|
||||
// PPC-dial fix (2026-07-12): the base Execute BLACK-FILLS + repaints the
|
||||
// panel on every draw-state flip -- wiping the recharge arc's pixels while
|
||||
// the arc's INCREMENTAL draw memory (previousSegment) still says "lit".
|
||||
// The stale arc then draws nothing until the value crosses new segments =
|
||||
// "tick marks frozen (at the backdrop's painted ring), dot still toggles"
|
||||
// (user-reported, intermittent). Detect the repaint and reset the arc's
|
||||
// draw memory so it fully redraws its true fill.
|
||||
int preDrawState = previousDrawState;
|
||||
SubsystemCluster::Execute();
|
||||
if (previousDrawState != preDrawState)
|
||||
{
|
||||
if (rechargeArc) rechargeArc->BecameActive();
|
||||
if (configMap) configMap->BecameActive();
|
||||
warningState = -2; // lamp: force redraw too
|
||||
}
|
||||
// PANEL DIAGNOSTIC (PPC-dial investigation): 1 Hz per-panel dump of the
|
||||
// exact value the recharge arc + warning lamp read. Answers whether a
|
||||
// frozen dial is a DATA fault (percentDone pegged) or a DRAW fault
|
||||
// (value cycles, segments don't).
|
||||
if (getenv("BT_PANEL_LOG"))
|
||||
{
|
||||
static int s_pl = 0;
|
||||
if ((s_pl++ % 60) == 0)
|
||||
DEBUG_STREAM << "[panel] " << (diagWeaponName ? diagWeaponName : "?")
|
||||
<< " percentDone=" << percentDone
|
||||
<< " warn=" << warningState << "\n" << std::flush;
|
||||
}
|
||||
if (previousDrawState == 0)
|
||||
{
|
||||
int warn = (WeaponWarnThreshold < percentDone) ? 1 : 0;
|
||||
@@ -1736,7 +1783,7 @@ BallisticWeaponCluster::BallisticWeaponCluster(
|
||||
"NumericDisplayScalarTwoState");
|
||||
|
||||
destroyedLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID,
|
||||
engPort, 0xc2, 0x85, "edestryd.pcc", 0, 0xff, (int *)&operating, "TwoState");
|
||||
engPort, 0xc2, 0x85, "edestryd.pcc", 0, 0xff, (int *)&failedState, "TwoState"); // the destroyed X: bright when FAILED
|
||||
|
||||
// ejectWipe (BitMapInverseWipeScalar @004c61c8, eject-timer wipe reading
|
||||
// subsys+0x3f8). BRING-UP: the BitMapInverseWipeScalar class is not yet
|
||||
|
||||
Reference in New Issue
Block a user