A_Tracer() and gametic

I'm suspecting that the usage of the gametic variable in A_Tracer() could create desync problems. Using the leveltime variable seems the most logical alternative. Any opinions?

Share this post


Link to post

In my opinion, this is one of the design decisions that they took when writing the monster code that sucks the most, due to the desynch that it causes in the demo loop when there's a Revenant. The other thing that I don't like is that the Mancubus fireballs are always shot in the same patterns, which doesn't cause a desynch, but makes the volley's direction predictable.

Share this post


Link to post

 

On 8/26/2017 at 11:19 AM, fabian said:

Using *leveltime* will cause multi-level demos to desync across map boundaries. This is how I have fixed this in Crispy:

https://github.com/fabiangreffrath/crispy-doom/commit/b9ccc9d9696648ee6b02983b385ab4e557fd5e11

Yeah, that's essentially how Boom fixed it as well.

 

On 8/26/2017 at 7:32 PM, axdoomer said:

In my opinion, this is one of the design decisions that they took when writing the monster code that sucks the most, due to the desynch that it causes in the demo loop when there's a Revenant. The other thing that I don't like is that the Mancubus fireballs are always shot in the same patterns, which doesn't cause a desynch, but makes the volley's direction predictable.

My guess is that Carmack was very careful with desync when finalizing Doom 1, and forgot to take as much care with Doom II. This theory also explains why Doom II missiles trigger linedefs, and also the arch-vile ghost bug. In other words, they rushed Doom II, and as a result, they created a bunch of new bugs. This can happen when you revisit a large code base some time later. Originally, the entire system is running in your head, and you can foresee complex effects of your actions. Later on, it's more difficult to see the system as a whole, and you start hacking new changes in, which causes new bugs to occur.

3 people like this

Share this post


Link to post
1 hour ago, kb1 said:

Doom II missiles trigger linedefs

I wasn't aware of this one. On the other hand, I noticed that missiles from monsters in Doom II were still flying at the normal speed on nightmare.

 

1 hour ago, kb1 said:

In other words, they rushed Doom II, and as a result, they created a bunch of new bugs. This can happen when you revisit a large code base some time later. Originally, the entire system is running in your head, and you can foresee complex effects of your actions. Later on, it's more difficult to see the system as a whole, and you start hacking new changes in, which causes new bugs to occur.

Probably another reason is the fact that Doom II came out at version 1.666 of the engine (late in the life of the engine) and Doom II was less accessible than Doom 1, because it didn't have a shareware version. id Software didn't want to put resources on fixing the bugs because they had to work on their new game, Quake. Also, the bugs introduced in Doom 2 are much less noticeable that similar bugs that you'd find in Doom 1 (the fact that an avid vanilla-Doomer like me didn't know about the previously quoted bug may prove it).

Share this post


Link to post

Yeah, there's a function with crappy "If TrooperShot, or if BaronShot, or...", instead of a dedicated flag or something. Same goes for the -fast speedup: It only targets specific hard-coded stuff.

 

Doom II was a quick, get-it-out-the-door hack. Thank God they added a weapon, some monsters, and some new linedefs. But, they also added a lot of bugs:

  • 4th demo bug when using non-ultimate wad
  • crushed gibs arch-vile ghosts
  • Doom II projectiles triggering linedefs
  • Doom II sky bug
  • IDCLEV/IDMUS bugs
  • Lost Souls going thru walls, or outside map
  • Lost Souls no longer count as monsters
  • Fast door double closing sound
  • No cool You Are Here intermission screens
  • Demo bugs, like A_Tracer()
  • A lot more I'm forgetting about...

It's just what happens when you revisit and modify a big, old code base. Like you said, a lot of these are subtle. It would take years to find some of them. But, all in all, they made a great sequel, and then gave us the code, to fix the bugs ourselves! But it does underline that fact that, when you go back to old code, these things happen.

 

1 person likes this

Share this post


Link to post
51 minutes ago, kb1 said:

and then gave us the code, to fix the bugs ourselves!

And yet, here we are, 20 years later, and the bugs won't get fixed in most ports because, well, that'd cause demo playback problems and - gosh - it'd be so cool if you could actually abuse them... >D

 

2 people like this

Share this post


Link to post
49 minutes ago, Graf Zahl said:

And yet, here we are, 20 years later, and the bugs won't get fixed in most ports because, well, that'd cause demo playback problems and - gosh - it'd be so cool if you could actually abuse them... >D

 

... Which bugs can't be fixed while maintaining demo compat, again? I'll be sure to tell @Quasar how impossible Eternity is.

Plus the bug being discussed right now (the tracer bug) was fixed. In Boom. Like, 17 years ago?

