Log In  

Hey All!

PICO-8 0.2.5 is now up on lexaloffle, Humble, itch.io, and for PocketCHIP and web (Education Edition).

Edit: 0.2.5b is now up for Linux users who had trouble connecting to the BBS: https://www.lexaloffle.com/bbs/?pid=116441#p

Built-in Help

PICO-8 0.2.5 has built-in documentation on API functions and other topics. Type "HELP" at the prompt to see a list of topics in blue, and then e.g. "HELP GFX" to get more information about that thing.

While in the code editor, you can also press CTRL-U to get help on whatever is under the cursor, including operators and Lua keywords.


String Indexing

Single characters can now be grabbed from strings using a familiar str[i] style syntax that means something similar to sub(str, i, i). The index is rounded down to the closest integer, and can be negative to index from the end of the string.

S[2.6]  -- "B"
S[-2]   -- "D"
S[99]   -- ""

Strings in Lua are immutable, so this can only be used in an expression (on the right hand side of an assignment). e.g. s[2] = "z" is not allowed.

rnd(str) is now also accepted and returns a random character from string str.
// update: this might not be true from 0.2.5c: https://www.lexaloffle.com/bbs/?pid=116415#p

I've reverted sub() to pre-0.2.4 behaviour, which means that sub("abcde",2,_) returns "bcde". 0.2.4 was returning single characters when the 3rd parameter is non-numeric, which is now much better handled by str[i] style indexing. (Apologies if you were already using this behaviour!)

Editor Features

gfx_grid_lines in config.txt now takes a colour if you'd like super-visible grid lines in the sprite editor:

Map selections are now on a separate floating layer:

Pressing cursor keys in the map when nothing is selected now moves the camera, as I noticed some new users struggling to find/understand the pan tool and instead instinctively reaching for the cursor keys.

PICO1K Tools

Some changes intended to be useful for PICO1K Jam starting in September:

  • ctrl-click on the compressed code capacity to get a realtime compressed size counter in bytes. This is the same size given with the INFO command, and when storing the cartridge as a tiny (code-only) binary rom with "EXPORT -T FOO.P8.ROM"

  • "EXPORT -T @CLIP" can be used to get a hexdump of that same data written to clipboard. This is not very useful -- just a way to visualise exactly how much data is stored.

  • SAVE @URL saves 3 characters when there is no sprite data included by omitting the redundant "&g=".

Web-Exportable Audio

extcmd("audio_rec"), extcmd("audio_end") can be used in web exports. The output file shows up as a downloadable .wav file. I'm hoping this will open the door for some cute sound generation tools.

Variable Width P8SCII Fonts

PICO-8 0.2.5 custom fonts can now specify how wide each character is when printed. This previously handled by doing things like injecting extra P8SCII commands into the output string, but this was cumbersome and meant that such fonts could not be shared as plain data. There were already pixel-wise cursor positioning commands in P8SCII, so I felt it makes sense to make this simpler. Also, I needed it for Picotron 8)

To specify a 3-bit width adjustment and 1-bit y offset for character 16..255, the data at 0x5608..0x567f is used. These would otherwise be the bitmap data for characters 1..15 which are all control characters and thus never printed.

Each 4-bit nibble in 0x5608..0x567f (low nibbles first) encodes the adjustments for one character:

bits 0x7: adjust character width by 0,1,2,3,-4,-3,-2,-1
bit  0x8: when set, draw the character one pixel higher (useful for latin accents)

An addition bit must be set at 0x5605 to enable size adjustments: poke(0x5605, 1). Otherwise the data at 0x5608..0x567f is ignored.

To test this out, try loading the font snippet generation tool:


And then paste the following after the comment starting with "font attributes"

POKE(0x5605,1) -- turn on adjustments
POKE(0x5634,0x70) -- set nibble for i to 0x7

You can now observe that the big "i" in "quick" near the top of the screen only has 1px of space to the right instead of 2px.

Here's a helper function for setting the 4-bit nibble (val) for a given character (c):

  LOCAL ADDR = 0X5600 + ORD(C)\2
  LOCAL SHFT = (ORD(C) & 1) * 4

File Listings (for Dev Tool Carts)

