diff --git a/MUNGA/APP.cpp b/MUNGA/APP.cpp index 66c301f..ad438b5 100644 --- a/MUNGA/APP.cpp +++ b/MUNGA/APP.cpp @@ -606,6 +606,133 @@ Time startUpdate = Now(); updateManager->Execute(start_of_frame); Time endUpdate = Now(); + // + //-------------------------------------------------------------------------- + // RP412PHYSTRACE=1: the player's position, sampled on the SIMULATION's + // own clock rather than per frame. + // + // This is the acceptance test for decoupling physics from frame rate. + // Run the same egg at two frame rates and diff the traces: today they + // diverge, because the simulation advances by whatever the last frame + // happened to cost (SIMULATE.cpp, slice = till - lastPerformance), so a + // 30 fps machine integrates in 33 ms steps and a 144 fps machine in 7 ms + // ones and they are not the same race. Fixed-step them and the two + // traces have to agree. + // + // Sampled every 0.25 s of SIM time on purpose - sampling per frame would + // compare different instants and prove nothing. + //-------------------------------------------------------------------------- + // + { + static int physTrace = -1; + + if (physTrace < 0) + { + const char *setting = getenv("RP412PHYSTRACE"); + physTrace = (setting != NULL && atoi(setting) != 0) ? 1 : 0; + } + if (physTrace && GetApplicationState() == RunningMission) + { + static Logical traceStarted = False; + static Time traceOrigin; + static Scalar traceDue = (Scalar) 0; + + if (!traceStarted) + { + traceStarted = True; + traceOrigin = start_of_frame; + traceDue = (Scalar) 0; + + // + //---------------------------------------------------------- + // Start the measurement from a known state, not merely a + // known place. + // + // The pod sits on its pad simulating while the mission + // loads, and a load is not the same length twice - two runs + // of the same egg reached the green light 776 steps in and + // 599 steps in. Same pad, same position, different VELOCITY, + // and a trajectory compared from there measures the loader, + // not the physics. + // + // So: stop the vehicle dead and put its clock on the same + // mark. Every run then starts from rest at the same instant + // and any difference that follows belongs to the simulation. + // + // Test scaffolding, and it only runs with the trace asked + // for - it would be a cheat in a real race. + //---------------------------------------------------------- + // + Player *reset_player = GetMissionPlayer(); + Entity *reset_vehicle = + (reset_player != NULL) + ? reset_player->GetPlayerVehicle() : NULL; + + if (reset_vehicle != NULL && + reset_vehicle->IsDerivedFrom(*Mover::GetClassDerivations())) + { + Mover *reset_mover = (Mover *) reset_vehicle; + + reset_mover->localVelocity = Motion::Identity; + reset_mover->localAcceleration = Motion::Identity; + + // + // The clock is NOT touched. lastPerformance sits on the + // vehicle's own step grid and the trace reads that grid + // instead. The first version forced it to the frame + // timestamp, which knocked the vehicle off its grid by + // a random fraction of a step per run - and that read + // as physics drift when it was only ever measurement. + // + traceOrigin = reset_mover->GetLastPerformance(); + + DEBUG_STREAM << "PhysTrace: vehicle stopped " + << "at the green light\n" << std::flush; + } + } + + // + // Sampled on the SIMULATION's clock - the vehicle's own + // lastPerformance, which advances in whole fixed steps - so two + // runs sample at identical step counts and their traces compare + // exactly. Frame time samples mid-step at whatever phase the + // frame happened to land on, which compares different instants + // and calls the difference physics. + // + Player *clock_player = GetMissionPlayer(); + Entity *clock_vehicle = + (clock_player != NULL) ? clock_player->GetPlayerVehicle() : NULL; + + Scalar elapsed = + (clock_vehicle != NULL) + ? (Scalar)(clock_vehicle->GetLastPerformance() - traceOrigin) + : (Scalar)(start_of_frame - traceOrigin); + if (elapsed >= traceDue) + { + traceDue += (Scalar) 0.25; + + Player *trace_player = GetMissionPlayer(); + Entity *trace_vehicle = + (trace_player != NULL) ? trace_player->GetPlayerVehicle() : NULL; + + if (trace_vehicle != NULL) + { + extern long gPhysicsStepsTaken; + char buffer[160]; + + sprintf(buffer, + "PhysTrace: t=%7.3f steps=%6ld pos %12.5f %12.5f %12.5f\n", + (double) elapsed, + gPhysicsStepsTaken, + (double) trace_vehicle->localOrigin.linearPosition.x, + (double) trace_vehicle->localOrigin.linearPosition.y, + (double) trace_vehicle->localOrigin.linearPosition.z); + DEBUG_STREAM << buffer << std::flush; + } + } + } + } + CLEAR_UPDATE_MANAGER(); // diff --git a/MUNGA/DROPZONE.cpp b/MUNGA/DROPZONE.cpp index 2e9c1ff..5cdd1e0 100644 --- a/MUNGA/DROPZONE.cpp +++ b/MUNGA/DROPZONE.cpp @@ -224,9 +224,47 @@ void //----------------------------------------------------- // highest = highest - lowest + 1; + + // + //------------------------------------------------------------------------ + // RP412SPAWNZONE pins which drop zone is tried first, so a test run can + // be repeated. + // + // The pick below is Random(), and Random() is seeded - but the seed only + // makes a run repeatable if the same NUMBER of draws happens first, and + // that depends on how many frames the mission load took. So two runs of + // the same egg with the same RANDOM= still start on different pads, over + // different ground, and no two traces can be compared. That is not a + // game bug, but it makes the physics unmeasurable. + // + // Pinned, the zone is tried first and the loop falls back to the random + // walk if it is taken - so this can never wedge, and it changes nothing + // unless it is set. + //------------------------------------------------------------------------ + // + static int + pinnedZone = -2; + + if (pinnedZone == -2) + { + const char *setting = getenv("RP412SPAWNZONE"); + pinnedZone = (setting != NULL) ? atoi(setting) : -1; + } + + Logical + tryPinnedZone = (pinnedZone >= 0) ? True : False; + while (remaining) { - i = lowest + Random(highest); + if (tryPinnedZone) + { + tryPinnedZone = False; + i = lowest + (pinnedZone % highest); + } + else + { + i = lowest + Random(highest); + } Verify(i < dropZoneCount && i >= 0); if (IsAvailable(i)) {