Add replay validation

This commit is contained in:
Miloslav Ciz 2025-06-11 22:25:29 +02:00
parent f0d8521e90
commit 57097c57bb
3 changed files with 78 additions and 20 deletions

View file

@ -2,6 +2,7 @@ fuck issue trackers :D
=========== GENERAL ============== =========== GENERAL ==============
- menu: key repeat?
- controller supports? analog input could be "tapping" the keys with varying - controller supports? analog input could be "tapping" the keys with varying
frequency frequency
- frontends: - frontends:
@ -44,6 +45,7 @@ fuck issue trackers :D
=========== HANDLED ============== =========== HANDLED ==============
- should drifting make a sound? NO NEED - should drifting make a sound? NO NEED
- replay validation? maybe yes?
- ghost color - ghost color
- make reverse maps - make reverse maps
- remake TM maps in the third party repo - remake TM maps in the third party repo
@ -71,8 +73,6 @@ fuck issue trackers :D
again (reshape iterations, tension, ...); seem acceptable now maybe? again (reshape iterations, tension, ...); seem acceptable now maybe?
- in tiny resolution the sky jumps when rotating - in tiny resolution the sky jumps when rotating
- car particles seem too big in low res - car particles seem too big in low res
- replay validation? MAYBE NOT, make a separate tool if needed, shouldn't likely
be part of the game
- at high resolution (like 1920) buggy triangles sometimes appeard, tried to - at high resolution (like 1920) buggy triangles sometimes appeard, tried to
fix this in S3L now but needs to be tested fix this in S3L now but needs to be tested
- shift the car texture a bit to align the wheels? KINDA DOESN'T GETT BETTER NOW - shift the car texture a bit to align the wheels? KINDA DOESN'T GETT BETTER NOW

18
game.h
View file

