Generation II of the Pokémon games seen a lot of TASes over the last few month (1
), and none of them lasted very long before being beaten.
The fact that only one of them made it to a publication at all is a good indicator on how fast the TAS strategies for this game evolved lately.
So, here comes another attempt I guess.
This submission uses the same basic route I used in my previous run
, but with several improvements, some of which FractalFusion
introduced in his submission
, and some of which are new.
Most of the basic game and route information is explained in detail in my previous submission text and remains valid for this run, I encourage you to read it if you haven't seen a run using the Coin Case glitch before.
In this submission text, I will merely point out the differences and improvements compared to FractalFusion's submission, which is the fastest submitted route at the time of writing.
- Aims for the fastest completion time
- Heavy luck manipulation
- Executes arbitrary code
While the Coin Case glitch setup is pretty much fixed up to the point where your program counter lands in the party Pokémon data, you have multiple options from there on.
This run uses the same basic idea as FractalFusion's run, namely using the move Tackle, which conveniently corresponds to ld hl,d16
, to load a jump to the Box names which are set up to spell out our program.
It uses some improvements, though, the biggest one is skip teaching Mud-Slap, but instead exploit that an empty move slot has the value 00.
That means, instead of
Move 3 Move 4 Trainer ID Code
21 BD D8 E9 ld hl,D8BD; jp (hl)
21 00 D9 E9 ld hl,D900; jp (hl)
which puts us in Box 8 instead. The time saved by not learning Mud-Slap outweighs the additional time needed to scroll down when renaming the boxes.
Another improvement is the written program itself, now only requiring two box names instead of three.
The explanation of the inner workings of the arbitrary code execution follows, which is lengthy, technical an maybe boring to you. So if you're not interested you can just call it magic and skip the rest of this section.
The program itself is a simple interpreter loop, it reads joypad inputs and executes them as opcodes:
| Bytes|| Instruction|| Comment|
| Box 7|
| aa|| xor d|| d stores last joypad input: find out differences to current input|
| ea fd f8|| ld (f8fd),a|| Write difference; will be executed as opcode later in the cycle|
| aa|| xor d|| Restore current joypad input value|
| f5|| push af|| Copy current joypad input from a...|
| d1|| pop de|| ... to d (store it as last joypad input)|
| f1|| pop af|| Restore a and f from the previous cycle|
| 50 ($f8fd)|| (any)|| Execute opcode written earlier this cycle|
| Box 8|
| f5|| push af|| Save a and f for next cycle|
| a4|| and h|| Clears carry flag, needed for the jump|
| fa a6 ff|| ld a,(ffa6)|| Entry point; reads current joypad inputs into a|
| d2 f5 f8|| jp nc,f8f5|| Loop back to start of Box 7; carry will never be set|
Since this loop will run hundreds of times per frame, but we can only give it one input per frame, we need to prevent it from running the same opcode over and over again.
This is done by not using the input directly as the opcode, but the difference of the input from the previous one.
This means that for all cycles in a frame after the first one, the executed opcode is 0, which conveniently is nop
Note that there is no way to give the opcodes additional data, so you are limited to using opcodes that don't require any (using other opcodes doesn't break the program, but messes with the stack since the push af
is not executed but used as data instead).
This may seem quite limiting at first, since there is no way to set a register to a value you want directly, but it's possible to circumvent this using the properties of the xor operation.
It's easiest to see in an example from the actual inputs used in this run:
Our goal is to set l to 0x22 (the current value is 0x83), and the last input (stored in d) is set to 0xB3.
This can be done using only 2 frame cycles:
Input opcode operation comment
0x48 (0xb3 xor 0x48 = ) 0xfb ei Does nothing important.
0x22 (0x48 xor 0x22 = ) 0x6a ld l,d loads d=0x22 into l
The trick is to set it up so that the pressed buttons are your data while the difference is your desired opcode.
Because of the properties of xor, there is always such a number to achieve this and it takes only 1 frame to set it up.
The opcode executed during the setup cycle is irrelevant, as long is does not crash the program or corrupt the values you currently care about.
The inputs used in this run make use this technique extensively:
e1 // Initial value of d when entering the loop
e8 09 (add hl,bc) // d900 + 00a4 = d9a4
cd 25 (dec h)
e0 2d (dec l)
97 77 (ld (hl),a) // d8a3 <- 0; Enable Red in Mt. Silver
b3 24 (inc h)
48 fb (ei) // setup cycle
22 6a (ld l,d)
06 24 (inc h)
71 77 (ld (hl),a) // da22 <- 0; Having no Pokémon in your party wins all battles instantly
b0 c1 (pop bc) // setup cycle
d2 62 (ld h,d)
61 b3 (or e) // setup cycle
0b 6a (ld l,d)
04 0f (rrca) // setup cycle
76 72 (ld (hl),d) // d20b <- 76; Change tile the character stands on, needed for warping
63 15 (dec d) // setup cycle; This temporarily corrupts d, but it is fixed in the next cycle
19 7a (ld a,d)
bb a2 (and d) // setup cycle; 0xbb and 0x19 = 0x19
d9 62 (ld h,d)
fb 22 (ld (hl+),a) // d90b <- 19; Y coordinate of the character position
79 82 (add d) // setup cycle
03 7a (ld a,d)
21 22 (ld (hl+),a) // d90c <- 03; X coordinate of the character position
65 44 (ld b,h) // setup cycle
17 72 (ld (hl),d) // d90d <- 17; (Glitch) map entrance 0x17, directly in front of Red
34 23 (inc hl)
16 22 (ld (hl+),a) // d90e <- 03; Map group
0c 1a (ld a,(de) ) // setup cycle
46 4a (ld c,d)
36 70 (ld (hl),b) // setup cycle
44 72 (ld (hl),d) // d90f <- 44; Map index: In Mt. Silver
2d 69 (ld l,c)
71 5c (ld e,h) // setup cycle
0b 7a (ld a,d)
29 22 (ld (hl+),a) // d946 <- 0b; Set warp pointer to our fake warp data
5d 74 (ld (hl),h) // d947 <- d9
79 24 (inc h) // setup cycle
81 f8 (ld hl, sp+f5)
eb 6a (ld l,d)
12 f9 (ld sp,hl) // Fix the stack pointer; leave directly to the overworld
db c9 (ret) // Return to normal game code