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>
472 lines
9.4 KiB
C++
472 lines
9.4 KiB
C++
#pragma once
|
|
|
|
#include "entity.h"
|
|
#include "icom.h"
|
|
#include "scnrole.h"
|
|
|
|
class Mission;
|
|
class DropZone__ReplyMessage;
|
|
|
|
//##########################################################################
|
|
//##################### Player::MakeMessage ##########################
|
|
//##########################################################################
|
|
class Player__MakeMessage:
|
|
public Entity::MakeMessage
|
|
{
|
|
public:
|
|
|
|
int
|
|
playerBitmapIndex;
|
|
|
|
Player__MakeMessage(
|
|
Receiver::MessageID message_ID,
|
|
size_t length,
|
|
Entity::ClassID class_ID,
|
|
const EntityID &owner_ID,
|
|
ResourceDescription::ResourceID resource_ID,
|
|
LWord instance_flags,
|
|
const Origin &origin,
|
|
int player_bitmap_index
|
|
):
|
|
Entity::MakeMessage(
|
|
message_ID,
|
|
length,
|
|
class_ID,
|
|
owner_ID,
|
|
resource_ID,
|
|
instance_flags,
|
|
origin
|
|
),
|
|
playerBitmapIndex(player_bitmap_index)
|
|
{ }
|
|
|
|
Player__MakeMessage(
|
|
Receiver::MessageID message_ID,
|
|
size_t length,
|
|
const EntityID &entity_ID,
|
|
Entity::ClassID class_ID,
|
|
const EntityID &owner_ID,
|
|
ResourceDescription::ResourceID resource_ID,
|
|
LWord instance_flags,
|
|
const Origin &origin,
|
|
int player_bitmap_index
|
|
):
|
|
Entity::MakeMessage(
|
|
message_ID,
|
|
length,
|
|
entity_ID,
|
|
class_ID,
|
|
owner_ID,
|
|
resource_ID,
|
|
instance_flags,
|
|
origin
|
|
),
|
|
playerBitmapIndex(player_bitmap_index)
|
|
{ }
|
|
|
|
};
|
|
|
|
//##########################################################################
|
|
//################### Player::UpdateRecord ####################
|
|
//##########################################################################
|
|
|
|
struct Player__UpdateRecord :
|
|
public Entity::UpdateRecord
|
|
{
|
|
Scalar
|
|
currentScore;
|
|
Point3D
|
|
dropZoneLocation;
|
|
};
|
|
|
|
//##########################################################################
|
|
//################# ScoreZone::ReplyMessage ##########################
|
|
//##########################################################################
|
|
|
|
class Player__ScoreMessage :
|
|
public Entity::Message
|
|
{
|
|
public:
|
|
Scalar
|
|
scoreAward;
|
|
|
|
Player__ScoreMessage(
|
|
Receiver::MessageID message_ID,
|
|
size_t length,
|
|
Scalar score
|
|
):
|
|
Entity::Message(message_ID, length),
|
|
scoreAward(score)
|
|
{}
|
|
};
|
|
|
|
//##########################################################################
|
|
//################# ScoreZone::ReplyMessage ##########################
|
|
//##########################################################################
|
|
|
|
class Player__VehicleDeadMessage:
|
|
public Entity::Message
|
|
{
|
|
public:
|
|
int deathCount;
|
|
EntityID dropZoneID;
|
|
|
|
Player__VehicleDeadMessage(
|
|
Receiver::MessageID message_ID,
|
|
size_t length,
|
|
const EntityID &dropzone_id=EntityID::Null
|
|
):
|
|
Entity::Message(message_ID, length),
|
|
deathCount(-1),
|
|
dropZoneID(dropzone_id)
|
|
{}
|
|
};
|
|
|
|
//##########################################################################
|
|
//###################### Player::StatusMessage #########################
|
|
//##########################################################################
|
|
class Player;
|
|
|
|
class Player__StatusMessage:
|
|
public Node
|
|
{
|
|
public:
|
|
Player__StatusMessage(
|
|
Player *player_involved,
|
|
int message_type,
|
|
Scalar display_time
|
|
);
|
|
|
|
enum {
|
|
NoMessage = -1,
|
|
NextMessageType = 0
|
|
};
|
|
|
|
int
|
|
messageType;
|
|
Player
|
|
*playerInvolved;
|
|
Scalar
|
|
displayTime;
|
|
};
|
|
|
|
//##########################################################################
|
|
//########################### Player ################################
|
|
//##########################################################################
|
|
|
|
class Player:
|
|
public Entity
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data support
|
|
//
|
|
public:
|
|
static Derivation *GetClassDerivations();
|
|
static SharedData
|
|
DefaultData;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute Support
|
|
//
|
|
enum
|
|
{
|
|
CurrentScoreAttributeID = Entity::NextAttributeID,
|
|
DropZoneLocationAttributeID,
|
|
PlayerRankingAttributeID,
|
|
StatusMessagePointerAttributeID,
|
|
PlayerBitmapIndexAttributeID,
|
|
PlayerHighlightedAttributeID,
|
|
NextAttributeID
|
|
};
|
|
|
|
private:
|
|
|
|
static const IndexEntry
|
|
AttributePointers[];
|
|
|
|
protected:
|
|
|
|
//static AttributeIndexSet AttributeIndex;
|
|
static AttributeIndexSet& GetAttributeIndex();
|
|
|
|
virtual int
|
|
CalcRanking();
|
|
|
|
public:
|
|
|
|
Logical
|
|
playerHighlighted;
|
|
|
|
Scalar
|
|
currentScore;
|
|
int
|
|
playerRanking;
|
|
|
|
Point3D
|
|
dropZoneLocation;
|
|
|
|
Player__StatusMessage
|
|
*statusMessagePointer;
|
|
|
|
int
|
|
playerBitmapIndex;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Gauge Support
|
|
//
|
|
public:
|
|
typedef Player__StatusMessage
|
|
StatusMessage;
|
|
void
|
|
AddStatusMessage(StatusMessage *status_message);
|
|
void
|
|
StatusMessageUpdate(Scalar time_slice);
|
|
|
|
protected:
|
|
ChainOf<StatusMessage*>
|
|
statusMessageQueue;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Message Support
|
|
//
|
|
public:
|
|
enum
|
|
{
|
|
DropZoneReplyMessageID = Entity::NextMessageID,
|
|
ScoreMessageID,
|
|
VehicleDeadMessageID,
|
|
MissionStartingMessageID,
|
|
MissionEndingMessageID,
|
|
NextMessageID
|
|
};
|
|
|
|
typedef Player__VehicleDeadMessage VehicleDeadMessage;
|
|
|
|
private:
|
|
static const HandlerEntry MessageHandlerEntries[];
|
|
|
|
protected:
|
|
//static MessageHandlerSet MessageHandlers;
|
|
static MessageHandlerSet& GetMessageHandlers();
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Game start/stop support
|
|
//
|
|
public:
|
|
typedef Message MissionStartingMessage;
|
|
typedef Message MissionEndingMessage;
|
|
|
|
protected:
|
|
void
|
|
MissionStartingMessageHandler(Message *message);
|
|
void
|
|
MissionEndingMessageHandler(Message *message);
|
|
|
|
Scalar
|
|
fadeTimeRemaining;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Model Support
|
|
//
|
|
public:
|
|
|
|
enum {
|
|
DropZoneAcquiredState = Entity::StateCount,
|
|
VehicleTranslocatedState,
|
|
MissionStartingState,
|
|
MissionEndingState,
|
|
StateCount
|
|
};
|
|
|
|
typedef void
|
|
(Player::*Performance)(Scalar time_slice);
|
|
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
|
|
virtual void
|
|
PlayerSimulation(Scalar time_slice);
|
|
|
|
virtual void
|
|
CameraShipSimulation(Scalar time_slice);
|
|
|
|
void
|
|
GoToVehicle(Scalar time_slice);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Vehicle creation
|
|
//
|
|
protected:
|
|
void
|
|
DropZoneReplyMessageHandler(DropZone__ReplyMessage *message);
|
|
void
|
|
VehicleDeadMessageHandler(VehicleDeadMessage *message);
|
|
|
|
void
|
|
HuntForDropZone(Scalar time_slice);
|
|
|
|
Mission*
|
|
playerMission;
|
|
|
|
Entity*
|
|
playerVehicle;
|
|
|
|
int
|
|
deathCount;
|
|
|
|
public:
|
|
Entity*
|
|
GetPlayerVehicle()
|
|
{Check(this); return playerVehicle;}
|
|
|
|
void
|
|
SetPlayerVehicle(Entity *player_vehicle)
|
|
{
|
|
Check(player_vehicle);
|
|
Check(this);
|
|
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*
|
|
GetMission()
|
|
{
|
|
Check(this);
|
|
return playerMission;
|
|
}
|
|
|
|
int
|
|
GetDeathCount()
|
|
{Check(this); return deathCount;}
|
|
protected:
|
|
|
|
virtual void
|
|
CreatePlayerVehicle(Origin);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Renderer Support
|
|
//
|
|
public:
|
|
Enumeration
|
|
GetAudioRepresentation(Entity *linked_entity);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Intercom Support
|
|
//
|
|
public:
|
|
Icom*
|
|
GetIntercom()
|
|
{
|
|
Check(this);
|
|
return intercomPointer;
|
|
}
|
|
|
|
protected:
|
|
Icom
|
|
*intercomPointer;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Scoring Support
|
|
//
|
|
public:
|
|
typedef Player__ScoreMessage ScoreMessage;
|
|
|
|
const ScenarioRole*
|
|
GetScenarioRole() const
|
|
{Check(this); return scenarioRole; }
|
|
|
|
protected:
|
|
void
|
|
ScoreMessageHandler(ScoreMessage *message);
|
|
|
|
ScenarioRole
|
|
*scenarioRole;
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Flag Support
|
|
//
|
|
public:
|
|
enum {
|
|
NonScoringPlayerBit = Entity::NextBit,
|
|
CameraShipPlayerBit,
|
|
NextBit
|
|
};
|
|
|
|
enum {
|
|
NonScoringPlayerFlag = 1 << NonScoringPlayerBit,
|
|
CameraShipPlayerFlag = 1 << CameraShipPlayerBit,
|
|
DefaultFlags =
|
|
Entity::DefaultFlags
|
|
| NonScoringPlayerFlag
|
|
| PreRunFlag
|
|
};
|
|
|
|
void
|
|
SetNonScoringPlayerFlag()
|
|
{Check(this); simulationFlags |= NonScoringPlayerFlag;}
|
|
void
|
|
SetScoringPlayerFlag()
|
|
{Check(this); simulationFlags &= ~NonScoringPlayerFlag;}
|
|
|
|
Logical
|
|
IsScoringPlayer()
|
|
{Check(this); return (simulationFlags&NonScoringPlayerFlag) == 0;}
|
|
|
|
Logical IsCameraShipPlayer()
|
|
{
|
|
Check(this);
|
|
return (simulationFlags & CameraShipPlayerFlag) != 0;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Update Support
|
|
//
|
|
|
|
typedef Player__UpdateRecord UpdateRecord;
|
|
|
|
protected:
|
|
void
|
|
WriteUpdateRecord(
|
|
Simulation::UpdateRecord *message,
|
|
int update_model
|
|
);
|
|
void
|
|
ReadUpdateRecord(Simulation::UpdateRecord *message);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
|
|
typedef Player__MakeMessage MakeMessage;
|
|
|
|
static Player*
|
|
Make(MakeMessage *creation_message);
|
|
|
|
Player(
|
|
MakeMessage *creation_message,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
~Player();
|
|
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
protected:
|
|
|
|
void
|
|
ManageApplicationStatus(Scalar time_slice);
|
|
};
|