Locally running programs can now use ls(path) to get a listing of any local directory. Entries that end with a "/" are directories. Use stat(124) to get the current path.

PATH = STAT(124) -- E.G. /FOO/
  COLOR(F[-1] == "/" AND 14 OR 6)

BBS cartridges are not able to access local file listings -- this is intended for developing tools to use locally. For example: a utility that makes it easier to browser cartridge data and copy between cartridges using reload() / cstore() which are able to read and write any local cartridge file using the 4th parameter.

Keyboard Scancode Remapper

If you're having trouble getting PICO-8 to detect some keys (especially numeric keypad keys / unusual laptop layouts), there is now a built-in tool for mapping those keypresses to something SDL2 understand. Run PICO-8 from commandline with -scancodes:

pico8 -scancodes

Or use some other tool to find out which scancodes the keys in question are producing, and then map them to something else in config.txt (look for map_scancodes)

Dynamic Libcurl Support (Linux)

The 32-bit and 64-bit linux builds now try to dynamically load libcurl by default in order to make bbs requests (e.g. download carts from splore). This will hopefully make installing on some platforms easier, including Steam Decks. When libcurl.so is not available, it drops down to the old wget behaviour (requires wget to be installed). If this works well, I'll also see about moving to the same scheme for raspi and mac builds.

More Doodlemud

Doodlemud is a toy multiplayer game designed to test the backend services that PICO-8's high score table (and future projects) will be based on. Try it here: https://www.doodlemud.com/#skatepark

This version is running on a completely new backend, custom written from scratch as a self-contained program using libwebsockets. My first attempt was built out of opensource components (nchan, redis, openresty, nginx), but because of PICO-8's unusual requirements, this was making customisation, debugging and devops more complex that it needed to be. Sometimes just reinventing the wheel with a bespoke C program is still the right approach even in the world of web services. There are currently 5 nodes running which are selected based on the user's location when making a new room:

Next Steps

It looks like PICO-8's 0.2.5 API is unlikely to change now (really, this time!), and the next task is to port the new additions to Voxatron for Voxatron 0.3.7. After that I'll continue working on the BBS functionality needed to log in from PICO-8 and submit high scores. If you're curious, you can see the current plan for the SCORESUB() function by typing HELP SCORESUB.

That's all for now -- I hope you enjoy the update and as usual let me know here or in bugs/ if you find anything spooky going on.

Full Changelog:


Added: Help topics. Use help command, or ctrl-u in code editor to get help on whatever is at the cursor.
Added: (html exports / bbs) downloadable .wav export using extcmd("audio_rec"), extcmd("audio_end")
Added: inext (to match next). -> can do: for i,v in inext,tbl do ... end
Added: floating selection layer in map editor (solves various bugs and undo / selection issues)
Added: ~ can be used as xor instead of ^^ (same as Lua 5.3/5.4)
Added: When running a program locally, ls() can now take a directory name; use stat(124) to get pwd
Added: Variable width P8SCII fonts
Added: ctrl-click on compressed capcity (bottom right) to get realtime updates of compressed size in bytes
Added: export -t @clip to get a hexdump of compressed code section copied to clipboard
Added: pico8 -scancodes and map_scancodes (config.txt) for manually mapping keys to alternative scancodes
Added: sub(str,pos,pos) can be written as str[pos].
Changed: host_frameratecontrol 1 (config.txt) now means "let PICO-8 decide"; is disabled for Mac/Win/Linux
Changed: in map editor, pan with cursor keys when nothing is selected
Changed: use scancodes for sfx navigation (US:-=
+) and spd change (US:,.<>) to avoid azerty collisions
Changed: gfx_grid_lines in config.txt is taken to be a colour for the grid lines (16 for black)
Changed: can ctrl-h in gfx editor to toggle hex mode (sprite index shown in hex; map vals shown)
Changed: '-' allowed in filenames when not first character
Changed: linux builds use libcurl.so for bbs requests, or drops down to wget on failure to dlopen
Changed: increased maximum gif len for web exports to 30 seconds
Changed: peek/poke can now read/write up to 32767 values (was 8192)
Changed: web player default gif len is 16 seconds (was 8)
Changed: sub(str, pos, nil) returns whole string (pre-0.2.4 behaviour). For single chars, can now use str[pos].
Fixed: Windows reserved filenames (lpt1, com1 etc) are accepted
Fixed: Nested coroutines unexpectedly end when interrupted by reaching end of frame
Fixed: print() colour is remapped twice when set in parameter // pal(6,7) pal(7,8) print("white",6)
Fixed: circ() breaks on 32-bit builds, with radius > 1024
Fixed: ctrl-c to copy commandline error message does not encode glyphs as unicode
Fixed: LS command not resolving relative paths
Fixed: twitter char count for chr(127) โ—‹ should be 2 (was 1) and chr(149) ห‡ should be 1 (was 2)
Fixed: colour parameter not converted from string in rect, rectfill, pset (regression from 0.2.2)
Fixed: ord("foo", 1, 0) returns "too many ord results" -- should return nothing
Fixed: save @url includes ?g= when no gfx data (is redundant)
Fixed: (web export) html pause button does not show up as btnp(6) / btn(6)
Fixed: (web export) codo_textarea triggering Mac accent character selector even when cart doesn't use clipboard
Fixed: save @url failing when encoded length is > 2000 chars long instead of > 2040 charss
Fixed: can enter an illegal note (e-5) in sfx editor

