fix(moving platform): end the subcomponent list with a 0 bit

The client reads a moving platform's subcomponents while a 1 bit comes
before one (LWOMovingPlatformComponent::Deserialize), as live wrote them.
DLU wrote the mover without the closing 0 bit, so the client took the next
component's first bit for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-30 08:39:23 -05:00
parent 3018312f29
commit 3956df8877
2 changed files with 30 additions and 0 deletions

View File

@@ -118,6 +118,9 @@ void MovingPlatformComponent::Serialize(RakNet::BitStream& outBitStream, bool bI
} else {
mover->Serialize(outBitStream, bIsInitialUpdate);
}
// The client reads subcomponents while a 1 bit comes before one: a 0 bit ends the list
outBitStream.Write0();
}
}

View File

@@ -258,6 +258,33 @@ TEST_F(ReplicaConstructionTest, SimplePhysicsConstructionLikeLive) {
info.settings.values.clear();
}
// The client reads a moving platform's subcomponents while a 1 bit comes before one (as live wrote them): the list
// ends with a 0 bit, which DLU left out, so the client took the next component's first bit for it
TEST_F(ReplicaConstructionTest, MovingPlatformEndsItsSubcomponentList) {
info.lot = 2349;
Entity platform(288300744895900005, info);
auto* const component = platform.AddComponent<MovingPlatformComponent>(-1, "");
component->SetSerialized(true);
RakNet::BitStream stream;
component->Serialize(stream, true);
bool bit{};
ASSERT_TRUE(stream.Read(bit));
EXPECT_TRUE(bit); // has subcomponents
ASSERT_TRUE(stream.Read(bit));
EXPECT_FALSE(bit); // no path
ASSERT_TRUE(stream.Read(bit));
EXPECT_TRUE(bit); // a subcomponent follows
uint32_t type{};
ASSERT_TRUE(stream.Read(type));
EXPECT_EQ(type, 4u); // mover
// The mover: a 1 bit, state, desired waypoint, 2 flags, percent, position, current and next waypoint, 2 times
stream.IgnoreBits(1 + 32 + 32 + 2 + 32 + 96 + 32 + 32 + 32 + 32);
ASSERT_TRUE(stream.Read(bit));
EXPECT_FALSE(bit); // no more subcomponents
EXPECT_EQ(stream.GetNumberOfUnreadBits(), 0u);
}
// Live replicated the DestructibleComponent factionList, so a list of -1 as [-1] (12,829 constructions: vendors,
// quickbuilds, bouncers; DLU dropped it and sent []), and a row with no faction but factionList 6 as [6].
TEST_F(ReplicaConstructionTest, TemplateFactionMinusOneIsReplicated) {