diff --git a/MUNGA/MOVER.cpp b/MUNGA/MOVER.cpp index 827c795..64d3d8e 100644 --- a/MUNGA/MOVER.cpp +++ b/MUNGA/MOVER.cpp @@ -36,6 +36,20 @@ EntityID return gTracedEntity; } +// +// Prediction-error totals for the RP412CAMLOG trace. Shared across +// replicants deliberately: the question - does constant-velocity +// extrapolation hold over one send interval - is about the model, not +// about any one pod, so a whole grid contributing samples is a better +// answer rather than a muddled one. +// +static int gPredictSamples = 0; +static Scalar gPredictAlong = 0.0f; +static Scalar gPredictAlongAbs = 0.0f; +static Scalar gPredictAcross = 0.0f; +static Scalar gPredictMilliseconds = 0.0f; +static Scalar gPredictNextSay = 0.0f; + // // Bounds on the replication interval estimate, in seconds. // @@ -1177,6 +1191,107 @@ void ResetUpdateIntervals(); } + // + // RP412CAMLOG: is constant-velocity extrapolation actually + // accurate over one interval, or is the pod manoeuvring? + // + // The corrections measured 0.25 to 0.66m against a step of + // about a metre, which is what collapses one step to a third + // and shows as the tick. At 52 m/s half a metre is ten + // milliseconds of travel, so the question is whether we are + // evaluating the projection at the wrong INSTANT or whether the + // pod simply is not going in a straight line. + // + // This settles it without involving any clock we do not trust: + // take the position and velocity the sender reported last time, + // carry them forward by the difference between the two SENDER + // timestamps, and compare against the position the sender + // reports now. Both stamps come from the same machine, so + // latency, clock offset and RP412NETCLOCK play no part - it + // measures the prediction and nothing else. + // + // Split the error along the direction of travel and across it. + // Error ALONG the path is time: divided by speed it IS the + // number of milliseconds the window is out by, and its sign + // says which way. Error ACROSS the path cannot be a timing + // problem at all - that is a pod turning, and no clock fix + // would touch it. + // + if (RPCameraLog()) + { + UpdateRecord *sample = (UpdateRecord*)record; + + if (haveSenderSample) + { + Scalar dt = sample->timeStamp - senderStamp; + Scalar speed = senderVelocity.Length(); + + if (dt > 0.001f && dt < 1.0f && speed > 1.0f) + { + Vector3D error; + error.x = sample->localOrigin.linearPosition.x + - (senderPosition.x + senderVelocity.x * dt); + error.y = sample->localOrigin.linearPosition.y + - (senderPosition.y + senderVelocity.y * dt); + error.z = sample->localOrigin.linearPosition.z + - (senderPosition.z + senderVelocity.z * dt); + + Scalar along = + (error.x * senderVelocity.x + + error.y * senderVelocity.y + + error.z * senderVelocity.z) / speed; + + Vector3D across; + across.x = error.x - (senderVelocity.x / speed) * along; + across.y = error.y - (senderVelocity.y / speed) * along; + across.z = error.z - (senderVelocity.z / speed) * along; + + gPredictSamples++; + gPredictAlong += along; + gPredictAlongAbs += (along < 0.0f) ? -along : along; + gPredictAcross += across.Length(); + gPredictMilliseconds += (along / speed) * 1000.0f; + + Scalar now_say = (Scalar) Now(); + + if (now_say >= gPredictNextSay) + { + if (gPredictNextSay > 0.0f && gPredictSamples > 0) + { + Scalar mean_along = gPredictAlong / gPredictSamples; + Scalar mean_across = gPredictAcross / gPredictSamples; + Scalar mean_ms = + gPredictMilliseconds / gPredictSamples; + + DEBUG_STREAM << "CamLog: prediction error - " + << gPredictSamples << " intervals, along " + << mean_along << "m (" << mean_ms + << "ms of travel), across " << mean_across + << "m, verdict " + << (((mean_along < 0.0f ? -mean_along : mean_along) + > mean_across * 2.0f) + ? "TIMING - the window is off" + : ((mean_across + > (mean_along < 0.0f ? -mean_along : mean_along) * 2.0f) + ? "MANOEUVRE - the pod is turning" + : "mixed")) + << "\n" << std::flush; + } + gPredictNextSay = now_say + 5.0f; + gPredictSamples = 0; + gPredictAlong = 0.0f; + gPredictAlongAbs = 0.0f; + gPredictAcross = 0.0f; + gPredictMilliseconds = 0.0f; + } + } + } + senderStamp = sample->timeStamp; + senderPosition = sample->localOrigin.linearPosition; + senderVelocity = sample->worldLinearVelocity; + haveSenderSample = True; + } + // //--------------------------------------- // Handle updating the entity information @@ -2199,6 +2314,12 @@ Mover::Mover( widestGap = 0.0f; longGapCount = 0; queuedGapCount = 0; + haveSenderSample = False; + senderStamp = lastUpdate; + senderPosition = localOrigin.linearPosition; + senderVelocity.x = 0.0f; + senderVelocity.y = 0.0f; + senderVelocity.z = 0.0f; normalizeCount = 0; if (IsInitialStasis()) diff --git a/MUNGA/MOVER.h b/MUNGA/MOVER.h index 3073d17..86220db 100644 --- a/MUNGA/MOVER.h +++ b/MUNGA/MOVER.h @@ -324,6 +324,21 @@ protected: 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