P#116373 2022-08-28 10:32 ( Edited 2022-08-29 00:38)

I know this is unrelated but, did you know can use Pico 8 Education on Android without using a external keyboard and it's fully functional but it can be a bit junkie.
If you want to know more about this, can we please talk on discord?

P#116380 2022-08-28 10:47 ( Edited 2022-08-28 10:50)

Amazing stuff as always @zep! Super cool that you keep supporting community activities too.

P#116383 2022-08-28 11:31

Wow, what an awesome update @zep! ๐Ÿฅณ
Thank you for the #pico1k jam features (+for the shout-out!) ๐Ÿ™
All very much appreciated โค

FYI @zep - release notes above (+full notes) both say ctrl-t for help, instead of ctrl+u

P#116384 2022-08-28 12:00 ( Edited 2022-08-28 13:10)

Alright, @zep ! I can see you put quite a bit of effort in this latest release including my suggest for grid in the sprite editor. Thanks so much !


Add to HELP [] to demonstrate how arrays work.

Add to stat() all elements, including mouse. If need be the coder can use the PGUP and PGDN to scroll the screen for an especially long HELP. If the cursor is off the edge of the screen, scrolled below to see a long help for instance, then typing any normal key would immediately return to that spot in the text and cursor location.

By allowing HELP to scroll up and down you could have it like QBasic or GFA where a working code example is included where desired for every single help command and function.

Please return "-" as a valid character for filenames to load and save. I could do this earlier and now it will only take "_" .

Add ability of 0x5f5c and 0x5f5d to normal keyboard.

I'm still not able to use my real joystick for the correct arrow keys HERE:


Suggest ability to remap online buttons to fix that problem.

Suggest ability to import audio from WAV or PCM to direct code that can be used in Pico-8, to make it easier for those who don't want to use external tools to do so.

import *.wav

Have onboard Palette editor to modify the 16-colors. This color-set is saved with the cart *.p8 but does not occupy visual source-code space. So a pal() in the code would reset the colors yes, but to the custom set saved for this cart.

Show a piano keyboard in addition to the usual 'screamtracker' arrangement for developing sounds and music.

Beef up the sound driver so it's possible to get all sounds the Atari 2600 can make. Bonus if you can get sound ranges equivalent to NES. So you can have glass sounds like a wind chime and deep sounds like a rumbling space engine.

Allow for the arrow keys and PgUp and PgDn on the number keypad to allow you to maneuver source-code. Currently it does not.

Have option to read raw keystrokes, so this is possible.

if _btnk(113) and _btnk(119) and _btnk(101) then
  print("you are holding down q w e keys.")


This is a long time in coming and still is not here. Functions like any normal INPUT. Type a value followed by the ENTER key. Use backspace to correct in entry.

_input("what is your name?",name)
_input("enter lucky number:",valu)

