A scripted lap - full throttle, a steer, a crash at speed, the burn, the tumble, death, respawn, a second crash, a second respawn - now plays out bit-identical between identical runs and across 30 and 144 fps under RP412PHYSICSHZ. Ninety of ninety samples exact in the repro pair, sixty of sixty across frame rates, max difference 0.000000. The crash was already deterministic; this makes the RECOVERY deterministic, and it took five pieces, every one found by measurement: - The respawn teleport moves onto the vehicle's own step grid. VTV::ScheduleRespawn stores it and BeginStep applies it at the first step whose clock reaches the due time, teleport and turn-toward-goal together, because the goal flip reads the POST-reset heading. The old path applied the Reset from the event queue, which runs on wall clock, and identical runs diverged on the first step after the pod stood back up. - The handler keeps its Reset for the FIRST spawn of a mission, gated by a flag rather than by mode. A Mover is born in StasisState and the first Reset is what wakes it; gating on "is fixed stepping on" - the first attempt - skipped that wake-up and parked the pod frozen at its spawn point for an entire race. The scripted-lap harness caught it in one run. - The vehicle stamps its own death clock, at the single site that sets BurningState - inside the step machinery, which is why the crash measured exact. The schedule anchors to the death, the last step-exact event in the chain. - The due time is quantized to a half-second grid ANCHORED AT THE DEATH. The instrument showed the naive anchor was four seconds stale by scheduling time: the fry chain reposts itself at wall-clock Now()+2.0 and the drop-zone reply lands about five sim-seconds after death, jittered by a few steps of queue timing. Firing "next step" inherited that jitter whole. Rounding up to the next half-second after the death puts hundredths of jitter against tenths of headroom, so every run lands in the same cell - and the felt delay stays the six-ish seconds it has always been. - The out-of-world tumble draws from a per-vehicle random stream seeded by creation order. The global Random is shared with the frame loop's consumers - particles, mostly - so its position at the moment a burning pod drew from it depended on how many frames had rendered, and the kick went straight into angular velocity. Last wall-clocked input in the whole death cycle. The respawn scheduling and firing log under RP412PHYSTRACE in run-comparable terms - pad identity, due offset, lateness - because those lines are what cracked this: "due in -4.06 sim-s" said more in one glance than three rounds of hypothesis. Still outside the claim: multi-vehicle contact (DynamicBounce writes the victim's state from the striker's step) and network play. That is the lockstep frontier, and it now has a harness waiting for it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
430 lines
9.0 KiB
C++
430 lines
9.0 KiB
C++
#pragma once
|
|
|
|
#include "..\munga\player.h"
|
|
#include "..\munga\damage.h"
|
|
#include "..\munga\vchain.h"
|
|
|
|
#define POINTS_SPEED_RATIO 400
|
|
|
|
class ScoreZone__ReplyMessage;
|
|
class DropZone__ReplyMessage;
|
|
class RPPlayer;
|
|
|
|
//##########################################################################
|
|
// struct Offensive Player
|
|
//
|
|
|
|
class RPPlayer__OffensivePlayer:
|
|
public Node
|
|
{
|
|
public:
|
|
RPPlayer__OffensivePlayer()
|
|
{}
|
|
RPPlayer__OffensivePlayer(
|
|
RPPlayer *offensive_player,
|
|
int damage_type,
|
|
Time time_stamp
|
|
);
|
|
|
|
//
|
|
//-----------------------------------
|
|
// The player who caused the damage
|
|
//-----------------------------------
|
|
//
|
|
RPPlayer
|
|
*offensivePlayer;
|
|
//
|
|
//---------------------------------------------------------------
|
|
// How many points the offensive player gets if this players dies
|
|
//---------------------------------------------------------------
|
|
//
|
|
Scalar
|
|
deathBonus;
|
|
//
|
|
//------------------------------------------------
|
|
// The type of damage the offensive player caused
|
|
//------------------------------------------------
|
|
//
|
|
int
|
|
damageType;
|
|
//
|
|
//-------------------------------
|
|
// Time the damage was inflicted
|
|
//-------------------------------
|
|
//
|
|
Time
|
|
timeStamp;
|
|
};
|
|
|
|
//##########################################################################
|
|
//#################### RPPlayer::StatusMessage #########################
|
|
//##########################################################################
|
|
|
|
class RPPlayer__StatusMessage:
|
|
public Player__StatusMessage
|
|
{
|
|
public:
|
|
RPPlayer__StatusMessage();
|
|
|
|
RPPlayer__StatusMessage(
|
|
RPPlayer *player_involved,
|
|
int message_type,
|
|
Scalar display_time,
|
|
Scalar damage_amount = 0.0f
|
|
);
|
|
|
|
enum {
|
|
DestroyedMessage = Player__StatusMessage::NextMessageType,
|
|
DestroyedByMessage,
|
|
RunnerMessage,
|
|
BlockerMessage,
|
|
CrusherMessage,
|
|
ScoreZoneMessage,
|
|
PilotErrorMessage,
|
|
Score2xMessage,
|
|
Score4xMessage,
|
|
Score8xMessage,
|
|
Score16xMessage,
|
|
CrushedMessage = Player__StatusMessage::NextMessageType,
|
|
CrushedByMessage,
|
|
BlockedMessage = Player__StatusMessage::NextMessageType,
|
|
BlockedByMessage,
|
|
DingedMessage = Player__StatusMessage::NextMessageType,
|
|
DingedByMessage,
|
|
NextMessageType
|
|
};
|
|
|
|
Scalar
|
|
damageAmount;
|
|
|
|
//------------------------------------------------------
|
|
// Memory block utilization
|
|
//------------------------------------------------------
|
|
private:
|
|
static MemoryBlock* GetAllocatedMemory();
|
|
enum
|
|
{
|
|
numberOfAllocatedBlocks=8 // seven kills and a suicide!!!
|
|
};
|
|
|
|
public:
|
|
void*
|
|
operator new(size_t)
|
|
{return GetAllocatedMemory()->New();}
|
|
void
|
|
operator delete(void *where)
|
|
{ GetAllocatedMemory()->Delete(where);}
|
|
};
|
|
|
|
//##########################################################################
|
|
//##################### RPPlayer::MakeMessage ########################
|
|
//##########################################################################
|
|
|
|
class RPPlayer__MakeMessage:
|
|
public Player::MakeMessage
|
|
{
|
|
public:
|
|
int
|
|
teamID;
|
|
|
|
Enumeration
|
|
playerType;
|
|
|
|
Logical
|
|
scoreCompression;
|
|
|
|
RPPlayer__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,
|
|
int player_type,
|
|
int team_ID,
|
|
Logical score_compression
|
|
):
|
|
Player::MakeMessage(
|
|
message_ID,
|
|
length,
|
|
class_ID,
|
|
owner_ID,
|
|
resource_ID,
|
|
instance_flags,
|
|
origin,
|
|
player_bitmap_index
|
|
),
|
|
teamID(team_ID)
|
|
{ playerType = player_type; scoreCompression = score_compression;}
|
|
|
|
RPPlayer__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,
|
|
int player_type,
|
|
int team_ID,
|
|
Logical score_compression
|
|
):
|
|
Player::MakeMessage(
|
|
message_ID,
|
|
length,
|
|
entity_ID,
|
|
class_ID,
|
|
owner_ID,
|
|
resource_ID,
|
|
instance_flags,
|
|
origin,
|
|
player_bitmap_index
|
|
),
|
|
teamID(team_ID)
|
|
{ playerType = player_type; scoreCompression = score_compression;}
|
|
};
|
|
|
|
class RPPlayer__ScoreMessage:
|
|
public Player::ScoreMessage
|
|
{
|
|
public:
|
|
int
|
|
scoreType;
|
|
|
|
EntityID
|
|
pointSender;
|
|
|
|
RPPlayer__ScoreMessage(
|
|
Receiver::MessageID message_ID,
|
|
size_t length,
|
|
int score_type,
|
|
Scalar score_award,
|
|
EntityID point_sender
|
|
):
|
|
Player::ScoreMessage(message_ID, length, score_award)
|
|
{
|
|
scoreType = score_type;
|
|
pointSender = point_sender;
|
|
}
|
|
};
|
|
|
|
|
|
//##########################################################################
|
|
//########################### Player ################################
|
|
//##########################################################################
|
|
|
|
class RPPlayer:
|
|
public Player
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data support
|
|
//
|
|
public:
|
|
static Derivation *GetClassDerivations();
|
|
static SharedData DefaultData;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Message Support
|
|
//
|
|
enum {
|
|
ScoreZoneReplyMessageID = Player::NextMessageID,
|
|
NextMessageID
|
|
};
|
|
private:
|
|
static const HandlerEntry MessageHandlerEntries[];
|
|
|
|
protected:
|
|
//static MessageHandlerSet MessageHandlers;
|
|
static MessageHandlerSet& GetMessageHandlers();
|
|
void MissionEndingMessageHandler(Message *message);
|
|
void MissionStartingMessageHandler(Message *message);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Vehicle creation
|
|
//
|
|
public:
|
|
typedef RPPlayer__ScoreMessage ScoreMessage;
|
|
typedef RPPlayer__OffensivePlayer OffensivePlayer;
|
|
typedef RPPlayer__MakeMessage MakeMessage;
|
|
typedef RPPlayer__StatusMessage RPStatusMessage;
|
|
|
|
protected:
|
|
virtual void
|
|
DropZoneReplyMessageHandler(
|
|
DropZone__ReplyMessage *message
|
|
);
|
|
|
|
virtual void
|
|
ScoreZoneReplyMessageHandler(
|
|
ScoreZone__ReplyMessage *message
|
|
);
|
|
|
|
void
|
|
VehicleDeadMessageHandler (VehicleDeadMessage *message);
|
|
|
|
virtual void
|
|
TakeDamageMessageHandler (TakeDamageMessage *message);
|
|
|
|
virtual void
|
|
ScoreMessageHandler(ScoreMessage *message);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Damage Points Support
|
|
//
|
|
|
|
VChainOf<OffensivePlayer*, Enumeration>
|
|
offensivePlayerList;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute Support
|
|
//
|
|
public:
|
|
enum{
|
|
GoalEntityAttributeID = Player::NextAttributeID,
|
|
NextAttributeID
|
|
};
|
|
|
|
Entity
|
|
*goalEntity;
|
|
|
|
//
|
|
// True between ResetAfterDeath handing the recovery to the vehicle's
|
|
// step grid (fixed-step only) and the bookkeeping message coming back
|
|
// round. The message handler must NOT Reset the vehicle again in that
|
|
// window - but it MUST still Reset on the first spawn of the mission,
|
|
// which is what wakes a Mover out of its initial stasis. Gating on
|
|
// "is fixed stepping on" instead of on this flag skipped that wake-up
|
|
// and froze the pod on its pad for the whole race.
|
|
//
|
|
Logical
|
|
respawnScheduled;
|
|
|
|
private:
|
|
|
|
static const IndexEntry AttributePointers[];
|
|
|
|
protected:
|
|
|
|
//static AttributeIndexSet AttributeIndex
|
|
static AttributeIndexSet& GetAttributeIndex();
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Model Support
|
|
//
|
|
public:
|
|
typedef void
|
|
(RPPlayer::*Performance)(Scalar time_slice);
|
|
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
|
|
void
|
|
PlayerSimulation(Scalar time_slice);
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
static RPPlayer*
|
|
Make(MakeMessage *creation_message);
|
|
|
|
RPPlayer(
|
|
MakeMessage *creation_message,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
~RPPlayer();
|
|
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Local data
|
|
//
|
|
public:
|
|
enum PlayerType{
|
|
GeneralPlayerType,
|
|
RunnerPlayerType,
|
|
CrusherPlayerType,
|
|
BlockerPlayerType,
|
|
CameraShipPlayerType
|
|
} playerType;
|
|
|
|
enum {
|
|
NullPointType = 0,
|
|
ScoreZonePointType,
|
|
DeathBonusPointType,
|
|
DeathScoreLossPointType,
|
|
DamageBonusPointType,
|
|
DamageScoreLossPointType,
|
|
SpeedBonusPointType,
|
|
CrusherScorePointType,
|
|
DropZoneHitType,
|
|
DropZoneKillType,
|
|
OutOfBoundsType,
|
|
PointTypeCount
|
|
};
|
|
|
|
virtual void
|
|
AddOffensivePlayer(OffensivePlayer &offensive_player);
|
|
|
|
int
|
|
teamID;
|
|
|
|
int
|
|
CalcRanking();
|
|
|
|
void
|
|
CalcFootballRanking();
|
|
|
|
virtual Vector3D
|
|
GetWorldLinearVelocity()
|
|
{return Vector3D(0,0,0);}
|
|
|
|
protected:
|
|
//
|
|
// protected Interface
|
|
//
|
|
|
|
void
|
|
DeleteAllOffensivePlayers();
|
|
|
|
RPPlayer*
|
|
GetPlayer(EntityID playerID);
|
|
|
|
void
|
|
CleanUpOffensivePlayers();
|
|
|
|
void
|
|
CreatePlayerVehicle(Origin);
|
|
|
|
void
|
|
ResetAfterDeath(DropZone__ReplyMessage *message);
|
|
|
|
void
|
|
InitializePlayerLink();
|
|
|
|
void
|
|
SetGoalToFirstScoreZone();
|
|
|
|
virtual void
|
|
PointVTVTowardGoal();
|
|
|
|
void
|
|
AccumulateSpeedPoints(Scalar time_slice);
|
|
|
|
Logical
|
|
scoreCompression;
|
|
|
|
Time
|
|
lastConsoleUpdate;
|
|
};
|