The way it works is: states are being made constantly and held in memory, and any time you change your inputs (e.g., you press a button or let go of a button that’s currently held), it rolls back X frames (where X is your runahead value) and applies your input change in the past and then emulates the frames between then and the current frame with your input change applied, all within the time it takes to show the next frame.
So, depending on your perspective, it’s either showing you the future based on your current inputs (i.e., running ahead) or sending your inputs into the past and then retroactively applying them to the present.
As long as the runahead frames do not exceed the game’s internal latency, you will not see any of this. It all happens invisibly, under the hood. However, if you exceed the game’s internal latency, you will see rollback artifacts in the form of skipped animation frames, choppy motion, etc., just like you would see during rollback-based netplay on a dodgy connection.
To make the latency match the original hardware, you first need to measure the total latency of the original hardware setup (we’ll call this A), then measure the total latency of your emulation setup (we’ll call this B). Subtract A from B and the remainder is what you need to shave off, either with runahead or whatever other mitigation strategy (frame delay, hard gpu sync, etc). Since latency is experienced as a total of all contributing factors, reducing it via any of the available means feels the same. That is, you reduce the time between your finger pressing a button and your eyeballs seeing the effects produced on-screen.