Log In  

starving artist, technologist, and oxford comma advocate

:: Unfold ::

On 0.2.5e, the following message is printed to stdout whenever a cart containing any arbitrary meta section is run:

$ pico8 -run test.p8 
codo_free fail 21 0


pico-8 cartridge // http://www.pico-8.com
version 39

Carts seem to run fine, so there don't seem to be any other negative effects.

P#124003 2023-01-10 01:44

:: Unfold ::

Cart #runningout-0 | 2023-01-05 | Code ▽ | Embed ▽ | No License

Hi, friends!

Here's a demo featuring 5 glorious channels of lo-fi audio! Making a chipbreak-style tune in Pico-8 has been in the back of my mind ever since @carlc27843 discovered the undocumented PCM channel, and now I can finally check this off my list! After almost 2 years! πŸ˜…

All the drums are samples, triggered by watching the tracker with stat(56). The samples are then fed to the PCM channel by monitoring the buffer with stat(108). I'd originally planned on building a 5-channel tracker from this demo, but I had a tough time getting the samples to sync consistently. I'm not sure if that's because of my own shortcomings, or maybe because as @zep literally said, the sync is "not perfect".

For the gfx, the star animations are also triggered by stat(56), whenever a snare sample is played. The road is just one gigantic image using a palette cycle. It looks like this:

Anyway, I had a lot of fun putting this together. I hope you like it! πŸ˜ƒ

Special thanks to @pahammond for GEM, which helped keep this cart under the compressed limit!

P#123686 2023-01-05 01:09 ( Edited 2023-01-09 09:34)

:: Unfold ::

Cart #ruwezumuwu-0 | 2022-12-19 | Code ▽ | Embed ▽ | No License

Hi Zep! :) I think I found a bug in one of the more esoteric stat calls?

According to the manual, stat(56) should return the number of ticks played in the current pattern. Currently (0.2.5e), it seems to poll the leftmost sfx in the pattern for the count. That makes sense bc the overall pattern length is determined by the leftmost sfx which is not a loop, but as the title says, stat(56) will reset back to 0 early if the leftmost sfx is a loop.

Expected behavior:
stat(56) should return the number of pattern ticks played regardless of looped sfx

Thanks for your time! :)

P#76244 2022-12-19 23:41 ( Edited 2022-12-19 23:43)

:: Unfold ::

Cart #strangersagain-0 | 2022-12-15 | Code ▽ | Embed ▽ | No License

I was going to wait to post this until the weekend, but I have no self-control. πŸ˜… Enjoy the rest of your week, everyone!

P#121314 2022-12-15 23:43

:: Unfold ::

P#122267 2022-12-12 01:42

:: Unfold ::

Cart #hangingon-1 | 2022-12-13 | Code ▽ | Embed ▽ | No License

Here's another new song! Have a great weekend! πŸ˜ƒ

P#122103 2022-12-09 17:16 ( Edited 2022-12-13 10:51)

:: Unfold ::

Cart #beautifulrunaway-0 | 2022-11-27 | Code ▽ | Embed ▽ | No License

This one is short but sweet. I hope you enjoy it! 😊

P#121486 2022-11-27 18:17

:: Unfold ::

Cart #dragondeity-0 | 2022-11-24 | Code ▽ | Embed ▽ | No License

One last chiptune. I hope you like it! πŸ˜€

P#121318 2022-11-24 14:19 ( Edited 2022-12-19 12:23)

:: Unfold ::

Cart #thegodhandisreal-1 | 2022-11-22 | Code ▽ | Embed ▽ | No License

One more! πŸ˜…

P#121148 2022-11-22 03:20 ( Edited 2022-11-22 14:29)

:: Unfold ::

Cart #aurapower-0 | 2022-11-21 | Code ▽ | Embed ▽ | No License

Hi everyone, I wrote this song over the weekend, I hope you like it! πŸ˜€

P#121087 2022-11-21 00:44

:: Unfold ::

Cart #downstreamdream-2 | 2023-01-14 | Code ▽ | Embed ▽ | No License

UPDATE 1/14/2023, v1.1:
This update couldn’t have happened without @thisismypassword’s contributions to the community. I recently began overhauling my build pipeline for future projects, and one of the first features I added was Shrinko8. Since Downstream Dream is my only project to use the full build pipeline, I used it for testing output from the new minifier, which led to some token golfing. Thanks to this tip, also by @thisismypassword, I was able to free up about 100 tokens! Changes include:

  • Midboss section contains more action and a couple new sprites/sfx
  • Rocks spawn gradually at the start of each lap (so you don’t crash into them immediately after winning the midboss section)
  • Turtle hit/hurtbox size reduced
  • Speed gauge colors changed to represent the danger level of being ejected
  • Slight gfx adjustments
  • Faster boot time