Self. This would be useful not just to build a calculator but have self-modifying code.

if flag then

If variable flag is true, then the array code will have 3-elements of 1, 2, and 3.
If not, it will be a clean array, {} .

Also this is possible:


So if you typed: 1+2*3/4 followed by ENTER you would get 2.5 - or type really anything else including variables, sin(), cos(), the works.

Would be nice to have "##" to return length of string minus P8SCII codes. So:


Results would be 12 and 10

For those curious the \# converts to chr(1)

. . .

All these things, and all these suggestions, @zep, just means I really do enjoy Pico-8 and want to see it grow in users and usability. :)

P#116388 2022-08-28 13:17 ( Edited 2022-08-28 19:33)

Probably worth noting that the change to how strings behave breaks a couple of carts which use the token optimization rnd(x) -> rnd"x". Instead of casting the inputted string to a number, it treats it as a table and returns a random element of it, making things like rnd"2" deterministically return the same number, and things like rnd"0.5" throw an error as sampling "." will break any math/comparisons done on the random number.

P#116415 2022-08-28 19:10

exciting changes! the compressed size counter is a great addition to export -T -- we're going to see some really wild stuff in the pico8 demoscene soon, I think :D

Btw, this doesn't seem to be true:

> Added: sub(str,pos,pos) can be written as str[pos] (but returns nil when pos is not a valid index)


If I'm reading the changelog correctly, I think the return values should be nil and "", so this should print false and then true. (or, this is working as intended and the changelog is wrong?)

P#116432 2022-08-28 22:08

PICO-8 0.2.5b (Linux)

If you have trouble accessing the BBS with 0.2.5 under Linux (i386/amd64), I've uploaded a quick patch (0.2.5b) that improves dropping down to wget when libcurl fails to load.

It also gives some extra debugging information in log.txt -- if you had this problem please search for libcurl in log.txt and paste it here! (log.txt can be found in the folder that opens after typing "FOLDER CONFIG" from pico-8).

P#116441 2022-08-29 00:28 ( Edited 2022-08-29 00:40)

Thanks, and of course!

I couldn't find you on discord -- maybe [email protected]?

There might be a few tweaks I could make to get it working better on such devices, but I suspect there will always be show-stopping UI issues that won't be solved until there's a touch-enabled build designed for that. I'm quite impressed it almost works though!

> Suggest ability to remap online buttons to fix that problem.

It's a shame this doesn't work automatically with the web gamepad API, but yes -- I think the ability to remap will be a necessary fallback solution. I've wishlisted it.

> Please return "-" as a valid character for filenames to load and save

You can use - in 0.2.5 filenames (just not as the first character).

> Allow for the arrow keys and PgUp and PgDn on the number keypad to allow you to maneuver source-code. Currently it does not.

You can fix this with "pico8 -scancodes" from commandline. I might be able to add hard-coded mappings for NUMPAD_PAGEUP etc. but I'm not sure that they are standardized enough to do that.

Thanks for the other suggestions, although it's unlikely there will be much change in the API & language.

oof -- this is a big problem. Looks like 0.2.5c will be arriving soon!

@pancelor, @Liquidream
Thanks, that was outdated information and is fixed in the changelog now.

P#116442 2022-08-29 00:35

Thanks, @zep. Any additions you make are usually good editions.

As you mentioned Doodlemud above, is there a chance Pico-8 will have the ability for every cart can get a 256-bytes or so of online storage ?

Would be great to have games with high-scores you can compete for with other onliners players as well as sharing other gaming data - within the 256-byte constraint.

P#116443 2022-08-29 01:02

0.2.5b solves the issue, thanks!

in the log file, I only see found libcurl.so.4

P#116451 2022-08-29 01:23

Progress like this is making me more excited for what's next!

P#116466 2022-08-29 04:14

A small addition could be a poke() to set the value returned by mget when outside map boundaries instead of always being 0. And the manual might have a typo about it, since it says mset returns 0?

Get or set map value (VAL) at X,Y

When X,Y is outside of the map range, MSET returns 0.

P#116467 2022-08-29 05:40 ( Edited 2022-08-29 05:41)

