A Statistical Analysis of Guppy's Tail!
Howdy! This is the second post in a series about assorted hyperfixations of mine. If you missed the last post about recreating DOOM in Isaac’s modding API, you can find that here.
This is a project that I’ve had on the back-burner for a VERY long time, and I conducted the first version over two years ago. It's even part of how I got my internship last summer, but that's a story for another time.
Long and anticlimactic pauses aside, I’ve been mulling this over ever since that first attempt, and now that I’m older and (hopefully) wiser, I decided to revisit it!
Context Before We Go
If you've never played Isaac before, imagine if A Link to the Past was a twin-stick shooter with roguelike mechanics. You can pick up items throughout your run, and there's an absurd level of complexity to how different items interact with each other!
This project will focus on a specific item named "Guppy's Tail". As the name would imply, it's a dead cat's tail, and it happens to be one of the most divisive items in the game.
At a high level, Guppy's Tail replaces a portion of room rewards with chests that contain various things. That chest has a 50% chance of requiring a key to open, though they also give more rewards to compensate.
The downside is that Guppy's Tail adds a chance to remove the room drop entirely. According to the wiki, it's a 33% chance to drop a chest, a 22% chance to drop nothing, and a 44% chance to drop normal room rewards.
So what's the problem? Well, the item has a reputation for being a massive headache. Because half of the chests require keys, as do many other things during your run, they become much more scarce, and runs with Guppy's Tail often have long stretches of no keys at all.
It's not like the item kills you outright, but it can be demoralizing to miss out on so many potential rewards.
That being said, you do have the option to avoid Guppy's Tail altogether, and a lot of people choose to! Many players refuse this item out of principle, either due to word-of-mouth or a gut feeling. However, at the risk of sounding like an ultra-nerd, I've never seen anyone back up that gut feeling with rigorous experimentation.
... oh, who am I kidding.
I'm going to sound like an ultra-nerd regardless :p
So what exactly am I planning for this "rigorous experimentation"? Well, the plan from here is to run simulations with the data we already have, make predictions, design an experimental scenario within the game to gather data, analyze said data, and then come to a conclusion based on our results. Pretty standard scientific method stuff. With that in mind, let's start going down the list!
Unfortunately, It's Time for Statistics
Based on the info that we have so far, it seems like the magic number is our key count. If we ignore the rest of the game and only consider the inflow and outflow of keys over the course of a run, it will simplify things a lot without too much loss of generality.
Going forward, I'm going to make a rough approximation that each Isaac run clears 200 rooms. That number will vary slightly depending on secret floors or Curse of the Labyrinth, but 200 feels like a good estimate.
Now that we have our total room count, we can start crunching some numbers. To get a sense of scale, let's start by calculating how many keys we expect during a regular run: no items, no trinkets, no nothing.
Expected Value of Keys with No Items:
According to the Isaac wiki, keys have a 20% chance to drop from room clear rewards.
Since each cleared room has an independent and equal chance of paying out with rewards, we can just multiply the chance by the total room count to get a rough estimate for keys over the course of a run:
200 * 0.2 = 40 keys
However, that value isn't quite right, since unlocked chests have a 5% chance to drop from room rewards, and those might also contain keys. That being said, it's not as simple as just multiplying two numbers together.
Figure 1: Drop tables for unlocked chests. For context, Daemon's Tail is a trinket that turns 80% of heart drops into key drops.
These are the tables from the Isaac wiki, and unfortunately we've got problems here.
Problem #1 is the issue of recursion, since these chests have a small chance to pay out with a copy of themselves. However, that outcome can be ignored, since the smaller chest doesn't have any accompanying drops. We're essentially just replacing the chest with a copy of itself.
However, Problem #2 is a bit more concerning: pickups are dropped in batches of 2 or 3 at a time, and that means there can be anywhere from 0 to 3 keys dropped per chest. How would we calculate the odds of that?
Well, I could go through the calculations manually, but the probability math is hard enough as it is, and it'll only get harder from here. So, I'm going to switch gears and get an approximate answer using Python. I wrote a quick script to simulate the conditions described above, and it came back with 0.292 keys per trial on average.
Figure 2: The first iteration of my ramshackle Python code.
Using that value, we can work out the following:
0.292 * 200 * 0.05 = 2.92 keys
Adding the two together, the final key count is 42.92 keys. Not bad all things considered! That's way more than we'd ever need in a normal run. Plus, since we're only considering room drops for this experiment, there is no outflow for this problem. However, this is the most real math we're going to use for this problem.
Expected Value of Keys with Guppy's Tail:
Similar logic as before, but now we also have to account for Guppy's Tail, which makes things MUCH more challenging.
For starters, Golden Chests have a slightly different distribution for rewards:
Figure 3: Drop tables for locked chests. Gilded Key adds trinkets, pills, and cards into the pickup drop pool, lowering the chance of the main four to compensate (more on that below).
However, the most troubling aspect of this problem is that 50% of the chests require keys to open. Because of this, we have to account for outflow as well as inflow. Not only that, but there's also the problem of bankruptcy. We can't dip into a negative number of keys. Once we hit 0 (or whatever other arbitrary key limit we've set for ourselves), we're beholden to reduced payouts from regular chests and key drops until we can pull ourselves back up.
(also, yes, I am going to consistently put "bankruptcy" in red bolded text because I'm having way too much fun with Tumblr's text formatting)
So how do we account for this? Well, for starters, let's ditch the purely theoretical math approach. Brute force simulations will arrive at the same result for large N, and I'm fine with waiting for the code to finish if it means that we can get correct values.
With that in mind, I modified my original Python simulation to account for Golden Chests, and while the results aren't perfect, it should be perfect for our circumstances!
That being said, the initial projections don't fill me with the greatest of confidence.
The Numbers Look Bad, Folks
Figure 4: 5 attempts with 200 timesteps each, plotting key count vs. time. If a chest can't be opened, it is kept in a backlog until there are enough keys to do so.
So not only is there a LOT of variance, but I can already see the majority of the attempts getting stuck in bankruptcy and not being able to break out of it due to the reduced room loot.
On the bright side, though, the Python code is working really well! It should allow for some more complex analysis, in line with what I'm envisioning for the experimental design. If we increase the number of attempts from 5 all the way to 50000, we can overcome the random variation and try to get some more general stats, Monte Carlo style!
Figure 5: 50000 attempts with 200 timesteps each (only showing 500 runs so my computer doesn't explode).
However, even with a large sample size, the numbers don't look great. It almost feels like the lines are being pulled down by gravity.
Maybe it has a very slight positive slope as you extend further out, but we're locked to 200 timesteps, so that's a moot point.
Mootness aside, it's a bit hard to see... any sort of trend in the current graph. However, if I make a histogram of the endpoints of each line, we might be able to see how the lines are distributed:
Figure 6: Histogram of the frequency of endpoint values for 200 timesteps and 50000 repetitions.
There's an overwhelming majority of runs that end at 0 keys, and if you're just going off of the endpoints, you have ~60:40 odds of being dead broke by the end of each run.
It's not quite as damning as it looks, since the key count during the run matters far more than it does after the run, but all the same, it says a lot about the distribution.
Next, let's check the maximum recorded value for each run:
Figure 7: Histogram of the frequency of maximum values for 200 timesteps and 50000 repetitions.
Still not awful, but it's very concerning that over 25% of all trials never get more than 2 keys, and about 12.5% of all trials never get more than one key.
I would consider more than 2 keys to be comfortable, but according to this graph, 37.5% of runs don't ever reach those numbers. The margins are razor-thin, and we can get a sense of those margins by tracking the average counts of chests opened and keys obtained:
Figure 8: Two overlaid histograms, one tracking chests opened, and the other tracking total keys obtained, for 200 timesteps and 50000 repetitions. The Key-Chest margin is approximately 2.07.
Like I said, the margins are VERY strict, since you're also not always going to recoup your keys from opening chests.
One last chart: if we track the amount of time spent bankrupt in each trial, we can make yet another histogram, though the x-axis is a bit less helpful due to the granularity:
Figure 9: Histogram for frequency of bankruptcy times for 200 timesteps and 50000 repetitions.
Based on the chart, it seems like there's a minor peak around 0, and a major peak around 175. There are a select few trials that manage to fly away without much issue, but nearly every trial has some amount of trouble, and most of the runs end up spending more than half of their time bankrupt.
Sooo... Now What?
Where does that leave our little cat's tail? Well, at least in a vacuum, it seems like a massive risk to use this item. Unless you get VERY lucky and reach escape velocity with your key count, you're likely to spend most of your run bankrupt.
So, in a vacuum, it's not worth picking the item up unless you get VERY lucky.
However, we still haven't even started the "experiment" part of this experiment. So where am I going with this? Well, it seems likely that Guppy's Tail performs poorly based on our calculations, but what if we supplement it with a secondary item? After all, there are plenty of Isaac items that modify room drops in different ways, and some of those methods affect key drops!
The Experiment and Such
After some consideration, I decided on nine total supplements that we'll be cycling through in-game:
1. Control (No other items/gimmicks)
See the calculations above. I don't have very high hopes for the in-game variant.
2. 3-Key Advantage
This number was chosen more or less at random, but once I get the Python program fully working, I want to try and scale the starting key count to see if there's a "magic value" where you're guaranteed to never go bankrupt. My guess is it'll be ~30, but we'll see.
3. Mom's Key
Mom's Key feels tailor-made for this situation! Whenever a chest spawns pickups while you're holding this, it grants a second set of rewards. That second set might give enough extra keys for some of the runs to reach escape velocity!
4. Paper Clip
This is the easy answer. Chests don't cost keys to open anymore, so problem solved!
5. Daemon's Tail
For an Isaac who's in the mood for some added danger. This trinket converts 80% of hearts into more keys. I imagine it would be good, but it certainly wouldn't make the run any more comfortable.
6. Contract from Below
This is a weird case, but I have high hopes for it! Contract from Below doubles room rewards, but also has a 33% chance to remove room rewards. Ideally, it will increase the amount of room drops by 33%, which in turn, could increase the amount of keys dropped! Granted, that also increases the amount of chests dropped, but I'm willing to give it a shot regardless.
7. Rusted Key
Unassuming at first, but this trinket actually has two different effects. Effect A adds a 10% chance to replace the room clear reward with a key, and Effect B gives a 33% chance to add a key to chest rewards. Either of these effects would be helpful, but both? I expect great things out of this one :D
8. Lucky Foot
This is a bit more of a random guess than an actual option, but according to the wiki, Lucky Foot increases the chance of a room rewards dropping a pickup, so that could mean more keys! That being said, it also independently boosts the chance for chests to drop as a room reward, so maybe it balances out?
9. Gilded Key
This last scenario is also a bit more of an experimental choice. All unlocked chests are replaced by locked chests, and chest loot is also modified to be slightly different:
Figure 10: Drop tables for locked chests while holding Gilded Key. As before, Gilded Key reduces the chance of keys dropping. Okay, this might be a bad idea...
The mildly sarcastic figure notes are correct, and this trinket actually REDUCES the chances of getting keys from chests. Suddenly I don't have very high hopes for this one, but I'm willing to run it through the experimental setup anyway to see what happens. Who knows, maybe it surprises me!
What's My Prediction?
This isn't strictly required, but it could be interesting to compare my thoughts on these supplemental items/circumstances after the data is fully analyzed. So, what am I thinking?
Well, based on what I can see so far, I expect Key Advantage to do about as well as the control case, Mom's Key and Paper Clip will be excellent, Daemon's Tail and Rusted Key will be slightly less good (but still excellent), Contract from Below will provide marginal benefits with a higher variance, and Lucky Foot and Gilded Key will be actively harmful.
So with that, we can start generating our experimental setting!
Lua My Beloved
You might be wondering: how the hell am I going to create a testing framework in Isaac? Well, I have experience making mods for the game, and since they're all made in Lua, there's a shocking amount of things that this API can do!
Lua and Python are sister languages in a lot of ways, so technically, I could just port my Python code over into Isaac and compare the numbers directly!
But, we're getting ahead of ourselves. To simulate room drops, we first need a way to repeatedly clear rooms. To do that, we can instantly clear out the enemies, let the room rewards spawn, and then reset the room after it spawns pickups using a callback.
Isaac's modding API has a set of callbacks that you can attach code to, and luckily for us, MC_PRE_SPAWN_CLEAN_AWARD fires whenever a room is cleared. Additionally, there's an item in the base game that respawns enemies (the D7), and if we rig it up to use the item whenever the callback fires, we get a loop of continuous room rewards!
Now that we have the loop itself, we can handle the pickups. Chests will be opened automatically, and everything else will be deleted, then shoved into a massive frequency table. By noting down the values every time a room is cleared, we can track how the pickup counts increase over time for each pickup type.
Figure 11: The full recording of the experimental setup with a line chart, pie chart, and manual readouts of the values for each pickup.
I also created a set of graphs to capture the distribution, as well as how those distributions change over time.
The pie chart was made by calculating the size of each slice, and then pulling the associated image and rotating it into place. It's messy, but it works! (story of my life :p)
As for the line graph, I used a series of line sprites to draw the lines, and I also implemented some functionality to scale the x and y axes, as well as simplifying the display after passing a multiple of 50 (since the graph would make the game lag near the end of each trial without optimization).
It took a while to complete all the trials (about 2 hours in real time), but once everything was in place, I rigged up the system to output to a CSV file, which I could then copy over to a spreadsheet for analysis!
Analyzing the Real Data
This is where things get a lot more interesting!
The raw data isn't the prettiest thing in the world, but now that it's in Excel, we can start making calculated fields and generating some graphs. If we can retrieve data points like final values, max values, and bankruptcy time, we can plug them back into the Python simulation and overlay the test values onto the histograms. If there's any egregious outliers, then we'll know that something went wrong.
So, with that in mind, let's start by analyzing some raw numbers!
Raw Numbers
First, let's take a look at the long run behavior of all nine trials. I took some creative liberties with the colors, but we only really care about chests and keys for this visualization.
Figure 12: Full spread of all nine different test cases for 200 timesteps averaged after 5 repetitions. All graphs are mapped to the same vertical scale.
Right off the bat, we can notice some patterns:
The key advantage actually helped quite a bit! Not only did it lift up the left end of the graph, but it maintained that advantage throughout the experiment, which I wasn't expecting.
Mom's Key and Paper Clip both did very well, though Paper's Clip's non-key rewards were identical to the control case. I guess that's to be expected, but I was hoping that more keys would correlate with opening more chests. I guess the margins are way thinner than I previously thought.
Daemon's Tail was surprisingly good, but I was also shocked by how close its results were to Rusted Key! Despite the downsides of the former, they both did admirably with regards to generating keys.
Contract from Below was VERY similar to the control case, though there are more chests and less overall consistency. Kinda disappointing.
Lucky Foot was surprisingly good here! I was skeptical of its performance, especially since I wasn't able to work it into the Python model, but the increased drop chance seems to have a slight positive effect on the key count. Nothing worth skipping dinner plans for, but interesting nonetheless.
Finally, Gilded Key was truly awful in every way. Maybe I should have seen this coming given the projections, but all the same, wow. Apart from a peak at 5 keys near the start of one of the attempts, nearly every other moment was spent in total bankruptcy.
Mean, Max, and R-squared!
This is where the comparisons get a bit complex. Although I do want to focus on the circumstances where I have Guppy's Tail, I can wrap everything up into one set of comparisons by comparing the control values with Guppy's Tail to every other experimental scenario (also with the tail).
I also opted to add an R-squared measure to the calculations as well. For the uninitiated, imagine that you have a trend line that runs through all of the data. The R-squared value of that data is how closely it sticks to the trend line. In practice, it serves as a nice measure of consistency!
The result of this consolidating process is one chart that shows all of the calculated fields in one place!
Figure 13: Chart showing the percent difference in key counts between the control case and each experimental case.
Interestingly, it seems like every added item had a positive effect on the average and max key count compared to the control (except for Gilded Key, lmao). Contract from Below barely scraped by, and Lucky Foot still seemed a bit dubious, but interesting results all the same!
Just for the hell of it, let's also make a chart of the max values for each attempt, and each case. We'll need these values later to check the accuracy of the models, after all :p
Figure 14: Chart of the maximum values for each attempt and each test case with 200 timesteps.
Speaking of "nothing", let's move on to talk about the bankruptcy times for these experiments.
Bankruptcy, Endpoints, and Long-Term Gains
As before, we can track the amount of time spent without any keys, and create a chart of per-trial bankruptcy.
Unlike the previous set of graphs, though, I'm not necessarily interested in the amount of keys so much as I'm interested in their long-term trend. After all, we already have two other ways to check how well the different scenarios are performing.
If we let all the graphs fill the space as usual and don't override the scaling, then it's much easier to see trends in the data. With that in mind, here's a spread of the key counts for all nine cases:
Figure 15: Key counts of all 5 attempts for each test case and 200 timesteps. Note that the graphs are not mapped to the same scale this time.
If we look back at the collected r-squared values, this makes a lot of sense!
Key Advantage, Mom's Key, Daemon's Tail, and Rusted Key show a subtlely positive slope, Paper Clip shows a very clean positive slope, the Control and Lucky Foot tests feel a bit more obfuscated, Contract from Below feels too random to get a bead on, and Gilded Key is still terrible.
It feels a bit redundant to even bring it up, but we can also check the chart of bankruptcy time, and it shows very similar patterns to everything else that's been mentioned up to this point:
Figure 16: Chart of bankruptcy time for each attempt and 200 timesteps. Also includes the average and standard deviation!
OK, last thing before we get back to the Python code. If we make one final chart that tracks the endpoint values for each attempt, we can get a sense of how these different cases worked in the long run, but like I said before, this also lets us expose a set of values that can be plugged back into the Python projections!
(that, and these charts also look nice :D)
Figure 17: Chart of endpoint values for each attempt, 200 timesteps, yada yada. Also has the average and standard deviation again!
Now Back To The Good Part
Returning to the Python simulations, I extended the system to be able to modify starting conditions based on the current test case, and then programmed eight of the nine different test cases we've discussed previously!
The only scenario I left out was Lucky Foot, mostly because the wiki doesn't have clear numbers for what the item actually does, just approximations. That being said, this section is mostly meant to verify the Python code with real data, not vice versa, so I'm fine leaving it out.
Either way, it's already proven itself to be decent based on the real data, so I'm fine giving it this W and moving on.
As for the other cases, the plan is to validate the brute force approximations by generating a new set of simulation data, and then laying the real values over the graph as vertical lines. If the positions of the lines are way off-base with the distribution of the graph, then either I got extremely unlucky, or something went wrong with the calculations themselves.
So hopefully that doesn't happen!
The different sets of rules are applied with the same standard variables, and I also hooked up the system to output everything to one large image. With that, here's the final image of all the Python results!!
(I recommend opening the image up in a different tab so you can read the results clearly!)
Figure 18: Full spread of experimental results for nine different test cases, 200 timesteps, and 50000 repetitions. Also includes vertical lines corresponding to the results of in-game testing.
Based on our findings, it seems like our Python code correlates almost perfectly with the actual in-game behavior!
There appears to be a few outliers in Key Advantage and Gilded Key, but I think I have explanations for the first case. Even though the histogram works as a good measure of density, outliers can still pop up in the fringes, especially since this is just 5 trials measured up against a normal distribution of 50000.
They're very lucky outcomes, but they don't necessarily devalue the entire model, since the experiment was already know to have a ton of variance.
As for the Gilded Key example, though, I'm surprised that the real data was even more aggressively bad than the simulation. My best guess as to "why" is that the chance of getting a key while in bankruptcy is roughly 12.5%, like we established earlier, but that trend isn't likely to manifest unless you do tons and tons of trials, since most of the time you'll just end up bankrupt for the whole run.
Additional Key Advantage Testing
One more thing I want to test on this front is how the key advantage changes these graphs, and by iterating the process used for the 3 Key Advantage over and over again, we can generate a set of animated graphs!
This will serve two main purposes. The primary purpose is so that we can track the minimum observed value across all tests. Once that minimum value is not equal to 0, we can infer that going bankrupt is no longer mathematically feasible.
That being said, the secondary purpose is that these graphs look cool as hell!
Figure 19: Animated graph of 50000 attempts with 200 timesteps each (again, throttled down to 500 attempts so it doesn't explode my computer)
Figure 20: Animated histogram of endpoints of 50000 attempts with 200 timesteps each.
Figure 21: Animated histogram of max values of 50000 attempts with 200 timesteps each.
Figure 22: Animated histograms of chests opened and keys obtained for 50000 attempts with 200 timesteps each.
Figure 23: Animated histogram of bankruptcy time for 50000 attempts with 200 timesteps each.
Overall, the results are fascinating!
As the starting key count increases, the raw values are picked up by the scruff of their neck and carried into the air. After a certain point, there's not a single attempt that reaches 0!
The graph of endpoints slowly shifts left into a beautiful normal distribution. It looks amazing :D
Conversely, the graph of max values stays eerily consistent regardless of the value of N, and hovers along towards the right edge of the graph.
The graph of key and chest totals shows a slow drift between the two distributions. If you look closer, though, you can also see a subtle change in the girth of the key count histogram, as it becomes less reliant on solely room drops for key generation after escaping bankruptcy.
Lastly, the histogram of bankruptcy time rapidly fizzles out to nothing as the chance of 0 starts to dominate the graph. This is partially due to the way that the histogram is scaled, but we can also see the probability of 0 continuing to rise, until it settles at a perfect 1.0.
Cool stuff all around! The final missing piece is to find that point where it's no longer feasible for bankruptcy to occur, and we can test that by making one final graph of the percentage of trials with a minimum value at or below N (ranging N from 0 to 3).
(this is the last graph, I swear)
Figure 24: Graph of the percentage of the minimum values of each trial being above N for several N's. After N=38, all attempts recorded a minimum key count above 0.
We have our answer to this little side story: our figurative water level is 38 keys. After that point, it is mathematically unlikely that a Guppy's Tail run will EVER run out of keys.
We can contract this value even further, since even having a 20% chance of never running out of keys is a pretty sweet deal.
With that in mind, 16 keys is the water level for 90% of runs NEVER going bankrupt, which feels impressively low given the circumstances. Furthermore, 13 keys is the water level for 80% of runs being bankrupt-free, and 11 is the mark for 70% of runs.
Flipping the script a bit, this also means that if you have 3 keys, your chance of having a run without bankruptcy is about 20%. That sounds bad on paper, but as before, this chart scales RAPIDLY as you acquire more keys. I suppose it's up to your own tolerance for danger at the end of the day.
We've Reached The End
Well, we made it! It took nearly two years of pondering over this experiment, several reruns of the in-game testing, and a whole lot of elbow grease, but we made it.
So with everything in mind, what's our final verdict for Guppy's Tail?
Well, when used by itself, the item is incredibly unreliable, and the graphs show that every run that ends up with Guppy's Tail is almost guaranteed to go bankrupt at some point of the course of the run.
In a vacuum, it's a pretty dreadful item, and even if it technically provides more pickups in the long-run average, it's nearly guaranteed to inconvenience you at some point during the run.
When we add in supplementary items, though, things get a lot more optimistic.
Key Advantage managed to be the most important factor in the entire experiment, though maybe I should've seen that coming. The more keys you have, the less likely you are to backslide into poverty, and the more you can take advantage of the Golden Chests and their increased rewards.
As for the other options, I offered up a prediction earlier for which scenarios I was expecting to perform well:
I expect Key Advantage to do about as well as the control case, Mom's Key and Paper Clip will be excellent, Daemon's Tail and Rusted Key will be slightly less good (but still excellent), Contract from Below will provide marginal benefits with a higher variance, and Lucky Foot and Gilded Key will be actively harmful.
I ended up severely underestimating the value of Key Advantage, and Lucky Foot performed surprisingly well despite not having Python data to compare it with. Apart from those two inaccuracies, though, I feel like my predictions were pretty spot-on!
So should you pick up Guppy's Tail?
Well, without any other support, the answer is no, but if you have more than a few keys, or if you have some supplemental items, then this item can be excellent, generating far more pickups in the long run as well as the occasional collectible (which I didn't account for in the experiment, but it is worth mentioning at least once).
Ideally, you'd want as many keys as possible, but if you have no other supplemental items, my absolute minimum before picking this up without other support would be 7. That way, you have a ~50% chance of never going bankrupt, and even if you do, it's not likely that you'll stay bankrupt for very long.
It's still a somewhat risky item at the end of the day, but the inherent risk falls off quickly once you have resources to work with, and if you leverage those resources properly, it'll all work out in the end. :)
Closing Remarks
If you've made it this far, then thanks a lot! I deeply appreciate that you're willing to indulge me in my hyperfixations, and there's certainly more where that came from.
The contents of this post are eventually going to be packaged into an edited video, and if you want to see all of this information presented in a slightly different way, you can check the top of this post for a link (once it's ready of course).
As for the code, you can click the link right here to head to the GitHub page. I also ended up posting the Lua half of the project to the Steam Workshop as well, and you can find that here. Feel free to mess with the code and try different combinations! If there's something big that I missed, I'll consider doing some follow-up experiments at some point in the future.
If you liked reading this admittedly long write-up, feel free to reblog and share it around! This is a pretty niche topic, and I'm fairly certain that all of these posts will end up being niche topics, but all the same, I had a blast making this experiment happen, and I hope you enjoyed reading as well!
That's all I've got for now. Take care, y'all! Hope you have a happy Halloween!
















