VOGONS


First post, by Tarvis

User metadata
Rank Newbie
Rank
Newbie

Hello,

Many know of Descent's famous issues of the game speeding up on fast CPUs, or homing missiles being too good at steering at high FPS.

Attached are some patched EXEs that cap the game's FPS, so you can play the original executables at constant speeds on Pentium 2's and 3's (or DOSBOX at Max cycles!) rather than needing a source port.
This is accomplished by busy looping while the game is running above a target framerate.

Technical explanation

This patch has been accomplished by rewriting part of the function calc_frame_time() as can be found in the Descent/Descent 2 source code release MAIN.C, then finding the function in dissasembly of the original EXEs, and applying the same logic changes by modifying instructions.

Descent has a 'minimum frametime' between frames, after which it will simulate the game with the minimum frametime even if the real time between frames was a lot faster. This is the "Game speeds up past 150FPS" problem. Breaking this limit is not a great idea; when the time step becomes too small many math calculations start rounding to 0 due to imprecision of fixed point numbers, and things in the game world just stop moving properly.

Additionally, some behaviors (such as the homing missile turn speed) don't adjust well for lower frametimes. This is a separate problem from the above. While the game makes some attempt to scale homer turning for current framerate, it just doesn't do it in an effective way, and due to the vector math involved there is not an apparent correction for it. Modern source ports solve this problem by simulating homers at a fixed frame rate separate from everything else, but this would take way too much code to cram into an EXE hack.

This patch avoids both problems by making the game busy-loop until the current frametime has surpassed a target frametime that is quite lower than 150Hz. Some parts of the game such as the Credits already take this approach.

The "correct" framerate is pretty ambiguous. Descent 1 at the time of release was lucky to reach 20FPS, when most people had 486es. By the time of Descent 2 on the other hand most people had Pentiums, and D2 runs on those around 40-50 FPS. During the days of Kali people would be playing either game on any number of CPUs, some faster than those.

So, the patch should allow for some choice.


Right now these are just hex edited EXEs, I can work on a more generic patcher that will let you choose a target FPS.
The D2 patches also include Cloaked Thargoid's No-CD modification.
Included are 30FPS and 40FPS versions for Descent 1, and 40FPS and 60FPS versions for Descent 2.
30FPS is what the current multiplayer community has decided on for its homing missile speed, I believe, while 40 offers a decent amount of extra responsiveness at the cost of homing missiles being a bit tougher.

EDIT: 60FPS versions added for Descent 2.

Included patches for:
Descent 1 V1.5
Descent 2 V1.2
Descent 2 V1.2 With Vertigo
Descent 2 Win95

If you have a preferred OEM/video card version you would like to be patched you will have to send it to me and I can adapt the patch for you.

Last edited by Tarvis on 2025-12-25, 10:59. Edited 18 times in total.

Reply 1 of 12, by BEEN_Nath_58

User metadata
Rank l33t
Rank
l33t

I "felt" that Descent 2 should run at 60 fps on a period correct hardware.

On my Win11 machine, I capped the Infernal Abyss to 60 and it is doing fine. 30 seems too low when compared.

previously known as Discrete_BOB_058

Reply 2 of 12, by Tarvis

User metadata
Rank Newbie
Rank
Newbie

I think most people had P1s at the time so I think 40-50 was about what it would get, maybe dipping into 30s in big fights.

Like I said, I'm gonna develop a patcher that will let you select any arbitrary FPS.

D1's homers on the other hand definitely get way too crazy above 40 or so.
I doubt most people got much more than 20FPS at the time of its release.

Reply 3 of 12, by ntalaec

User metadata
Rank Member
Rank
Member
Tarvis wrote on 2025-12-22, 16:38:
I think most people had P1s at the time so I think 40-50 was about what it would get, maybe dipping into 30s in big fights. […]
Show full quote

I think most people had P1s at the time so I think 40-50 was about what it would get, maybe dipping into 30s in big fights.

Like I said, I'm gonna develop a patcher that will let you select any arbitrary FPS.

D1's homers on the other hand definitely get way too crazy above 40 or so.
I doubt most people got much more than 20FPS at the time of its release.

Many thanks for this. But, as discussed here: Re: Sound Issues with Descent 2 and AWE64 it seems that there is a 60 frame cap in some hardware.

It's possible to have a 60 fps limit for this executables?

Reply 4 of 12, by Tarvis

User metadata
Rank Newbie
Rank
Newbie

To tide you guys over until I write the generic patcher for any desired FPS limit, I've updated the attachment to include 60FPS versions for Descent 2.

Note that this doesn't change anything about syncing to VBlank or whatever so it might have more apparent tearing at 60.

To fit in the attachment 5MB limit I removed the 30FPS versions of Descent 2 (but kept it for D1) since the average PC in 1996 probably ran D2 better than that.

Reply 5 of 12, by ntalaec

User metadata
Rank Member
Rank
Member
Tarvis wrote on 2025-12-22, 19:31:

To tide you guys over until I write the generic patcher for any desired FPS limit, I've updated the attachment to include 60FPS versions for Descent 2.

Note that this doesn't change anything about syncing to VBlank or whatever so it might have more apparent tearing at 60.

To fit in the attachment 5MB limit I removed the 30FPS versions of Descent 2 (but kept it for D1) since the average PC in 1996 probably ran D2 better than that.

Thanks again. Besides tearing, using in game frame counter (typing FRAMETIME during gameplay) frame rate now is 58.82.

Reply 6 of 12, by digger

User metadata
Rank Oldbie
Rank
Oldbie

Just wondering from a technical point of view... Do these patches replace delay loops with routines that properly use the system timer as the programmers should have done in the first place, or do they simply increment the maximum counter values?