@TheRoboZ, you reminded me of something I had forgotten. Conditional checks for validity in commands.

If you did: a=(function)

The command in all cases would not be executed, however it would be tested to make sure it is valid. For instance if the function were mset(x,y) a check would be made to ensure the arguments inside the mset were within the parameters.

And yes, an additional poke for reading outside values of mset() dset() pset() and sget() to return not just 0. It could be -1, NIL, or FALSE if out of bounds.

While it's on the board, I'd also still like the ability LOAD and SAVE state, just like a true emulator. To load during execution, press SHIFT 0-9 and to save during execution press CTRL 0-9. Must receive POKE inside code to activate.

That and optional screen filters such as NTSC, Interpolation, 2xSAI, Super Eagle, and HQ - available and configurable in offline play and online play.

Maybe add additional argument to export image in code. `export ( "image" { , memory=0x00 (default) or choose another memory location such as the screen 0x60 } ).

P#116468 2022-08-29 06:17 ( Edited 2022-08-29 17:18)

My idea is more to specify a default tile value other than 0, to save tokens instead of writing the conditions yourself. In prince of Persia this would be use to always return a wall instead of an empty tile

P#116469 2022-08-29 06:21

why is the macos version still x86_64 only? i've been waiting on an arm64 build to be able to run it, and still there is nothing

P#116481 2022-08-29 08:11

@PineberryFox: Seems to me you forgot (at the very least) to say... "please" ๐Ÿคจ

P#116528 2022-08-29 21:03

@PineberryFox The x64 build works fine on my ARM Mac. Do you need to install Rosetta?

P#116530 2022-08-29 21:13

@Liquidream This isn't some free open-source project. When you pay for a commercial product that's advertised to run on your system, you expect to be able to run it without installing any emulators.

P#116531 2022-08-29 21:15 ( Edited 2022-08-29 21:15)

Rosetta is distributed by Apple to cover this exact use case and it runs transparently. If you don't want to install it for a $15 product, I don't know what to tell you.


P#116532 2022-08-29 21:27 ( Edited 2022-08-29 21:55)

the point of rosetta2 is to give developers time to transition, not to be a permanent solution for consumers. it will be discontinued soon. i'm making noise in hope that this project moves before that point.

in all honesty i have several computers, including a raspberry pi, a windows laptop, a couple linux machines, two intel macs, two arm macs, and many more. so i'm actually as unaffected as one could possibly be. but i have still stepped away from the pico-8, as i don't want its mac support to be a ticking time-bomb

this is the only paid application (nay, the only application) i have that doesn't support the arm CPUs yet. asking an application advertising mac support to truly support the only macs apple sells feels like a very different thing than asking for say, sparc support — which we WOULD have if the pico-8 were open-source

tl;dr: sorry for being a bit frustrated over a problem that will only truly be a problem in the future

P#116535 2022-08-29 22:06 ( Edited 2022-08-29 22:11)

but why bring that frustration and unfriendly tone to tiny 1-person (2-person?) company instead of megacorp that changed the definition of its platform from under developers’ feet?

anyway arm support will certainly come at some point, it’s just that pico-8 is not a fast-moving project and has other priorities.

P#116544 2022-08-29 23:27 ( Edited 2022-08-29 23:27)

@merwok: two reasons. first and foremost because it isn't a difficult change to make. in most cases it's a single flag in the build tools. especially since the system already runs on arm devices (raspberry pi) it is clear that there can't be any kind of technical limitation. second, the transition was more than two years ago and there have been multiple releases of the pico-8 since that time.

P#116548 2022-08-29 23:53

There is an arm build in the works, but it won't be out for a while still. Unfortunately it's not as simple as setting a flag in the build tools. I'd be more than happy to issue a refund if you feel you didn't get what you paid for -- you can get me at hey[at]lexaloffle[dot]com.

Great, thanks for the log entry. It looks like the libcurl requests are now working in 0.2.5b anyway -- the change is that it now trys to dlopen not only libcurl.so (which I take to mean "preferred version", but seems is not always present), but also explicitly libcurl.so.* (which is what it picked up in your case).

> As you mentioned Doodlemud above, is there a chance Pico-8 will have the ability for every cart can get a 256-bytes or so of online storage ?

You'll be able to abuse the score's 'extra' field for that! Type HELP SCORESUB to see how it will work.

The rate limit on table access allows for 2 different score tables to be used at the same time, so you could use one for 'storage' (by bumping the top score of whoever is submitting the change by 0x0.0001), and then the other table for per-player data.

Display options (pixel shaders etc.) are wishlisted, but would be a post-1.0 kind of thing.

I did look at state saving a while ago, but it is very difficult to get a complete, reconstructable Lua state that is secure and shareable cross-platform, so I put it in the not-worth-it basket. Voxatron has partial Lua state saving, but even then I had to scale it back further for 0.3.7 because it is so bug-prone.

> to set the value returned by mget when outside map boundaries

I really like this idea, and am already doing something similar in Picotron for exactly the kind of case you describe. The main issue is that there is no space left in 0x5f00~0x5f7f! I'll have a look see.

P#116554 2022-08-30 01:18 ( Edited 2022-08-30 01:19)

@zep again i'm sorry for being frustrated. thank you for the update! i certainly won't be requesting a refund. just glad to hear something is in the works, and i eagerly — but patiently — await its release
thank you!

P#116555 2022-08-30 02:01

Hi @zep ! Wow I'm feeling quite proud now you addressed my very concerns.

OK how about this ? Save-State + Load-State could be nothing more than just saving/loading the extended memory ? 0x8000-0xFFFF.

You could then map variables directly to that memory. Then just load/save that chunk of memory 0x8000-0xFFFF.

Let me think ...

for i=1,52 do

mapstate() the 2nd argument is optional. The system would be smart enough to know where to store all the variables and arrays and subsequent mapstate() commands following this one would neatly store then in the memory to follow after the first.

So in this case that would store deck{}, 4-bytes per element, 52-in all yielding a total of 208-bytes to store at 0x8000 as that is the beginning of load/save state storage.

The advantage is you just use the array as normal but when you LOAD STATE that automatically updates deck{} (and anything else) as an earlier command was given to map it starting at 0x8000.

SAVE STATE would quickly take all variables that were mapped earlier with mapstate(), stuff them into memory at 0x8000 and then save it off.

Now these COULD be individual files yet I don't know how fast that would be to be both loading and saving external files for load/save states. cartname.st0 through cartname.st9

One distinct advantage of course is the coder would never need to worry about making a 'save game' feature as that would truly be built in by using the load/save state ability of mapstate()

As for the high-score idea. Wow ! That's a lot more complex than I imagined. I guess you need all that security for - as you say - someone would mess with the values and make themselves high-score even if they weren't.

P#116557 2022-08-30 02:52 ( Edited 2022-08-30 03:35)

Yeah, Firefox really dropped the ball on the gamepad API, refusing to include support for Xbox360 and bluetooth controllers for some dumb reason or other... It's to the point where I actually wrote a plugin-side JavaScript injection just to remap the gamepad myself.

P#116567 2022-08-30 08:11

builtin help is something ive always wanted for a long time, really excited about that, because I can finally code pico8 completely offline if I need to without switching between the help txt and the program, which isn't a problem, but it's not as easy as just typing "help tline"

P#116582 2022-08-30 15:21

@zep, I'd really love to help write up the remaining help files you've done. I've been trying to maintain the unofficial API for a while now (here's the link if you didn't know) and I've got a mostly complete knowledge of the fantasy console.

This is one of my favorite features that you've added in a long time, and has made a lot of the work I did on my book obsolete - that said, I'd love to fill in the remaining chunks of functions with good definitions.

Please get in touch here or on twitter if you'd like!

Some of the features I'd like to work on:

  • Keyboard Shortcuts
  • Metatables
  • Memory
  • And some other things, if you'd be interested in the offer.

Just let me know! I've been making stuff like this for a while, and would love to help. Just posted a cart that I'd made a tweet about previously too.

Cart #apiview-0 | 2022-08-30 | Code ▽ | Embed ▽ | No License

P#116602 2022-08-30 21:22

@zep Thanks for the update! I had a cart right at the token + compressed size limits using some custom sprite and P8SCII-for-spacing fonts, so I'm already trying out the variable-width font and swapping in [] for string indexing.

One minor documentation bug: HELP SUB describes the previous 0.2.4-only behavior of sub(str,i,_)

P#116638 2022-08-31 01:42 ( Edited 2022-08-31 01:43)

Oh, right. It would probably help to actually post my code, in case anyone happens to have any use for it.
It's a Greasemonkey/Tampermonkey/Violentmonkey script intended for Firefox, specifically to add mappings for my gamepad to the gamepad API, specifically for use with web games like Pico-8. ^^;;

You should still make a full button remapping GUI at some point, but might you be able to pick apart this tiny little hunk of JavaScript might act as a small bandaid to slightly boost compatibility as a baseline, after the gamepad API says it isn't an Xinput-compatible device...?

// ==UserScript==
//  @Name        Gamepad Remapper 
//  @Namespace   Violentmonkey Scripts
// @match       *://*/*
// @grant       none
// @version     1.0
// @author      cat
// @description 4/17/2022, 3:42:33 PM
// ==/UserScript==

const GAMEPAD_MAP = new Map();

function GamepadButton() {
  this.pressed = false;
  this.touched = false;
  this.value = 0.0;

  this.setValue = function(value) {
    value = Math.min(Math.max(value, 0.0), 1.0);
    this.pressed = (value > 0.5);
    this.touched = (value > 0.0);
    this.value = value;

function Gamepad(from) {
  this.id = from.id;
  this.index = from.index;
  this.connected = true;
  this.timestamp = from.timestamp;
  this.mapping = "standard";
  this.axes = new Array(4);
  this.buttons = new Array(17);
  this.axisMapping = new Array(4);
  this.buttonMapping = new Array(17);

  this.hapticActuators = from.hapticActuators;
  this.vibrationActuator = from.vibrationActuator;

  for (let i = 0; i < 4; i++) {
    this.axisMapping[i] = i;
  for (let i = 0; i < 17; i++) {
    this.buttons[i] = new GamepadButton();
    this.buttonMapping[i] = {type: "button", index: i};

  this.update = function() {
    this.timestamp = from.timestamp;
    for (let i = 0; i < 4; i++) {
      let index = this.axisMapping[i];
      if (from.axes.length > index) {
        this.axes[i] = from.axes[index];
    for (let i = 0; i < 17; i++) {
      let mapping = this.buttonMapping[i];
      if (mapping.type == "button" && from.buttons.length > mapping.index) {
        this.buttons[i] = from.buttons[mapping.index];
      } else if (mapping.type == "axis" && from.axes.length > mapping.index) {
        let value = (from.axes[mapping.index] - mapping.min) / (mapping.max - mapping.min);

// Xbox 360 Controller
GAMEPAD_MAP.set("045e-028e", function() {
  this.axisMapping[2] = 3;
  this.axisMapping[3] = 4;
  this.buttonMapping[6] = {type: "axis", index: 2, min: -1.0, max: 1.0};
  this.buttonMapping[7] = {type: "axis", index: 5, min: -1.0, max: 1.0};
  this.buttonMapping[8] = {type: "button", index: 6};
  this.buttonMapping[9] = {type: "button", index: 7};
  this.buttonMapping[10] = {type: "button", index: 9};
  this.buttonMapping[11] = {type: "button", index: 10};
  this.buttonMapping[12] = {type: "axis", index: 7, min: 0.0, max: -1.0};
  this.buttonMapping[13] = {type: "axis", index: 7, min: 0.0, max: 1.0};
  this.buttonMapping[14] = {type: "axis", index: 6, min: 0.0, max: -1.0};
  this.buttonMapping[15] = {type: "axis", index: 6, min: 0.0, max: 1.0};
  this.buttonMapping[16] = {type: "button", index: 8};

// Xbox Wireless Controller
GAMEPAD_MAP.set("045e-02e0", function() {
  this.axisMapping[2] = 3;
  this.axisMapping[3] = 4;
  this.buttonMapping[6] = {type: "axis", index: 2, min: -1.0, max: 1.0};
  this.buttonMapping[7] = {type: "axis", index: 5, min: -1.0, max: 1.0};
  this.buttonMapping[8] = {type: "button", index: 6};
  this.buttonMapping[9] = {type: "button", index: 7};
  this.buttonMapping[10] = {type: "button", index: 8};
  this.buttonMapping[11] = {type: "button", index: 9};
  this.buttonMapping[12] = {type: "axis", index: 7, min: 0.0, max: -1.0};
  this.buttonMapping[13] = {type: "axis", index: 7, min: 0.0, max: 1.0};
  this.buttonMapping[14] = {type: "axis", index: 6, min: 0.0, max: -1.0};
  this.buttonMapping[15] = {type: "axis", index: 6, min: 0.0, max: 1.0};

let tamper_gamepads = new Array();

function fetch_gamepad(pad) {
  if (tamper_gamepads[pad.index]) {
    return tamper_gamepads[pad.index];

  let mapping = GAMEPAD_MAP.get(pad.id.slice(0, 9));
  if (!mapping) {
    console.log("No mapping for gamepad: " + pad.id + "\nSorry! :(");
    return null;
  let new_pad = new Gamepad(pad);
  tamper_gamepads[pad.index] = new_pad;
  return new_pad;

const superGetGamepads = navigator.getGamepads;
navigator.getGamepads = function() {
  let gamepads = superGetGamepads.call(this);
  for (const pad of gamepads) {
    if (pad && pad.connected && pad.mapping != "standard") {
      let my_pad = fetch_gamepad(pad);
      if (!my_pad) {
      gamepads[pad.index] = my_pad;
  return gamepads;

P#116639 2022-08-31 03:09 ( Edited 2022-08-31 03:09)

Was dir() intended to be removed? I realise ls(stat(124)) is equivalent now.

P#116653 2022-08-31 10:18

I found a typo

"The range of possile values is: -32768.0 to 32767.99999"
It's spelled "possile" instead of "possible"

P#116730 2022-09-01 13:30

> Added: ~ can be used as xor instead of ^^ (same as Lua 5.3/5.4)

nice! feature request: can you add ~= as well? (x^^=1 works but x~=1 does not: syntax error near '~=')

edit: hmm, I hope this wouldn't cause ambiguous parsing... e.g. if x~=3 then versus x~=3 (those aren't ambiguous but maybe a more complicated or future example would be?)

P#117185 2022-09-11 00:16 ( Edited 2022-09-12 22:50)

about the "Variable Width P8SCII Fonts":

Is there a command to calculate the width of a text in pixels?
The fast "#string*8" will not work any more.

P#117257 2022-09-12 08:11

@GPI print can do that: (from the manual)

> PRINT returns the right-most x position that occurred while printing. This can be used to find out the width of some text by printing it off-screen:
> W = PRINT("HOGE", 0, -20) -- returns 16

P#117264 2022-09-12 10:17

hmmm... i would prefer a "PRINTSIZE" function... drawing outside the screen is not really elegant.

P#117265 2022-09-12 10:22

that’s a matter of taste ¯\_(ใƒ„)_/¯

pico-8 is all about giving us the minimal required tools to find techniques to do advanced things.
going from #string*5 to custom strlen function that checks normal/wide glyph to printing off-screen is a normal progression. the thing is possible, and people who find the technique distateful can hide it in a function.

P#117271 2022-09-12 13:46 ( Edited 2022-12-21 15:43)

I have a small suggestion for the help feature.

When I first used it I did help(map) and got the message no help topic under cursor. It took me a minute to realize that the syntax of the command was supposed to be help map, like a command rather than a function.

Maybe the error message could be something like invalid usage. try help <topic> instead of help(<topic>). Or if you don't mind encouraging the function-like syntax, help could accept optional parentheses.

P#119162 2022-10-15 22:23

Can't seem to be able to move in Doodlemud. I can spawn my character but pressing left / right does nothing to my lil dude. Tried on two different PCs. Am I missing something?

P#122803 2022-12-21 13:08

Hi Zep, how did you do the top page Pico8 0.2.5 animation, the trees,water, fireflies movement are stunning. voxatron? thx

P#123850 2023-01-07 00:26

[Please log in to post a comment]