I had this game as a kid on one of those bootleged 50000-in-1 carts and would play it on my bootleg Famicom. I didn't really like it back then; it was kinda hard and there were just too many stages. As a grown-up TASer, I always pondered the idea of TASing this game, but it still seemed like a very hard challenge for a couple reasons.
First, the current solution by Bisqwit seemed already very optimized. It wasn't clear I could come up with a better solution. Second, my tasing bot (JaffarPlus) uses a breadth-first approach to exploration, which is entirely the wrong approach for this kind of games. That is, games where the action (hitting the cue ball) and knowing its outcome (how the table ends up) are separated in time by several hundred frames.
In the end, I decided to give it a try but I knew I had to come up with a different approach. For this, I developed LunarBot, a hybrid depth-first-breadth-second brute force bot. Here, I consider strokes instead of frames for the outer breadth-first exploration. Within each stroke, I advance as many frames as necessary to know its outcome. After each stroke, I keep the combination of angles/power that scored the most balls and had least amount of frames.
Those who read Bisqwit's submission notes in previous publications know there are many more factors to this challenge. Score rate and tallying is whole drama that I had to factor into the bot. This tallying forces the player to make "suboptimal" shots, just to reduce the rate and prevent bonuses. I went into great lengths in adding heuristics and calibrating the reward function to properly handle this. (For details, see the code)
Compared to Bisqwit's setup 15 years ago, I count with a much stronger hardware resources and a much more refined software/emulation setup (quickerNES is much faster than anything available back then). This play a determinant role in this taking me a few weeks, compare to the months it took him.
Nevertheless, I do feel like making this movie was, above all else, a personal chess-like battle of algorithms.
I purposefully went into this not wanting to know the tricks that Bisqwit had applied and only took inspiration from his movie encode. I did not read his sub notes nor did I take a look at his code. I believe the end result came down to the heuristics and daring optimization techniques we both applied. At the end of the day, I truly respect his work and am honored to be the one to obsolete it.
Comparison Movie
This movie compares [1565] NES Lunar Pool by Bisqwit in 23:47.52 with this submission. The old movie finishes certain stages faster. This is because the initial conditions carried on from the previous stage has an effect on the best possible solution. Also emulation differences make it so that executing a shot under the same conditions (angle, position, power, power increase/decrease direction) would result in different outcomes.
Definite yes vote on entertainment! Seeing 3, 4, 5, if not all the balls, sunk in a single shot is a wonderful sight.
Unfortunately, I wasn't able to console verify it. The TAS consistently desynced at the start of the first stage. Didn't investigate any further, but it seemed like the game ate the input(s) to aim and/or fire the first shot.
What is it that makes this the case? That seems strange.
It's impressive botting work as usual, though I'm not really a fan of using QuickerNES for cases like this where emulation accuracy matters, to me it's like using a lot of resources to get an incorrect answer. But I'm not the one doing the work either. Out of curiosity though, how fast would an accurate emulator need to be for you to consider using it for botting?
In some stages I wanted to replicate Bisqwit's solution, but even if I got all the conditions (angle, power, power direction), pressing the B button would react one frame slower or faster. I am pretty sure this has to do with emulation timing disagreements between the emulators.
Alyosha wrote:
It's impressive botting work as usual, though I'm not really a fan of using QuickerNES for cases like this where emulation accuracy matters, to me it's like using a lot of resources to get an incorrect answer.
I agree this is not the ideal emulator to be using when accuracy plays such a role. However, given the previous movie was done and accepted using FCEU, it can be considered an improvement even. Had that movie been done with NesHawk, I wouldn't have considered using a less accurate emu.
Alyosha wrote:
Out of curiosity though, how fast would an accurate emulator need to be for you to consider using it for botting?
I'd say anything in the ballpark. I can get around 1.7M rerecords/s with QuickerNES. If an accurate emu could get me 100k, I could consider using it for cases like these. Less than ~50k would be completely useless.
This was fun to watch. Curious though if either this version or the previous one can be console verified.
Not syncable to NESHawk, even the first 2 shots, so no way it will work on console.
For both versions? Too bad.
It's the same for Bisqwit's run.
I looked at a trace log, and it looks like the primary issue is that the game polls for input constantly. Most games poll input during or immediately after vblank, but apparently polling for input is this game's version of a busy loop. This means both emulation timing (in the case where a latch happens to fall near an NMI), and how / when the emulator handles input, are both factors for sync.
NESHawk only changes input state on latch, and changes controller state on vblank. I'm not sure how FCEUX and QuickerNES do it.
I checked again, and the main thing keeping the run from syncing is power on RAM state. QuickerNES uses 0xFF for everything, and if I use that, I can successfully sync the first level with only minor input changes in NESHawk.
I believe the run is not too timing sensitive otherwise, so it's unfortunate that start up state has such an impact.
So, with some re-syncing effort, assuming nothing else is wrong, this in fact can probably be console verified assuming the bot can handle the input spam.
Well, and assuming enough of the 0xFF RAM state can be maintained with RAM setting cart.
EDIT: Bisqwit's original can also be resynced, but you need to remove a reset press that gets put on frame 0 when importing the fcm.
I looked at a trace log, and it looks like the primary issue is that the game polls for input constantly. Most games poll input during or immediately after vblank, but apparently polling for input is this game's version of a busy loop. This means both emulation timing (in the case where a latch happens to fall near an NMI), and how / when the emulator handles input, are both factors for sync.
Additionally, I just discovered that the quickerNES core doesn't support Lua memory callbacks. So even if the accuracy was there, I'm not sure how anyone would reliably dump the inputs.