The latter seems much easier and more feasible to patch in place, but runs the risk of requiring updated patches in the future as CPUs get even faster.

But then again, at that point we'll probably have moved on from x86 and we'll run those games in emulators anyway. Also, the single-core clock frequencies aren't expected to increase much further with the end of Moore's Law in sight.

Reply 7 of 12, by Tarvis

User metadata
Rank Newbie
Rank
Newbie

I put a more lengthy description in the first post spoiler tag, but it's more like the former.

The most accurate description was that previously there was no delay loop in-game, instead the game runs as fast as it can, using the system timer to measure how long it's been since the previous frame and uses that to scale the object movements accordingly. (AKA "Delta Time") This was pretty forward thinking for its time.

That time delta was bounded to a minimum period of about 7 ms (150 Hz); so above 150FPS the game lies to itself about how long it's been since the previous frame, causing it to speed up faster and faster with higher FPS.
My experimentation showed that removing this limit leads to other problems such as things not moving, because the math involved in the physics starts rounding to 0 with smaller and smaller time steps.

So this patch adds a loop to poll the system timer (which Descent sets to 120 Hz) until it has been a long enough time (30Hz, 40Hz, 60Hz depending on which exe is used) since the previous frame to proceed.
The Credits in the game does it this way already.

So, the patch does 2 things:
1. Make the game wait on the timer if it runs above 150 FPS until it has really been 150 FPS, rather than running faster and speeding up the physics
2. Change the 150 FPS check in Step 1 to something lower (30, 40, 60, etc) to rein in the homing missiles.

Reply 8 of 12, by zyzzle

User metadata
Rank Member
Rank
Member

In your custom fps patcher version, may you also included the option to disable the annoying bobbing up and down? I remember .exe hexpatches back in the day. My .exes are long-since patched, but aren't fps-limited patched of course. 30 years on, I no longer remember the exact bytes to patch to disable the bobbing, but I do remember strings of hex 90s (NOPs) being necessary in the patched binaries.

Reply 9 of 12, by Tarvis

User metadata
Rank Newbie
Rank
Newbie

I don't plan on it, but I can say that the bobbing definitely gets much more subtle at lower FPS. Try it at 30.

Once I write the automatic patcher you will be able to fps patch your bobbing-disabled EXE.

Reply 10 of 12, by zyzzle

User metadata
Rank Member
Rank
Member

Fair enough. Your fps-limited patched binaries are excellent. Breathes new life into Descent I'll just patch the bobbing myself.

Reply 11 of 12, by Tarvis

User metadata
Rank Newbie
Rank
Newbie

Work is progressing on the patcher but I have found that my DOS method of waiting on the timer is only working in divisions of 120 FPS, so currently I can only limit to 20, 24, 30, 40, 60, or 120 FPS.

This is certainly due to Descent using the PIT set to 120 Hz, to get the time, but the open source release code for the timer indicates it should also be using the PIT countdown value to get current time between the 120Hz interrupts as well, but somehow that does not seem to be happening. So the function to get the current time in msecs is locked to 120Hz intervals.

I will investigate further, as I want the game to be able to run at divisions of 70 Hz to minimize judder at higher framerates.


EDIT:

DOS timer technobabble

The Descent timer function I was using (timer_get_approx_seconds) only gets the number of PIT interrupts, which as expected only increments at 120 Hz interval.

Using Descent's more accurate timer function (timer_get_fixed_seconds) which reads the counter from the PIT to get more granular timing has issues.
It seems when a whole lot of time is spent in a loop getting the latched counter from the PIT over and over the timer starts to drift too fast at least in DOSBOX.
Maybe it's due to that operation requiring disabling interrupts, which means in such a loop a majority of the time is spent with interrupts disabled. Or it could be due to the amount of time spent in I/O with the PIT.
I figure those would result in the timer drifting too slow rather than too fast, but figuring that out is probably a bit over my head.

timer_get_approx_seconds() doesnt have this problem because all it does is read a variable from memory, it does not mess with interrupts or access the PIT device.

Instead, it seems that changing the PIT from 120Hz to 140Hz is totally safe in order to continue using timer_get_approx_seconds().


So with that I have Descent 2 in DOS running at a buttery smooth 70 FPS.
Only problem is the tearing is really apparent there, with tearing staying in the same spot on the screen.

I think there is a struct which I can enable page flipping (vsync) for the game resolutions (320x400 already uses this) to eliminate this.


EDIT 2:

Page flipping doesn't seem to be viable, it seems that Descent's screen drawing functions never implemented cockpit drawing with that, and it would be too much for an EXE hack to add in. So there is still screen tearing for now.

Instead, I tried a different timing method which uses delay(1) until the more accurate timer_get_fixed_seconds() gets us to the desired frametime. This has a few FPS variance compared to the changing PIT + timer_get_approx_seconds() method ( which results in rock solid FPS), but this variance actually helps to break up the screen tearing pattern (making it more like the unpatched EXE being limited by performance). So I'm OK with how this looks up to about 60FPS.

I have yet to try this method on real hardware yet, hopefully delay(1) is accurate enough there too. It does work just fine in DOSBOX.

Reply 12 of 12, by Tarvis

User metadata
Rank Newbie
Rank
Newbie

Good news, I figured out how to hack in a wait for vblank before blitting the screen, which eliminates tearing.

So, attached are 35FPS and 70FPS EXEs for Descent 2 v1.2 (non-Vertigo) which should have no judder nor screen tearing under DOS.

Give it a try and let me know if there are any issues. Meanwhile I will port it back to Descent 1 and the Vertigo version, and build this option into the upcoming generic patcher.

EDIT: Added 30/60FPS versions, since 640x480 is probably outputting at 60Hz for most people (though DOSBOX Staging uses 70Hz for it)