@ -853,8 +853,8 @@ uint8_t LCR_gameLoadMap(unsigned int mapIndex)
/** /**
Loads replay by its index, returns index of a map for the replay (and the map Loads replay by its index, returns index of a map for the replay (and the map
will be loaded as with LCR_mapLoadFromStr) or -1 if the map wasn't found or -2 will be loaded as with LCR_mapLoadFromStr) or -1 if the map wasn't found or -2
if the replay couldn't be loaded. This function potentially reloads current if the replay couldn't be loaded or -3 if the replay is invalid. This function
map! potentially reloads current map!
*/ */
unsigned int LCR_gameLoadReplay(unsigned int replayIndex) unsigned int LCR_gameLoadReplay(unsigned int replayIndex)
{ {
@ -900,7 +900,7 @@ unsigned int LCR_gameLoadReplay(unsigned int replayIndex)
LCR_LOG2("map name hash matches"); LCR_LOG2("map name hash matches");
if (LCR_gameLoadMap(mapIndex) && mapHash == LCR_currentMap.hash) if (LCR_gameLoadMap(mapIndex) && mapHash == LCR_currentMap.hash)
return mapIndex; return LCR_replayValidate() ? mapIndex : -3;
else else
{ {
LCR_LOG2("bad map"); LCR_LOG2("bad map");
@ -1243,7 +1243,7 @@ void LCR_gameDraw3DView(void)
LCR_GameUnit carTransform[6]; LCR_GameUnit carTransform[6];
LCR_GameUnit physicsInterpolationParam = LCR_GameUnit physicsInterpolationParam =
!(LCR_racing.playingReplay && LCR_replayHasFinished()) ? !(LCR_racing.replay.on && LCR_replayHasFinished()) ?
LCR_GAME_UNIT - LCR_GAME_UNIT -
((LCR_game.nextRacingTickTime - LCR_game.time) * LCR_GAME_UNIT) ((LCR_game.nextRacingTickTime - LCR_game.time) * LCR_GAME_UNIT)
/ LCR_RACING_TICK_MS / LCR_RACING_TICK_MS
@ -1388,7 +1388,7 @@ void LCR_gameHandleInput(void)
LCR_currentMap.targetTime = LCR_game.runTime; LCR_currentMap.targetTime = LCR_game.runTime;
} }
LCR_gameResetRun(LCR_racing.playingReplay,LCR_game.ghost.active); LCR_gameResetRun(LCR_racing.replay.on,LCR_game.ghost.active);
} }
} }
else if (LCR_game.state == LCR_GAME_STATE_RUN_STARTING) else if (LCR_game.state == LCR_GAME_STATE_RUN_STARTING)
@ -1448,7 +1448,7 @@ void LCR_gameHandleInput(void)
LCR_rendererMoveCamera(offsets,offsets + 3); LCR_rendererMoveCamera(offsets,offsets + 3);
} }
else if (LCR_game.keyStates[LCR_KEY_A] == 1) else if (LCR_game.keyStates[LCR_KEY_A] == 1)
LCR_gameResetRun(LCR_racing.playingReplay,LCR_game.ghost.active); LCR_gameResetRun(LCR_racing.replay.on,LCR_game.ghost.active);
} }
else // LCR_GAME_STATE_MENU else // LCR_GAME_STATE_MENU
{ {
@ -1549,7 +1549,7 @@ void LCR_gameHandleInput(void)
case 3: case 3:
if (LCR_game.statePrev == LCR_GAME_STATE_RUN_FINISHED && if (LCR_game.statePrev == LCR_GAME_STATE_RUN_FINISHED &&
!LCR_racing.playingReplay) !LCR_racing.replay.on)
LCR_gameSaveReplay(); LCR_gameSaveReplay();
else else
LCR_gamePopupMessage(LCR_texts[LCR_TEXTS_FAIL]); LCR_gamePopupMessage(LCR_texts[LCR_TEXTS_FAIL]);
@ -1719,12 +1719,12 @@ uint8_t LCR_gameStep(uint32_t time)
LCR_LOG1_NUM(LCR_game.runTime); LCR_LOG1_NUM(LCR_game.runTime);
if (LCR_game.runTime <= LCR_currentMap.targetTime && if (LCR_game.runTime <= LCR_currentMap.targetTime &&
!LCR_racing.playingReplay) !LCR_racing.replay.on)
{ {
LCR_gameSaveReplay(); LCR_gameSaveReplay();
if (!LCR_game.mapBeaten && !LCR_game.ghost.active && if (!LCR_game.mapBeaten && !LCR_game.ghost.active &&
!LCR_racing.playingReplay) !LCR_racing.replay.on)
{ {
LCR_LOG1("map beaten"); LCR_LOG1("map beaten");
LCR_game.mapBeaten = 1; LCR_game.mapBeaten = 1;

View file

@ -168,10 +168,9 @@ struct
uint16_t crashState; uint16_t crashState;
uint8_t playingReplay;
struct struct
{ {
uint8_t on; ///< Currently playing replay?
uint16_t eventCount; uint16_t eventCount;
uint16_t events[LCR_SETTING_REPLAY_MAX_SIZE]; uint16_t events[LCR_SETTING_REPLAY_MAX_SIZE];
@ -376,7 +375,7 @@ int LCR_replayHasFinished(void)
for (int i = 0; i < LCR_racing.replay.eventCount; ++i) for (int i = 0; i < LCR_racing.replay.eventCount; ++i)
totalTime += LCR_racing.replay.events[i] >> 4; totalTime += LCR_racing.replay.events[i] >> 4;
return totalTime >= LCR_racing.replay.achievedTime; return totalTime > LCR_racing.replay.achievedTime;
} }
return LCR_racing.replay.currentEvent > LCR_racing.replay.eventCount; return LCR_racing.replay.currentEvent > LCR_racing.replay.eventCount;
@ -445,7 +444,8 @@ int LCR_replayRecordEvent(uint32_t frame, uint8_t input)
frame -= previousFrame; // convert to offset frame -= previousFrame; // convert to offset
while (frame > 4095 && LCR_racing.replay.eventCount < LCR_SETTING_REPLAY_MAX_SIZE) while (frame > 4095 && LCR_racing.replay.eventCount <
LCR_SETTING_REPLAY_MAX_SIZE)
{ {
// add intermediate events // add intermediate events
frame -= 4095; frame -= 4095;
@ -982,7 +982,7 @@ void LCR_racingRestart(uint8_t replay)
LCR_racing.tick = 0; LCR_racing.tick = 0;
LCR_racing.fanForce = 0; LCR_racing.fanForce = 0;
LCR_racing.playingReplay = replay; LCR_racing.replay.on = replay;
TPE_bodyActivate(&(LCR_racing.carBody)); TPE_bodyActivate(&(LCR_racing.carBody));
LCR_racing.wheelCollisions = 0; LCR_racing.wheelCollisions = 0;
@ -1066,7 +1066,7 @@ void LCR_racingInit(void)
TPE_worldInit(&(LCR_racing.physicsWorld), TPE_worldInit(&(LCR_racing.physicsWorld),
&(LCR_racing.carBody),1,_LCR_racingEnvironmentFunction); &(LCR_racing.carBody),1,_LCR_racingEnvironmentFunction);
LCR_racing.playingReplay = 0; LCR_racing.replay.on = 0;
LCR_racing.physicsWorld.collisionCallback = _LCR_racingCollisionHandler; LCR_racing.physicsWorld.collisionCallback = _LCR_racingCollisionHandler;
} }
@ -1217,7 +1217,7 @@ uint32_t LCR_racingStep(unsigned int input)
LCR_racing.groundMaterial = LCR_BLOCK_MATERIAL_CONCRETE; LCR_racing.groundMaterial = LCR_BLOCK_MATERIAL_CONCRETE;
if (LCR_racing.playingReplay) if (LCR_racing.replay.on)
{ {
if (LCR_racing.tick == 0) if (LCR_racing.tick == 0)
LCR_replayInitPlaying(); LCR_replayInitPlaying();
@ -1637,7 +1637,7 @@ uint32_t LCR_racingStep(unsigned int input)
{ {
result |= LCR_RACING_EVENT_FINISHED; result |= LCR_RACING_EVENT_FINISHED;
if (!LCR_racing.playingReplay) if (!LCR_racing.replay.on)
LCR_replayRecordEvent(LCR_racing.tick,LCR_REPLAY_EVENT_END); LCR_replayRecordEvent(LCR_racing.tick,LCR_REPLAY_EVENT_END);
} }
} }
@ -1677,5 +1677,63 @@ void LCR_physicsDebugDraw(LCR_GameUnit camPos[3], LCR_GameUnit camRot[2],
cPos,cRot,cView,16,LCR_PHYSICS_UNIT / 4,LCR_racing.tick * 4); cPos,cRot,cView,16,LCR_PHYSICS_UNIT / 4,LCR_racing.tick * 4);
#endif #endif
} }
/**
Validates replay, i.e. checks if it finishes and whether its achieved time
agrees with the stored time. The function can only be called when both the
replay and the correct map are loaded. The loaded replay's playing position
will be reset after this function returns.
*/
int LCR_replayValidate(void)
{
LCR_LOG1("validating replay");
int result = 0;
LCR_racingRestart(1);
while (LCR_racing.tick < 0x00f00000 // no infinite loops
&& LCR_racing.tick <= LCR_racing.replay.achievedTime)
{
int coords[3];
LCR_racingStep(0);
LCR_racingGetCarBlockCoords(coords);
coords[0] = LCR_mapGetBlockAt(coords[0],coords[1],coords[2]);
if (coords[0] && LCR_currentMap.blocks[coords[0] * LCR_BLOCK_SIZE] ==
LCR_BLOCK_FINISH)
{
uint8_t allCPsTaken = 1; // reuse as variable
for (int i = 0; i < LCR_currentMap.blockCount; ++i)
if (LCR_currentMap.blocks[i * LCR_BLOCK_SIZE] == LCR_BLOCK_CHECKPOINT_0)
{
allCPsTaken = 0;
break;
}
if (allCPsTaken)
{
result = LCR_racing.tick == (LCR_racing.replay.achievedTime + 1);
break;
}
}
}
if (result)
{
LCR_LOG1("replay valid");
}
else
{
LCR_LOG1("replay invalid!");
}
LCR_racingRestart(1);
return result;
}
#endif // guard #endif // guard