I tried reproducing the problem on my laptop, but I couldn't get the laptop to behave as well as your experiment. Deleting the "Sleep" line did not make the game run at the correct FPS. So I guess it will be back to you to help figure this out by testing my ideas. My first idea is to compare your current solution (see that it is using 100% CPU, or maybe 50% CPU if you have a dual core processor) to one where you use Sleep(0) instead of Sleep(sleepTime). If that doesn't work try only using sleep(0) when sleepTime is greater than 10. See how that affects the frame rate and the CPU usage. My next idea is to change both instances of the number "2" in my suggested alternative to a larger number. Since sleepTime was always a number greater than 10, you could try using something as high as 10 instead of 2, and still expect some extra CPU time back. Translating from code to English, that line would then be saying something like this:
"If the number of milliseconds before the end of this frame's time is greater than 2, then wait for the calculated number of milliseconds minus 2.". The intention is to sleep less if the Sleep function is having trouble returning control at the right time. We calculated that we should sleep for 11 milliseconds after this frame is done processing in order to wait for achieving 60 FPS, but apparently when we tell the system to sleep for 11 milliseconds (or even 9 milliseconds, apparently) it waits longer.
So I don't know if you have any other way to verify that or any other ideas to test. I know sleep(0) is supposedly a special value that yields the CPU only for the remainder of this process' time slice. Not entirely sure what that means, but that's why I suggested that as a test too.
Aha -- it looks like there are some answers here:
http://social.msdn.microsoft.com/Forums/en/clr/thread/facc2b57-9a27-4049-bb32-ef093fbf4c29. I will have to read that article and see if there is another solution.
Edit: I wonder if Thread.SpinWait would give CPU back to the system. It's hard to calculate what parameter value to pass to spinwait because apparently it depends on the speed of the processor. But if that does yield the CPU, maybe we could just use SpinWait(1000) and see if that is precise enough. (Surely 1000 CPU cycles is less than 1 millisecond... but I'm not sure if SpinWait counts CPU cycles.)
Edit 2: SpinWait does not return any CPU time. Maybe simply removing the Sleep is the best solution. People generally do not do other things at the same time they play games anyway. And if you de-activate the window do do something else, some CPU time is returned while the game is paused anyway. And most computers have dual core CPUs anyway so it will only be taking 1 core, and the other is available for other tasks. I guess it's alright to simply remove Sleep if you want reliable frame rates.