Edited by Edward850
2 people like this

Posted (edited)

Share this post


Link to post

This is why I almost wish Doom 2 didn't get made, at least not the way it turned out.

1 person likes this

Share this post


Link to post

Don't forget the horrendous savegame system that was actually breaking things in MAP30.

Share this post


Link to post

The savegame system wasn't really at fault here. They just forgot to save the global state for the shooter.

 

Share this post


Link to post

The only thing required to do that would be to store the actual thinker pointer with the thinker. Then you'd have all the needed data when reading it back and relocate them.

 

Edited by Graf Zahl

Posted (edited)

Share this post


Link to post

I wonder if you can just trim the demos so each internal demo starts at a multiple of 4 game tics.

Share this post


Link to post
On 8/30/2017 at 8:34 PM, Linguica said:

How hard would it have been to actually implement pointer swizzling and save the whole unmolested thinker list? Surely not that much more difficult than what they actually did?

 

edit: it looks like the Doom Classic port in the BFG Edition actually did as much https://github.com/id-Software/DOOM-3-BFG/blob/master/doomclassic/doom/p_saveg.cpp

On 8/30/2017 at 10:12 PM, Graf Zahl said:

The only thing required to do that would be to store the actual thinker pointer with the thinker. Then you'd have all the needed data when reading it back and relocate them.

 

Actually, even in plain old vanilla Doom all necessary information to reconstruct infighting (and missile targeting) info is already in place: the thinkers are already saved in the order they appear in the linked list, the prev/next pointers of the thinkers list are saved as part of the mobj_t, and the next/prev pointers can thus be used to match saved addresses to actual thinkers, kinda like a unique hash, and find out who's fighting with/targeting whom.

Edited by Maes
3 people like this

Share this post


Link to post
On 9/6/2017 at 4:28 AM, Maes said:

Actually, even in plain old vanilla Doom all necessary information to reconstruct infighting (and missile targeting) info is already in place: the thinkers are already saved in the order they appear in the linked list, the prev/next pointers of the thinkers list are saved as part of the mobj_t, and the next/prev pointers can thus be used to match saved addresses to actual thinkers, kinda like a unique hash, and find out who's fighting with/targeting whom.

Isn't there cases where it didn't work in Mocha Doom? Did you use a different approach? I ask because I saw that sometimes, a Revenant's fireball that's supposed to track you will not track you when restoring a savegame if you move as soon as the savegame is loaded.

Edited by axdoomer

Share this post


Link to post
5 hours ago, axdoomer said:

Isn't there cases where it didn't work in Mocha Doom? Did you use a different approach? I ask because I saw that sometimes, a Revenant's fireball that's supposed to track you will not track you when restoring a savegame if you move as soon as the savegame is loaded.

TBQH I didn't test it that extensively: just saved a couple of E1 games during infighting, verified that infighting continued after loading, and called it a day. That doesn't mean that there can't be corner cases or maybe a bug in the reconstruction code. The methods to check are reconstructPointers() and rewirePointer() in class VanillaDSG.

 

