Post subject: Refactoring JPC-rr Movie Files
Active player (372)
Joined: 9/25/2011
Posts: 652
One of the biggest pains working with JPC-rr is the fact that the movie files are so hard to refactor. To patch in a change into a finished movie is nearly impossible, as the movie files are stored with very specific relative time values. Changing any one of these values has a ripple effect that impacts all the others, and can often lead to invalid values and desyncs everywhere. After thinking about the problem for a while, I decided to try and create a set of lua scripts to make this process easier. Once finished, I tested it out on my Hero's Quest TAS, and was happy to find out that it was a great success. The scripts I created convert the complicated movie file format into something readable and easily editable. And although it's not perfect and does cause some desyncs, fixing them is a lot simpler now. To give some perspective, it took me about eight hours to refactor the Hero's Quest TAS and remove the resyncs. It would have taken me 3 - 4 times that long redoing it from scratch. When I found another possible improvement at the end of refactoring, it took me just two hours to refactor it from the start again and remove the resyncs a second time. I think it's at a state where I can share it with the rest of the community now, so without further ado, here is what I've named "TAS Scripts". Step 1: Record a TAS script The first step in refactoring the code is to convert a movie file to a TAS Script. 1) Download the recordtasscript.lua and fileio.lua files and put it in your lua directory. 2) Put the movie file you want to convert into the lua directory 3) Edit the recordtasscript.lua file in a text editor and update the moveFile variable with the name of the movie file you want to refactor, and the TASScriptFile variable with the name of the TAS Script file you want to create. Save. 4) Open JPC-rr and load the movie file 5) In the Lua window, run recordtasscript.lua. Wait until it has parsed the movie file and says you can start your movie running. 6) Run your movie to the end. The script will convert the movie to a TAS Script as it plays. 7) Terminate the script once the movie has fully played. If successful, your movie file which looks something like this:
+0 SAVESTATE 48ee390aca349821084b61652763eca08be308bf526e0b77 0
+87008950 SAVESTATE ca11a6f0219e1c4e48461de8b5641fca6ee5bfc0dc42ae0c 3
+14268150 SAVESTATE 1ac0e82719240fc23c8222406999edb018d8463475ef46b4 4
+55220 org.jpc.emulator.peripheral.Keyboard KEYEDGE 28
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 28
+614031020 SAVESTATE b6200e347c52b8ceeecc66dd64b47ab13b3dc878f66eece4 4
+13044500 SAVESTATE 78314316d124332100e7d03ea6705f0b90cbe47b229e2c54 4
+0 SAVESTATE d3d78c36ed464e355691fbe346b3d2057de3277fb28833a8 11
+0 SAVESTATE 83a31f8ee5f07ef4a95ba118cb39feff85f7ae11b0afe868 15
+51542 org.jpc.emulator.peripheral.Keyboard KEYEDGE 66
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 66
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 21
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 21
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 28
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 28
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 21
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 21
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 28
will have been turned into a TAS Script file that looks something like this:
Save:798
FAdv:3
Type:<lctrl>cc<lctrl>ooppeenn<enter><enter>
FAdv:42
Save:801
FAdv:6
Save:816
Type:<esc><esc>ggeett  rroocckk<enter><enter>
FAdv:8
Save:817
MXMove:-54
MYMove:29
FAdv:1
MClick:0
FAdv:1
MClick:0
FAdv:1
MXMove:64
MYMove:-40
The TAS Script The TAS Script has the following commands (case-sensitive): FAdv:# This command tells the script runner to frame advance # number of frames. You'll most likely be editing these values whenever you run into a desync. MXMove:# This translates to an XMOUSEMOTION command that moves the mouse along the X-axis by # MYMove:# This translates to an YMOUSEMOTION command that moves the mouse along the Y-axis by # MClick:# This translates into an MOUSEBUTTON command that clicks the # button on the mouse (0 for left button, 1 for right) Type:abcde This creates KEYEDGE commands for all the letters after the colon. Note that special keys are bracketed in '<' and '>' brackets (see fileio.lua for list of keys) and normal keys are represented by their un-shifted values. In other words, to type an exclamation mark, you need to put "<lshift>11<lshift>". Also note that usually you will type every key twice, once for the key press and once again for the key release. Save:# This represents a save that was made in the movie, along with the save number as #. When running the TAS file, it can't save, so it does a XMOUSEMOTION 0 instead. The save number can be useful to quickly find how far you are into your TAS file when you're running it. Pause: This is a very useful command that pauses playback when you're running your TAS script. Good for when you want to hone in on a particular section, or want to set up a savestate. You can also add your own comments to your TAS file, either by starting a line with a colon:
: This is a comment
or by adding a colon to the end of an existing line:
FAdv:4:This is a comment
Step 2: Running and Editing your TAS Script To run your TAS Script, first you need to download runtasscript.lua and save it in your lua directory. You also need the aforementioned fileio.lua file. Open runtasscript.lua in a text editor and change the scriptFile variable to be the name of your TAS Script file. Now assemble your game from scratch, then in your Lua window run runtasscript.lua. Wait until it has parsed your TAS Script file, then start JPC-rr running. You'll see it playing the commands listed in your script file. At some point in time it will desync. To better look at what is causing the desync, throw a Pause: command in somewhere before the desync, then step your movie frame-by-frame. Once you've identified where it's desyncing, you can update your TAS Script file to try and get around the desync. To test your changes, terminate the Lua VM, reassemble your game, and run runtasscript.lua again. Note that it is important that you do it in that order. If you're correct, you will have fixed the desync. If you are wrong, then you'll have to try something else to fix it. Soon you will get far enough that you don't want to start from the beginning every time. Throw a Pause: command in just before you desync. It will run one frame advance after the Pause: command and then stop. Create a save state, then delete everything from your script file from the start to the next FAdv after the Pause: command you entered (subtract one from that FAdv). Save it as a new TAS Script file (as you'll probably want to keep your old one around). Edit runtasscript.lua to point to your new TAS Script file and save. Then terminate the Lua VM, load your save state, and then re-run runtasscript.lua (again in that order). You should now be able to start running from your save state. Inserting new commands into a TAS Script file works the same as fixing desyncs, except instead you're now inserting new commands. Type what you want to type, move the mouse how you want to move it, and put frame advances where you want them. Then rerun and see what impacts your changes have. That's basically it. Let me know if you have any questions, and I'll leave you with two TAS Scripts that run the boot sequence for you. This one skips Config.sys and Autoexec.bat:
Save:0
FAdv:5
Save:2
Type:<enter><enter>
FAdv:40
Save:3
Type:<f5><f5>
FAdv:6
Save:12
and this one runs Config.sys (for extra memory) but skips Autoexec.bat which you shouldn't need:
Save:0
FAdv:4
Save:3
FAdv:1
Save:4
Type:<enter><enter>
FAdv:44
Save:4
FAdv:1
Save:4
Save:11
Save:15
Type:<f8><f8>yy<enter><enter>yy<enter><enter>yy<enter><enter><esc><esc>
If you need the mouse, just add
Type:ccttmmouousese  //rr3322
at the end Finally, it only seems to work if TAS files are saved in UNIX format. It doesn't like the line endings in PC format. I'll look into that sometime when I have more time.
Expert player, Reviewer (2389)
Joined: 5/21/2013
Posts: 414
This looks amazing. I was just working on something where I need to change the starting RTC and re-sync to deal with the new RNG - this will massively improve that process. I'll try it out when I have time and let you know how it goes.
Active player (372)
Joined: 9/25/2011
Posts: 652
slamo wrote:
This looks amazing. I was just working on something where I need to change the starting RTC and re-sync to deal with the new RNG - this will massively improve that process. I'll try it out when I have time and let you know how it goes.
Great! I’d love any feedback or suggestions. FYI, my copy of JPC-rr sometimes stalls when running or recording long tas scripts. It just means I have to try again until it doesn’t stall. I’m curious to see if it stalls on others’ computers
DrD2k9
He/Him
Editor, Expert player, Judge (2037)
Joined: 8/21/2016
Posts: 1009
Location: US
c-square wrote:
FYI, my copy of JPC-rr sometimes stalls when running or recording long tas scripts. It just means I have to try again until it doesn’t stall. I’m curious to see if it stalls on others’ computers
My copy (v11.2 with mouse) occasionally stalls when using lua scripts (i.e. the RNG seed script for SQ3). I've found that terminating the script will allow the game to continue running and I can usually just restart the script immediately.
Expert player, Reviewer (2389)
Joined: 5/21/2013
Posts: 414
So I've been trying this out, it works fairly well and the instructions are great. The only problem I'm having is that there are a lot of desyncs while running the recorded .tas file. I keep having to add extra frames every couple of seconds to get it to resync, so it seems like the script's definition of a frame is slightly shorter than what the game considers a frame. How do the scripts determine how many nanoseconds a frame is, and are there any adjustments I can make to line it up better?
Active player (372)
Joined: 9/25/2011
Posts: 652
slamo wrote:
I keep having to add extra frames every couple of seconds to get it to resync, so it seems like the script's definition of a frame is slightly shorter than what the game considers a frame. How do the scripts determine how many nanoseconds a frame is, and are there any adjustments I can make to line it up better?
Huh, that's odd. It doesn't convert nanoseconds to frames. The recorder first parses the movie file and converts all relative time entries to absolute times by adding them up as it goes. Next, it steps through the movie, frame by frame, and compares the current time (jpcrr.clock_time()) with the last processed line of the movie file. If the current time is greater than the last processed time, then it processes the lines until it catches up. If the current time is less than the last processed time, it records a frame advance and advances the frame. A quick question, how are you replaying the tas file? Are you letting JPC-rr run, or are you stepping frame-by-frame? Sometimes I've found stepping frame-by-frame ends up giving different results then letting it run freely. If you can send me your tas file, movie file and disk image, I could try it out on mine to see what I can find.
creaothceann
He/Him
Editor
Joined: 4/7/2005
Posts: 1874
Location: Germany
What is used to calculate time, ints or floats?
Active player (372)
Joined: 9/25/2011
Posts: 652
creaothceann wrote:
What is used to calculate time, ints or floats?
Lua doesn't have ints or floats, just reals. That said, all times are whole number values, so there should be no issue with any floating point addition rounding.
Expert player, Reviewer (2389)
Joined: 5/21/2013
Posts: 414
c-square wrote:
slamo wrote:
I keep having to add extra frames every couple of seconds to get it to resync, so it seems like the script's definition of a frame is slightly shorter than what the game considers a frame. How do the scripts determine how many nanoseconds a frame is, and are there any adjustments I can make to line it up better?
Huh, that's odd. It doesn't convert nanoseconds to frames. The recorder first parses the movie file and converts all relative time entries to absolute times by adding them up as it goes. Next, it steps through the movie, frame by frame, and compares the current time (jpcrr.clock_time()) with the last processed line of the movie file. If the current time is greater than the last processed time, then it processes the lines until it catches up. If the current time is less than the last processed time, it records a frame advance and advances the frame. A quick question, how are you replaying the tas file? Are you letting JPC-rr run, or are you stepping frame-by-frame? Sometimes I've found stepping frame-by-frame ends up giving different results then letting it run freely. If you can send me your tas file, movie file and disk image, I could try it out on mine to see what I can find.
I see, that should be a pretty airtight way of doing it. Running freely and going frame by frame doesn't seem to make a difference, as it desyncs in the same place. There does seem to be some differences in the original movie file and the movie file that results from the .tas file. The start of my movie for example:
!BEGIN events
+0 OPTION RELATIVE
+58472700 SAVESTATE 2d6a0a689bf5b99e43c1d1a0240451f1d08e4caefa7bf6d3 0
+0 SAVESTATE ab3d013c0817f84b9f47df68abf28923e98c60370901b1d6 0
+0 SAVESTATE 02e6de5911a825b97a28fed06c7855a04120ee547cde7635 0
+42859620 org.jpc.emulator.peripheral.Keyboard KEYEDGE 88
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 88
The tas file portion of this chunk:
FAdv:2
Save:0
Save:0
Save:0
FAdv:4
Type:<f12><f12>
And the result of playing back the .tas file:
!BEGIN events
+0 OPTION RELATIVE
+58472700 org.jpc.emulator.peripheral.Keyboard XMOUSEMOTION 0
+0 org.jpc.emulator.peripheral.Keyboard XMOUSEMOTION 0
+0 org.jpc.emulator.peripheral.Keyboard XMOUSEMOTION 0
+57126144 org.jpc.emulator.peripheral.Keyboard KEYEDGE 88
+666660 org.jpc.emulator.peripheral.Keyboard KEYEDGE 88
There seems to be a significant difference when the first KEYEDGE event starts. I'll send you a PM with what you need.
Active player (372)
Joined: 9/25/2011
Posts: 652
slamo wrote:
There does seem to be some differences in the original movie file and the movie file that results from the .tas file.
Unfortunately, it's not completely accurate, and will be off by a frame every once in a while. I've improved it as much as I can at the moment, though if you or others would like to take a crack at it, please feel free. In the meantime, fixing the desyncs isn't a huge issue; much better than hacking the movie file for sure. Comparing the original movie file to the desynced movie file is usually the best way to find out exactly where the desync is. I've created a script (generatemovieabs.lua) that converts a movie's relative values to absolute values (.abs file). Here's how to use it: First, convert your original movie to an .abs file. Then, whenever your run desyncs, save a movie, and then convert that movie to an .abs file. Finally, use a file-diff program to compare the two .abs files and see where the absolute times start getting consistently off just before the desync. That should be where you'll need to adjust it. Usually it's solved by simply adjusting a frame advance by one. Unfortunately, this method stops working if you've inserted any new instructions, or removed any. Then it's a matter of observing frame-by-frame and deducing where the desync is.
slamo wrote:
There seems to be a significant difference when the first KEYEDGE event starts.
That "significant difference" is actually just one frame. It varies (which is what makes JPC-rr annoying), but often a frame is roughly 14,000,000 nanoseconds, which is what the movie file counts in. I took your files and it took me about 10 minutes to get it syncing the boot up properly to get the right RNG seed. After that, it went smoothly until the room where you pick up the laser (22 seconds in). Then it desynced. I spent another 10 minutes fixing that room (two places needed fixing), that got me to the room with the balls (5 seconds further), where it desynced again. It's slow, but much faster than working from scratch. Here's the tas file up to the last desync I mentioned. I suggest creating a save point there, then running your script from that point onwards (everything after savestate 682).
FAdv:2
Save:0
Save:0
Save:0
FAdv:3
Type:<f12><f12>
FAdv:40
Type:11
FAdv:1
Save:1
Save:1
Save:1
Type:<f8><f8>yyyyyyyyyynnnn
FAdv:16
Save:1
Save:1
Save:1
Type:cc<rshift>;;<rshift><enter><enter>ddggeennvvggaa<enter><enter>
FAdv:3
Save:2
Save:2
FAdv:1
Save:10
Save:10
Save:10
Save:10
Type:<enter><enter>
FAdv:29
Save:10
FAdv:188
Save:12
Save:12
Save:12
FAdv:15
Save:12
Save:12
Save:12
FAdv:42
Type:<num6>
FAdv:31
Save:19
Save:19
Type:<num8><num6>
FAdv:4
Save:20
FAdv:5
Type:<num9><num8>
FAdv:3
Save:25
FAdv:2
Save:26
Save:26
FAdv:17
Save:29
FAdv:3
Type:<num6><num9>
FAdv:39
Save:30
FAdv:2
Save:31
FAdv:2
Save:32
FAdv:2
Save:33
FAdv:2
Save:34
FAdv:2
Save:37
FAdv:4
Save:42
FAdv:3
Type:<num3><num6>
FAdv:16
Save:43
FAdv:2
Save:44
Save:44
Type:<num9><num3>
FAdv:16
Save:44
Save:44
Save:44
Save:44
Save:44
FAdv:34
Save:80
FAdv:9
Save:82
FAdv:293
Save:83
Save:83
FAdv:15
Save:85
Save:85
Save:85
FAdv:139
Save:103
Save:103
Save:103
Type:<num8><num9>
FAdv:6
Save:103
FAdv:2
Save:104
FAdv:2
Save:105
FAdv:2
Save:106
FAdv:2
Save:107
FAdv:2
Save:108
Save:108
Save:108
Save:108
Type:<num9><num8>
FAdv:21
Save:110
FAdv:2
Save:119
Type:<num9>
FAdv:2
Save:122
Save:122
Save:122
FAdv:2
Save:123
FAdv:1
Save:124
Type:<num9>
FAdv:2
Save:126
FAdv:2
Save:127
FAdv:2
Save:128
Type:<num7><num9>
FAdv:30
Save:128
Save:128
FAdv:166
Save:176
Save:176
FAdv:14
Save:179
FAdv:20
Type:<num8><num7>
FAdv:1
Save:180
FAdv:3
Type:<num7><num8>
FAdv:7
Save:186
Save:186
Save:186
Save:520
Save:520
Save:520
Save:520
FAdv:5
Save:520
FAdv:8
Save:520
FAdv:6
Save:520
FAdv:3
Save:522
FAdv:1
Save:527
Type:<num8><num7>
FAdv:21
Save:527
FAdv:2
Save:528
FAdv:2
Save:529
FAdv:2
Save:530
FAdv:3
Type:<num9><num8>
FAdv:1
Save:535
FAdv:2
Save:539
FAdv:2
Save:540
Type:<num8><num9>
FAdv:4
Save:562
FAdv:5
Save:564
Save:564
FAdv:6
Save:564
FAdv:2
Save:565
FAdv:2
Save:566
FAdv:2
Save:567
Save:567
Type:<num9><num8>
FAdv:46
Save:567
Save:567
Save:567
Type:<num1><num9>
FAdv:2
Save:567
FAdv:2
Save:568
FAdv:3
Type: 
FAdv:6
Type: 
FAdv:1
Save:569
Save:569
Save:569
FAdv:11
Save:575
Save:575
Type:<num2><num1>
FAdv:2
Save:575
FAdv:2
Save:576
FAdv:2
Save:577
Type:<num1><num2>
FAdv:2
Save:578
Save:578
Save:578
FAdv:5
Save:579
FAdv:2
Save:581
FAdv:2
Save:582
FAdv:2
Save:583
Type: 
FAdv:3
Type: 
FAdv:1
Save:585
Save:585
FAdv:2
Save:587
FAdv:2
Save:606
FAdv:2
Save:607
Save:607
Save:607
Type:<num2><num1>
FAdv:8
Save:615
Save:615
Save:615
Type:<num1><num2> 
FAdv:2
Type:<num2><num1> 
FAdv:2
Save:615
FAdv:19
Save:620
FAdv:2
Save:621
FAdv:3
Save:622
FAdv:2
Save:623
FAdv:2
Save:624
Type:<num3><num2>
FAdv:4
Save:627
FAdv:12
Save:628
Save:645
Save:645
Save:645
Save:645
FAdv:7
Save:645
FAdv:2
Save:645
FAdv:2
Save:646
FAdv:3
Type:<num1><num3> 
FAdv:2
Type:<num3><num1> 
FAdv:45
Save:652
Save:652
FAdv:29
Save:652
Save:652
Save:652
Save:652
Save:652
FAdv:3
Save:652
Save:652
Save:652
Save:652
FAdv:32
Save:653
Save:653
Save:653
FAdv:7
Save:653
FAdv:2
Save:656
FAdv:3
Type:<num9><num3>
FAdv:4
Save:657
FAdv:4
Save:659
Save:659
Save:659
Type:<num8><num9>
FAdv:3
Type:<num9><num8>
FAdv:1
Save:669
FAdv:2
Save:670
Save:670
FAdv:2
Save:670
FAdv:2
Save:671
FAdv:2
Save:672
FAdv:1
Save:673
Type:<num3><num9>
FAdv:3
Type:<num6><num3>
FAdv:169
Save:675
Save:675
Save:675
Save:675
FAdv:4
Save:679
FAdv:3
Save:680
Type:<num9><num6>
FAdv:2
Save:681
FAdv:5
Expert player, Reviewer (2389)
Joined: 5/21/2013
Posts: 414
Thanks for checking it out. Your experience with my movie was about the same that I had, I just wasn't sure if it was desyncing more than usual. Your methods for recording and running the tas file seem pretty solid and I can't see where any inaccuracies would come from. I'll go on ahead with resyncing the run, it will still be much faster than redoing all the inputs. Again, thanks for looking into it and I'll definitely be using this in the future.