Game objectives
- Emulator used: BizHawk 2.9.1 (NesHawk core)
- Aims for fastest time
This run beats NES Flipull "Normal" mode in about 25:34. This is faster than my previous casual TAS by about 7 minutes. (Note that the old run has some advantages, because it starts from the stage 201 with an in-game code and the game timer ticks faster)
I used v1.0 of this game. There also seems to be v1.1, but I don't know anything about differences between them.
The RNG of this game depends on CPU cycles, so I used NesHawk for accuracy.
I used Lua bots to manipulate the RNG. The re-record count doesn't include trial-and-errors made by the bots.
About the game
"Flipull" is a simple puzzle game. The game objective is erase a pile of blocks. You always have a block and can throw it. When a thrown block collides with the same kind blocks, they will be erased. Afterwards, when a thrown blocks collides with the different kind block, they are swapped. This game was never released outside Japan.
This game has two modes, "Normal" and "Advance". In the normal mode, blocks are placed randomly. In the advance mode, placements of blocks are completely fixed.
The normal mode has 50 stages.
Comments
I wrote a solver to beat a stage generated by a specified RNG value with a minimum frame cost.
I examined the frame costs for throwing a block with a Lua script.
By the way, if some blocks remain when you beat a stage, it takes 11 frames per block to erase them automatically. And, if you achieve "perfect clear" in a stage, it takes 96 frames for a fireworks effect.
Every stage has a specific number of blocks to beat it. If you beat a stage with exactly the required block count, it is called "just clear". "just clear" doesn't take any additional frame cost.
Intuitively, you might think that it is always optimal to beat stages with "just clear". But for a certain reason, I often avoid "just clear" and also avoid erasing 5 or more blocks at once. This is related to the RNG (I will describe details later).
Note: in the NES version, if you have only moves which directly hits a wildcard block, it is considered a miss. (This point is different from the GB version).
About the RNG
In the normal mode, each stage has 65536 or 65535 placements which is determined by the RNG seeds.
This game has a very ad-hoc RNG, and you have to manipulate it carefully.
In this game, all the logics are processed within the NMI thread, while the main thread only updates the RNG state and increment the mainloop counter in an infinite loop. So, the RNG is sensitive to CPU cycles.
Here is the infinite loop of the main thread (
rand
is at $0122
):
@mainloop: lda rand tax lda 0,x adc rand sta rand adc rand+1 sbc #$11 sta rand+1 inc mainloop_counter jmp @mainloop
As you see, this game updates the RNG using the contents of the zeropage.
Note that the addition
rand[0] = adc(rand[0], mem[rand[0]])
. This adc instruction involves carry in many cases.
This game manages each players' inputs at $F3-$F8. So, if you can make rand[0] point to one of these addresses, you can manipulate the RNG by inputs. But, depending on the contents of the zeropage, this manipulation is not always possible.
For example, let's assume that rand[0] has value 0x70 and the zeropage is like below:
x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF ... 0x70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F1 ...
In this case, the result of adc(0x70, 0) is 0x70 or 0x71, so rand[0] will continue to be incremented a bit slowly. When rand[0] reaches 0x7F, adc(0x7F, 0xF1) is 0x70 or 0x71, so rand[0] "loops" to 0x70 or 0x71.
If such a loop occurs, it limits the range of the RNG state. So, if necessary, you have to manipulate to prevent such cases from occuring.
Especially, if the pattern [0x00, 0xFF] appears on the zeropage, rand[0] will be "stabilized" there.
In fact, this pattern can appear at $D4-$D5. $D5 is the flag which indicates that a wildcard block will be appear on the board at the next stage, and it will always be 0xFF if you erase 5 or more blocks at once. So basically, you should not erase 5 or more blocks at once, except the last stage. (If you erase 3 or 4 blocks at once, $D5 can probabilistically be 0xFF depending on the mainloop counter, but you can easily manipulate this)
And, if you achieve "just clear", $E9 will be set to 0x80 later, and it makes difficult for rand[0] to reach $F3-.
Especially, from stage 11 to stage 30, it is very difficult to manipulate the RNG in this condition due to the contents of the zeropage. So, I often avoided "just clear".
By the way, you can also cause the RNG stabilization intentionally with your inputs, and it is sometimes useful to manipulate the RNG. For example, when the player1 inputs nothing and the player2 inputs all the keys, the [0x00, 0xFF] pattern will appear at $F3-$F4, allowing you to stabilize the RNG at these addresses.
Stage by stage comments
Some RNG seeds might be impossible (but I'm not sure).
The stage placement loops every 32 stages, but sometimes I couldn't manipulate the RNG in the same way due to the difference of the zeropage.
From stage 10 to stage 29, I didn't consider "just clear", because it makes the next RNG manipulation very difficult.
I treated the stage 50 specially. It takes no block throwing cost for the last move, and you can erase 5 or more blocks at once.
The numbers after RNG seeds are their frame costs. Frame costs doesn't include the costs for converting time into score.
Stage | RNG | Note |
---|---|---|
1 | 0x89A2 (669) | best in just clear. |
2 | 0x89A2 (669) | 2nd best in just clear. best: 0xC558 (642) |
3 | 0xA9C4 (757) | 5th best in just clear. best: 0xEA18 (708) |
4 | 0xCA36 (971) | best in just clear. |
5 | 0xD03F (1228) | 24th best in just clear. best: 0x40BD (1122) |
6 | 0x9E16 (1066) | 8th best in just clear. best: 0x1817 (1022) |
7 | 0x79D8 (1108) | best in just clear. |
8 | 0xAB19 (1424) | best in non-just clear. best in just clear: 0xEB16 (1275) |
9 | 0xE028 (1318) | best in non-just clear. best in just clear: 0x02D6 (1307) |
10 | 0xE028 (1318) | best in non-just clear. |
11 | 0xEB16 (1275) | best in non-just clear. (and better than any just clear) |
12 | 0xB1A9 (1327) | 4th best in non-just clear. best in non-just clear: 0xF9CE (1253) |
13 | 0xA2BF (1392) | 2nd best in non-just clear. best in non-just clear: 0x518D (1376) |
14 | 0xA3EF (1423) | 2nd best in non-just clear. best in non-just clear: 0x518D (1376) |
15 | 0xB439 (1589) | 2nd best in non-just clear. best in non-just clear: 0x315D (1551) |
16 | 0xE69B (1092) | 3rd best in non-just clear. best in non-just clear: 0x58F7 (1045) |
17 | 0x9B85 (1134) | 4th best in non-just clear. best in non-just clear: 0xC777 (1096) |
18 | 0x3A3B (1456) | best in non-just clear. |
19 | 0xE69B (1092) | 3rd best in non-just clear. best in non-just clear: 0x58F7 (1045) |
20 | 0xB31F (1270) | 3rd best in non-just clear. best in non-just clear: 0xFE0B (1259) |
21 | 0xBE5F (1191) | 2nd best in non-just clear. best in non-just clear: 0xC6BC (1138) |
22 | 0xE78C (1509) | best in non-just clear. |
23 | 0x58F7 (1175) | best in non-just clear. |
24 | 0xAB19 (1449) | best in non-just clear. |
25 | 0xA2BF (1429) | best in non-just clear. |
26 | 0xA2BF (1429) | best in non-just clear. |
27 | 0xAB19 (1449) | best in non-just clear. |
28 | 0xAB19 (1449) | best in non-just clear. |
29 | 0xE156 (1790) | 2nd best in non-just clear. best in non-just clear: 0x693D (1780) |
30 | 0xD701 (1278) | 2nd best in just clear. best: 0x30E0 (1217) |
31 | 0xE69B (1092) | 3rd best in just clear. best: 0x58F7 (1045) |
32 | 0xE69B (1092) | 3rd best in just clear. best: 0x58F7 (1045) |
33 | 0x89A2 (669) | best in just clear. |
34 | 0x89A2 (669) | 2nd best in just clear. best: 0xC558 (642) |
35 | 0xBB87 (768) | 7th best in just clear. best: 0xEA18 (708) |
36 | 0xAE67 (1014) | best in non-just clear. best in just clear: 0xCA36 (971) |
37 | 0xF0EB (1124) | best in non-just clear. best in just clear: 0x40BD (1122) |
38 | 0xC6BC (1048) | 4th best in just clear. best: 0x1817 (1022) |
39 | 0x79D8 (1108) | best in just clear. |
40 | 0xEB16 (1275) | best in just clear. |
41 | 0xA2BF (1392) | 5th best in non-just clear. best in just clear: 0x02D6 (1307), best in non-just clear: 0xE028 (1318) |
42 | 0xB970 (1306) | best in just clear. |
43 | 0xEB16 (1275) | best in non-just clear. (and better than any just clear) |
44 | 0x46F7 (1254) | 2nd best in non-just clear. best in just clear: 0x79D8 (1108), best in non-just clear: 0xF9CE (1253) |
45 | 0xA2BF (1392) | 2nd best in non-just clear. best in just clear: 0xE028 (1318), best in non-just clear: 0x518D (1376) |
46 | 0xA2BF (1429) | 3rd best in non-just clear. best in just clear: 0xE028 (1318), best in non-just clear: 0x518D (1376) |
47 | 0x315D (1551) | best in non-just clear. best in just clear: 0x315D (1443) (the same RNG!) |
48 | 0x58F7 (1045) | best in non-just clear. best in just clear: 0xAE67 (1014) |
49 | 0xC777 (1096) | best in non-just clear. best in just clear: 0x4A74 (1056) |
50 | 0xAA44 (1170) | best. |
Possible improvements
I think my RNG manipulation is probably not theoretically perfect. But I believe you cannot improve this run without an understanding of the RNG and some computing resources.
ThunderAxe31: Claiming for judging.
ThunderAxe31: Oh boy, this looks extremely optimized. Great job! Accepting as a new branch.
despoa: Processing...