Files
RP412/MUNGA/MOVER.h
T
CydandClaude Opus 5 d147c093b2 Score the sender against itself
The tick is now fully accounted for. Corrections arrive about 26 times a
second per pod, mean 0.25 to 0.66m against a step of roughly a metre.
localOrigin is never snapped during a mission - only updateOrigin is
replaced - so the pod position stays continuous and its TARGET jumps. The
lerp then translates a target discontinuity into a step-size one: fifteen
steps smooth to a tenth of a percent, then a single step at 20-33%, then
recovery. Every capture has that shape and the ratios match the correction
size arithmetically.

Position is C0-continuous, so no amount of position interpolation can hide
it; the discontinuity is in the rate.

Before smoothing anything, ask whether the correction is even real. Half a
metre at 52 m/s is ten milliseconds of travel, and a dead reckoner tracking
constant velocity across a 38ms gap should be right to within centimetres.
That smells like evaluating the projection at the wrong instant rather
than like a prediction that genuinely failed.

Settle it without involving any clock we do not trust. Take the position
and velocity the sender reported last time, carry them forward by the gap
between the two SENDER timestamps, and compare against the position the
sender reports now. Both stamps come from one machine, so latency, clock
offset and RP412NETCLOCK play no part whatsoever.

Split the error along the path and across it. Along is time: divided by
speed it IS the milliseconds the window is out by, and its sign says which
way. Across cannot be a timing fault at all - that is a pod turning, and
no clock fix would touch it.

TIMING says fix the extrapolation window and the tick shrinks at the
source with no smoothing and no lag. MANOEUVRE says the corrections are
honest, the pods really are cornering, and smoothing is the only remaining
answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 10:50:53 -05:00

535 lines
12 KiB
C++