While I have your attention, I’d like to thank everyone who supported this game and said nice things about it, it means so much to me and it made my whole year! Special thanks to @extar, who recently featured it on his Pico Playtime: Best of 2022 video, and extra special thanks to @zep for featuring it on the front page on day one. It’s a huge honor to be listed among so many incredible projects from the community and I’m grateful beyond words to everyone here!

Anyway, if you liked the original release, I hope you enjoy the update!

UPDATE 06/11/2022, OST
I've added a special OST cart for the itch.io download, where you can select music from the soundtrack and watch the game play itself while you listen. I hope you enjoy it!)

Hi everyone,

Here's my new game. It's called Downstream Dream, it's a side-scroller that I guess is somewhere between kart racers and shoot-em-ups. I hope you like it!

It's also published on itch.io if you'd like to support it. Most of the content from that page is duplicated below, but first, a few thanks to BBS members:

  • To @zep, @Makke, @Felice, @Nodepond, and @pahammond for their code contributions to the community, some of which I wouldn't have been able to release this cart anytime soon without! (listed below)

  • To @freds72 and @johanp for their advice. This game would have been a lot worse if not for their kindness and willingness to help a stranger.

  • To @Liquidream for playtesting, and for lots of encouragement when I felt like giving up.

  • To everyone in the Pico-8 community. I'm very grateful that so many are willing to share your creativity and kindness with others. Trading knowledge and games with you all has brought me a lot of joy.

Thank you all!

Game Controls


β¬…οΈβž‘οΈβ¬†οΈβ¬‡οΈ = D-Pad or Keyboard Arrow Keys

πŸ…ΎοΈ = Button 1 or Keyboard Z

❎ = Button 2 or Keyboard X


⬅️ + πŸ…ΎοΈ - Paddle counter-clockwise

➑️ + πŸ…ΎοΈ - Paddle clockwise

⬆️ + πŸ…ΎοΈ - Steady ahead

⬇️ - Move backwards (with motor)


❎ - Throw

How to Play

From the Developer

My mom loved both adventure and the American southwest; she traveled there many times over her life, and her home was filled with art and souvenirs she'd collected from her visits to the region.

When I was about seven years old, my parents rented a minivan and took me on a long roadtrip across the United States. I don’t remember a lot from that trip other than staring out the window while the landscape slowly changed from cities and farmland, to mountains, to desert, but one memory stands out from the endless monotony of the road; a stop at the Grand Canyon where my mom went white water rafting.

My dad never learned how to swim, and someone needed to watch me anyway, so the two of us waited downstream in the hot afternoon sun, looking for mom's boat to come in. I don’t think the water was particularly rough at that time of year; but like my dad, I’ve never been good at swimming either, and I spent most of the time thinking about how scary the water looked to me. I imagined how if I was on the boat with mom, I would probably fall out and be carried away by the river. She seemed very bold and brave that day!

A couple years before she left this world, mom offered to take me on a trip out west; possibly to Las Vegas, or maybe the Grand Canyon. I politely turned her down for some made-up reason, while the real one probably had something to do with still being in my 20s and wrongly believing that I was too cool to go on vacation with my mother. As a young adult, I assumed that there would always be more time with the people in my life. Now that I'm a little older, I'm learning that there is rarely ever enough. I don’t know where we might have gone or what we might have done on that trip, if I hadn't foolishly said no; but sometimes, it’s nice to dream about it.

Anyway, I hope you enjoy the game. It contains a lot of love, a little regret, and the best game I could make with Pico-8.


Credits and Thanks

External Libraries:


Special Thanks To:

P#112916 2022-06-09 01:13 ( Edited 2023-01-14 10:16)

:: Unfold ::

Hi everyone,

My new project uses massive tables of graphics metadata, so I've written a new table serializer inspired by this pull request on @BenWiley4000's pico8-table-string to get the job done. Hopefully someone else finds this useful, too!

It uses less characters to store your table as a string than pico8-table-string does (to take up less character / compressed space), but at the cost of 14 more tokens to deserialize, and possibly with less reliability. (It will break if the table contains a string a certain character sequence, see below.) You should also run the output through something like Zep's escape_binary_string before saving to code.


  • string/number/boolean values
  • key/value pairs
  • consecutive indexed values starting from 1

