Spawn cubes miss east and west targets

From DoomWiki.org

A spawn cube stream caused by the bug

The spawn cubes spawned by the monster spawner will miss the monster spawn spot if the spot is directly east or west of the spawner.

Details[edit]

Missed spawn cubes flying directly due east or west will carry on almost indefinitely, even sailing through the walls of the level and eventually wrapping around to the other side of the map. Given enough time, this will result in an nearly endless string of spawn cubes crossing the level (or multiple such streams in case there are multiple monster spawners and/or spawn spots arranged in this fashion).

None of the original commercial Doom games exhibit this bug in their levels.

Cause[edit]

This is caused by the id developers finding that trigonometry is too complicated and should be avoided whenever possible.

The A_SpawnFly() codepointer counts down the spawn cube mobj's reactiontime variable every time it is called, and spawns the monster if it reaches zero after being decreased.

void A_SpawnFly (mobj_t* mo)
{
    <insert declarations of variables>
	
    if (--mo->reactiontime)
	return;	// still flying

    <insert code to spawn stuff and destroy the spawn cube>
}

Therefore, to obtain the effect of the spawn cube flying to its spawn point, it is important that the initial reactiontime value corresponds to how many times A_SpawnFly will be called until reaching the destination point. The idea is very simple: take the distance to run, divide it by the cube's flying speed, and by the number of tics it takes for A_SpawnFly to be called. (By the way, given how the computation is made, if the spawn cube's states are not homogeneous in length, this will break too, because A_BrainSpit assumes the length of the cube's spawn state is the length of all its flying states.)

This computation is made in A_BrainSpit:

void A_BrainSpit (mobj_t*	mo)
{
    <insert declarations of variables and some irrelevant code>

    // spawn brain missile
    newmobj = P_SpawnMissile (mo, targ, MT_SPAWNSHOT);
    newmobj->target = targ;
    newmobj->reactiontime =
	((targ->y - mo->y)/newmobj->momy) / newmobj->state->tics;

    S_StartSound(NULL, sfx_bospit);
}

Here, P_SpawnMissile will take care of the hard part of separating the spawn cube's speed into both a east-west component (momx) and an north-south component (momy). So we can just take one of these components, and the corresponding distance, and it'll be a lot simpler than having to bother with Pythagoreanizing both axes.

Of course, if the target is directly east or west, then the distance is 0.

The speed should also be zero, which would lead to a divide by zero crash, if it were not for the inaccurate trigonometry tables which prevents an actor's momentum from being purely orthogonal to the grid.

Time limit[edit]

Because reactiontime is a signed 32-bit integer, after 4,294,967,296 decrement operations the reactiontime value would wrap around the entire 32-bit space and finally reach 0, causing a monster to spawn. Since the decrement operation is called once every 3 tics, this would take approximately 11.67 years, making it essentially impossible to witness in practice. However, through the direct editing of memory values, it has been shown that the spawn cube would indeed eventually spawn a monster, discounting any other bugs that might cause the game to crash before 11.67 years had elapsed.

Demos[edit]

  • A level set up to demonstrate this bug (file info). The level contains a monster spawner which will dispense spawn cubes in each cardinal direction, with each spawn spot evenly spaced from it; wait until it shoots east and west to see this bug.