Feature #1648: Complete vanilla DOOM emulation
[Doom] Revenant missiles randomly switch from non-homing to homing
The attached save game from BTSXe2 Map25 shows a non-homing Revenant missile become homing if the player performs no action a split second after loading the game.
Performing an action immediately appears to cause the missile to remain un-homing.
I've encountered this bug a few times over the Dday 1.9 series when playing (i.e it had nothing to do with saving/loading, just while playing), but I've not been able to catch it in a save game until now.
#2 Updated by danij over 5 years ago
Initial testing reveals that this is indeed not a savegame issue. After instrumenting and comparing interpretation of the tracer pointers between a session in which the target is tracked correctly vs one where the target is lost shows identical output. Therefore the target must be lost some time after loading has completed.
This has nothing to do with player input. Repeatedly loading this savegame shows that the tracer will seemingly randomly lose its target once every 3-4 times the session is loaded.
#4 Updated by danij over 5 years ago
Further testing shows that the tracer's target is in fact not being lost at all. The problem is that the deterministic behavior inherent in the original logic has been subverted as a result of changes to how game time is managed.
The A_Tracer action will short-circuit the turn-to-face-player logic according to the value of (gametic & 3).
In Doomsday, gametic (int) has been replaced with a conversion from the timing loop's gameTime (timespan_t). This gameTime is sometimes reset to zero when the current map changes, meaning that upon loading a saved game, all tracers will occasionally revert to non-homing behavior immediately. (Note that gameTime is also changed in various other places/times.)
However, the gametic is not remembered in saved games, either by Doomsday or the original game. Therefore, this bug must also affect vanilla to the extent that tracers switch homing/non-homing behavior seemingly randomly when loading a savegame. (Note that due to the fact targets are not saved correctly in vanilla in any case, this behavior is doubly broken in vanilla).
Open question: Why is it that tracers do not revert to homing behavior once (((int)gameTime) & 3) != true?
#9 Updated by danij over 5 years ago
Logging the values of GAMETIC and (((int) GAMETIC) & 3) in A_Tracer shows that this bug occurs when the step value for a tic results in a predictable integral sequence of 1, 3, 5, 7, 9. If the step causes the integral to jump out of that sequence then the tracer will resume its homing behavior.
This suggests to me that the conversion from fractional time to game tic is fundamentally flawed when the game tic is assumed to step uniformly 0, 1, 2, 3...
#12 Updated by skyjake over 5 years ago
Here's my analysis of the situation. The GAMETIC & 3 is indeed at the root of the problem. However, this function (
A_Tracer) is only called on sharp ticks, so there's nothing wrong with the tick counting.
The condition is supposed to mean that the function is only executed once every four ticks. Furthermore, the mobj state has a tick count of 2, meaning the function is only called every two ticks. The intended end result is that the logic is run on every second time it gets called.
The nondeterministic nature of the glitch comes from GAMETIC not being reset when the game is loaded. Therefore, if the restored mobj happens to begin its cycle on an odd tick (bit 1 set), it never gets called on a tick where bit 1 isn't set (2 tick state cycle)— therefore, it never gets executed.
How to fix this, though? It seems to me the safest solution would be to save and restore the game tick counter. Alternatively, one could use a per-mobj counter that ensures the logic gets executed only on every second time the state cycle calls it, although that would not ensure that all
A_Tracer's in the map get executed on the same ticks (vanilla behavior risk).
#14 Updated by skyjake about 5 years ago
Studying the code more, another solution comes to mind.
A_Tracer is pretty much the only place where
GAMETIC is used like this — elsewhere in the code
mapTime is used for this purpose. I can't think of any reason why
A_Tracer couldn't use
mapTime, too, and since that is already restored when saving a game, it should fix the problem. Also, this would already handle the Hexen case appropriately.