A fraction of zero is a place, not an absence

The drawn pod stalled about fifteen times in 293 frames while the
simulation stepped perfectly smoothly through the same window - and 15 of
293 is 5%, which is exactly the count of frames the earlier trace found
sitting at a render fraction of zero. That was the whole clue.

GetRenderToWorld tested the fraction to decide whether to interpolate at
all:

    if (renderStepFraction <= 0 || !RenderInterpolationEnabled())
        *out = localToWorld;

Drawing at fraction f means drawing at the start of the step plus f of it,
so f = 0 means the START of the step. localToWorld is its END. The two are
a whole step apart, about a metre at racing speed.

behind is a whole number of milliseconds against a 20ms step, so it lands
on exactly zero roughly one frame in twenty. On those frames the pod was
drawn a full step ahead of itself and snapped back on the next one. Three
times a second at 59fps, regular because the beat between frame rate and
step rate is regular, and worst when a pod crosses the view quickly -
which is the symptom as it was first described, and it took this long to
find because every simulation trace was right. Only the drawing was wrong.

Ask renderStepTaken instead, which is what the condition meant all along.
Interpolating at f = 0 is continuous with its neighbours: each frame
advances the drawn position by frame_time / step whether or not a step
boundary falls between the two, which is the entire point.

The same mistake was in DPLEyeRenderable's rebuild gate, using the
fraction as a proxy for whether interpolation was running. Same fix.

renderStepTaken is cleared where localOrigin is assigned outside the step
loop, so a stale snapshot is never blended from.

Render path only - localOrigin is untouched, so physics, collisions and
determinism are unaffected.

The foreign-eye rejection added in the previous build turned out not to be
the cause: it rejected between zero and three samples per window against
stall counts in the twenties. Keeping it, since sampling one viewpoint
against another was still wrong, but it was not this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-11 11:15:53 -05:00
co-authored by Claude Opus 5
parent d680cce5a2
commit b0b40559d5
3 changed files with 43 additions and 5 deletions
+33 -4
View File
@@ -506,6 +506,13 @@ void
{
localOrigin = updateOrigin;
localToWorld = localOrigin;
//
// Set outside the step loop, so renderPreviousOrigin now
// describes a step that never happened. Draw plainly until
// a real one does.
//
renderStepTaken = False;
}
Simulation::ReadUpdateRecord(record);
}
@@ -1137,6 +1144,7 @@ Entity::Entity(
//
renderPreviousOrigin = localOrigin;
renderStepFraction = (Scalar) 0;
renderStepTaken = False;
// initialize camera stuff
cameraOffset = Origin::Identity;
@@ -1485,6 +1493,7 @@ void
{
Check(this);
renderPreviousOrigin = localOrigin;
renderStepTaken = True;
}
void
@@ -1501,11 +1510,31 @@ void
Check_Pointer(out);
//
// Nothing to blend towards: no fraction of a step outstanding, no
// fixed step at all, or interpolation switched off. Hand back exactly
// what every caller used before this existed.
// No fixed step at all, interpolation switched off, or this entity has
// not taken a step yet. Hand back exactly what every caller used before
// this existed.
//
if (renderStepFraction <= (Scalar) 0 || !RenderInterpolationEnabled())
// The test used to be on the fraction rather than on renderStepTaken,
// and that was wrong in a way that showed. Drawing at fraction f means
// drawing at the start of the step plus f of it, so f = 0 means the
// START of the step - while this early return hands back localToWorld,
// which is its END. The two are a whole step apart, about a metre at
// racing speed.
//
// behind is a whole number of milliseconds against a 20ms step, so it
// lands on exactly zero about one frame in twenty. On those frames a
// pod was drawn a full step ahead of itself and then snapped back on
// the next one: a jump three times a second at 59fps, regular because
// the beat between frame rate and step rate is regular, and worst when
// a pod crosses the view quickly. It never appeared in any simulation
// trace because the simulation was right - only the drawing was wrong.
//
// Interpolating at f = 0 is continuous with everything either side of
// it. Each frame advances the drawn position by frame_time / step
// whether or not a step boundary falls between the two, which is the
// entire point of doing it.
//
if (!RenderInterpolationEnabled() || !renderStepTaken)
{
*out = localToWorld;
return;
+9
View File
@@ -177,6 +177,15 @@ public:
Origin renderPreviousOrigin;
Scalar renderStepFraction;
//
// Whether renderPreviousOrigin describes a step this entity actually
// took. It has to be asked separately from the fraction, because a
// fraction of zero is a perfectly ordinary place to be drawing - see
// GetRenderToWorld for the one-step jump that testing the fraction
// instead used to produce.
//
Logical renderStepTaken;
//
// The transform to DRAW with. Falls back to localToWorld verbatim when
// interpolation is off, when there is no fixed step to interpolate
+1 -1
View File
@@ -5973,7 +5973,7 @@ void
// whenever interpolation is off or idle, so the old behaviour is
// untouched. See Entity::GetRenderToWorld.
//
if((myEyepointRotation && *myEyepointRotation != oldEyepointRotation) || oldLocalToWorld != myEntity->localToWorld || mForceUpdate || myEntity->renderStepFraction > (Scalar) 0)
if((myEyepointRotation && *myEyepointRotation != oldEyepointRotation) || oldLocalToWorld != myEntity->localToWorld || mForceUpdate || myEntity->renderStepTaken)
{
mForceUpdate = false;
oldLocalToWorld = myEntity->localToWorld;