Not supported:

  • 0-indexed values
  • non-consecutive indexed values
  • strings containing the ascii sequence \3\120\23

Writing parsers is not one of my strengths, so pull requests on the GitHub repo are open and appreciated!


---serialize lua table to string
[email protected] {table} tbl
--  input table
[email protected] {string}
--  serialized table
function tbl_serialize(tbl)
    local function encode_value(value)
        --ascii unit separator
        --delimits number
        local value_token = "\31"

        --ascii acknowledge
        --delimits true boolean
        local bool_true = "\6"

        --ascii negative acknowledge
        --delimits false boolean
        local bool_false = "\21"

        --ascii start of text
        --delimits start of string
        local str_token = "\2"

        --end of string sequence
        --ascii end of text + x
        --ascii end of transmission block
        local str_end_token = "\3x\23"

        --asci group separator
        --delimits start of table
        local tbl_token = "\29"

        if type(value) == "boolean" then
            return value and bool_true or bool_false

        elseif type(value) == "string" then
            --check for
            --ascii control chars
            for i = 1, #value do
                if ord(sub(value, i, i)) < 32 then
                    --delimit binary string
                    return str_token .. value .. str_end_token

            --encode regular string
            return value_token .. value

        elseif type(value) == "table" then
            return tbl_token .. tbl_serialize(value)

        return value_token .. value

    local str = ""

    --ascii record separator
    --delimits table key
    local key_token = "\30"

    --ascii end of medium
    --delimits end of table
    local tbl_end_token = "\25"

    --get indexed values
    for _, v in ipairs(tbl) do
        str = str .. encode_value(v)

    --get keyed values
    for k, v in pairs(tbl) do
        if type(k) ~= "number" then
            str = str .. key_token .. k .. encode_value(v)

    str = str .. tbl_end_token

    return str

---validate output
[email protected]
--  output = tbl_serialize(my_table)
--  tbl_serialize_validate(
--      my_table,
--      tbl_deserialize(output)
--  )
[email protected] {table} tbl1
--  input table
[email protected] {table} tbl2
--  output table
function tbl_serialize_validate(tbl1, tbl2)
    for k, v in pairs(tbl1) do
        if type(v) == "table" then
            tbl_serialize_validate(tbl2[k], v)
                tbl2[k] == v,
                tostr(k) .. ": " .. tostr(v) .. ": " .. tostr(tbl2[k])


---deserialize table
[email protected] {string} str
--  serialized table string
[email protected] {table}
--  deserialized table
function tbl_deserialize(str)
    ---get encoded value
    [email protected] {string} str
    --  serialized table string
    [email protected] {integer} i
    --  position of
    --  current delimiter
    --  in serialized table string
    local function get_value(str, i)
            sub(str, i, i),
            i + 1

        if char == "\29" then
                    sub(str, i_plus1)

            return tbl, i + j

        --binary string
        elseif char == "\2" then
            for j = i_plus1, #str do
                if sub(str, j, j + 2) == "\3x\23" then
                    return sub(str, i_plus1, j - 1), j + 2

        --bool true
        elseif char == "\6" then
            return true, i

        --bool false
        elseif char == "\21" then
            return false, i

        --number, string,
        --or table key
        --("\30" or "\31")
        for j = i_plus1, #str do
            if ord(sub(str, j, j)) < 32 then
                local value = sub(str, i_plus1, j - 1)

                return tonum(value) or value, j - 1


    --loop start

    local char = sub(str, i, i)

    --end of table
    if char == "\25" then
        return tbl, i

    --table key
    elseif char == "\30" then
        local key, j = get_value(str, i)
        local value, k = get_value(str, j + 1)

        tbl[key] = value

        i = k + 1

    elseif ord(char) < 32 then
        local value, j = get_value(str, i)

        add(tbl, value)

        i = j + 1

    --loop end
    goto parse

    return tbl


