
Here's a very weird bug I just discovered accidentally.
- Visit a page with a comment that contains a bullet point list, like this (and possibly a page 1|2 section at the bottom, not sure).
- Click and drag your mouse to highlight the text.
- While still holding drag, move your mouse cursor to the right. A horizonal scroll bar will appear along the bottom, apparently equal to the length of the largest line of text in the bullet point list, if there wasn't any word wrapping. It seems very weird but related to the length of the text, so I'm just going to ramble on here for a bit to prove my point. Doesn't seem to work with the post preview though, so maybe this is comment-only.
More funny than anything, tbh. You can see this bug in the wild on this page: https://www.lexaloffle.com/bbs/?pid=46706#p
Bug occurs in Chrome on Windows 10.



Especially in tweetcarts, using sub(s,i,i)
has always felt like a waste of so many chars, and less intuitive than the standard s[i]
in most other programming languages. Also, because of the massive amount of string parsing that happens in Pico-8 for data compression, and the fact that Lua does support this feature with a small amount of work, I'd really like this feature added. chr and ord were a step in the right direction (as previously, these needed two lookup tables generated at startup), but I think with the recent addition of sub(s,i,_)
, it's clear that a major string parsing use case is missing.
See this link, the implementation of __index that will still support t:func(a) is at the bottom of the page:
http://lua-users.org/wiki/StringIndexing
Why should this be added to the API though? Unfortunately, the main issue with people implementing this feature themselves seems to be that the basic Lua string library isn't present. getmetatable('')
returns nil in Pico-8 code, so you can't assign functions to it. You could assign a custom metatable via setmetatable that re-implements all of the string functionality in every string created, but that would destroy your token count, as it would have to cover concatenation, construction, tostr, etc. And since the carts that usually need this are character limited (tweetcarts and maxed out carts), you don't have the space to do it yourself anyway.








Okay, so after realizing that c0-e5 was 65 total notes, I did a little digging into this. The sfx format specifies that pitch is a number from 0-63, so why does e5 work when you press [p] in the editor, while octave 4 is selected? The sound is higher than d#5, too.
First, let's try some p8scii. In the console, print("\a0szd#5e5") cstore(0x3200,0x3200,68)
both plays a constant d#5d#5, and stores that in sfx 0. Then go to the sfx editor and select octave 4, and change it to d#5e5 via pressing [p], and press space to play back. Two different notes!
Now, go back to the console and do sfx(0)
. This gets the very weird behavior of d#5c1, almost like a default, but not the same default that \a has.
Then, go back to the editor and add a c1 directly below the e5, and press space for playback. Three notes. Then go back to the console and run sfx(0)
again. This time you get a weird fading blend between the two c1 notes, almost like pico-8 sees them as two separate pitch values internally.




