There are many carts on here that involve corruptions of existing games, but as far as I can tell this is something new: a game designed to corrupt itself from the start! Whether this makes for good gameplay is for you to decide. Spikes increase the rate of corruption, so you should probably avoid them. Go fast, and try to reach the end before the corruption takes over everything. You'll want to reset the cart if that happens. Good luck!
I haven't made this game alone, because it's my second on pico-8 and I didn't feel like reinventing the wheel. The biggest help came from @mhughson's Advanced Micro Platformer, which formed the base of the whole game. Without it, this game wouldn't have really been possible (or at least wouldn't have been as good), so big thanks to @mhughson for releasing it! The music is "Dimensional Gate" from @Gruber's Pico-8 Tunes Vol. 2, which fit the game very well and added one of the best consequences of corruption where the music goes all weird. I also used @qbicfeet's horizontal distortion effect, which triggers (for no good reason) when a randomly-selected byte gets corrupted. Finally, I took inspiration from Jelpi's corrupt mode, but altered its corruption code to be more like the "RANDOMTILT" mode from the Real Time Corruptor (also an inspiration for this game). RANDOMTILT has a chance of simply incrementing or decrementing ("tilting") a byte instead of setting it completely randomly, making for slightly nicer corruptions.
Really cool game, corruption implemented well.
ohhh this is wicked! I love this stuff. The game has been playing in the background for about 10mins now, and it sounds awesome.
Can you recommend any good resources for learning how this is done and how to do it? I'm specifically interested in the music/audio stuff. I also know 'very little' about programming :)
Great stuff, thanks for using my music!
In this game specifically, there are two methods of "corruption", which each have a 50% chance of occurring:
- Setting a random memory address (which range from 0x0000 to 0x7fff) to a random byte.
- Randomly incrementing or decrementing a random memory address.
Both are done somewhat trivially; for example, here is a simple line of code that corrupts one random byte of memory:
@Gruber there's a very good explanation in this thread already, but basically the corruption just hits a random byte of pico-8's memory and occasionally makes the music sound "better" because of it. You can get similar musical effects from just about any corruption, so if you're interested in that try using the RTC or the Vinesauce ROM corruptor on some NES ROMS. Though the way pico-8 stores music makes it turn out much more reliably interesting. Glad you liked the game!
@JWinslow23 @twotwos Thanks for the explanation!
Just so I understand, there are random memory addresses that are being randomly incremented, decremented or randomized. Is this happening over a specific period of time? Like, is it possible to slow down or quicken these occurrences?
Also, once random bytes have been corrupted, is it possible to reset those byes to their original values without resetting the cart? Is it possible to only target music/sfx?
Asking because as a musical composition, it’d be interesting to compose and loop, say Pattern 00 and 01. Pattern 00 is set, never changes, but pattern 01 is always being randomly corrupted.
(sorry again for my ignorance :)
@Gruber in my game here, the corruption happens per three 60fps frames a number of times equal to the amount of damage. For general purpose use, you could put the main corruption code in _update(), and set 'health' to be the number of corruptions you want to do per three frames (or remove the ticks%3 if statement for once per frame). You still would need a 'ticks' (or similiar) variable that increases once per frame in order to do the TILT function properly, though.
As for only affecting music and sfx, you can change all instances of rnd(0x8000) to rnd(0x11ff)+0x3100. 0x3100 here is the first byte of music in memory, and 0x11ff is the difference between the end of sfx and the start of music (conveniently they're right next to each other). I've never done reloading before, but from the wiki I imagine to reload all of the music and sfx from the cart you'd run reload(0x3100,0x3100,0x11ff) just once.
As for only affecting specific music and sfx, that's a fair bit harder. You'd have to figure out their exact locations in memory (though seeing as the memory is all documented this isn't impossible) and then run the corruption over those specifically. Music patterns in memory are tiny, so you'd probably want to focus on corrupting the sfx you use for them (or at least only corrupt the patterns much more rarely). If you're using the same sfx for multiple patterns, you'd have to make copies if you only wanted to affect one. I'm not sure how much I could help with this, because it could be very fiddly, but I might be able to help in finding the right memory addresses to affect. Or @JWinslow23 maybe, he seems to know what he's doing ;)
@Gruber Yes, it is possible within PICO-8 to reset "corrupted" bytes to their original values. In fact,
will reload len bytes from the src_addr memory location in cart_file (the current cart, if this is not specified) into the dest_addr memory location. When using this for reverting changed values, dest_addr and src_addr will be the same.
Oh, and it is possible to target specific sections of memory (or even single bytes!) with the "corruption". Since what we're really doing is just poking random values in memory locations, we can restrict the range of the memory locations we're changing by simply changing the arguments to poke().
For example, the following snippet will only target music and SFX (see this wikia page for more info on the PICO-8's memory locations and what they control):
function rnd_range(n_min,n_max) --this function is here to --make it easier to generate --random numbers in a range. return n_min+rnd(n_max-n_min) end --this is the "corruptor". --(note: memory addresses are --integers, and our random --commands can return fixed --point decimals, but poke() --chops off the decimals of its --arguments anyway. poke(rnd_range(0x3100,0x4300),rnd(0x100))
Changing one of the music patterns would be done by restricting our random range to one of four bytes in this range, like so:
--change this number to any one --pattern you want to change. pattern=1 poke(rnd_range(0x3100+pattern*4,0x3100+(pattern+1)*4),rnd(0x100)) --can you extend this to cover --a *range* of patterns?
If we're changing the music patterns, however, we have to be very careful. For each of the 64 music patterns, there are 4 bytes that store the information about them, and bit 7 of each of those bytes control whether or not that pattern is a loop start, loop end, or stop. This means that, if we want to preserve these, we need to be more cautious than simply setting those bytes to random values, and I'll leave the problem of randomizing every bit but bit 7 as an exercise to the reader. ;)
One final interesting thing I'd like to say about music corruption, though; those 4 usable registers in the "hardware state" memory range actually control aspects of the audio hardware that not even the SFX/music editors can control! By setting bits 0, 1, 2, and 3 of their bytes, you can enable or disable audio effects for channels 0, 1, 2, and 3, respectively.
- 0x5f40, if a bit is set, halves a channel's clock rate (which causes SFX and arpeggio speeds to be halved, and the pitch to be an octave lower).
- 0x5f41, if a bit is set, enables a crude form of echo/reverb to a channel.
- 0x5f42, if a bit is set, distorts the output of a channel.
- 0x5f43, if a bit is set, enables a low-pass filter for a channel.
Experiment with these! Who knows, maybe you'll find effects you'll want to use in a track composed on purpose!
@JWinslow23 @twotwos ahh, you're the best. Thanks for the explanations. I may have some music I'd like to try this on in a few months. I'll play around with it!
[Please log in to post a comment]