Doors run on the mission clock instead of being replicated
A door's position was an integrated countdown owned by whichever machine the map-entity round-robin happened to deal it to. That left doors one one-way-latency behind on every other machine, re-acquired at each state change; drifting permanently on any frame hitch over a second, which the old code dropped outright rather than clamping; and frozen mid-cycle, collision volumes included, when their owning peer left, since ownership transfer is not implemented. Doors are clockwork with no inputs, and door/VTV physics is already local pointer access - VTV::ProcessCollision reads door->currentVelocity off the local object and the crush test is local VTV state - so a door does not need an owner at all. Door::SlideDoor is now a phase function of Application::GetMissionElapsed(), anchored so phase zero reproduces the original DefaultState entry: fully open, starting to close. Every host builds its own doorframe out of the map stream as a HermitInstance, the instance kind DynamicEntityCreation does not broadcast, so nothing is sent, nothing is received, and a peer leaving takes no doors with it. Verified against a copy of the old integrator at 25fps with the real 10s travel / 3s dead timings: identical 26s cycle, a constant one-frame offset, and no drift across a 3s stall that leaves the old code permanently 3 seconds out of phase. Also fixes a latent bug found on the way: UpdateManager iterates the dynamic master socket, which holds Independant and Hermit instances as well as masters, and handed all of them to EntityUpdateReplicants, which asserts MasterInstance. Doorframes no longer consume a slot in the map-entity ownership cursor, which shifts who owns every map entity dealt after them, so this cannot share a session with an older build. The lobby publishes a simulation revision and refuses to launch a mixed room. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+65
-140
@@ -72,172 +72,96 @@ Door::AttributeIndexSet& Door::GetAttributeIndex()
|
||||
//#############################################################################
|
||||
// Model Support
|
||||
//
|
||||
void
|
||||
Door::ReadUpdateRecord(Simulation::UpdateRecord *message)
|
||||
{
|
||||
Check(this);
|
||||
Check_Pointer(message);
|
||||
Subsystem::ReadUpdateRecord(message);
|
||||
UpdateRecord* record = (UpdateRecord*) message;
|
||||
|
||||
percentOpen = record->percentOpen;
|
||||
switch (GetSimulationState())
|
||||
{
|
||||
case Opening:
|
||||
case Closing:
|
||||
phaseTimeRemaining = travelTime;
|
||||
break;
|
||||
case Opened:
|
||||
case Closed:
|
||||
phaseTimeRemaining = deadTime;
|
||||
break;
|
||||
}
|
||||
// DEBUG_STREAM << GetEntity()->GetEntityID() << " door updated to state "
|
||||
// << GetSimulationState() << " @ "
|
||||
// << application->GetSecondsRemainingInGame() << endl;
|
||||
MoveCollisionVolume(percentOpen);
|
||||
Check_Fpu();
|
||||
}
|
||||
// There is no ReadUpdateRecord/WriteUpdateRecord pair here on purpose. Doors
|
||||
// are Hermit instances built independently on every host, so no door state is
|
||||
// ever sent or received - the phase function below is the only thing that
|
||||
// decides where a door is, and it reaches the same answer everywhere.
|
||||
//
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
void
|
||||
Door::WriteUpdateRecord(Simulation::UpdateRecord *record, int update_model)
|
||||
{
|
||||
Check(this);
|
||||
Check_Pointer(record);
|
||||
|
||||
Subsystem::WriteUpdateRecord(record, update_model);
|
||||
|
||||
UpdateRecord *update = (UpdateRecord*)record;
|
||||
update->percentOpen = percentOpen;
|
||||
update->recordLength = sizeof(*update);
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
void
|
||||
Door::SlideDoor(Scalar time_slice)
|
||||
Door::SlideDoor(Scalar)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
//------------------------------------------------------------
|
||||
// Advance the clock, then branch based upon our current state
|
||||
//------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------
|
||||
// The door is clockwork. Its position is a function of how long the race
|
||||
// has been running, not of a countdown integrated frame by frame, so:
|
||||
//
|
||||
int new_state;
|
||||
if (time_slice > 1.0f)
|
||||
// - every machine puts this door in the same place from the same mission
|
||||
// clock, without a byte crossing the wire,
|
||||
// - a frame hitch of any length costs nothing, because there is no
|
||||
// accumulated state left to fall behind (the old code dropped any slice
|
||||
// over a second outright and never got that time back).
|
||||
//
|
||||
// Phase zero is the instant the door starts to close, fully open, which is
|
||||
// where the original state machine began from DefaultState:
|
||||
//
|
||||
// [0, travel) Closing 1 -> 0
|
||||
// [travel, travel+dead) Closed 0
|
||||
// [travel+dead, 2travel+dead) Opening 0 -> 1
|
||||
// [2travel+dead, cycle) Opened 1
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
if (cycleTime <= 0.0f)
|
||||
{
|
||||
MoveCollisionVolume(0.0f);
|
||||
SetSimulationState(Closed);
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
phaseTimeRemaining -= time_slice;
|
||||
Scalar percent_open;
|
||||
switch (GetSimulationState())
|
||||
|
||||
Check(application);
|
||||
Scalar phase = fmod(application->GetMissionElapsed() - phaseOffset, cycleTime);
|
||||
if (phase < 0.0f)
|
||||
{
|
||||
phase += cycleTime;
|
||||
}
|
||||
|
||||
//
|
||||
//------------------------------------------------------------------------
|
||||
// If the door is not done opening, set its new position, otherwise branch
|
||||
// to the opened state
|
||||
//------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------
|
||||
// Pick the band. Each division below is guarded by the comparison that
|
||||
// selected the branch, so a door with a zero travelTime or deadTime simply
|
||||
// loses that band rather than dividing by zero.
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
case Opening:
|
||||
Door_Opening:
|
||||
new_state = Opening;
|
||||
if (phaseTimeRemaining > 0.0f)
|
||||
{
|
||||
percent_open = 1.0f - phaseTimeRemaining/travelTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
phaseTimeRemaining += deadTime;
|
||||
// DEBUG_STREAM << GetEntity()->GetEntityID() << " door opened @ "
|
||||
// << application->GetSecondsRemainingInGame() << endl;
|
||||
goto Door_Opened;
|
||||
}
|
||||
currentVelocity.Subtract(
|
||||
worldExtent,
|
||||
GetEntity()->localOrigin.linearPosition
|
||||
);
|
||||
currentVelocity /= travelTime;
|
||||
Check_Fpu();
|
||||
break;
|
||||
Scalar open_start = travelTime + deadTime;
|
||||
int new_state;
|
||||
Scalar percent_open;
|
||||
|
||||
//
|
||||
//-------------------------------------------------------------
|
||||
// If the door is ready to start closing, jump to closing state
|
||||
//-------------------------------------------------------------
|
||||
//
|
||||
case Opened:
|
||||
Door_Opened:
|
||||
new_state = Opened;
|
||||
if (phaseTimeRemaining <= 0.0f)
|
||||
{
|
||||
phaseTimeRemaining += travelTime;
|
||||
// DEBUG_STREAM << GetEntity()->GetEntityID() << " door closing @ "
|
||||
// << application->GetSecondsRemainingInGame() << endl;
|
||||
goto Door_Closing;
|
||||
}
|
||||
percent_open = 1.0f;
|
||||
currentVelocity = Vector3D::Identity;
|
||||
Check_Fpu();
|
||||
break;
|
||||
|
||||
//
|
||||
//------------------------------------------------------------------------
|
||||
// If the door is not done closing, set its new position, otherwise branch
|
||||
// to the closed state
|
||||
//------------------------------------------------------------------------
|
||||
//
|
||||
case DefaultState:
|
||||
phaseTimeRemaining = travelTime;
|
||||
// DEBUG_STREAM << GetEntity()->GetEntityID() << " door default @ "
|
||||
// << application->GetSecondsRemainingInGame() << endl;
|
||||
case Closing:
|
||||
Door_Closing:
|
||||
if (phase < travelTime)
|
||||
{
|
||||
new_state = Closing;
|
||||
if (phaseTimeRemaining > 0.0f)
|
||||
{
|
||||
percent_open = phaseTimeRemaining/travelTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
phaseTimeRemaining += deadTime;
|
||||
// DEBUG_STREAM << GetEntity()->GetEntityID() << " door closed @ "
|
||||
// << application->GetSecondsRemainingInGame() << endl;
|
||||
goto Door_Closed;
|
||||
}
|
||||
percent_open = 1.0f - phase/travelTime;
|
||||
currentVelocity.Subtract(
|
||||
GetEntity()->localOrigin.linearPosition,
|
||||
worldExtent
|
||||
);
|
||||
currentVelocity /= travelTime;
|
||||
Check_Fpu();
|
||||
break;
|
||||
|
||||
//
|
||||
//-------------------------------------------------------------
|
||||
// If the door is ready to start opening, jump to opening state
|
||||
//-------------------------------------------------------------
|
||||
//
|
||||
case Closed:
|
||||
Door_Closed:
|
||||
}
|
||||
else if (phase < open_start)
|
||||
{
|
||||
new_state = Closed;
|
||||
if (phaseTimeRemaining <= 0.0f)
|
||||
{
|
||||
phaseTimeRemaining += travelTime;
|
||||
// DEBUG_STREAM << GetEntity()->GetEntityID() << " door opening @ "
|
||||
// << application->GetSecondsRemainingInGame() << endl;
|
||||
goto Door_Opening;
|
||||
}
|
||||
percent_open = 0.0f;
|
||||
currentVelocity = Vector3D::Identity;
|
||||
Check_Fpu();
|
||||
break;
|
||||
|
||||
}
|
||||
else if (phase < open_start + travelTime)
|
||||
{
|
||||
new_state = Opening;
|
||||
percent_open = (phase - open_start)/travelTime;
|
||||
currentVelocity.Subtract(
|
||||
worldExtent,
|
||||
GetEntity()->localOrigin.linearPosition
|
||||
);
|
||||
currentVelocity /= travelTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
new_state = Opened;
|
||||
percent_open = 1.0f;
|
||||
currentVelocity = Vector3D::Identity;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -344,7 +268,8 @@ Door::Door(
|
||||
//
|
||||
// Initialize variables
|
||||
//
|
||||
phaseTimeRemaining = 0.0f;
|
||||
phaseOffset = 0.0f;
|
||||
cycleTime = 2.0f*(travelTime + deadTime);
|
||||
currentPosition = Point3D::Identity;
|
||||
|
||||
SetPerformance(&Door::SlideDoor);
|
||||
|
||||
Reference in New Issue
Block a user