#pragma once
#include "entity.h"
#include "motion.h"
class Mover__SharedData;
class Mover;
class CollisionAssistant;
class BoundingBoxTreeNode;
class BoxedSolid;
class BoxedSolidCollision;
class BoxedSolidCollisionList;
class Normal;
class Line;
class Environment;
//##########################################################################
//#################### Mover::UpdateMessage ##########################
//##########################################################################
struct Mover__UpdateRecord:
public Entity::UpdateRecord
{
public:
Motion
localVelocity,
localAcceleration;
Vector3D
worldLinearVelocity,
worldLinearAcceleration;
};
//##########################################################################
//##################### Mover::MakeMessage ##########################
//##########################################################################
class Mover__MakeMessage:
public Entity::MakeMessage
{
public:
Motion
localVelocity,
localAcceleration;
Mover__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,
const Motion &velocity,
const Motion &acceleration
):
Entity::MakeMessage(
message_ID,
length,
class_ID,
owner_ID,
resource_ID,
instance_flags,
origin
),
localVelocity(velocity),
localAcceleration(acceleration)
{}
Mover__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,
const Motion &velocity,
const Motion &acceleration
):
Entity::MakeMessage(
message_ID,
length,
entity_ID,
class_ID,
owner_ID,
resource_ID,
instance_flags,
origin
),
localVelocity(velocity),
localAcceleration(acceleration)
{}
};
//##########################################################################
//##################### Mover::ModelResource #########################
//##########################################################################
struct Mover__ModelResource
{
Scalar moverMass;
Vector3D
momentOfInertia,
positiveLinearDragCoefficients,
negativeLinearDragCoefficients,
angularDragCoefficients;
Scalar
frictionCoefficient,
elasticityCoefficient,
minimumBounceSpeed;
};
//##########################################################################
//############################ Mover #################################
//##########################################################################
class Mover:
public Entity
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data support
//
public:
static Derivation *GetClassDerivations();
static SharedData DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support
//
public:
enum {
LocalVelocityAttributeID = Entity::NextAttributeID,
LocalAccelerationAttributeID,
WorldLinearVelocityAttributeID,
WorldLinearAccelerationAttributeID,
MoverMassAttributeID,
MomentOfInertiaAttributeID,
PositiveLinearDragCoefficientsAttributeID,
NegativeLinearDragCoefficientsAttributeID,
AngularDragCoefficientsAttributeID,
FrictionCoefficientAttributeID,
ElasticityCoefficientAttributeID,
MinimumBounceSpeedAttributeID,
NextAttributeID
};
private:
static const IndexEntry AttributePointers[];
protected:
//static AttributeIndexSet AttributeIndex
static AttributeIndexSet& GetAttributeIndex();
//
// public attribute declarations go here
//
public:
Motion
localVelocity,
localAcceleration;
Vector3D
worldLinearVelocity,
worldLinearAcceleration;
Scalar moverMass;
Vector3D
momentOfInertia,
positiveLinearDragCoefficients,
negativeLinearDragCoefficients,
angularDragCoefficients;
Scalar
frictionCoefficient,
elasticityCoefficient,
minimumBounceSpeed;
//
// virtual access
//
public:
virtual Vector3D
GetWorldLinearVelocity()
{return worldLinearVelocity;}
virtual Vector3D
GetWorldLinearAcceleration()
{return worldLinearAcceleration;}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Model Support
//
public:
enum {
StasisState = Entity::StateCount,
StateCount
};
typedef Mover__UpdateRecord UpdateRecord;
typedef Mover__MakeMessage MakeMessage;
typedef void
(Mover::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
UpdateWorldMotion();
void
UpdateLocalMotion();
void
ApplyWorldAccelerations(Scalar time_slice);
void
ApplyAirResistanceAndGravity(Scalar power=1.0f);
void
CalculateDrag(
Vector3D *drag,
const Vector3D &velocity,
const Vector3D &positive_CODs,
const Vector3D &negative_CODs,
Scalar power
);
void
ApplyLocalForce(
const Vector3D &force,
const Vector3D &moment
);
void
ApplyLocalAcceleration(
const Vector3D &acceleration,
const Vector3D &moment
);
Environment*
GetEnvironment()
{Check(this); return localEnvironment;}
typedef Logical (Mover::*DeadReckoner)();
void
SetDeadReckoner(DeadReckoner reckoner)
{Check(this); deadReckoner = reckoner;}
Logical
NoDeadReckoner();
Logical
LinearDeadReckoner();
Logical
AcceleratedDeadReckoner();
void
DeadReckon(Scalar time_slice);
protected:
void
WriteUpdateRecord(
Simulation::UpdateRecord *message,
int update_model
);
void
ReadUpdateRecord(Simulation::UpdateRecord *message);
void
PerformAndWatch(
const Time& till,
MemoryStream *update_stream
);
//
// Per-step set-up under fixed stepping: clears the force accumulator
// the thrusters add into, so each step integrates only its own
// forces. See the definition for the frame-jitter bug this closes.
//
void
BeginStep();
int
normalizeCount;
Environment
*localEnvironment;
DeadReckoner
deadReckoner;
Origin
projectedOrigin,
previousOrigin;
Motion
projectedVelocity,
updateAcceleration,
updateVelocity;
Time
nextUpdate;
//
// Recent gaps between replication updates for this entity, as a ring,
// and the running estimate drawn from them. The original code predicted
// the next gap from the single previous gap; see PredictUpdateInterval
// for why that stalls a step every time a packet runs late.
//
enum {UpdateIntervalSamples = 8};
Scalar
updateIntervals[UpdateIntervalSamples];
int
updateIntervalCount,
updateIntervalWrite;
//
// The interval last predicted, and how wrong the prediction before it
// proved to be once the gap it described actually elapsed. Per entity,
// so a trace reads the entity it is watching.
//
Scalar
predictedInterval,
predictionError;
//
// Arrival statistics for the window a trace reports over. A long gap
// followed by normal gaps means the sender went quiet; a long gap
// followed by a burst of near-zero ones means OUR loop stalled and the
// packets queued up behind it. The two look identical from inside the
// dead reckoner and want opposite fixes.
//
Scalar
widestGap;
int
longGapCount,
queuedGapCount;
//
// The last position, velocity and timestamp the SENDER reported, kept
// so an arriving update can be scored against what the one before it
// predicted. All three come from the same machine, so the comparison
// owes nothing to latency or to clock alignment.
//
Time
senderStamp;
Point3D
senderPosition;
Vector3D
senderVelocity;
Logical
haveSenderSample;
Scalar
PredictUpdateInterval(Scalar latest);
void
ResetUpdateIntervals();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Collision support
//
public:
virtual void
MoveCollisionVolume();
BoundingBoxTreeNode*
GetMoverCollisionRoot()
{Check(this); return containedByNode;}
BoxedSolidCollisionList*
AllocateCollisionList();
BoxedSolidCollisionList*
GetCurrentCollisions(BoxedSolidCollisionList *list=NULL);
BoxedSolid*
FindBoxedSolidHitBy(
Line *line,
Entity *except_by
);
BoxedSolidCollisionList*
CollideCenterOfMotion(
Line *line,
BoxedSolidCollisionList *list
);
void
ProcessCollisionList(
BoxedSolidCollisionList *collisions,
Scalar time_slice,
const Point3D &old_position,
Damage *damage
);
Scalar
StaticBounce(
const Point3D &old_position,
Scalar delta_t,
Scalar penetration,
const Normal &normal,
Scalar *elasticity,
Scalar bounce_min,
Scalar *friction
);
Scalar
DynamicBounce(
Mover *other,
Scalar delta_t,
Scalar penetration,
const Normal &normal,
Scalar *elasticity
);
BoxedSolid*
GetCollisionVolume()
{Check(this); return collisionVolume;}
BoxedSolid*
GetCollisionTemplate()
{Check(this); return collisionTemplate;}
int
GetCollisionVolumeCount()
{Check(this); return collisionVolumeCount;}
virtual void
StartCollisionAssistant();
protected:
int collisionVolumeCount;
BoxedSolid
*collisionVolume,
*collisionTemplate;
BoundingBoxTreeNode *containedByNode;
BoxedSolidCollisionList
*collisionLists,
*lastCollisionList;
CollisionAssistant *collisionAssistant;
virtual void
ProcessCollision(
Scalar time_slice,
BoxedSolidCollision &collision,
const Point3D &old_position,
Damage *damage
);
void
CheckAgainstBoxedSolidChain(
BoxedSolidCollisionList *collisions,
BoxedSolid *chain
);
public:
void
CheckVolumeAgainstBoxedSolidChain(
BoxedSolidCollisionList *collisions,
BoxedSolid *chain
);
protected:
BoxedSolid*
CheckLineAgainstBoxedSolidChain(
Line *line,
BoxedSolid *chain
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Flag Support
//
public:
enum {
NoCollisionVolumeBit = Entity::NextBit,
NoCollisionTestBit,
InitialStasisBit,
NextBit
};
enum {
NoCollisionVolumeFlag = 1<<NoCollisionVolumeBit,
NoCollisionTestFlag = 1<<NoCollisionTestBit,
InitialStasisFlag = 1<<InitialStasisBit,
DefaultFlags = DynamicFlag|MasterInstance
};
void
NoCollisionVolume()
{Check(this); simulationFlags |= NoCollisionVolumeFlag;}
void
SetCollisionVolume()
{Check(this); simulationFlags &= ~NoCollisionVolumeFlag;}
Logical
IsCollisionVolume()
{Check(this); return (simulationFlags&NoCollisionVolumeFlag) == 0;}
void
NoCollisionTest()
{Check(this); simulationFlags |= NoCollisionTestFlag;}
void
SetCollisionTest()
{Check(this); simulationFlags &= ~NoCollisionTestFlag;}
Logical
IsCollisionTestable()
{Check(this); return (simulationFlags&NoCollisionTestFlag) == 0;}
Logical
IsInitialStasis()
{Check(this); return (simulationFlags&InitialStasisFlag) != 0;}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
typedef Mover__ModelResource ModelResource;
static Mover*
Make(MakeMessage *creation_message);
//
// Warning... This function requires a properly set up strtok!!!!
//
static Logical
CreateMakeMessage(
MakeMessage *creation_message,
NotationFile *model_file,
const ResourceDirectories *directories
);
static ResourceDescription::ResourceID
CreateModelResource(
ResourceFile *resource_file,
const char* model_name,
NotationFile *model_file,
const ResourceDirectories *directories,
ModelResource* model = NULL
);
Mover(
MakeMessage *creation_message,
SharedData &virtual_data
);
~Mover();
Logical
TestInstance() const;
};
//
// The replicant the RP412CAMLOG traces are describing, so the renderer can
// report on the same pod from the other end - what its motion looks like on
// screen. Null until a replicant has stepped at least once.
//
EntityID
MoverTracedEntity();