Post subject: Timing of input/frame advance
Player (102)
Joined: 6/7/2006
Posts: 20
I always assumed that when I frame advance in any emulator, it will pause pretty much when the vertical interrupt routine would be called. This would make sense since a whole frame has been rendered at this point, and likely the calculations for the next frame havent started. Lately I've been playing around ith BizHawk and Gens and they both seem to show a different behaviour. This can be a problem if you want to read out RAM values that describe a position of a moving object, since at some frames these values have been updated already, while at others they haven't. The problem I'm having is simplified looking like this: Consider an object moving with the speed of 1:
Frame:  X-Value
   1      1
   2      2
   3      2
   4      4
   5      5
   6      5
   7      7
This is anoying since I can't really trust the values my LUA script tells me. Even further this made me question for what time period the input is valid? If the input I am giving for one frame is not synced to something like v_int, how can I know that I am not giving input for 2 frames instead of one? So my questions are: What is the timing for frame advance concerning the emulation of the main cpu of a console? Is the same timing used for input given by the user? I hope I made my problem understandable. I post it here since it seems like that the behaviour is shared among multiple emulators and not bound to one specific emulator.
Post subject: Re: Timing of input/frame advance
MESHUGGAH
Other
Skilled player (1889)
Joined: 11/14/2009
Posts: 1349
Location: 𝔐𝔞𝔤𝑦𝔞𝔯
It would be a good idea to share at least the name of the game you are talking about. There are many reasons while a specific value doesn't changes each frame. 1. I see a very simple pattern in your values : (increment with) 1, 0, 2, 1, 0, 2. Are you sure it should be 1, 1, 1, 1, 1, 1? 2. The "position" of calculations are (nearly) completely up to the developers. There are games where the input polling is on the end of the frame instead of the start, so for example a movement-specific subroutine won't takes account your up/down/left/right because it's executed earlier. 3. If you are unsure that your input isn't calculated on that frame, you have plenty choices: - monitor in-game time / frame counter (I doubt there's a game without these) - monitor the "polled input" (same as above) - watch the memory for these values (x-value in this case), recognize the pattern so you know when you are fall behind - look for a "lag indicator" (not all games have this, and some games have multiple types to handle different problems (too many sprites on screen, too much data to load because of high speed, etc) Also, you should post your lua script, maybe you forgot to advance it each frame or you didn't render the value on next frame. edit: forgot to mention "wrong" address/calulation, for example you found X hitbox not position, or Cam X needed to get proper "X" position of character.
PhD in TASing 🎓 speedrun enthusiast ❤🚷🔥 white hat hacker ▓ black box tester ░ censorships and rules...
Post subject: Re: Timing of input/frame advance
Player (102)
Joined: 6/7/2006
Posts: 20
MESHUGGAH wrote:
It would be a good idea to share at least the name of the game you are talking about. There are many reasons while a specific value doesn't changes each frame.
Okay, I will explain my specific problem (I thought this was a more general thing with emulators, thats why I tried to explain it without mentioning the specific game). The game I tested this with is High Seas Havoc (or Havoc / Captn Lang, depending on the region). It's a basic jump'n'run game. This is the lua script:
state = savestate.create()

gui.register( function ()
	
	havocVX= memory.readlongsigned(0xFFDBCC)        -- Havocs velocity (x)
	camX = memory.readlongunsigned(0xFFF6D0)        -- Camera Position (x)
	havocXonScr = memory.readlongunsigned(0xFFDBB0) -- Havocs Position relative to the camera (x)
	havocX = camX + havocXonScr                     -- Havocs Position (x)
	cpuPC = memory.getregister ("pc")               -- program counter

	savestate.save(state)
	
	  gens.emulateframeinvisible()
	  effectiveVxonScr = (memory.readlongunsigned(0xFFF6D0)+memory.readlongunsigned(0xFFDBB0)) - havocX
	  cpuPC2 = memory.getregister ("pc")
	
	savestate.load(state)
	
	message = string.format("PC: %X, %X", cpuPC,cpuPC2)
	gui.text(10, 42, message, "#FFFF00FF", "black")
	message = string.format("vel:      %10d", havocVX)
	gui.text(10, 58, message, "#FFFF00FF", "black")
	message = string.format("real vel: %10d", effectiveVxonScr)
	gui.text(10, 66, message, "#FFFF00FF", "black")	
	message = string.format("pos:      %10d", havocX)
	gui.text(10, 74, message, "#FFFF00FF", "black")
	
end)
In case you are wondering about why I add the camera position to havocs relative position: This is the same thing the game does (ROM at 0x004AD0):
move.l  (0xFFF6D0).l, d0
add.l   (0xFFDBB0).l, d0
move.l  (0xFFF6D8).l, d1
add.l   (0xFFDBB4).l, d1
move.l  d0, (0xFFDBB8).l
move.l  d1, (0xFFDBBC).l
So with the script I can calculate how far the player has effectively traveled along the x-axis. At least if the game has done the calculations on the position in the time that Gens leaves for the frame advance. I added the display of the program counter to see where the game stops after frame advancing. This is what what I get when running in the first level:
((0xFFF6D0) + (0xFFDBB0)) of next frame
-((0xFFF6D0) + (0xFFDBB0)) of current frame
                                   |
                                   |
(0xFFF6D0) + (0xFFDBB0) --+        |
                          |        |
   (0xFFDBCC) --+         |        |
                |         |        |
Frame    PC     vX      posX  real vX 
  850  457C      0  19398656        0
  851  B3E6      0  19398656        0
  852  457C   8192  19406848    16384
  853  4676  16384  19423232    24576
  854  63C4  24576  19447808    32768
  855  4678  32768  19480576        0
  856  6226  40960  19480576    90112
  857  B1DC  49152  19570688    57344
  858  45BC  57344  19628032    65536
  859  B1B6  65536  19693568    73728
  860  4580  73728  19767296    81920
  861  456A  81920  19849216        0
  862  5930  90112  19849216   188416
  863  4580  98304  20037632   106496
(The real vX looks one frame into the future, thats why the values are offset by 1 frame.) So here it happened twice that the position calculation was not yet done when the cpu was paused: Frame 856 and 862. I am wondering if there is a reason, why the emulator is not halting at the time it triggers the vertical blank interrupt (if it's enabled). I guess synchronizing the calculations to the vertical blank period should be extremely common in 8/16 bit games, so this would create more reliable results when reading values from RAM.
Site Admin, Skilled player (1236)
Joined: 4/17/2010
Posts: 11274
Location: RU
Are you sure that game has no sub-pixels?
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
Former player
Joined: 7/22/2010
Posts: 14
This is one of those things where your background and mental model really affect how you approach an issue. From a TAS/lua hacker point of view, I understand why 'end the frame at the vertical refresh interrupt/end of active display' makes sense. If an emulator author did not have that specific background, that is not really the natural way to do things. So just as a simple example, the SMS has 262 scanlines, of which 192 are active display. Vertical interrupt will fire at scanline 192. So... Frame 0 will execute 192 scanlines, and every subsequent frame will execute 262 scanlines, with each frame starting at scanline 192 and ending at scanline 191. On other consoles things can get weirder. The pcengine is very configurable regarding the size of active display, and the line on which v-int fires is not fixed. So now we have variable-length frames. So then throw, for example, audio sync into the mix. I may not have generated 1/60th of a second worth of samples in a short frame. Those problems are all solvable, but my point is that there are non-trivial architectural sacrifices you must make to end the frame right at v-int. I am just trying to explain why an emulator author may have chosen not to do that.
Former player
Joined: 7/22/2010
Posts: 14
I should add that you would make those sacrifices for probably dubious benefit. On most systems input can be polled whenever the game wants. Sometimes it's multiple times per frame, other are only every couple frames... And at varying times within the frame. It is definitely common, but by no means universal, to poll after vsync. I had at one point experimentally altered my pce core to do this and it was not a categorical improvement, so I switched it back to the simpler mode of fixed-length frames starting at scanline 0.
MESHUGGAH
Other
Skilled player (1889)
Joined: 11/14/2009
Posts: 1349
Location: 𝔐𝔞𝔤𝑦𝔞𝔯
As feos mentioned, you forgot to add subpixels in to the calculations. As far as I can see, the next X subpos is at 0xFFDBB2 and 0xFFDBC2 (probably one of them is hitbox (since they are equal)). 0xFFDBBA and 0xFFFDDE gets the value from the 2 address above, so check it out which should be used for your assistance.
PhD in TASing 🎓 speedrun enthusiast ❤🚷🔥 white hat hacker ▓ black box tester ░ censorships and rules...
Player (97)
Joined: 12/12/2013
Posts: 376
Location: Russia
Also, don't forget that VBLANK is not instant! It has start, and end. VBLANK start -> Active display end. VBLANK end -> Active display start. So, there are two points where developer of emulator can update input. But game can read input at any point. It can read input twice or more... For real system, joypad state is not changing once in frame. Game reads current state of input when it needs.
Player (102)
Joined: 6/7/2006
Posts: 20
Thanks for the answers!
feos wrote:
Are you sure that game has no sub-pixels?
MESHUGGAH wrote:
As feos mentioned, you forgot to add subpixels in to the calculations.
The 32bit position values include 16 bits of subpixel information. So in order to get the position without subpixel precision you would calculate (0xFFF6D0.L + 0xFFDBB0.L) >> 16. Or you could wait until the game added 0xFFF6D0.L and 0xFFDBB0.L, and then read FFDBB8.W (or shift FFDBB8.L >> 16). Reading what vecna wrote made me realize why the way current emulators work is intended. I hadn't thought about audio mixing or variable vertical-length. I've changed my lua script so that all values are read at the beginning of v-blank, and everything works nice now. I didn't realize you could link lua functions to certain code areas, that's why I didn't try it earlier. So thanks for helping me a) understand why emulators work the way they do and b) understand how to fix my script so that it works the way it is supposed to :)