Now that I look back at my code, the approach I used (locating the player first, among unarchived mobj_t's, and treating him as the first object in the list) may not be 100% correct. I'm not sure why I did it that way, TBH.

Share this post


Link to post
6 hours ago, axdoomer said:

a Revenant's fireball that's supposed to track you will not track you when restoring a savegame if you move as soon as the savegame is loaded.

This has to do with the game being restored at a different gametic than it was saved (the relevant part is gametic & 3):

 

https://tracker.dengine.net/issues/1868#note-12

Share this post


Link to post
34 minutes ago, fabian said:

This has to do with the game being restored at a different gametic than it was saved (the relevant part is gametic & 3):

 

https://tracker.dengine.net/issues/1868#note-12

So Doom's own savegames are not "demo compatible", so to speak?

 

If so, then using the demo recording mechanism (instead of the complicated savegame routines) and "playing back" everything without screen rendering would result in a much more accurate recreation of the playing field, as well as automatically creating a demo of everything done until that point and giving the player much more flexibility. Provided that the decreased input resolution issue was fixed, ofc. I think prBoom+ already does that, so...

 

For the majority of maps, this would also result in smaller overall "save" files, as the information saved would be independent of map geometry or monster placement. Of course there would be drawbacks like reserving memory for player input anyway, risks of overrflowing it and, on complex maps, longer load times (perhaps unacceptably so), and games "saved" in this would be totally intolerant of even minor differences in the engine.

Edited by Maes

Share this post


Link to post
37 minutes ago, fabian said:

This has to do with the game being restored at a different gametic than it was saved (the relevant part is gametic & 3):

 

https://tracker.dengine.net/issues/1868#note-12

I tested several times (2 years ago), that how I did:

 

Wait for a Revenant to shoot a homing fireball at you. When it's moving straight in your direction, save the game. Then test:

1- Load the game and hold some movement keys to get out of the fireball's trajectory as soon as the save game finishes loading. This will result in the missile continuing forward and it won't be tracking you.

2- Load the game and stay in position for half a second before running away. The fireball will continue to follow you.

 

I probably had tested 10 times, so I don't believe the missiles randomly change between homing and non-homing. Since I need to move out of the trajectory of the fireball when loading the savegame to trigger the bug, I don't think it could have anything to do with the gametic.

Share this post


Link to post

The gametic & 3 check might give you an extra chance to "nullify" the tracer if you happen to move the right amount at just the right time. E.g. you may be still in the rocket's LOS on a gametic & 3, thus A_Tracer() will immediately abort and not correct the rocket's trajectory, and if you had built up sufficient speed you may be entirely out of its LOS on the next tic, so the rocket will become "non-homing". What you found out by experimenting, in other words, is good ol' save scumming, plain and simple :-p

 

Of course, a proper TASer would know exactly on which gametic he could break contact and how far he would need to move for that to happen, if he really wanted to maximize proximity with the rocket, and I'd be surprised if this trick wasn't already known and (ab)used by TASers.

 

BTW, if you save a game with a Rev rocket close enough for save scumming to make a difference, saving/restoring target pointers would be pretty irrelevant anyway, as the rocket would immediately acquire the player as a target anyway.

Edited by Maes

Share this post


Link to post
31 minutes ago, Maes said:

chance to "nullify" the tracer if you happen to move the right amount at just the right time. E.g. you may be still in the rocket's LOS on a gametic & 3, thus A_Tracer() will immediately abort and not correct the rocket's trajectory, and if you had built up sufficient speed you may be entirely out of its LOS on the next tic, so the rocket will become "non-homing".

Are you talking about loading savegames in your port, or are you implying that is vanilla behavior? As far as I know, in vanilla, homing missiles never become non-homing due to losing line of sight. They always track the player purely based on position. They never even check line of sight.

Edited by scifista42

Share this post


Link to post
1 hour ago, Maes said:

So Doom's own savegames are not "demo compatible", so to speak?

Already the case for different reasons. With vanilla, all the sector lists for each actor are missing, and the blockmap isn't guaranteed to be reconstructed in the same order which can result in a changed order of interactions for the next few frames (it's dependent on actor movement). There's likely a couple of other actor lists as well that are also rebuilt (crushers?), and while seemingly insignificant, it can easily change the order of prng calls.

 

In fact, I don't think any source port keeps the blockmap order.

Share this post


Link to post
19 minutes ago, scifista42 said:

Are you talking about loading savegames in your port, or are you implying that is vanilla behavior? As far as I know, in vanilla, homing missiles never become non-homing due to losing line of sight. They always track the player purely based on position. They never even check line of sight.

Talking about savegame in general. Even in vanilla, tracers eventually "give up" on you if you move out of the way fast enough, until they regain a visual.

 

OK, technically what happens is NOT that they become non-homing, but what the A_Tracer algo does is more or less this:

 

  1. Angle between target and rocket is computed.

  2. If the rocket's heading already matches that angle, no change is performed.

  3. If not, then a fixed amount of correction (TRACEANGLE) is tentatively added

  4. If adding said correction would result in the rocket losing LOS contact with its target (in the usual monster LOS sense of 180 degrees), then the correction is canceled.

  5. Finally, on gametics that are divisible by 3, no correction is applied at all.

 

TL;DR: it's far from impossible to evade or neutralize a homing rocket. What axdoomer experienced is simply confirmation bias: by replaying the same situation over and over (imminent hit by homing rocket), eventually he found out that moving in certain directions and with a certain timing results in a successful dodge (I leave it as an exercise to the reader to determine under which conditions a dodge is completely impossible). That's save scumming, plain and simple.

Edited by Maes

Share this post


Link to post
4 minutes ago, Maes said:

4. If adding said correction would result in the rocket losing LOS contact with its target (in the usual monster LOS sense of 180 degrees), then the correction is canceled.

I don't see it in the code:

Spoiler

