A fried pod stops being anybody's vehicle
Caught by the podium rig on the ninth launch, under full page heap, after
eight clean ones - the first free named by the debugger rather than inferred,
which is what that rig exists for.
Player::playerVehicle is a bare Entity* that nothing ever cleared. When a
vehicle is destroyed it goes on death row, and FryDeathRowTask deletes one
entity per frame from it. The Player that was driving it keeps pointing at
the freed block. A replicant Player runs GoToVehicle EVERY frame - that is
its whole performance, reading playerVehicle->localOrigin to follow its pod
around the map - so from the moment the vehicle was fried, every frame read
freed memory.
It normally gets away with it, because the block is still mapped and still
holds plausible-looking garbage. That is its own bug hiding inside this one:
a remote pod quietly following coordinates that are no longer anything.
Under page heap the block is decommitted instead of merely stale, so it
faults at once:
ExceptionAddress: rpl4opt!Quaternion::operator=
[inlined in Player::GoToVehicle+0xe]
reading 31e08aea - a freed allocation whose free stack is
operator delete <- VTV::`scalar deleting destructor'
<- FryDeathRowTask::Execute
while `this` was a live, busy RPPlayer built from a network packet
(RPPlayer::Make <- Registry::MakeEntityMessageHandler <-
InterestManager::NewDynamicEntityHandler) - a REMOTE player, exactly the one
whose performance is GoToVehicle.
So the vehicle stops being anybody's vehicle as it is condemned. Done there
rather than in ~Entity on purpose: at condemn time the entity is still whole
and every group is still standing, and it happens exactly once - a destructor
doing this would also run during Shutdown, when the Players group may already
be gone.
NOT yet verified as fixed: the fault appeared once in nine launches, so
showing it gone needs a run of twenty-odd under page heap, which needs the
elevated gflags step. The trap log and the tally are kept in
playtestlogs\podium-trap-2026-08-14 (gitignored), and the 752 MB dump is at
%TEMP%\rp412-podium-repro\podB\.
Whether this is ALSO the original podium teardown crash is untested. It fits
- a VTV freed while others still hold it would be deleted a second time at
Shutdown, giving ~JointedMover a segment table full of reused memory, which
is the vtable-dword-holding-a-small-float signature those dumps had - but
that is a hypothesis, and the trapped stack is not that stack.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1483,6 +1483,45 @@ void
|
|||||||
//
|
//
|
||||||
if (!IsCondemned())
|
if (!IsCondemned())
|
||||||
{
|
{
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Stop being anybody's vehicle before going on the row.
|
||||||
|
//
|
||||||
|
// Player::playerVehicle is a bare pointer that nothing ever
|
||||||
|
// cleared. The task that empties this row deletes one entity per
|
||||||
|
// frame, and a replicant Player runs GoToVehicle EVERY frame -
|
||||||
|
// it reads playerVehicle->localOrigin to follow its pod around.
|
||||||
|
// So from the moment the vehicle was fried, every one of those
|
||||||
|
// frames read freed memory. It normally survives on the garbage
|
||||||
|
// still lying in the block, which is its own kind of bug: a
|
||||||
|
// remote pod following coordinates that are no longer anything.
|
||||||
|
//
|
||||||
|
// Caught by the podium rig under full page heap, where the block
|
||||||
|
// is decommitted instead of merely stale: an access violation in
|
||||||
|
// Player::GoToVehicle reading a VTV whose free stack was
|
||||||
|
// FryDeathRowTask::Execute -> VTV::`scalar deleting destructor'.
|
||||||
|
//
|
||||||
|
// Cleared here rather than in ~Entity: the entity is still whole
|
||||||
|
// at this point, the groups are all still standing, and it
|
||||||
|
// happens exactly once. A destructor doing this would run during
|
||||||
|
// Shutdown too, when the Players group may already be gone.
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
//
|
||||||
|
EntityGroup *player_group = entity_manager->FindGroup("Players");
|
||||||
|
if (player_group != NULL)
|
||||||
|
{
|
||||||
|
ChainIteratorOf<Node*> iterator(player_group->groupMembers);
|
||||||
|
Player *player;
|
||||||
|
|
||||||
|
while ((player = (Player*) iterator.ReadAndNext()) != NULL)
|
||||||
|
{
|
||||||
|
if (player->GetPlayerVehicle() == this)
|
||||||
|
{
|
||||||
|
player->ClearPlayerVehicle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
entity_manager->deathRow->Add(this);
|
entity_manager->deathRow->Add(this);
|
||||||
SetCondemnedFlag();
|
SetCondemnedFlag();
|
||||||
NeverExecute();
|
NeverExecute();
|
||||||
|
|||||||
@@ -331,6 +331,16 @@ public:
|
|||||||
playerVehicle = player_vehicle;
|
playerVehicle = player_vehicle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The vehicle is going away. SetPlayerVehicle cannot say this - it
|
||||||
|
// Checks its argument, so it will not take NULL - and every reader
|
||||||
|
// of playerVehicle tests it for NULL first, so this is all they need
|
||||||
|
// to stop following a pod that no longer exists.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
ClearPlayerVehicle()
|
||||||
|
{Check(this); playerVehicle = NULL;}
|
||||||
|
|
||||||
Mission*
|
Mission*
|
||||||
GetMission()
|
GetMission()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user