#191 FIXED + 2-node benched: peers can see a coolant flush again. The manual promises it (p24 'other pilots will see steam rise from your Mech on the battlefield') and on the machine it was FREE: BTL4VID.CPP built a mode-1 effect renderable per Reservoir on its ReservoirState ATTRIBUTE (part_014.c:10308 -- FUN_0041bfc0(sub,'ReservoirState') resolves to the alarm's own address) and the renderable's tick started the pfx when the watched state changed to 1, on whichever node it was built on. The port consolidated those renderables into the psfx layer and hung the spawn off Reservoir::InjectCoolantMessageHandler, which mech4 dispatches for the VIEWPOINT mech only and delivers with a local Dispatch -- so nothing about a flush ever left the flusher's machine.
Fixed the way the binary does subsystem replication (MechWeapon::WriteUpdateRecord @004b9690, Emitter @004ba65c, already mirrored in emitter.cpp): extend the record, set recordLength, append the field; the receive half applies it on the EDGE only, so a long flush spawns ONE cloud rather than one per packet. TRAP AVOIDED, worth recording: the obvious route was Simulation::simulationState, which every subsystem record already carries -- but on a MechSubsystem that cell is the TECH STATUS vocabulary (StateCount 7) and state 1 is DestroyedState, which btl4gaug.cpp:427 paints as a full critical tint. Publishing the flush there would have drawn the reservoir as DESTROYED on every damage schematic for the duration of a flush. Surfaced only because a [flush-tx] probe read the state back as 0 and I went looking for why. Bench (scratchpad/night18/mp_flush7.sh, madcat/grass, A flushes at frame 900 under BT_FLUSH_TEST): before = A logs the cloud, B logs nothing; after = B spawns the cloud at A's exact position (-241.894,7,-904.136) with a clean STARTED/ended pair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e2224fc587
commit
566daf358f
@@ -800,9 +800,86 @@ void
|
||||
}
|
||||
injectAccumulator = 0; // this+0x228
|
||||
}
|
||||
// #191 -- tell the peers. The manual promises this ("other pilots will
|
||||
// see steam rise from your 'Mech on the battlefield", p24) and on the
|
||||
// machine it was free: the cloud renderable WATCHED the ReservoirState
|
||||
// attribute, and a replicant's copy of that attribute moved with the
|
||||
// update stream, so the peer's own renderable fired. The port
|
||||
// consolidated those renderables into the psfx layer and hung the spawn
|
||||
// off this handler -- which mech4 dispatches for the VIEWPOINT mech only,
|
||||
// and Dispatch delivers locally, so a flush never left the flusher's
|
||||
// machine (bench: scratchpad/night18/mp_flush7.sh -- A logged the cloud,
|
||||
// B logged nothing).
|
||||
//
|
||||
// ⚠ Do NOT route this through Simulation::simulationState even though
|
||||
// every subsystem record already carries it: on a MechSubsystem that cell
|
||||
// is the TECH STATUS vocabulary (StateCount 7) and **state 1 is
|
||||
// DestroyedState** -- btl4gaug.cpp:427 paints a full critical tint on it.
|
||||
// Publishing the flush there would have drawn the reservoir as destroyed
|
||||
// on every damage schematic for the duration of a flush. (Tried it; the
|
||||
// [flush-tx] probe read state=0 back and sent me looking, which is how the
|
||||
// collision surfaced before it shipped. Gotcha class: one cell, many
|
||||
// meanings -- see reconstruction-gotchas #30.)
|
||||
ForceUpdate(); // this+0x18 |= 1
|
||||
}
|
||||
|
||||
//
|
||||
// #191 -- FLUSH REPLICATION, both halves.
|
||||
//
|
||||
// The machine did not need these: BTL4VID.CPP built a mode-1 effect
|
||||
// renderable per Reservoir on its "ReservoirState" ATTRIBUTE
|
||||
// (part_014.c:10308, FUN_0041bfc0(sub,"ReservoirState") -> the alarm's own
|
||||
// address) and the renderable's tick started the pfx when the watched state
|
||||
// changed to 1 -- on whichever node it was built on, master or replicant.
|
||||
// The port consolidated the effect renderables into the psfx layer and
|
||||
// spawns from the InjectCoolant handler, which only runs on the flusher, so
|
||||
// the flush state has to be shipped explicitly.
|
||||
//
|
||||
// Shape copied from the binary's own subsystem replication (MechWeapon
|
||||
// @004b9690, Emitter @004ba65c): chain the base, set recordLength, append
|
||||
// the field.
|
||||
//
|
||||
void
|
||||
Reservoir::WriteUpdateRecord(Simulation::UpdateRecord *message, int update_model)
|
||||
{
|
||||
HeatSink::WriteUpdateRecord(message, update_model);
|
||||
|
||||
Reservoir__UpdateRecord *rec = (Reservoir__UpdateRecord *)message;
|
||||
rec->recordLength = sizeof(Reservoir__UpdateRecord);
|
||||
rec->flushActive = (reservoirAlarm.GetLevel() != 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
//
|
||||
// The receive half: turn the replicated flush state back into the cloud,
|
||||
// where the attribute-watching renderable used to do it. EDGE only -- a
|
||||
// long flush ships many records and the machine's watcher fired on the
|
||||
// state CHANGE, not on every tick.
|
||||
//
|
||||
void
|
||||
Reservoir::ReadUpdateRecord(Simulation::UpdateRecord *record)
|
||||
{
|
||||
HeatSink::ReadUpdateRecord(record);
|
||||
|
||||
const Reservoir__UpdateRecord *rec = (const Reservoir__UpdateRecord *)record;
|
||||
if (record->recordLength < (int)sizeof(Reservoir__UpdateRecord))
|
||||
return; // older/base record -- nothing to apply
|
||||
|
||||
const int now = rec->flushActive;
|
||||
const int was = (reservoirAlarm.GetLevel() != 0) ? 1 : 0;
|
||||
if (now == was)
|
||||
return;
|
||||
|
||||
reservoirAlarm.SetLevel(now ? 1 : 0); // keep the peer's gauge/state in step
|
||||
if (now)
|
||||
{
|
||||
extern void BTSpawnFlushCloud(void *owner_mech); // mech4.cpp (psfx 19)
|
||||
BTSpawnFlushCloud(owner);
|
||||
}
|
||||
if (getenv("BT_FLUSH_LOG"))
|
||||
DEBUG_STREAM << "[flush] PEER flush " << (now ? "STARTED -- cloud spawned" : "ended")
|
||||
<< std::endl << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
// Gitea #7 -- the Reservoir handler registration (table @0x50e680: exactly
|
||||
// one entry {4, "InjectCoolant", @4aee70}). Chained onto the engine Receiver
|
||||
|
||||
@@ -233,6 +233,33 @@
|
||||
static Receiver::MessageHandlerSet& GetMessageHandlers();
|
||||
static SharedData DefaultData;
|
||||
|
||||
// #191 -- FLUSH REPLICATION. On the machine the cloud came free: the
|
||||
// mode-1 effect renderable was built per Reservoir on its
|
||||
// "ReservoirState" ATTRIBUTE (BTL4VID.CPP build loop, part_014.c:10308
|
||||
// -- FUN_0041bfc0(subsystem,"ReservoirState") resolves to the alarm's
|
||||
// own address) and its tick started the pfx when the watched state
|
||||
// changed to 1, on WHATEVER node it was built on. The port
|
||||
// consolidated those renderables into the psfx layer and spawns from
|
||||
// the InjectCoolant handler instead -- which only ever runs on the
|
||||
// flusher -- so the state has to reach the peer explicitly.
|
||||
//
|
||||
// Done the way the binary does subsystem replication (MechWeapon
|
||||
// @004b9690 / Emitter @004ba65c, mirrored in emitter.cpp): extend the
|
||||
// record, set recordLength, append our field. Reservoir has no
|
||||
// authored record of its own in the binary because it never needed
|
||||
// one; this is the port's equivalent of the attribute the renderable
|
||||
// used to watch.
|
||||
struct Reservoir__UpdateRecord:
|
||||
public Subsystem::UpdateRecord
|
||||
{
|
||||
int flushActive; // rec+0x18 -- reservoirAlarm level != 0
|
||||
};
|
||||
|
||||
virtual void
|
||||
WriteUpdateRecord(Simulation::UpdateRecord *message, int update_model);
|
||||
virtual void
|
||||
ReadUpdateRecord(Simulation::UpdateRecord *record);
|
||||
|
||||
// Attribute Support -- audio binds an AudioStateWatcher to ReservoirState;
|
||||
// it resolves to reservoirAlarm (@0x1D0, a 0x54 StateIndicator-compatible
|
||||
// GaugeAlarm54; level@0x1e4 is the inject flag). CoolantSimulation SetLevel's
|
||||
|
||||
Reference in New Issue
Block a user