void A_Tracer (mobj_t* actor)
{
    angle_t	exact;
    fixed_t	dist;
    fixed_t	slope;
    mobj_t*	dest;
    mobj_t*	th;
		
    if (gametic & 3)
	return;
    
    // spawn a puff of smoke behind the rocket		
    P_SpawnPuff (actor->x, actor->y, actor->z);
	
    th = P_SpawnMobj (actor->x-actor->momx,
		      actor->y-actor->momy,
		      actor->z, MT_SMOKE);
    
    th->momz = FRACUNIT;
    th->tics -= P_Random()&3;
    if (th->tics < 1)
	th->tics = 1;
    
    // adjust direction
    dest = actor->tracer;
	
    if (!dest || dest->health <= 0)
	return;
    
    // change angle	
    exact = R_PointToAngle2 (actor->x,
			     actor->y,
			     dest->x,
			     dest->y);

    if (exact != actor->angle)
    {
	if (exact - actor->angle > 0x80000000)
	{
	    actor->angle -= TRACEANGLE;
	    if (exact - actor->angle < 0x80000000)
		actor->angle = exact;
	}
	else
	{
	    actor->angle += TRACEANGLE;
	    if (exact - actor->angle > 0x80000000)
		actor->angle = exact;
	}
    }
	
    exact = actor->angle>>ANGLETOFINESHIFT;
    actor->momx = FixedMul (actor->info->speed, finecosine[exact]);
    actor->momy = FixedMul (actor->info->speed, finesine[exact]);
    
    // change slope
    dist = P_AproxDistance (dest->x - actor->x,
			    dest->y - actor->y);
    
    dist = dist / actor->info->speed;

    if (dist < 1)
	dist = 1;
    slope = (dest->z+40*FRACUNIT - actor->z) / dist;

    if (slope < actor->momz)
	actor->momz -= FRACUNIT/8;
    else
	actor->momz += FRACUNIT/8;
}

 

Edited by scifista42

Share this post


Link to post
52 minutes ago, scifista42 said:

I don't see it in the code:

 

 


    if (exact != actor->angle) // False if dead-on, no course correction needed
    {
    if (exact - actor->angle > 0x80000000) // Too much to the right/left
    {
        actor->angle -= TRACEANGLE; // Correct right/left
        if (exact - actor->angle < 0x80000000)
        actor->angle = exact;
    }
    else
    { // Too much to the left/right
        actor->angle += TRACEANGLE; // Correct left/right
        if (exact - actor->angle > 0x80000000)
        actor->angle = exact;
    }
    }

 

A BAM (Binary Angle Measurement) of "0x80000000" is actually 180 degrees.

OK, what goes on actually is that if the required course correction is LESS than TRACEANGLE, the rocket simply smoothly follows the target. My bad.

 

However, that doesn't preclude that the rocket can completely lose its target by LOS rules even after course corrections, though I'm not sure exactly where that check is implemented in the code. But since it IS possible to dodge homing rockets WITHOUT them coming back for you, it must be in there somewhere. But I'd rather leave backtracking through hundreds of lines of code as an exercise to the reader.

 

And it's no new thing that you can avoid or save-scum your way out of revenant rockets if you move just right/at the right time. Just like you may keep attracting them even past corners, if you don't move sharply enough.

 

Edit: I just read fabian's tracker issue link. "Doubly broken" is the key word here.

Edited by Maes

Share this post


Link to post
27 minutes ago, Maes said:

OK, what goes on actually is that if the required course correction is LESS than TRACEANGLE, the rocket simply smoothly follows the target.

Good, that's what I believed it was doing.

27 minutes ago, Maes said:

since it IS possible to dodge homing rockets WITHOUT them coming back for you,

Is it really? I have doubts about it. Never seen it happening, never seen it mentioned except by you, doomwiki says nothing about it.

32 minutes ago, Maes said:

it must be in there somewhere. But I'd rather leave backtracking through hundreds of lines of code as an exercise to the reader.

I have a better idea: Point me to a vanilla demo that proves this is real.

Share this post


Link to post
51 minutes ago, scifista42 said:

Good, that's what I believed it was doing.

Is it really? I have doubts about it. Never seen it happening, never seen it mentioned except by you, doomwiki says nothing about it.

I have a better idea: Point me to a vanilla demo that proves this is real.

Well, TBQH I never turned back to see what happened to Revenant rockets I just dodged, but now I'm curious. So far, all that we know is that there's quite a bit of "dead time" in the A_Tracer function (essentially, it only works one of every four tics), so in theory that does leave quite a bit of room for maneuvering and dodging.

 

51 minutes ago, scifista42 said:

I have a better idea: Point me to a vanilla demo that proves this is real.

I cannot quote any particular ones, but I guess a custom proof-of-concept map could be made, with a single Revenant shooting at the player.

 

Then the player would have the option of using a set of teleporters in order to simulate an extreme dodge to the left, to the right, and one right behind the Revenant, in order to see what the rocket would do. But I'm fairly certain that this scenario can be better answered by members of the TAS community.

 

Edited by Maes

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now