--example table
tbl1 = {
    foo = "bar",
        foo = "bar",
            foo = "bar"

str = tbl_serialize(tbl1)

tbl2 = tbl_deserialize(str)

tbl_serialize_validate(tbl1, tbl2)

printh(escape_binary_str(str), "@clip")

P#112705 2022-06-04 00:46

:: Unfold ::

I don't feel like I have the greatest grasp on Lua metatables/metamethods, but I came across some unexpected behavior this week and was wondering if this is a bug. Example cart attached.

I'm setting up a "class" like so:

class = {}
class.__index = class

function class:new(instance)
    local instance = instance or {}

    setmetatable(instance, self)
    instance.__index = instance

    add(self, instance)

    return instance

To save tokens, and since I never expect to have numeric indexes in class or any subclasses I instantiate from it, I simply add the instance to whatever object is self when :new() is called. ie, class[1] is a subclass, and subclass[1] == class[1][1].

This works great if I use vanilla Lua ipairs() to iterate over subclass, but if I use Pico-8's all() or foreach() built-ins, things start breaking. Unlike with ipairs(), if #subclass < #class, the loop continues and retrieves any remaining values from class! So, in the example below, the all() loop will correctly access class[1][1].foo on the first iteration, but instead of stopping, it will then try to access class[2].foo.

EDIT: Forgot to mention, #class[1][1] / #subclass1 and/or count(class[1][1]) / count(subclass1) report the expected counts, so a C-style for loop would also work.

subclass1 = class:new()
subclass2 = class:new()

subclass1:new({ foo = "bar" })

-- assertion passes
for _, instance in ipairs(subclass1) do

-- assertion fails
for instance in all(subclass1) do

I don't mind using ipairs(), but since it does cost an extra token, I'm hoping this is a bug!

Cart #yujisogeba-0 | 2022-03-11 | Code ▽ | Embed ▽ | No License

P#108418 2022-03-11 03:55 ( Edited 2022-03-11 09:06)

:: Unfold ::

Hi friends,

My next project is likely going to need to rotate sprites, so I've been looking at the various methods posted on the bbs. Something I'm wondering about while I've been looking at everyone's examples is, how does one approach collision detection for when a sprite is rotated?

I'm currently capable of AABB and bitmasking per row of pixels, but it seems I'm going to have to write some new methods to integrate either of these into a rotation algorithm, and I'm not sure where to start. I'd appreciate any links to any carts or non-p8 articles or anything, really!

CPU is also a concern for me, as I think I'm going to need a lot of it dedicated to drawing the backgrounds I need which would be rendered via something like spellcaster's rle library.

Thanks in advance! :)

P#80360 2020-08-04 21:56

:: Unfold ::

EDIT: See the first reply below if you'd rather compile and use the native tool, which does seem more streamlined!

Hi, friends!

I just put Pico-8 on my Raspberry Pi for the first time and ran into problems getting my USB gamepads to work. Unfortunately, there's no ARM build for the General Arcade SDL2 Gamepad Tool, and I didn't want to bother with compiling the native helper, so I had to take this manual route. This will only take you a couple minutes.

The final SDL string will look something like this:

First, let's get the GUID.

This issue in the GameControllerDB repo tells us a little bit about the GUID structure in SDL 2.0.5+. It varies between operating systems, and so if you copied it from a different OS to Raspbian, this might be the cause of your woes (as it was mine)!

Open a terminal and run:

$ cat /proc/bus/input/devices

You should see your device listed in the output. We'll be concerned with the three circled values in the sysfs path, and also the version number above it. We'll come back to this output later for the name and input device path:

Convert the endianness of the three syspath IDs, and append the version. (Swap the first two characters with the last two and add four 0s). So for my gamepad:

  • 0003 = 03000000
  • 081f = 1f080000
  • e401 = 01e40000
  • 0110 = 10010000

and the end result: 030000001f08000001e4000010010000

I'm not sure if case matters here, but I went with all lowercase since I know it works.

I tried different names and SDL doesn't seem to care what you call the gamepad, but I went with the weirdo name I was provided, extra spaces and all:

Finally, let's get our mappings. Install jstest if you don't have it:

$ sudo apt update
$ sudo apt install jstest

And also refer to the Handler section in our original output to know which input device to pass to jstest.

For me, it's js0, so I'll pass /dev/input/js0 to jstest:

$ jstest --normal /dev/input/js0

The output looks like this, and is interactive. Each switch should flip when you push a button on the gamepad. Be sure to note the corresponding axis or button numbers.

Now that we know what button is what, we just need to add them to our config string. SDL assumes an Xbox-like gamepad layout. Together with the standard Pico-8 layout it will be mapped like this, but with the button numbers you collected from jstest:

  • a: ❎
  • b: πŸ…ΎοΈ
  • x: πŸ…ΎοΈ
  • y: ❎
  • start: pause/options menu
  • dpup: ⬆️
  • dpdown: ⬇️
  • dpleft: β¬…
  • dpright: ➑

Finally, put it all together! Here's what mine looks like, with funky spaces in the name and all buttons mapped: (note how the axes are expressed as -a1, +a1, -a0, +a0)

030000001f08000001e4000010010000,USB gamepad           ,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0,lefttrigger:b6,righttrigger:b7,

Drop that into your sdl_controllers.txt, and it should map successfully. Fire up Pico-8 in a separate terminal window, and then check the log. You should see something like this towards the end:

If it was unsuccessful, it will look like this:

Good luck! πŸ˜ƒ

P#79379 2020-07-16 04:53 ( Edited 2020-07-28 08:34)

:: Unfold ::

Ran into this on MacOS Mojave. If you put a '.' in the filename when you export binaries, the unzipped app will launch just fine, but the zipped one won't.

I made a quick video:

P#79352 2020-07-15 14:40 ( Edited 2020-07-15 14:42)

:: Unfold ::

Cart #mozonogozi-0 | 2020-07-14 | Code ▽ | Embed ▽ | No License

Hi, friends!

I've just finished my first ever video game!

I just want to thank this entire community for existing and being so helpful and friendly. I discovered Pico-8 in April; at the peak of COVID-19 hitting my hometown, and in the depths of a massive depression brought on by isolation/lack of work/personal losses from the pandemic. It's been a lifelong dream to make a game, but I never thought I'd ever actually accomplish it, and making a little progress on this project each day feels like one of the few things that has really kept me going. So, thank you all!!!

Special thanks to @Liquidream for leading me here in the first place, and being kind enough to do some playtesting and providing lots of helpful feedback along the way. Also thanks to @MBoffin for their zine, which got me started, and for feedback in the forums when I was trying to figure out managing game state.

Also, I have a resprite with (better?) music up on itch.io:

I think I'm here to stay! On to the next one!

P#79308 2020-07-14 11:00 ( Edited 2020-07-14 11:04)

:: Unfold ::

Hi friends, does anyone have a hacky JS workaround for preventing pinch/zoom on itch.io in iOS? I just uploaded my first Pico-8 game there and it's more or less unplayable. (I don't own an Android device, so I don't know if this is also a problem on there.) Looks like the exported JS from 0.2.1b already employs a few workarounds to prevent touch gestures, but this doesn't do much when it's in an iFrame because you can still pinch and zoom on the parent document.

P#79230 2020-07-13 02:42

:: Unfold ::

Hello, new friends! I discovered Pico-8 a few weeks ago and this is my first post in here!

I'm currently trying to create a framework to manage game state for some future creation, and am hoping to get some feedback on what I've put together so far. Any insight would be greatly appreciated; I'm a web developer and consider myself reasonably proficient in PHP/JS, but this is my first Pico-8 project/first video game/first time working with Lua/first time writing my own FSM ever, so I'm feeling very unsure of myself! I'm looking over it and asking myself questions like; have I over-complicated this? Does the game/level hierarchy make sense? Is it DRY enough? Is there a better way!? etc, etc!

The state hierarchy of the cart looks like this:

|_Title Menu State
|_Config Menu State
|_Game State
  | |_Level State
  |   |_Start
  |   |_Running
  |   |_End

So, the first state the app is in is the Title Menu state, where you can either start the game or enter the Config Menu (for picking levels to start on or some other arbitrary config). From either of those, you can enter the Game State. Once we're in the Game State, it initializes, then begins running through the Level States (which just run 3s timers for demo purposes). Each level gets a Start state for saying 'ready, go' or whatever, a Running state for actually playing the game, and an End state for congratulating the player for completing the level or something.

One thing I did which I noticed is different from some of the tutorials I've seen is that I'm calling _init() rather than game_init() or level_init() when I make any state change. I realize this isn't super efficient, but I think it makes sense because any time I make a state change at any level, the program has to flow through the entire FSM hierarchy, which might be easier to debug in the future maybe? Does that make sense, or am I just wasting CPU cycles?

I'm looking forward to learning more from everyone, and hopefully making something actually enjoyable some day! Thanks!!! :)

Cart is attached, and also on GitHub

Cart #dojotizoko-0 | 2020-05-24 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

P#77097 2020-05-24 03:12 ( Edited 2020-05-24 03:28)

Follow Lexaloffle:          
Generated 2023-02-04 15:41:15 | 0.402s | Q:90