I was going through some old code and discovered a change in behavior. Previously (version 0.2.3 and earlier?), if you did sub(str,1,nil)
, you would get the full string. Now, you only get the first letter of that string. I believe the recent API update for optimization is incorrectly evaluating nil args as 0, both for start and end.
This behavior was useful if you wanted to have a table of string lengths for tokenized text display, as in ?sub(dialog,1,dialog_token[time])
. Once the time var had reached above the size of the dialog_token table, the default table nils had the same behavior as showing the full string. I can fix with a min(#dialog_token,...), so not crucial, but the old behavior feels more intuitive to me.





Okay this is going to be a hard bug to resolve, because it's tough to pin down. I'm on Win 10 64bit, and for the past few versions of Pico-8, games have been having this input dropping issue every so often. It's definitely in v0.2.4 (original, no b c d), and may be in v0.2.3 or earlier, but the recent stat(28) raw input addition makes me think it started happening around that time.
What happens: you're playing a game normally, then you lose all control for a few seconds. The game is still updating and drawing, but mashing keyboard keys does nothing. I could record the next time it happens, but it's random AND rare, and all you'd see is the game running with the player standing still. After a few seconds, input starts working again.
Why this sucks to debug: not only do you have to be paying active attention to the game, you also don't know when it's going to happen. Sometimes you just think you messed up and pressed the wrong buttons, since it's hard to be sure, and everything is normal after a few seconds. But this is a critical btn/btnp bug that's severely reducing the quality of gameplay, so it needs to be fixed. The best I can do to help is say that I'm playing in windowed mode on the left half of the screen, there are no alerts or Windows messages that might be grabbing focus (as far as I know), and the issues occur long into a gameplay session (sometimes a half-hour or more between drops?).


On a fresh boot of pico-8, this glitchy grid effect happens exactly once, the first time that secret colors are used in a screen palette. Then never again.

I discovered this in my latest cart, and was wondering if this is an intentional indicator of "hardware mode change", or a bad sign that something is going seriously wrong. The starting palette of this cart uses only normal 0-15 colors, as does the "raw" palette you see when you press up. However, every other palette will show this glitch once for about 0.5 seconds, before the screen returns to normal. Re-running the cart does not show it ever again, until you reboot pico-8 completely.
Using if(pi==3) poke(0x5f2e,1) stop() poke(0x5f2e,0)
at the end of the _draw function means that the effect will happen for the same amount of time without any game code running at all, which makes me think that this visual glitch is intentional. Note also that without the poke to make the screen palette persist in the editors, there is no glitch effect, likely because it doesn't flip() with secret colors active. Any idea why this is happening, @zep?




I was reading up on Bit Arrays to learn more secret bitwise knowledge, and stumbled onto a very clever way to count bits, via Hamming weights and a magic multiplication number. Short story is that this led to efficient fullscreen Anti-Aliasing for 1-bit screen data (colors 0 and 1 only).
Other convolution shaders are easily doable with similar techniques. This one takes every pixel and adds weighted amounts of the neighbors in this way:
1 131 1 |
You might be able to do gaussian blurs with a larger sampling neighborhood, or things like Conway's Game of Life. I'm really surprised how efficient some of these techniques are, even on pico-8. Counting every single 1 in the entire screen data is only ~6% CPU @ 30fps.




Since the introduction of large maps, I think there's one feature missing that would help many pico-8 games: the ability to flip and rotate individual tiles in the map. Games often use up a lot of their sprite space just for the map data, duplicating tiles for the different boundaries needed, or using manual spr() calls to get the necessary x_flip and y_flip needed. Since there's no visible editor to worry about anymore, this could be a quick patch (via a poke value to an alternate map() drawing mode), and would make map() much more useful.
Flip map mode, option one: 0x8000+ is used for map data (same format used now), 0x2000 is the flip data for that map data, using 2 bits per tile (x_flip, y_flip).
Flip map mode, option two: the 1 byte of map data stored anywhere now references a cell # in the first 16 sprites (n 0-15, 4 bits), x flip (1 bit), y flip (1 bit), and 90 degree clockwise rotations (2 bits). Like so:
map data: 1 byte per tile (same as now) nnnnxyrr nnnn = sprite number, 0 to 15 [ [size=16][color=#ffaabb] [ Continue Reading.. ] [/color][/size] ](/bbs/?pid=103189#p) |





During some glitchy sandstorm effect testing for a tweetcart, I managed to hard crash the Pico-8 executable completely. This minimal code reproduction does it consistently on my machine after about 10 seconds of moving around. It seems to be related to the interaction between sin, rnd, and line, because removing those line()s fixes things. Likely a specific hex value in line() blows up somewhere, and rnd hits it occasionally with this setup?
Warning: I have no idea what this code will do on the BBS. Run at your own risk, as it consistently crashes the PICO-8 exe completely in just a few seconds!







Psst, hey kid, you wanna make a megaman? I'm not supposed to tell anyone this, but check this out.

Two questions I often see asked by Pico-8 beginners are how to animate your player sprite, and how to make them shoot bullets. When setting up your game objects for tasks like this, tables can scare a lot of new Lua coders away, because it can feel easier to just use a bunch of variables. Don't fall into this trap though! It'll make the code a lot harder to maintain as your project grows, and doesn't work for things like bullets at all.
Tables are an easy way to organize objects that get created and die, update, draw to the screen at the same time, etc. Getting more confident with all of the features of Lua is the first step to improving as a programmer (check out the Lua manual for more info), but for now I'll just go through a simple and efficient table structure that will work for many game designs. Before we get into game related stuff though, let's talk about the basics of tables.







X to restart, arrows to move. Catch only one of each!

Welcome to the Glyphmon cave, Nop. Professor Treename here, explaining how to find and catch all 128 unique creatures in this tiny game.
Explore the different areas and walk up to new Glyphmon to add them to your Character Set. But I'm not made of money Nop, you only have 128 Glyphballs to collect them all. If you catch any duplicates, you fail!
Your Glyph scope at the bottom shows you which Glyphmon you've already collected in the room, and any you haven't caught yet. Pay attention to color, and don't get tricked by colors that look similar! You can also re-enter rooms if you've scared any Glyphmon off the first time, to try again.
Red Alert! The master hacker known only as The Stick has infiltrated the most secure server in the world. To prove yourself, avoid detection from the security field for as long as possible.

Your hacking skills allow you to dig through nearby firewalls to escape the lasers. Some say there are even secret digging strategies that help you further, like FAST TOGGLING and RISKY PUNCHTHROUGH. How long can you last?
This is a #tweetcart made for Tweet Tweet Jam 6. The entire source code (including gameplay, art, and sound effects!) is 557 letters of text. Click the CODE tab underneath the cartridge to view the horrible nonsense code. :P
Why? Because it's fun!


.png)

The 0x5600+ area doesn't get reset properly when a new cartridge is loaded, or the current one is run again. Pretty easy to replicate, but here's a demo:
poke(0x5600,unpack(split"5,8,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,0,0,0,0,7,7,7,0,0,0,0,0,7,5,7,0,0,0,0,0,5,2,5,0,0,0,0,0,5,0,5,0,0,0,0,0,5,5,5,0,0,0,0,4,6,7,6,4,0,0,0,1,3,7,3,1,0,0,0,7,1,1,1,0,0,0,0,0,4,4,4,7,0,0,0,5,7,2,7,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,2,0,0,0,0,0,0,3,3,0,0,0,5,5,0,0,0,0,0,0,2,5,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,0,0,5,5,0,0,0,0,0,0,10,31,10,31,10,0,0,0,2,7,1,6,7,2,0,0,5,4,2,1,5,0,0,0,6,6,3,5,7,0,0,0,1,1,0,0,0,0,0,0,2,1,1,1,2,0,0,0,2,4,4,4,2,0,0,0,4,21,14,21,4,0,0,0,0,2,7,2,0,0,0,0,0,0,0,0,2,1,0,0,0,0,7,0,0,0,0,0,0,0,0,0,1,0,0,0,4,2,2,2,1,0,0,0,7,5,5,5,7,0,0,0,2,3,2,2,7,0,0,0,7,4,7,1,7,0,0,0,7,4,6,4,7,0,0,0,5,5,7,4,4,0,0,0,7,1,7,4,7,0,0,0,1,1,7,5,7,0,0,0,7,4,4,4,4,0,0,0,7,5,7,5,7,0,0,0,7,5,7,4,4,0,0,0,0,1,0,1,0,0,0,0,0,2,0,2,1,0,0,0,4,2,1,2,4,0,0,0,0,7,0,7,0,0,0,0,1,2,4,2,1,0,0,0,3,4,6,0,2,0,0,0,6,9,13,1,14,0,0,0,0,3,6,5,7,0,0,0,1,3,5,5,7,0,0,0,0,2,1,1,3,0,0,0,4,6,5,5,7,0,0,0,0,6,5,3,6,0,0,0,2,1,3,1,1,0,0,0,0,6,5,7,4,3,0,0,1,3,5,5,5,0,0,0,1,0,1,1,1,0,0,0,2,0,2,2,2,1,0,0,1,5,5,3,5,0,0,0,1,1,1,1,2,0,0,0,0,15,21,21,21,0,0,0,0,3,5,5,5,0,0,0,0,6,5,5,3,0,0,0,0,6,5,7,1,1,0,0,0,6,5,7,4,4,0,0,0,3,1,1,1,0,0,0,0,3,1,2,3,0,0,0,1,3,1,1,2,0,0,0,0,5,5,5,6,0,0,0,0,9,9,5,2,0,0,0,0,17,21,21,30,0,0,0,0,5,2,2,5,0,0,0,0,5,5,7,4,3,0,0,0,3,2,1,3,0,0,0,3,1,1,1,3,0,0,0,1,2,2,2,4,0,0,0,3,2,2,2,3,0,0,0,2,5,0,0,0,0,0,0,0,0,0,0,7,0,0,0,1,2,0,0,0,0,0,0,6,5,7,5,5,0,0,0,7,5,3,5,7,0,0,0,6,1,1,1,6,0,0,0,3,5,5,5,3,0,0,0,7,1,3,1,7,0,0,0,7,1,3,1,1,0,0,0,6,1,13,9,6,0,0,0,5,5,7,5,5,0,0,0,1,1,1,1,1,0,0,0,6,4,4,4,3,0,0,0,5,5,3,5,5,0,0,0,1,1,1,1,7,0,0,0,17,27,21,17,17,0,0,0,9,11,13,9,9,0,0,0,6,9,9,9,6,0,0,0,7,5,7,1,1,0,0,0,6,9,9,9,6,12,0,0,7,5,3,5,5,0,0,0,6,1,7,4,3,0,0,0,7,2,2,2,2,0,0,0,5,5,5,5,6,0,0,0,17,17,10,10,4,0,0,0,17,17,21,27,17,0,0,0,5,5,2,5,5,0,0,0,5,5,7,2,2,0,0,0,7,4,2,1,7,0,0,0,6,2,3,2,6,0,0,0,2,2,2,2,2,0,0,0,3,2,6,2,3,0,0,0,0,10,5,0,0,0,0,0,0,2,5,2,0,0,0,0,127,127,127,127,127,0,0,0,85,42,85,42,85,0,0,0,65,127,93,93,62,0,0,0,62,99,99,119,62,0,0,0,17,68,17,68,17,0,0,0,4,60,28,30,16,0,0,0,28,46,62,62,28,0,0,0,54,62,62,28,8,0,0,0,28,54,119,54,28,0,0,0,28,28,62,28,20,0,0,0,28,62,127,42,58,0,0,0,62,103,99,103,62,0,0,0,127,93,127,65,127,0,0,0,56,8,8,14,14,0,0,0,62,99,107,99,62,0,0,0,8,28,62,28,8,0,0,0,0,0,85,0,0,0,0,0,62,115,99,115,62,0,0,0,8,28,127,62,34,0,0,0,62,28,8,28,62,0,0,0,62,119,99,99,62,0,0,0,0,5,82,32,0,0,0,0,0,17,42,68,0,0,0,0,62,107,119,107,62,0,0,0,127,0,127,0,127,0,0,0,85,85,85,85,85,0,0,0,14,4,30,45,38,0,0,0,17,33,33,37,2,0,0,0,12,30,32,32,28,0,0,0,8,30,8,36,26,0,0,0,78,4,62,69,38,0,0,0,34,95,18,18,10,0,0,0,30,8,60,17,6,0,0,0,16,12,2,12,16,0,0,0,34,122,34,34,18,0,0,0,30,32,0,2,60,0,0,0,8,60,16,2,12,0,0,0,2,2,2,34,28,0,0,0,8,62,8,12,8,0,0,0,18,63,18,2,28,0,0,0,60,16,126,4,56,0,0,0,2,7,50,2,50,0,0,0,15,2,14,16,28,0,0,0,62,64,64,32,24,0,0,0,62,16,8,8,16,0,0,0,8,56,4,2,60,0,0,0,50,7,18,120,24,0,0,0,122,66,2,10,114,0,0,0,9,62,75,109,102,0,0,0,26,39,34,115,50,0,0,0,60,74,73,73,70,0,0,0,18,58,18,58,26,0,0,0,35,98,34,34,28,0,0,0,12,0,8,42,77,0,0,0,0,12,18,33,64,0,0,0,125,121,17,61,93,0,0,0,62,60,8,30,46,0,0,0,6,36,126,38,16,0,0,0,36,78,4,70,60,0,0,0,10,60,90,70,48,0,0,0,30,4,30,68,56,0,0,0,20,62,36,8,8,0,0,0,58,86,82,48,8,0,0,0,4,28,4,30,6,0,0,0,8,2,62,32,28,0,0,0,34,34,38,32,24,0,0,0,62,24,36,114,48,0,0,0,4,54,44,38,100,0,0,0,62,24,36,66,48,0,0,0,26,39,34,35,18,0,0,0,14,100,28,40,120,0,0,0,4,2,6,43,25,0,0,0,0,0,14,16,8,0,0,0,0,10,31,18,4,0,0,0,0,4,15,21,13,0,0,0,0,4,12,6,14,0,0,0,62,32,20,4,2,0,0,0,48,8,14,8,8,0,0,0,8,62,34,32,24,0,0,0,62,8,8,8,62,0,0,0,16,126,24,20,18,0,0,0,4,62,36,34,50,0,0,0,8,62,8,62,8,0,0,0,60,36,34,16,8,0,0,0,4,124,18,16,8,0,0,0,62,32,32,32,62,0,0,0,36,126,36,32,16,0,0,0,6,32,38,16,12,0,0,0,62,32,16,24,38,0,0,0,4,62,36,4,56,0,0,0,34,36,32,16,12,0,0,0,62,34,45,48,12,0,0,0,28,8,62,8,4,0,0,0,42,42,32,16,12,0,0,0,28,0,62,8,4,0,0,0,4,4,28,36,4,0,0,0,8,62,8,8,4,0,0,0,0,28,0,0,62,0,0,0,62,32,40,16,44,0,0,0,8,62,48,94,8,0,0,0,32,32,32,16,14,0,0,0,16,36,36,68,66,0,0,0,2,30,2,2,28,0,0,0,62,32,32,16,12,0,0,0,12,18,33,64,0,0,0,0,8,62,8,42,42,0,0,0,62,32,20,8,16,0,0,0,60,0,62,0,30,0,0,0,8,4,36,66,126,0,0,0,64,40,16,104,6,0,0,0,30,4,30,4,60,0,0,0,4,62,36,4,4,0,0,0,28,16,16,16,62,0,0,0,30,16,30,16,30,0,0,0,62,0,62,32,24,0,0,0,36,36,36,32,16,0,0,0,20,20,20,84,50,0,0,0,2,2,34,18,14,0,0,0,62,34,34,34,62,0,0,0,62,34,32,16,12,0,0,0,62,32,60,32,24,0,0,0,6,32,32,16,14,0,0,0,0,21,16,8,6,0,0,0,0,4,30,20,4,0,0,0,0,0,12,8,30,0,0,0,0,28,24,16,28,0,0,0,8,4,99,16,8,0,0,0,8,16,99,4,8,0,0,0")) [ [size=16][color=#ffaabb] [ Continue Reading.. ] [/color][/size] ](/bbs/?pid=90266#p) |



A small cart I made a while back and just remembered to upload here, testing spirals and palette cycling. It's 241 tokens, and runs at 0.71CPU @ 60fps. Surprisingly cheap, considering the fastest render method was to just draw a bunch of circfills and memcpy half the screen. Could be great for a menu background!
I put a lot of tech into hiding the drawing and palette limitations of pico 8, so feel free to browse the code. Some of the exploration is also commented out in the cart, for anyone that's interested about alt method efficiency. :)
Per this discussion, I wanted to mockup a potential new idea for the Pico8 editor, to better incorporate some of the undocumented features that are being used more and more often by people. The 32 color extended palette is the major one, which I still think does not play nice with the existing editors, for three reasons:
- Repeatedly tweaking the colors in your art requires you to look up arbitrary numbers and re-type them into a line of code, which introduces a huge delay that makes doing this in Pico8 very unpleasant. Some tools have been coded by other people to help you with this in-game, but I'm talking about the editor itself. The current art tabs don't work with the alt colors at all, and color is important enough that this is kind of making them unusable in their current state. I feel like every game I've played in the past year here has tweaked at least one color from the starting 16 into an alternate, so it's a huge use case.


This is a bug in 0.2.1b, and possibly earlier, causing crashes in BBS carts like Pieces of Cake. After the new add() functionality was added, complex or token-optimized code causes a hard crash with this error:

As per @Felice, the bug is from an implied nil return from a function, which is apparently different than an explicit "return nil". add(t,nil) works, as should this (since it gets converted to a true nil through assignment):
function fn() end local f=fn() [ [size=16][color=#ffaabb] [ Continue Reading.. ] [/color][/size] ](/bbs/?pid=79749#p) |




Posted this in the discord, but decided it was worth a repost here. If you're new to binary math, this is a decent introduction of the concepts, and how you can use them in Pico-8.
Introduction to binary numbers
Numbers in pico-8 are stored in binary as 16.16 (32 bits, half above the decimal point, half below). A good way to visualize these numbers is like this:
... 128 64 32 16 8 4 2 1 . 1/2 1/4 1/8 1/16 ...
Thus, 1001.01 is how the number 9.25 actually looks in binary. To convert whole numbers, use your fingers and count powers of two until you hit the last one less than your number, then subtract that much from the number and repeat the process (so 47 is 32+8+4+2+1 == 101111).
pico-8 lets you type numbers as binary (0b101111) or hexadecimal (0x2f), but they're all the same as 47. Use whichever conversion method makes it easiest for you.
Negative numbers are a whole other thing involving two's complement (so -1 is 0b1111111111111111 in Pico-8, because then adding 1 to it makes it zero). Probably not worth mentioning if you're just starting out, but something to keep in mind.







So this is probably going to be a long, weird post, and is inspired by this post by @Mot a few weeks ago. I kind of dislike the way that Pico-8's cartridge RAM and Lua RAM exist in different worlds, and how everyone eventually uses huge strings to store data on the Lua side, when they run out of token space. It's fine to do that, and in fact I love that the option exists, but is it really a good default way for people to bypass the Pico-8 limitations? I have an idea I want to float to the community about this.
My proposal is to add two functions to the API, that convert between a Lua table and a cartridge bitstream. Using them to get your token count down would be a pre-publishing task, but it could be automated so that you only have to comment out the table to publish your game:
table = { true, x=15.5, [0]="yes", {"why?"}={"because","lua","supports","this","too"} } len = luastore(0x0,table [ [size=16][color=#ffaabb] [ Continue Reading.. ] [/color][/size] ](/bbs/?pid=77321#p) |

I've been looking into building a portable PICO-8 recently, so I'm going through display component websites for a square or nearly-square screen. This website (link) has a 60fps, 4" square touch display, so I was wondering if anyone has any experience with the company, Pimoroni.
They mention on the store page that it "works really well for Pico-8 games", which I think is pretty great, if it's made specifically for what I want to build. I thought @zep mentioned somewhere recently that he was looking for a square screen like this, too, so if the company is good then this may be a starting point for an official PICO-8 portable?
Here's a short video clip of the display in use that I found on another website. Pimoroni seems to be a reseller, but they've added code to get it working on Raspberry Pi, and I can't find anything online about "HyperPixel" that doesn't link back to them. I'm new to builds like this, so if anyone has experience with these kinds of projects, I could use some advice.