Submission Text Full Submission Page

Game objectives

  • Emulator used: lsnes rr2-β23
  • Aiming for fastest arbitrary code execution in the yellow version of the Japanese version.
  • Ensure that the highest possible quality Bad Apple! to be played.

Comments

I created this run based on the Amazing Run created in 2017 using the Yellow version and the Amazing Run created in 2024 using the NES version of Mario Bros. I was so impressed by the sheer precision of both runs and the beautiful graphics that I decided to try and create a similar run myself.
I had previously used BizHawk to create a TAS video playing BadApple, but BizHawk only allows one input per frame, so there were major issues with resolution and frame rate. Also, at this input speed, the data sent in real-time cannot be output directly to the screen in time, so the video is created by saving the data once in SRAM and rewriting the image based on it. Therefore, the capacity of the moving image depended on the storage capacity of the SRAM, and there was no choice but to create a moving image with an extremely low frame rate and resolution. Furthermore, it was extremely difficult to store even audio with such data capacity, so implementation was not possible.
The two runs referred to show that being able to input buttons in subframe units is a prerequisite. In this run, I reviewed the emulator and decided to use lsnes, which allows subframe-by-subframe input (I should have used this in the previous run, but I didn't know how to handle it due to my lack of knowledge). This freed me from the once-per-frame input limit and allowed me to send about 260 bytes of data per frame. The significant increase in the amount of data that could be sent solved the problems of low frame rate and resolution, and also enabled the successful output of sound.

Stage by stage comments

This run was based on one produced by MrWint, so in many respects the means used are similar.

ACE set-up

As with MrWing's, the execution is carried out in three phases.

First stage

After a successful SRAM glitch, swap the Pokémon in your possession as follows and run the map script execution. In the procedure below, the order is shown by (Pokémon number to swap->Pokémon number to swap to). The text next to it describes what is happening at the time.
  1. 1 -> 9 Expand the どうぐ
  2. 4 -> 21 0xFF chunks to set memory
  3. せってい(0xF5) 0xFF lumps to 0xF5
  4. 21 -> 14 Pull the 0xFF lump upwards
  5. Adjustment of どうぐ
    1. 4th わざマシン55 throw away everything
    2. 3rd わざマシン55 throw away everything
    3. 2nd わざマシン55 118 (76h)
    4. 3rd わざマシン45 240 (F0h)
    5. 4th わざマシン45 34 (22h)
  6. 21 -> 26 Script the name of the competitor
  7. 25 -> 15 Connecting separated rival names て(0xC3)
  8. 14 -> 24 Bump in the adjusted code
After completing this procedure, closing the menu and returning to the map immediately executes the map script and the 0xD2E1~ code that has been planted in the rival's name is executed. The push af and jr nc, D2EC with * on the way are rubbish that appears when replacing the equipment and can be ignored on execution.
WRA1:D2E1 76               halt
WRA1:D2E2 F5               push af ; *
WRA1:D2E3 F0 F5            ld   a,(ff00+F5)
WRA1:D2E5 22               ldi  (hl),a
WRA1:D2E6 F5               push af ; *
WRA1:D2E7 00               nop
WRA1:D2E8 00               nop
WRA1:D2E9 00               nop
WRA1:D2EA 30 00            jr   nc,D2EC ; *
WRA1:D2EC C3 E1 D2         jp   D2E1
By executing this code, the code 0xD2E1~ is rewritten using the button input, resulting in the following final code.
WRA1:D2E1 76               halt
WRA1:D2E2 00               nop
WRA1:D2E3 F0 F5            ld   a,(ff00+F5)
WRA1:D2E5 22               ldi  (hl),a
WRA1:D2E6 3C               inc  a
WRA1:D2E7 28 06            jr   z,D2EF
WRA1:D2E9 00               nop
WRA1:D2EA 00               nop
WRA1:D2EB 00               nop
WRA1:D2EC C3 E1 D2         jp   D2E1
This rewrite adds an exit condition to the original code, which had been an infinite loop, allowing it to express the end of execution. This code is used to write the second stage code into memory below.
The second stage code has 0xFF(rst 38) at the end, which is read to determine that the first stage has finished reading. The moment this is written, the second stage code of 0xD2EF~ is executed.

Second stage

Write the following code in the code created in the first stage directly below the code of the first stage.
From this code, all controller inputs in subframes are free and free from memory limitations.
This code receives the controller input in subframes and writes 0xD9B2~ the received number. This code is used to write the 593-byte third stage code.
WRA1:D2EF 3E 01            ld   a,01
WRA1:D2F1 EA 00 D0         ld   (D000),a
WRA1:D2F4 F3               di
WRA1:D2F5 21 B2 D9         ld   hl,D9B2
WRA1:D2F8 3E 10            ld   a,10
WRA1:D2FA E0 00            ld   (ff00+00),a
WRA1:D2FC F0 00            ld   a,(ff00+00)
WRA1:D2FE 47               ld   b,a
WRA1:D2FF CB 30            swap b
WRA1:D301 F0 00            ld   a,(ff00+00)
WRA1:D303 A8               xor  b
WRA1:D304 22               ldi  (hl),a
WRA1:D305 47               ld   b,a
WRA1:D306 FE FD            cp   a,FD ; Processing of the end of the Second stage
WRA1:D308 20 0E            jr   nz,D318
WRA1:D30A FA 1E D3         ld   a,(D31E)
WRA1:D30D 3C               inc  a
WRA1:D30E EA 1E D3         ld   (D31E),a
WRA1:D311 FE 03            cp   a,03
WRA1:D313 D2 B2 D9         jp   nc,D9B2
WRA1:D316 18 E4            jr   D2FC
WRA1:D318 AF               xor  a
WRA1:D319 EA 1E D3         ld   (D31E),a
WRA1:D31C 18 DE            jr   D2FC
WRA1:D31E 00               nop
WRA1:D31F FF               rst  38 ; First stage end command
In the second stage, as in the first stage, a termination condition is attached by a terminating character. The code in the third stage is terminated with three 0xFD characters to indicate the end of the code. Originally, this code should have been shortened to 1 byte, but in order to ensure flexibility in the code, we decided to use 3 bytes to indicate the end of the code. Between you and me, it was a pain to find the numbers I didn't use...

Third stage

It executes the code written in the second stage. It is responsible for initialising the tiles and outputting images and sound.
The set-up is now ready for arbitrary code execution up to video output.

Execution of code

It executes the code written in the second step.
What this code does is as follows.
  1. Rewriting tiles
  2. write the input at the controller to VRAM
  3. write the input at the controller to the waveform memory
  4. finish writing and rewrite the memory for the map, player's name, Pokémon status, etc. and watch the ending
While rewriting, the image is output by looping through 2 and 3. A single screen of images requires 360 bytes (18*20 squares). This code acquires 180 bytes per frame and updates the screen once every two frames to maintain approximately 30 fps.
The audio is output at approximately 9709 Hz, which consists of 4 bits per sample and requires approximately 80 bytes per frame. Adding up the image and sound data, a frame loop consumes 180 + 80 = 260 bytes, requiring input at approximately 16,000 Hz.
For further information, see source code.

ending

The Bad Apple! and head towards the ending.
To start the ending, rewrite the map data, player names, Pokémon status, Pokémon's kits, spending money, play time, etc., in advance.
The ending is performed by running the programme that originally exists in the ROM. The game is now complete, GG!

Ingenuity in production

This run was produced with various innovations and improvements from previous run. In particular, there are quite few documents on lsnes, and I could find very few articles (especially in Japanese) by people who actually use it, so I will leave them lightly here, including as a reminder, in the hope that they will be of use to someone else.

Combined use of emulators

lsnes is a very good emulator that supports subframe-based input and has high reproducibility in terms of audio and VRAM, but as a tool for creating TASvideo it is a little quirky and difficult to handle.
Therefore, I decided to use BizHawk in situations where I did not need to input data in units of subframes up to set-up. This is then read by the lua script and the controller input is executed from the script. Fortunately, the minimum necessary functions such as soft reset were also provided, so it was easy to create.
 -- Example of lua code
 function line_input(line)
     local input_flg = {["A"] = false, ["B"] = false, ["s"] = false, ["S"] = false, ["R"] = false, ["L"] = false, ["U"] = false, ["D"] = false}
     for i = 1, #line do
         local char = string.sub(line, i, i)
         local button = input_dict[char]
         if button == -1 then
             input.reset()
             return
         end
         if button ~= nil then
             input_flg[char] = true
         end
     end
     for key, value in pairs(input_flg) do
         if value then
             input.set(0, input_dict[key], 1)
         else
             input.set(0, input_dict[key], 0)
         end
     end
 end
However, the barrier here was the difference in the way the emulator worked. In the previous run, there were several occasions where the instrument with internal number 0x00 was selected or discarded, but the timing of the freeze here was quite severe, and it seemed that if there were slight differences in the emulation method, the emulator might freeze.
In fact, in BizHawk, when I tried to run lsnes with successful input data, it kept freezing and did not work as I wanted it to. We tried delaying the input by a few frames, but this also ended in failure. In addition to freezing, the time required for scrolling varies because the length of the name changes depending on the method of emulation of the 0x00 toy when moving the toy column, and this causes a large difference in movement. In the early version (r0), this is not a problem because the name of the 0x00 instrument is fixed, but only in the later version is it possible to input every frame to 0xFFF5, so there is no choice but to execute it in the later version
After some trial and error, we changed the set-up procedure slightly from the previous run, and developed a procedure whereby the 0x00 toy does not appear in the screen. As a result, the time has been reduced compared to the previous run, and the run is now more reliable. I can't say for sure that this is the best way to do it, but I'm pleased that I was able to come up with a reliable, version-independent way to run it.

Image

The image is represented by 36*40 dots, with tiles as the smallest unit; each tile consists of 4 squares of 2*2, each of which can have 4 colours: white, light grey, dark grey and black. 4 squares have 4 types, so there are 4^4 256 tile types, which just matches the Game Boy tiles' This matches the Game Boy tile types and fits perfectly within the VRAM.
I would have liked to use the original tiles as in OnehundredthCoin's video, but as there are only a few Pokémon tiles that use black, I decided that it would be difficult to express them nicely, and decided to rewrite the tiles as a whole.
For the conversion of the video, Python's Cv2 was used. The code for the conversion is as follows Here the conversion to the input text file is also done at the same time.
# Python code example.
tileDict = { 0: 0, 85: 1, 170: 2, 255: 3 }
inputDict = { 0: "A", 1: "B", 2: "s", 3: "S" }
for i in range(0, lenY):
    for j in range(0, lenX):
        byte = 0
        for k in range(0, 2):
            for l in range(0, 2):
                byte = byte * 4 + tileDict[image_4color[i * 2 + k][j * 2 + l][0]]
        for m in range(0, 2):
            writeStr = ""
            for key, value in inputDict.items():
                if (not (byte >> (0 if m == 0 else 4)) & (1 << key)) ^ (key == 1):
                    writeStr += value
                else:
                    writeStr += "."
            tileFile.write(writeStr + "\n")
The rewriting of the image is based on the video by OnehundredthCoin, and the window and BG are alternately rewritten half by half, the window display is enabled once every two frames, and the image is displayed at approximately 30 fps.
The video content is not compressed and is input as it is. As for the method of compression, I was thinking of performing differential compression similar to OnehundredthCoin's, but the lsnes specification impairs the stability of the input and the input may shift by 4 bits, or the amount of data required may vary greatly from frame to frame, so I had to abandon the idea. In fact, this should have been compressed as well...

Sound

I had very little knowledge of audio output and referred to MrWint's article, but this was the point where I had the most difficulty.
To summarise pretty well what we are doing, it is a simple story of rewriting the waveform memory at the right time. How MrWint timed the rewrites is a mystery, but I was able to get the timing right. I adjust the timer interrupt to write to the 16-byte waveform memory at once, just after all 32 samples in the waveform memory have been read. The frequency of ch3 is adjusted at 65536 / xHz for all 32 samples. In this case x = 216, so the audio output will be at 32 * 65536 / 216 = 9709Hz. The timer interrupt on the other hand is adjusted at 262144 / yHz, this time with y = 54, so the interrupt is triggered at 262144 / 54 = 4854Hz. If the timing of the 16 interrupts is the same as the frequency of ch3, the audio output can be adjusted successfully. The audio output can be synchronised with the interrupts by applying the equation x = 4y. The advantage of using timer interrupts is that they can be implemented separately from the screen output; MrWint article wrote that the rewriting of the waveform memory is synchronised to the V-Blank, but by using timer interrupts, there is no need to synchronise the image by itself and the timing can be automatically timed. This makes it possible to measure the timing automatically. This allows the sampling rate of the audio to be adjusted flexibly (although this is only a creative device and does not affect the quality of the video at all...).
The sound quality, as you can hear, is not very good due to the 4-bit quantisation and low sampling rate. Noise was especially bad at low frequencies, so I had to reduce the quality of the sound by applying a high-pass filter to the original source to reduce the low-frequency sounds.
In contrast, MrWint achieved quantisation of over 100 by adjusting the master volume, and the sampling rate is more than twice higher than my own, so the sound quality is quite good. I couldn't have copied this technique. What on earth is happening...? There is still room for improvement when it comes to sound.

source code

All the source code used in this run is available here.
Many of them were written temporarily when needed, so I may not be able to answer your questions.
In addition, the items compiled here have only been tested in my own environment and may not work properly.

At the end

We were able to produce a video of much better quality than our previous challenges in this run. Although we still have some work to do, we are happy with the improved resolution and frame rate, and the audio output. My regrets are that the video did not compress well and the audio quality is quite low. I tried my best to work on both, but with my knowledge and skills, the current quality was the limit. I am a little disappointed that I was able to surpass my previous goal, but not quite up to the quality of MrWint's and OnehundredthCoin's videos.
I would like to express my gratitude to both of them for inspiring me to create my own. I would also like to express my gratitude to Bad Apple! 's Kagee MV, I would like to pay tribute to all the people who produced it! Thank you for the wonderful video!

Memory: Replacing file with full since it was unable to fit under the limit


TASVideoAgent
They/Them
Moderator
Joined: 8/3/2004
Posts: 15889
Location: 127.0.0.1
CoolHandMike
He/Him
Editor, Judge, Experienced player (966)
Joined: 3/9/2019
Posts: 801
Bad Apple is now the standard for arbitrary code execution lol. Yet another system bad appled. Excellent work!
discord: CoolHandMike#0352
Spikestuff
They/Them
Editor, Publisher, Expert player (2747)
Joined: 10/12/2011
Posts: 6498
Location: The land down under.
These apples seem bad. Yes vote.
WebNations/Sabih wrote:
+fsvgm777 never censoring anything.
Disables Comments and Ratings for the YouTube account. Something better for yourself and also others.
Challenger
He/Him
Skilled player (1745)
Joined: 2/23/2016
Posts: 1084
Bad Apple in high quality this time. Very good!
My homepage --Currently not much motived for TASing as before...-- But I'm still working.
Player (53)
Joined: 4/1/2016
Posts: 300
Location: Cornelia Castle
Hello and welcome to TASVideos! While this is a nice run, there are a couple things I should mention: 1. LSnes, while used for the previous ACE run, is not the best anymore. BizHawk has a SubGBHawk core, which also has subframe input, and allows you to use an official boot rom for console verification. 2. While the Japanese version may be faster, the US version is easier for most viewers to understand, which helps out for runs like this. 3. There's almost certainly a way to skip Oak's hall of fame speech and go straight to the pokemon registration screen. I'll hold off on voting until I know if any of this will be considered.
DJ Incendration Believe in Michael Girard and every speedrunner and TASer!
Memory
She/Her
Site Admin, Skilled player (1567)
Joined: 3/20/2014
Posts: 1770
Location: Dumpster
DJ_Incendration wrote:
Hello and welcome to TASVideos! While this is a nice run, there are a couple things I should mention: 1. LSnes, while used for the previous ACE run, is not the best anymore. BizHawk has a SubGBHawk core, which also has subframe input, and allows you to use an official boot rom for console verification. 2. While the Japanese version may be faster, the US version is easier for most viewers to understand, which helps out for runs like this. 3. There's almost certainly a way to skip Oak's hall of fame speech and go straight to the pokemon registration screen. I'll hold off on voting until I know if any of this will be considered.
1. Not really a requirement by any means 2. The submitter is Japanese, it's easier for them to understand Japanese. 3. Does it really matter in a playaround???
[16:36:31] <Mothrayas> I have to say this argument about robot drug usage is a lot more fun than whatever else we have been doing in the past two+ hours
[16:08:10] <BenLubar> a TAS is just the limit of a segmented speedrun as the segment length approaches zero
Player (53)
Joined: 4/1/2016
Posts: 300
Location: Cornelia Castle
1. I understand that it's not a requirement, but I am so used to movies beginning with the bios sound. Also, the audio on LSnes doesn't seem to be as good as that on BizHawk. 2. I get that it's easier for them to understand, but it's a lot harder for us. 3. I guess it doesn't.
DJ Incendration Believe in Michael Girard and every speedrunner and TASer!
Joined: 1/9/2023
Posts: 31
Location: Quebec Province
I don't think it being in japanese or in english change anything. A normal viewer will see the Bad Apple video and won't really care about what happen before (in the english ACE Pokemon TAS, you don't really understand either) and the author's explanation are in english for those who wanna dig further.
Bigbass
He/Him
Moderator
Joined: 2/2/2021
Posts: 210
Location: Midwest
DJ_Incendration wrote:
1. I understand that it's not a requirement, but I am so used to movies beginning with the bios sound. Also, the audio on LSnes doesn't seem to be as good as that on BizHawk.
lsnes is clearly defined as one of the preferred and accepted emulators for GB movie submissions. Additionally, the desired goal of this TAS was clearly achievable using lsnes despite any emulation differences that may exist. As such, implying that Bizhawk is a better option or that lsnes shouldn't be used, isn't warranted. You personally may not like lsnes being used for submissions, but people are completely allowed to use it if they wish to.
DJ_Incendration wrote:
2. I get that it's easier for them to understand, but it's a lot harder for us.
I doubt the language of the text matters given the goal of this submission. Regardless, I'd much rather the person putting in all the work to create the TAS be able to understand what the game is doing, than for the convenience of being able to read the text myself. In any case, the site's movie rules explain that "regional differences such as text and cutscene length are not counted as improvements or time losses." So no, the region choice will not be considered.
TAS Verifications | Mastodon | Github | Discord: @bigbass
Emulator Coder, Judge, Experienced player (849)
Joined: 2/26/2020
Posts: 822
Location: California
For more an actual criticism for this TAS. It's Bad Apple. As funny as that is as a meme... it's a well known old meme. As such, the actual playaround segment is effectively completely unoriginal in an artistic sense. And to be frank, this criticism also applies to the Super Mario Bros Bad Apple TAS. Even on a technical aspect, the TAS is somewhat unoriginal. It follows the same principle as MrWint's Yellow TAS and the other Bad Apple TAS: you turn the video game into a video. The originality then just becomes the setup for such (which is impressive mind you), but at such a point it seems kind of like wasted potential if it's just used for Bad Apple (something that the other Bad Apple TAS frankly has for a criticism, but slightly less so since they were "first" wrt doing it within a TAS). At the very least, MrWint's Yellow TAS was fairly original for what it was, and kept a semblance artistically of it still being a game (even if by a technical aspect it's not actually much different than playing a Bad Apple video).
Player (249)
Joined: 6/16/2020
Posts: 30
Very cool TAS! I also really appreciate the details you've written about how you created the TAS, as well as providing the source code! I'm going to build off what CasualPokePlayer said.
CasualPokePlayer wrote:
It follows the same principle as MrWint's Yellow TAS and the other Bad Apple TAS: you turn the video game into a video. The originality then just becomes the setup for such (which is impressive mind you), but at such a point it seems kind of like wasted potential if it's just used for Bad Apple (something that the other Bad Apple TAS frankly has for a criticism.)
I'll also accept the criticism for my own TAS, because yeah- it really is "just Bad Apple". (I initially had plans to tell a story similar to Mr. Wint's TAS, but got distracted.) But even though there's already an ACE playaround TAS of Pokemon Yellow, and even though there's already a playaround TAS of another game that plays the Bad Apple music video, I still think this run has merit. I'm paraphrasing here, but saying "you shouldn't do this because it has already been done" is pretty unhelpful. I strongly encourage people to make TASes similar to this one. It's a wonderful programming exercise that will teach you a lot about the hardware you're working with. You mentioned in your submission notes you recognize that there is room for improvement. For my Bad Apple TAS, I was also quite unhappy with the audio quality for a long time, so I did a bunch of research and rewrote my code countless times just to try new things and see what works. I initially created my TAS in early January of 2024, and planned to submit it in April, so that gave me a few months of time to make the graphics and audio cleaner. If you still think there's room for improvement, perhaps you should take more time researching the audio. This also brings up the discussion of what it means to "obsolete" a playaround TAS. Ideally this run wouldn't be replacing Mr. Wint's, but I don't see any reason why there can't be two simultaneously published ACE demonstrations. The story telling and humor of that run serve a completely different purpose than the technical feat of playing Bad Apple. Great work, and welcome to TASVideos!
Emulator Coder, Judge, Experienced player (849)
Joined: 2/26/2020
Posts: 822
Location: California
To be clear, I am not saying "you shouldn't do this because it has already been done." I'm saying the run becomes heavily devalued from an artistic perspective. Purely on the technical side, it isn't really devalued from that (even the "wasted potential" is more on the artistic side if anything).

1741288992