fix(camera): rest-snap render position — kills the indoor doorway standing-still flicker
Root cause (pinned live, flap-churn.log at the Holtburg cottage doorway): the physics
body is byte-stable at rest (rawPlayer = 1 distinct value), but
PlayerMovementController.ComputeRenderPosition's Lerp(prev, curr, alpha) dithers the
render position by microns — the two physics-tick snapshots lag the settled body
(per-frame resolve edge-settles the resting sphere against the doorframe after the last
tick wrote curr) while the leftover-accumulator alpha varies every frame. The grazing-
doorframe camera-collision sweep (PhysicsCameraCollisionProbe.SweepEye) amplifies that
~1000x into a ~1.3 mm eye jitter (eye 17 distinct, RenderPosition 15 distinct) that trips
the PortalVisibilityBuilder clip -> the standing-still flicker (blue void / grass over the
cellar entrance) the user reported.
Fix: at rest (body velocity below RestVelocityEpsilonSq) render AT the authoritative
byte-stable body position instead of interpolating between two stale tick snapshots, so the
camera's pivot input is byte-stable and the sweep output stops jittering. Mirrors retail (a
resting object renders bit-stable) + the boom convergence snap
(RetailChaseCamera.ApplyConvergenceSnap, d2212cf), one layer earlier. Sub-tick interpolation
is preserved during motion (velocity above epsilon).
This SUPERSEDES the committed bounded-propagation plan: the live pin proved ZERO portal
re-enqueue churn during the flap (maxPop=1 across 13k oscillating frames; 0/63k reciprocals
ever clipped empty), so the flap was never the churn the spec hypothesized. The
ACDREAM_PROBE_PORTAL_CHURN apparatus did its job (refuted the hypothesis before the wrong
fix was built); plan/spec/memory updates to follow.
TDD: extracted the rest-snap into an internal-static pure ComputeRenderPosition; RED rest-
snap test (stale prev!=curr + varying alpha dithers) -> GREEN after the gate; motion test
guards interpolation; precondition test confirms a settled body's velocity is below the
gate threshold. 29 controller+cellar + 62 camera+portal tests green, no regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b3a9884dff
commit
cd974b29bc
2 changed files with 91 additions and 1 deletions
|
|
@ -810,7 +810,30 @@ public sealed class PlayerMovementController
|
|||
private Vector3 ComputeRenderPosition()
|
||||
{
|
||||
float alpha = Math.Clamp(_physicsAccum / PhysicsBody.MinQuantum, 0f, 1f);
|
||||
return Vector3.Lerp(_prevPhysicsPos, _currPhysicsPos, alpha);
|
||||
return ComputeRenderPosition(_prevPhysicsPos, _currPhysicsPos, _body.Position, _body.Velocity, alpha);
|
||||
}
|
||||
|
||||
// Render-position rest-snap (2026-06-08, indoor doorway flap). At rest the authoritative
|
||||
// body position is byte-stable, but the two physics-tick snapshots (prev/curr) can lag it by
|
||||
// microns — the per-frame resolve edge-settles the resting sphere against doorframe geometry
|
||||
// after the last tick wrote curr — so Lerp(prev, curr, alpha) with a per-frame-VARYING
|
||||
// leftover-accumulator alpha dithers the render position by microns. The grazing-doorframe
|
||||
// camera-collision sweep (PhysicsCameraCollisionProbe.SweepEye) amplifies that ~1000x into a
|
||||
// ~1.3 mm eye jitter that trips the portal-flood clip → the standing-still indoor flicker
|
||||
// (pinned live, flap-churn.log: rawPlayer 1 distinct, RenderPosition 15 distinct, eye 17).
|
||||
// When the body is at rest (velocity below epsilon) render AT the authoritative position so
|
||||
// the camera's pivot input is byte-stable. Mirrors retail (a resting object renders
|
||||
// bit-stable) + the boom convergence snap (RetailChaseCamera.ApplyConvergenceSnap, d2212cf),
|
||||
// one layer earlier. Interpolation between tick snapshots is preserved during motion
|
||||
// (velocity above epsilon), so sub-tick movement stays smooth.
|
||||
internal const float RestVelocityEpsilonSq = 1e-4f; // (0.01 m/s)^2 — below this the body is at rest
|
||||
|
||||
internal static Vector3 ComputeRenderPosition(
|
||||
Vector3 prevPhysicsPos, Vector3 currPhysicsPos, Vector3 bodyPosition, Vector3 bodyVelocity, float alpha)
|
||||
{
|
||||
if (bodyVelocity.LengthSquared() < RestVelocityEpsilonSq)
|
||||
return bodyPosition; // at rest: render at the authoritative byte-stable position
|
||||
return Vector3.Lerp(prevPhysicsPos, currPhysicsPos, alpha);
|
||||
}
|
||||
|
||||
public MovementResult Update(float dt, MovementInput input)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue