Thanks for the quick feedback. Now I can confirm: ATW itself definitely works. When I set the sleep to like 5 seconds, I do get reprojection during these 5 seconds (i.e., the world feels stationary while turning the head). The actual problem is that when the frame refreshes, it somehow uses the old view pose. This means that if the view direction has changed in these 5 seconds, the world suddenly "jumps", centering the old view direction in the new view direction. After the next frame update, the new position will be used, so the world jumps back to where it was before. So it looks like ATW simply trails by one frame, which causes the world to jump back and forth each frame. I hope it is just a bug in my wrapper code above or maybe some frame queuing issue.
Update:
Okay, found my mistake. This behavior is caused by having a calling order of ovr_GetTextureSwapChainCurrentIndex --> ovr_SubmitFrame --> ovr_CommitTextureSwapChain. In fact, this is probably a common pitfall when migration from 0.8. In most 0.8 examples, the last step of the rendering loop was incrementing the texture index. Since the migration guide suggests that ovr_CommitTextureSwapChain essentially replaces this texture index increment, I kept it at the end of the loop. However, ovr_CommitTextureSwapChain must be called between the other two calls, so the correct order is ovr_GetTextureSwapChainCurrentIndex --> ovr_CommitTextureSwapChain --> ovr_SubmitFrame.