Pico-8 Manual
Pico-8 Manual
=================
PICO-8 User Manual
===================================================================================
=================
PICO-8 v0.2.5g
https://www.pico-8.com
(c) Copyright 2014-2023 Lexaloffle Games LLP
Author: Joseph White // hey@lexaloffle.com
SDL2 http://www.libsdl.org
Lua 5.2 http://www.lua.org // see license.txt
ws281x by jgarff // see license.txt
GIFLIB http://giflib.sourceforge.net/
WiringPi http://wiringpi.com/
libb64 by Chris Venter
miniz by Rich Geldreich
z8lua by Sam Hocevar https://github.com/samhocevar/z8lua
Latest version of this manual (as html, txt) and other resources:
https://www.lexaloffle.com/pico-8.php?page=resources
:: Welcome to PICO-8!
PICO-8 is a fantasy console for making, sharing and playing tiny games and
other computer
programs. When you turn it on, the machine greets you with a shell for typing
in Lua programs
and provides simple built-in tools for creating sprites, maps and sound.
The harsh limitations of PICO-8 are carefully chosen to be fun to work with,
encourage small
but expressive designs and hopefully to give PICO-8 cartridges their own
particular look and
feel.
:: Specifications
===================================================================================
=================
Getting Started
===================================================================================
=================
-----------------------------------------------------------------------------------
-----------------
Keys
-----------------------------------------------------------------------------------
-----------------
To change the default keys use the KEYCONFIG utility from inside PICO-8:
> KEYCONFIG
-----------------------------------------------------------------------------------
-----------------
Hello World
-----------------------------------------------------------------------------------
-----------------
After PICO-8 boots, try typing some of these commands followed by enter:
(Note: PICO-8 only displays upper-case characters -- just type normally without
capslock!)
You can build up an interactive program by using commands like this in the code
editing mode
along with two special callback functions @_UPDATE and @_DRAW. For example, the
following
program allows you to move a circle around with the cursor keys. Press Esc to
switch to the
code editor and type or copy & paste the following code:
X = 64 Y = 64
FUNCTION _UPDATE()
IF (BTN(0)) THEN X=X-1 END
IF (BTN(1)) THEN X=X+1 END
IF (BTN(2)) THEN Y=Y-1 END
IF (BTN(3)) THEN Y=Y+1 END
END
FUNCTION _DRAW()
CLS(5)
CIRCFILL(X,Y,7,14)
END
Now press Esc to return to the console and type RUN (or press CTRL-R) to see it
in action.
Please refer to the demo cartridges for more complex programs (type
INSTALL_DEMOS).
If you want to store your program for later, use the SAVE command:
-----------------------------------------------------------------------------------
-----------------
Example Cartridges
-----------------------------------------------------------------------------------
-----------------
These cartridges are included with PICO-8 and can be installed by typing:
> INSTALL_DEMOS
> CD DEMOS
> LS
Press escape to stop the program, and once more to enter editing mode.
> INSTALL_GAMES
-----------------------------------------------------------------------------------
-----------------
File System
-----------------------------------------------------------------------------------
-----------------
LOAD BLAH load a cart from the current directory SAVE BLAH save a cart to the
current
directory
If you want to move files around, duplicate them or delete them, use the FOLDER
command and do
it in the host operating system.
Windows: C:/Users/Yourname/AppData/Roaming/pico-8/carts
OSX: /Users/Yourname/Library/Application Support/pico-8/carts
Linux: ~/.lexaloffle/pico-8/carts
Tip: The drive directory can be mapped to a cloud drive (provided by Dropbox,
Google Drive or
similar) in order to create a single disk shared between PICO-8 machines spread
across
different host machines.
-----------------------------------------------------------------------------------
-----------------
Loading and Saving
-----------------------------------------------------------------------------------
-----------------
When using LOAD and SAVE, the .P8 extension can be omitted and is added
automatically.
Cartridge files can also be dragged and dropped into PICO-8's window to load
them.
Using .p8.png filename extension will write the cartridge in a special image
format that looks
like a cartridge. Using .p8.rom" writes in a raw 32k binary format.
Once a cartridge has been loaded or saved, it can also be quick-saved with
CTRL-S
To generate a label image saved with the cart, run the program first and
press CTRL-7 to
grab whatever is on the screen. The first two lines of the program starting
with '--' are
also drawn to the cart's label.
When saving in .png or .rom format, the compressed size of the code must be
less than 15360
bytes so that the total data is <= 32k.
To find out the current size of your code, use the INFO command. The
compressed size limit
is not enforced for saving in .p8 format.
-----------------------------------------------------------------------------------
-----------------
Using an External Text Editor
-----------------------------------------------------------------------------------
-----------------
It is possible to edit .p8 files directly with a separate text editor. Using
CTRL-R to run a
cartridge will automatically re-load the file if:
1. There are no unsaved changes in the PICO-8 editors, AND<br> 2. The file
differs in
content from the last loaded version.
If there are changes to both the cart on disk and in the editor, a notification
is displayed:
Alternatively, .lua text files can be modified in a separate editor and then
included into the
cartridge's code each time it is run using @{#INCLUDE} (in the desired code
location):
#INCLUDE YOURFILE.LUA
-----------------------------------------------------------------------------------
-----------------
Backups
-----------------------------------------------------------------------------------
-----------------
To open the backups folder in the host operating system's file browser, use:
It is then possible to drag and drop files into the PICO-8 window to load them.
From 0.2.4c, periodic backups are also saved every 20 minutes when not idle in
the editor,
which means the backups folder will grow by about 1MB every 5 hours. This can
be disabled or
adjusted in config.txt
-----------------------------------------------------------------------------------
-----------------
Configuration
-----------------------------------------------------------------------------------
-----------------
The location of the config.txt file depends on the host operating system:
Windows: C:/Users/Yourname/AppData/Roaming/pico-8/config.txt
OSX: /Users/Yourname/Library/Application Support/pico-8/config.txt
Linux: ~/.lexaloffle/pico-8/config.txt
Use the -home switch (below) to use a different path to store config.txt
and other data.
Some settings can be changed while running PICO-8 by typing CONFIG SETTING
VALUE. (type
CONFIG by itself for a list)
:: Commandline parameters
:: Controller Setup
PICO-8 uses the SDL2 controller configuration scheme. It will detect common
controllers on
startup and also looks for custom mappings in sdl_controllers.txt in the same
directory as
config.txt. sdl_controllers.txt has one mapping per line.
To generate a custom mapping string for your controller, use either the
controllermap program
that comes with SDL2, or try http://www.generalarcade.com/gamepadtool/
To set up which keyboard keys trigger joystick buttons presses, use KEYCONFIG.
-----------------------------------------------------------------------------------
-----------------
Screenshots and GIFS
-----------------------------------------------------------------------------------
-----------------
You can save a video at any time (it is always recording); CTRL-8 simply resets
the video
starting point. To record more than 8 seconds, use the CONFIG command (maximum:
120)
CONFIG GIF_LEN 60
If you would like the recording to reset every time (to create a non-
overlapping sequence),
use:
CONFIG GIF_RESET_MODE 1
The gif format can not match 30fps exactly, so PICO-8 instead uses the closest
match: 33.3fps.
-----------------------------------------------------------------------------------
-----------------
Sharing Cartridges
-----------------------------------------------------------------------------------
-----------------
1. Share the .p8 or .p8.png file directly with other PICO-8 users
Type FOLDER to open the current folder in your host operating system.
http://www.lexaloffle.com/pico-8.php?page=submit
-----------------------------------------------------------------------------------
-----------------
SPLORE
-----------------------------------------------------------------------------------
-----------------
SPLORE is a built-in utility for browsing and organising both local and bbs
(online)
cartridges. Type SPLORE [enter] to launch it, or launch PICO-8 with -splore.
When viewing a list of BBS carts, use the top list item to re-download a list
of cartridges. If
you are offline, the last downloaded list is displayed, and it is still
possible to play any
cartridges you have downloaded.
If you have installed PICO-8 on a machine with no internet access, you can also
use
INSTALL_GAMES to add a small selection of pre-installed BBS carts to /games
===================================================================================
=================
Editing Tools
===================================================================================
=================
Press ESC to toggle between console and editor.<br> Click editing mode tabs at
top right to
switch or press ALT+LEFT/RIGHT.
-----------------------------------------------------------------------------------
-----------------
Code Editor
-----------------------------------------------------------------------------------
-----------------
To enter special characters that represent buttons (and other glyphs), use
SHIFT-L,R,U,D,O,X
There are 3 additional font entry modes that can be toggled:
:: Code Tabs
Click the [+] button at the top to add a new tab. Navigate tabs by left-
clicking, or with
CTRL-TAB, SHIFT-CTRL-TAB. To remove the last tab, delete any contents and
then moving off
it (CTRL-A, DEL, CTRL-TAB)
:: Code Limits
The number of code tokens is shown at the bottom right. One program can
have a maximum of
8192 tokens. Each token is a word (e.g. variable name) or operator. Pairs
of brackets, and
strings each count as 1 token. commas, periods, LOCALs, semi-colons, ENDs,
and comments are
not counted.
-----------------------------------------------------------------------------------
-----------------
Sprite Editor
-----------------------------------------------------------------------------------
-----------------
The sprite editor is designed to be used both for sprite-wise editing and for
freeform
pixel-level editing. The sprite navigator at the bottom of the screen provides
an 8x8
sprite-wise view into the sprite sheet, but it is possible to use freeform
tools (pan, select)
when dealing with larger or oddly sized areas.
:: Draw Tool
Click and drag on the sprite to plot pixels, or use RMB to select the
colour under the
cursor.
All operations apply only to the visible area, or the section if there is
one.
:: Stamp Tool
Click to stamp whatever is in the copy buffer. Hold CTRL to treat colour 0
(black) as
transparent.
:: Fill Tool
Fill with the current colour. This applies only to the current selection,
or the visible
area if there is no selection.
:: Shape Tools
Click the tool button to cycle through: oval, rectangle, line options.
:: Extra keys
CTRL-Z: Undo
CTRL-C/X: Copy/Cut selected area or selected sprites
CTRL-V: Paste to current sprite location
Q/A,W/Z: Switch to previous/next sprite
1,2: Switch to previous/next colour
TAB: Toggle fullscreen view
Mousewheel or < and > to zoom (centered in fullscreen)
CTRL-H to toggle hex view (shows sprite index in hexadecimal)
CTRL-G to toggle black grid lines when zoomed in
:: Sprite Flags
The 8 coloured circles are sprite flags for the current sprite. These have
no particular
meaning, but can be accessed using the @FGET() / @FSET() functions. They
are indexed from 0
starting from the left.
To load a png file of any size into the sprite sheet, first select the
sprite that should
be the top-left corner destination, and then either type "IMPORT
IMAGE_FILE.PNG" or drag
and drop the image file into the PICO-8 window. In both cases, the image is
colour-fitted
to the current display palette.
-----------------------------------------------------------------------------------
-----------------
Map Editor
-----------------------------------------------------------------------------------
-----------------
The PICO-8 map is a 128x32 (or 128x64 using shared space) block of 8-bit
values. Each value is
shown in the editor as a reference to a sprite (0..255), but you can of course
use the data to
represent whatever you like.
WARNING: The second half of the sprite sheet (banks 2 and 3), and the bottom
half of the map
share the same cartridge space. It's up to you how you use the data, but be
aware that drawing
on the second half of the sprite sheet could clobber data on the map and vice
versa.
The tools are similar to the ones used in sprite editing mode. Select a sprite
and click and
drag to paint values into the map.
To draw multiple sprites, select from sprite navigator with shift+drag To copy
a block of
values, use the selection tool and then stamp tool to paste To pan around the
map, use the pan
tool or hold space Q,W to switch to previous/next sprite Mousewheel or < and >
to zoom
(centered in fullscreen) CTRL-H to toggle hex view (shows tile values and
sprite index in
hexadecimal)
Moving sprites in the sprite sheet without breaking reference to them in the
map is a little
tricky, but possible:
// Note: this operation modifies the undo history for both the map and
sprite editors, but
// PICO-8 will try to keep them in sync where possible. Otherwise, changes
caused by moving
// map sprites can be reverted by also manually undoing in the sprite
editor.
-----------------------------------------------------------------------------------
-----------------
SFX Editor
-----------------------------------------------------------------------------------
-----------------
There are 64 SFX ("sound effects") in a cartridge, used for both sound and
music.
A play speed (SPD) : the number of 'ticks' to play each note for.
// This means that 1 is fastest, 3 is 3x as slow, etc.
Loop start and end : this is the note index to loop back and to
// Looping is turned off when the start index >= end index
When only the first of the 2 numbers is used (and the second one is 0), it
is taken to mean
the number of notes to be played. This is normally not needed for sound
effects (you can
just leave the remaining notes empty), but is useful for controlling music
playback.
There are 2 modes for editing/viewing a SFX: Pitch mode (more suitable for
sound effects) and
tracker mode (more suitable for music). The mode can be changed using the top-
left buttons, or
toggled with TAB.
:: Pitch Mode
Click and drag on the pitch area to set the frequency for each note, using
the currently
selected instrument (indicated by colour).
:: Tracker Mode
Click and then shift-click to select a range that can be copied (CTRL-C)
and pasted
(CTRL-V). Note that only the selected attributes are copied. Double-click
to select all
attributes of a single note.
Navigation:
PAGEUP/DOWN or CTRL-UP/DOWN to skip up or down 4 notes
HOME/END to jump to the first or last note
CTRL-LEFT/RIGHT to jump across columns
:: Effects
0 none
1 slide // Slide to the next note and volume
2 vibrato // Rapidly vary the pitch within one quarter-tone
3 drop // Rapidly drop the frequency to very low values
4 fade in // Ramp the volume up from 0
5 fade out // Ramp the volume down to 0
6 arpeggio fast // Iterate over groups of 4 notes at speed of 4
7 arpeggio slow // Iterate over groups of 4 notes at speed of 8
:: Filters
Each SFX has 5 filter switches that can be accessed while in tracker mode:
When BUZZ is used with instrument 6, and NOIZ is off, pure brown noise is
generated.
-----------------------------------------------------------------------------------
-----------------
Music Editor
-----------------------------------------------------------------------------------
-----------------
:: Flow control
Playback flow can be controlled using the 3 buttons at the top right.
When a pattern has finished playing, the next pattern is played unless:
When a pattern has SFXes with different speeds, the pattern finishes playing
when the left-most
non-looping channel has finished playing. This can be used to set up double-
time drum beats or
unusual polyrhythms.
For time signatures like 3/4 where less than 32 rows should be played before
jumping to the
next pattern, the length of a SFX can be set by adjusting only the first loop
position and
leaving the second one as zero. This will show up in the sfx editor as "LEN"
(for "Length")
instead of "LOOP".
To select a range of patterns: click once on the first pattern in the pattern
navigator, then
shift-click on the last pattern. Selected patterns can be copied and pasted
with CTRL-C and
CTRL-V. When pasting into another cartridge, the SFX that each pattern points
to will also be
pasted (possibly with a different index) if it does not already exist.
:: SFX Instruments
When an SFX instrument note is played, it essentially triggers that SFX, but
alters the note's
attributes:
SFX instruments are only retriggered when the pitch changes, or the previous
note has zero
volume. This is useful for instruments that change more slowly over time. For
example: a bell
that gradually fades out. To invert this behaviour, effect 3 (normally 'drop')
can be used when
triggering the note. All other effect values have their usual meaning
when triggering SFX
instruments.
===================================================================================
=================
Exporters / Importers
===================================================================================
=================
The EXPORT command can be used to generate png, wav files and stand-alone html and
native binary
cartridge applications. The output format is inferred from the filename extension
(e.g. .png).
You are free to distribute and use exported cartridges and data as you please,
provided that you
have permission from the cartridge author and contributors.
> IMPORT BLAH.PNG -- EXPECTS 128X128 PNG AND COLOUR-FITS TO THE PICO-8
PALETTE
> EXPORT BLAH.PNG -- USE THE "FOLDER" COMMAND TO LOCATE THE EXPORTED PNG
When importing, -x and -y switches can be used to specify the target location
in pixels: -s can
be used to shrink the image (3 means scale from 384x384 -> 128x128)
Use the -l switch with IMPORT and EXPORT to instead read and write from the
cartridge's label:
To export music from the current pattern (when editor mode is MUSIC), or the
current SFX:
Map images are 1024x512 (128x32 8x8 sprites). Lua images are sized to fit, but
each line is
fixed (and cropped) at 192 pixels wide.
EXPORT can also be used to perform cartridge file format conversions from
commandline. For
example, from a Linux shell:
-----------------------------------------------------------------------------------
-----------------
Web Applications (.html)
-----------------------------------------------------------------------------------
-----------------
When exported as .wasm, the page needs to be served by a webserver, rather than
just opening it
directly from the local file system in a browser. For most purposes, the
default .js export is
fine, but .wasm is slightly smaller and faster.
-----------------------------------------------------------------------------------
-----------------
Binary Applications (.bin)
-----------------------------------------------------------------------------------
-----------------
For example, to use a 2x2 sprite starting at index 32 in the sprite sheet,
using colour 12 as
transparent:
To include an extra file in the output folders and archives, use the -E switch:
Windows file systems do not support the file metadata needed to create a Linux
or Mac
executable. PICO-8 works around this by exporting zip files in a way that
preserves the file
attributes. It is therefore recommended that you distribute the outputted zip
files as-is to
ensure users on other operating systems can run them. Otherwise, a Linux user
who then
downloads the binaries may need to "chmod +x mygame" the file to run it, and
Mac user would
need to "chmod +x mygame.app/Contents/MacOS/mygame"
-----------------------------------------------------------------------------------
-----------------
Uploading to itch.io
-----------------------------------------------------------------------------------
-----------------
During runtime, the extra carts can be accessed as if they were local files:
Exported cartridges are unable to load and run BBS cartridges e.g. via
LOAD("#FOO")
-----------------------------------------------------------------------------------
-----------------
Running EXPORT from the host operating system
-----------------------------------------------------------------------------------
-----------------
Use the -export switch when launching PICO-8 to run the exporter in headless
mode. File paths
are relative to the current directory rather than the PICO-8 file system.
===================================================================================
=================
Lua Syntax Primer
===================================================================================
=================
PICO-8 programs are written using Lua syntax, but do not use the standard Lua
library. The
following is a brief summary of essential Lua syntax.
For more details, or to find out about proper Lua, see www.lua.org.
:: Comments
NUM = 12/100
S = "THIS IS A STRING"
B = FALSE
T = {1,2,3}
Numbers in PICO-8 are all 16:16 fixed point. They range from -32768.0 to
32767.99999
?0x11 -- 17
?0x11.4000 -- 17.25
Numbers written in decimal are rounded to the closest fixed point value. To see
the 32-bit
hexadecimal representation, use PRINT(TOSTR(VAL,TRUE)):
?TOSTR(-32768,TRUE) -- 0x8000.0000
?TOSTR(32767.99999,TRUE) -- 0X7FFF.FFFF
:: Conditionals
IF NOT B THEN
PRINT("B IS FALSE")
ELSE
PRINT("B IS NOT FALSE")
END
-- with ELSEIF
IF X == 0 THEN
PRINT("X IS 0")
ELSEIF X < 0 THEN
PRINT("X IS NEGATIVE")
ELSE
PRINT("X IS POSITIVE")
END
:: Loops
FOR X=1,5 DO
PRINT(X)
END
-- PRINTS 1,2,3,4,5
X = 1
WHILE(X <= 5) DO
PRINT(X)
X = X + 1
END
FOR X=1,10,3 DO PRINT(X) END -- 1,4,7,10
Variables declared as LOCAL are scoped to their containing block of code (for
example, inside a
FUNCTION, a FOR loop, or IF THEN END statement).
Y=0
FUNCTION PLUSONE(X)
LOCAL Y = X+1
RETURN Y
END
PRINT(PLUSONE(2)) -- 3
PRINT(Y) -- 0
:: Tables
In Lua, tables are a collection of key-value pairs where the key and value
types can both be
mixed. They can be used as arrays by indexing them with integers.
> A = {11,12,13,14}
> PRINT(A[2]) -- 12
But if you prefer 0-based arrays, just write something the zeroth slot:
> A = {[0]=10,11,12,13,14}
Tables with 1-based integer indexes are special though. The length of such an
array can be
found with the # operator, and PICO-8 uses such arrays to implement ADD, DEL,
DELI, ALL and
FOREACH functions.
> PRINT(#A) -- 4
> ADD(A, 15)
> PRINT(#A) -- 5
PLAYER = {}
PLAYER.X = 2 -- is equivalent to PLAYER["X"]
PLAYER.Y = 3
:: PICO-8 Shorthand
PICO-8 also allows several non-standard, shorter ways to write common patterns.
1. IF THEN END statements, and WHILE THEN END can be written on a single line
with:
Is equivalent to:
2. Assignment operators
A += 2 -- EQUIVALENT TO: A = A + 2
// note that the LHS appears twice, so for TBL[FN()]+=1, FN() will be called
twice.
3. != operator
Not shorthand, but pico-8 also accepts != instead of ~= for "not equal to"
PRINT(1 != 2) -- TRUE
PRINT("FOO" == "FOO") -- TRUE (STRING ARE INTERNED)
===================================================================================
=================
PICO-8 Program Structure
===================================================================================
=================
When a PICO-8 programs runs, all of the code from tabs is concatenated (from
left to right) and
executed. It is possible to provide your own main loop manually, but typically
PICO-8 programs
use 3 special functions that, if defined by the author, are called during
program execution:
FUNCTION _INIT()
-- ALWAYS START ON WHITE
COL = 7
END
FUNCTION _UPDATE()
-- PRESS X FOR A RANDOM COLOUR
IF (BTNP(5)) COL = 8 + RND(8)
END
FUNCTION _DRAW()
CLS(1)
CIRCFILL(64,64,32,COL)
END
_DRAW() is normally called at 30fps, but if it can not complete in time, PICO-8
will attempt to
run at 15fps and call _UPDATE() twice per visible frame to compensate.
_UPDATE60()
- both _UPDATE60() and _DRAW() are called at 60fps<br> - half the PICO-8 CPU is
available per
frame before dropping down to 30fps
Note that not all host machines are capable of running at 60fps. Older
machines, and / or web
versions might also request PICO-8 to run at 30 fps (or 15 fps), even when the
PICO-8 CPU is
not over capacity. In this case, multiple _UPDATE60 calls are made for every
_DRAW call in the
same way.
:: #INCLUDE
Source code can be injected into a program at cartridge boot (but not during
runtime), using
"#INCLUDE FILENAME", where FILENAME is either a plaintext file (containing Lua
code), a tab
from another cartridge, or all tabs from another cartridge:
#INCLUDE SOMECODE.LUA
#INCLUDE ONETAB.P8:1
#INCLUDE ALLTABS.P8
When the cartridge is run, the contents of each included file is treated as if
it had been
pasted into the editor in place of that line.
:: Quirks of PICO-8
- The bottom half of the sprite sheet and bottom half of the map occupy the
same memory. //
Best use only one or the other if you're unsure how this works.
- PICO-8 numbers have limited accuracy and range; the minimum step between
numbers is
approximately 0.00002 (0x0.0001), with a range of -32768 (-0x8000) to
approximately 32767.99999
(0x7fff.ffff)<br> // If you add 1 to a counter each frame, it will overflow
after around 18
minutes!
- Lua arrays are 1-based by default, not 0-based. FOREACH starts at TBL[1], not
TBL[0].
- @COS() and @SIN() take 0..1 instead of 0..PI*2, and @SIN() is inverted.
- @SGN(0) returns 1.
:: CPU
Although PICO-8 does not have a clearly defined CPU, there is a virtual CPU
speed of 8MHz,
where each lua vm instruction costs around 2 cycles. Built-in operations like
drawing sprites
also have a CPU cost. This means that a PICO-8 cartridge made on a host machine
with a powerful
CPU can still be guaranteed to run (reasonably) well on much slower machines,
and to not drain
too much battery on phones / when running on the web.
To view the CPU load while a cartridge is running, press CTRL-P to toggle a CPU
meter, or print
out @STAT(1) at the end of each frame.
===================================================================================
=================
API Reference
===================================================================================
=================
PICO-8 is built on the Lua programming language, but does not include the Lua
standard library.
Instead, a small api is offered in keeping with PICO-8's minimal design and
limited screen
space. For an example program that uses most of the api functions, see
/DEMOS/API.P8
FUNCTION_NAME(PARAMETER, [OPTIONAL_PARAMETER])
Note that PICO-8 does not have upper or lower case characters -- if you are
editing a .p8 or
.lua file directly, function names should all be in lower case.
-----------------------------------------------------------------------------------
-----------------
System
-----------------------------------------------------------------------------------
-----------------
System functions called from commandline can omit the usual brackets and string
quotes. For
example, instead of LOAD("BLAH.P8"), it is possible to write:
>LOAD BLAH.P8
SAVE(FILENAME)
Filenames that start with '#' are taken to be a BBS cart id, that is
immediately downloaded
and run:
FOLDER
LS([DIRECTORY])
List .p8 and .p8.png files in given directory (folder), relative to the
current directory.
Items that are directories end in a slash (e.g. "foo/").
When called from a running cartridge, LS can only be used locally and
returns a table of
the results. When called from a BBS cart, LS returns nil.
RUN([PARAM_STR])
STOP([MESSAGE])
RESUME
Use a single "." from the commandline to advance a single frame. This
enters frame-by-frame
mode, that can be read with stat(110). While frame-by-frame mode is active,
entering an
empty command (by pressing enter) advances one frames.
ASSERT(CONDITION, [MESSAGE])
REBOOT
RESET()
Reset the values in RAM from 0x5f00..0x5f7f to their default values. This
includes the
palette, camera position, clipping and fill pattern. If you get lost at the
command prompt
because the draw state makes viewing text impossible, try typing RESET! It
can also be
called from a running program.
INFO()
Print out some information about the cartridge: Code size, tokens,
compressed size
Also displayed:
FLIP()
Flip the back buffer to screen and wait for next frame. This call is not
needed when there
is a @_DRAW() or @_UPDATE() callback defined, as the flip is performed
automatically. But
when using a custom main loop, a call to FLIP is normally needed:
::_::
CLS()
FOR I=1,100 DO
A=I/50 - T()
X=64+COS(A)*I
Y=64+SIN(A)*I
CIRCFILL(X,Y,1,8+(I/4)%8)
END
FLIP()GOTO _
If your program does not call FLIP before a frame is up, and a @_DRAW()
callback is not in
progress, the current contents of the back buffer are copied to screen.
Use stat(4) to read the clipboard, but the contents of the clipboard are
only available
after pressing CTRL-V during runtime (for security).
TIME()
T()
Returns the number of seconds elapsed since the cartridge was run.
This is not the real-world time, but is calculated by counting the number
of times
STAT(X)
Audio values 16..26 are the legacy version of audio state queries 46..56. They
only report on
the current state of the audio mixer, which changes only ~20 times a second
(depending on the
host sound driver and other factors). 46..56 instead stores a history of mixer
state at each
tick to give a higher resolution estimate of the currently audible state.
"video" and "screen": P1: an integer scaling factor that overrides the
system setting.
P2: when > 0, save to the current folder instead of to desktop
:: Recording GIFs
The user data folder can be opened with EXTCMD("FOLDER") and defaults
to the same path
as the cartridge, or {pico-8 appdata}/appdata/appname for exported
binaries.
Due to the nature of the gif format, all gifs are recorded at 33.3fps,
and frames
produced by PICO-8 are skipped or duplicated in the gif to match
roughly what the user
is seeing. To record exactly one frame each time @FLIP() is called,
regardless of the
runtime framerate or how long it took to generate the frame, use:
EXTCMD("REC_FRAMES")
The default filename for gifs (and screenshots, audio) is foo_%d, where
foo is the name
of the cartridge, and %d is a number starting at 0 and automatically
incremented until
a file of that name does not exist. Use EXTCMD("SET_FILENAME","FOO") to
override that
default. If the custom filename includes "%d", then the auto-
incrementing number
behaviour is used, but otherwise files are written even if there is an
existing file
with the same name.
-----------------------------------------------------------------------------------
-----------------
Graphics
-----------------------------------------------------------------------------------
-----------------
PICO-8 has a fixed capacity of 128 8x8 sprites, plus another 128 that overlap
with the bottom
half of the map data ("shared data"). These 256 sprites are collectively called
the sprite
sheet, and can be thought of as a 128x128 pixel image.
All of PICO-8's drawing operations are subject to the current draw state. The
draw state
includes a camera position (for adding an offset to all coordinates), palette
mapping (for
recolouring sprites), clipping rectangle, a drawing colour, and a fill pattern.
The draw state is reset each time a program is run, or by calling @RESET().
Colour indexes:
CLIP(X, Y, W, H, [CLIP_PREVIOUS])
CLIP() to reset.
When CLIP_PREVIOUS is true, clip the new clipping region by the old one.
PSET(X, Y, [COL])
FOR Y=0,127 DO
FOR X=0,127 DO
PSET(X, Y, X*Y/8)
END
END
PGET(X, Y)
Returns the colour of a pixel on the screen at (X, Y).
WHILE (TRUE) DO
X, Y = RND(128), RND(128)
DX, DY = RND(4)-2, RND(4)-2
PSET(X, Y, PGET(DX+X, DY+Y))
END
When X and Y are out of bounds, PGET returns 0. A custom return value can
be specified
with:
POKE(0x5f36, 0x10)
POKE(0x5f5B, NEWVAL)
SGET(X, Y)
SSET(X, Y, [COL])
When X and Y are out of bounds, SGET returns 0. A custom value can be
specified with:
POKE(0x5f36, 0x10)
POKE(0x5f59, NEWVAL)
FGET(N, [F])
The initial state of flags 0..7 are settable in the sprite editor, so can
be used to create
custom sprite attributes. It is also possible to draw only a subset of map
tiles by
providing a mask in @MAP().
PRINT(STR, X, Y, [COL])
PRINT(STR, [COL])
Print a string STR and optionally set the draw colour to COL.
Shortcut: written on a single line, ? can be used to call print without
brackets:
?"HI"
Additionally, when X, Y are not specified, printing text below 122 causes
the console to
scroll. This can be disabled during runtime with POKE(0x5f36,0x40).
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:
See @{Appendix A} (P8SCII) for information about control codes and custom
fonts.
CURSOR(X, Y, [COL])
COLOR([COL])
CLS([COL])
CAMERA([X, Y])
CAMERA() to reset
CIRC(X, Y, R, [COL])
CIRCFILL(X, Y, R, [COL])
Draw an oval that is symmetrical in x and y (an ellipse), with the given
bounding
rectangle.
If (X1, Y1) are not given, the end of the last drawn line is used.
LINE() with no parameters means that the next call to LINE(X1, Y1) will
only set the end
points without drawing.
CLS()
LINE()
FOR I=0,6 DO
LINE(64+COS(I/6)*20, 64+SIN(I/6)*20, 8+I)
END
Draw a rectangle or filled rectangle with corners at (X0, Y0), (X1, Y1).
0: Draw Palette
The draw palette re-maps colours when they are drawn. For example, an
orange flower
sprite can be drawn as a red flower by setting the 9th palette value to
8:
Changing the draw palette does not affect anything that was already
drawn to the
screen.
1: Display Palette
The display palette re-maps the whole screen when it is displayed at
the end of a
frame. For example, if you boot PICO-8 and then type PAL(6,14,1), you
can see all of
the gray (colour 6) text immediate change to pink (colour 14) even
though it has
already been drawn. This is useful for screen-wide effects such as
fading in/out.
2: Secondary Palette
PAL(TBL, [P])
When the first parameter of pal is a table, colours are assigned for each
entry. For
example, to re-map colour 12 and 14 to red:
PAL({[12]=9, [14]=8})
PAL({1,1,5,5,5,6,7,13,6,7,7,6,13,6,7,1}, 1)
PALT(C, [T])
PALT(8, TRUE) -- RED PIXELS NOT DRAWN IN SUBSEQUENT SPRITE/TLINE DRAW CALLS
PALT(0B1100000000000000)
SSPR(SX, SY, SW, SH, DX, DY, [DW, DH], [FLIP_X], [FLIP_Y]]
Stretch an rectangle of the sprite sheet (sx, sy, sw, sh) to a destination
rectangle on the
screen (sx, sy, dw, dh). In both cases, the x and y values are coordinates
(in pixels) of
the rectangle's top left corner, with a width of w, h.
FILLP(P)
The PICO-8 fill pattern is a 4x4 2-colour tiled pattern observed by:
@CIRC() @CIRCFILL()
@RECT() @RECTFILL() @OVAL() @OVALFILL() @PSET() @LINE()
.-----------------------.
|32768|16384| 8192| 4096|
|-----|-----|-----|-----|
| 2048| 1024| 512 | 256 |
|-----|-----|-----|-----|
| 128 | 64 | 32 | 16 |
|-----|-----|-----|-----|
| 8 | 4 | 2 | 1 |
'-----------------------'
The default fill pattern is 0, which means a single solid colour is drawn.
To specify a second colour for the pattern, use the high bits of any colour
parameter:
FILLP(0b0011010101101000)
CIRCFILL(64,64,20, 0x4E) -- brown and pink
Additional settings are given in bits 0b0.111:
0b0.100 Transparency
When set, the fill pattern is applied to sprites (spr, sspr, map,
tline), using a
colour mapping provided by the secondary palette.
Each pixel value in the sprite (after applying the draw palette as
usual) is taken
to be an index into the secondary palette. Each entry in the
secondary palette
contains the two colours used to render the fill pattern. For
example, to draw a
white and red (7 and 8) checkerboard pattern for only blue pixels
(colour 12) in a
sprite:
When set, the secondary palette mapping is also applied by all draw
functions that
respect fill patterns (circfill, line etc). This can be useful when
used in
conjunction with sprite drawing functions, so that the colour index
of each sprite
pixel means the same thing as the colour index supplied to the
drawing functions.
FILLP(0b0011001111001100.001)
PAL(12, 0x87, 2)
CIRCFILL(64,64,20,12) -- red and white checkerboard
circle
PAL(3,12)
CIRCFILL(64,64,20,3)
The fill pattern can also be set by setting bits in any colour parameter
(for example, the
parameter to @COLOR(), or the last parameter to @LINE(), @RECT() etc.
When using the colour parameter to set the fill pattern, the following
bits are used:
-----------------------------------------------------------------------------------
-----------------
Table Functions
-----------------------------------------------------------------------------------
-----------------
With the exception of PAIRS(), the following functions and the # operator apply
only to tables
that are indexed starting from 1 and do not have NIL entries. All other forms
of tables can be
considered as hash maps or sets, rather than arrays that have a length.
TBL[#TBL + 1] = VAL
DEL(TBL, VAL)
Delete the first instance of value VAL in table TBL. The remaining entries
are shifted left
one index to avoid holes.
Note that VAL is the value of the item to be deleted, not the index into
the table. (To
remove an item at a particular index, use DELI instead). DEL returns the
deleted item, or
returns no value when nothing was deleted.
A={1,10,2,11,3,12}
FOR ITEM IN ALL(A) DO
IF (ITEM < 10) THEN DEL(A, ITEM) END
END
FOREACH(A, PRINT) -- 10,11,12
PRINT(A[3]) -- 12
DELI(TBL, [I])
Like @DEL(), but remove the item from table TBL at index I When I is not
given, the last
element of the table is removed and returned.
COUNT(TBL, [VAL])
Returns the length of table t (same as #TBL) When VAL is given, returns the
number of
instances of VAL in that table.
ALL(TBL)
Used in FOR loops to iterate over all items in a table (that have a 1-based
integer index),
in the order they were added.
T = {11,12,13}
ADD(T,14)
ADD(T,"HI")
FOR V IN ALL(T) DO PRINT(V) END -- 11 12 13 14 HI
PRINT(#T) -- 5
FOREACH(TBL, FUNC)
For each item in table TBL, call function FUNC with the item as a single
parameter.
PAIRS(TBL)
Used in FOR loops to iterate over table TBL, providing both the key and
value for each
item. Unlike @ALL(), PAIRS() iterates over every item regardless of
indexing scheme. Order
is not guaranteed.
T = {["HELLO"]=3, [10]="BLAH"}
T.BLUE = 5;
FOR K,V IN PAIRS(T) DO
PRINT("K: "..K.." V:"..V)
END
Output:
K: 10 v:BLAH
K: HELLO v:3
K: BLUE v:5
-----------------------------------------------------------------------------------
-----------------
Input
-----------------------------------------------------------------------------------
-----------------
BTN([B], [PL])
B: 0..5: left right up down button_o button_x<br> PL: player index 0..7
BTNP(B, [PL])
BTNP is short for "Button Pressed"; Instead of being true when a button is
held down, BTNP
returns true when a button is down AND it was not down the last frame. It
also repeats
after 15 frames, returning true every 4 frames after that (at 30fps --
double that at
60fps). This can be used for things like menu navigation or grid-wise
player movement.
The state that BTNP reads is reset at the start of each call to @_UPDATE or
@_UPDATE60, so
it is preferable to use BTNP from inside one of those functions.
Custom delays (in frames @ 30fps) can be set by poking the following memory
addresses:
POKE(0X5F5C, DELAY) -- SET THE INITIAL DELAY BEFORE REPEATING. 255 MEANS
NEVER REPEAT.
POKE(0X5F5D, DELAY) -- SET THE REPEATING DELAY.
In both cases, 0 can be used for the default behaviour (delays 15 and 4)
-----------------------------------------------------------------------------------
-----------------
Audio
-----------------------------------------------------------------------------------
-----------------
Play sfx N (0..63) on CHANNEL (0..3) from note OFFSET (0..31 in notes) for
LENGTH notes.
N can be a command for the given CHANNEL (or all channels when CHANNEL <
0):
MUSIC(0, 1000)
MUSIC(0, NIL, 7) -- 1 | 2 | 4
Reserved channels can still be used to play sound effects on, but only when
that channel
index is explicitly requested by @SFX().
-----------------------------------------------------------------------------------
-----------------
Map
-----------------------------------------------------------------------------------
-----------------
The PICO-8 map is a 128x32 grid of 8-bit values, or 128x64 when using the
shared memory. When
using the map editor, the meaning of each value is taken to be an index into
the sprite sheet
(0..255). However, it can instead be used as a general block of data.
MGET(X, Y)
MSET(X, Y, VAL)
When X and Y are out of bounds, MGET returns 0, or a custom return value
that can be
specified with:
POKE(0x5f36, 0x10)
POKE(0x5f5a, NEWVAL)
Draw section of map (starting from TILE_X, TILE_Y) at screen position SX,
SY (pixels).
To draw a 4x2 blocks of tiles starting from 0,0 in the map, to the screen
at 20,20:
TILE_W and TILE_H default to the entire map (including shared space when
applicable).
MAP() is often used in conjunction with CAMERA(). To draw the map so that a
player object
(at PL.X in PL.Y in pixels) is centered:
LAYERS is a bitfield. When given, only sprites with matching sprite flags
are drawn. For
example, when LAYERS is 0x5, only sprites with flag 0 and 2 are drawn.
Draw a textured line from (X0,Y0) to (X1,Y1), sampling colour values from
the map. When
LAYERS is specified, only sprites with matching flags are drawn (similar to
MAP())
MX, MY are map coordinates to sample from, given in tiles. Colour values
are sampled from
the 8x8 sprite present at each map tile. For example:
2.0, 1.0 means the top left corner of the sprite at position 2,1 on
the map
2.5, 1.5 means pixel (4,4) of the same sprite
MDX, MDY are deltas added to mx, my after each pixel is drawn. (Defaults to
0.125, 0)
The map coordinates (MX, MY) are masked by values calculated by subtracting
0x0.0001 from
the values at address 0x5F38 and 0x5F39. In simpler terms, this means you
can loop a
section of the map by poking the width and height you want to loop within,
as long as they
are powers of 2 (2,4,8,16..)
POKE(0x5F38, 8)
POKE(0x5F39, 4)
TLINE(...)
The default values (0,0) gives a masks of 0xff.ffff, which means that the
samples will loop
every 256 tiles.
POKE(0x5F3A, OFFSET_X)
POKE(0x5F3B, OFFSET_Y)
Sprite 0 is taken to mean "empty" and not drawn. To disable this behaviour,
use:
POKE(0x5F36, 0x8)
The number of bits used for the fractional part of each pixel is stored in
a special
register that can be adjusted by calling TLINE once with a single argument:
-----------------------------------------------------------------------------------
-----------------
Memory
-----------------------------------------------------------------------------------
-----------------
1. Base RAM (64k): see layout below. Access with PEEK() POKE() MEMCPY()
MEMSET()
2. Cart ROM (32k): same layout as base ram until 0x4300
3. Lua RAM (2MB): compiled program + variables
Technical note: While using the editor, the data being modified is in cart
rom, but api
functions such as @SPR() and @SFX() only operate on base ram. PICO-8
automatically copies
cart rom to base ram (i.e. calls @RELOAD()) in 3 cases:<br> 1. When a
cartridge is
loaded<br> 2. When a cartridge is run<br> 3. When exiting any of the editor
modes // can
turn off with: poke(0x5f37,1)<br>
0X0 GFX
0X1000 GFX2/MAP2 (SHARED)
0X2000 MAP
0X3000 GFX FLAGS
0X3100 SONG
0X3200 SFX
0X4300 USER DATA
0X5600 CUSTOM FONT (IF ONE IS DEFINED)
0X5E00 PERSISTENT CART DATA (256 BYTES)
0X5F00 DRAW STATE
0X5F40 HARDWARE STATE
0X5F80 GPIO PINS (128 BYTES)
0X6000 SCREEN (8K)
0x8000 USER DATA
User data has no particular meaning and can be used for anything via
@MEMCPY(), @PEEK() &
@POKE(). Persistent cart data is mapped to 0x5e00..0x5eff but only stored
if @CARTDATA()
has been called. Colour format (gfx/screen) is 2 pixels per byte: low bits
encode the left
pixel of each pair. Map format is one byte per tile, where each byte
normally encodes a
sprite index.
The GFX, MAP and SCREEN memory areas can be reassigned by setting values at
the following
addresses:
0X5F54 GFX: can be 0x00 (default) or 0x60 (use the screen memory as the
spritesheet)
0X5F55 SCREEN: can be 0x60 (default) or 0x00 (use the spritesheet as screen
memory)
0X5F56 MAP: can be 0x20 (default) or 0x10..0x2f, or 0x80 and above.
0X5F57 MAP SIZE: map width. 0 means 256. Defaults to 128.
Addresses can be expressed in 256 byte increments. So 0x20 means 0x2000,
0x21 means 0x2100
etc. Map addresses 0x30..0x3f are taken to mean 0x10..0x1f (shared memory
area). Map data
can only be contained inside the memory regions 0x1000..0x2fff,
0x8000..0xffff, and the
map height is determined to be the largest possible size that fits in the
given region.
GFX and SCREEN memory mapping happens at a low level which also affects
memory access
functions (peek, poke, memcpy). The 8k memory blocks starting at 0x0 and
0x6000 can be
thought of as pointers to a separate video ram, and settings the values at
0X5F54 and
0X5F56 alters those pointers.
PEEK(ADDR, [N])
A, B = PEEK(0x6000, 2)
Write one or more bytes to an address in base ram. If more than one
parameter is provided,
they are written sequentially (max: 8192).
PEEK2(ADDR)
POKE2(ADDR, VAL)
PEEK4(ADDR)
POKE4(ADDR, VAL)
16-bit and 32-bit versions of PEEK and POKE. Read and write one number
(VAL) in
little-endian format:
16 bit: 0xffff.0000
32 bit: 0xffff.ffff
Alternatively, the following operators can be used to peek (but not poke), and
are slightly
faster:
@ADDR -- PEEK(ADDR)
%ADDR -- PEEK2(ADDR)
$ADDR -- PEEK4(ADDR)
MEMCPY(DEST_ADDR, SOURCE_ADDR, LEN)
Copy LEN bytes of base ram from source to dest. Sections can be overlapping
The code section ( >= 0x4300) is protected and can not be read.
The code section ( >= 0x4300) is protected and can not be written to.
Write the 8-bit value VAL into memory starting at DEST_ADDR, for LEN bytes.
-----------------------------------------------------------------------------------
-----------------
Math
-----------------------------------------------------------------------------------
-----------------
MAX(X, Y)
MIN(X, Y)
MID(X, Y, Z)
> ?MID(7,5,10) -- 7
FLR(X)
> ?FLR ( 4.1) --> 4
> ?FLR (-2.3) --> -3
CEIL(X)
COS(X)
SIN(X)
Returns the cosine or sine of x, where 1.0 means a full turn. For example,
to animate a
dial that turns once every second:
FUNCTION _DRAW()
CLS()
CIRC(64, 64, 20, 7)
X = 64 + COS(T()) * 20
Y = 64 + SIN(T()) * 20
LINE(64, 64, X, Y)
END
ATAN2(DX, DY)
X=20 Y=30
FUNCTION _UPDATE()
IF (BTN(0)) X-=2
IF (BTN(1)) X+=2
IF (BTN(2)) Y-=2
IF (BTN(3)) Y+=2
END
FUNCTION _DRAW()
CLS()
CIRCFILL(X,Y,2,14)
CIRCFILL(64,64,2,7)
A=ATAN2(X-64, Y-64)
PRINT("ANGLE: "..A)
LINE(64,64,
64+COS(A)*10,
64+SIN(A)*10,7)
END
SQRT(X)
ABS(X)
RND(X)
SRAND(X)
Sets the random number seed. The seed is automatically randomized on cart
startup.
FUNCTION _DRAW()
CLS()
SRAND(33)
FOR I=1,100 DO
PSET(RND(128),RND(128),7)
END
END
:: Bitwise Operations
Say you have two numbers (written here in binary using the "0b" prefix):
X = 0b1010
Y = 0b0110
A bitwise AND will give you bits set when the corresponding bits in X /and/
Y are both set
Operator versions are also available: & | ^^ ~ << >> >>> <<> >><
:: Integer Division
-----------------------------------------------------------------------------------
-----------------
Custom Menu Items
-----------------------------------------------------------------------------------
-----------------
Index should be 1..5 and determines the order each menu item is displayed
label should be a
string up to 16 characters long callback is a function called when the item
is selected by
the users
If the callback returns true, the pause menu remains open. The callback
takes a single
parameter that is a bitfield of L,R,X button presses
MENUITEM(1, "FOO",
FUNCTION(B) IF (B&1 > 0) THEN PRINTH("LEFT WAS PRESSED") END END
)
-----------------------------------------------------------------------------------
-----------------
Strings and Type Conversion
-----------------------------------------------------------------------------------
-----------------
Strings in Lua are written either in single or double quotes or with matching
[[ ]] brackets:
S = "THE QUICK"
S = 'BROWN FOX';
S = [[
JUMPS OVER
MULTIPLE LINES
]]
>PRINT(#S)
Strings can be joined using the .. operator. Joining numbers converts them to
strings.
>PRINT(2+"3") --> 5
TOSTR(VAL, [FORMAT_FLAGS])
FORMAT_FLAGS is a bitfield:
TOSTR(17) -- "17"
TOSTR(17,0x1) -- "0x0011.0000"
TOSTR(17,0x3) -- "0x00110000"
TOSTR(17,0x2) -- "1114112"
TONUM(VAL, [FORMAT_FLAGS])
Converts VAL to a number.
TONUM("17.5") -- 17.5
TONUM(17.5) -- 17.5
TONUM("HOGE") -- NO RETURN VALUE
FORMAT_FLAGS is a bitfield:
When
CHR(64) -- "@"
CHR(104,101,108,108,111) -- "hello"
Convert one or more characters from string STR to their ordinal (0..255)
character codes.
Use the INDEX parameter to specify which character in the string to use.
When INDEX is out
of range or str is not a string, ORD returns nil.
When NUM_RESULTS is given, ORD returns multiple values starting from INDEX.
ORD("@") -- 64
ORD("123",2) -- 50 (THE SECOND CHARACTER: "2")
ORD("123",2,3) -- 50,51,52
Grab a substring from string str, from pos0 up to and including pos1. When
POS1 is not
specified, the remainder of the string is returned. When POS1 is specified,
but not a
number, a single character at POS0 is returned.
SPLIT("1,2,3") -- {1,2,3}
SPLIT("ONE:TWO:3",":",FALSE) -- {"ONE","TWO","3"}
SPLIT("1,,2,") -- {1,"",2,""}
TYPE(VAL)
> PRINT(TYPE(3))
NUMBER
> PRINT(TYPE("3"))
STRING
-----------------------------------------------------------------------------------
-----------------
Cartridge Data
-----------------------------------------------------------------------------------
-----------------
If more than 256 bytes is needed, it is also possible to write directly to the
cartridge using
@CSTORE(). The disadvantage is that the data is tied to that particular version
of the
cartridge. e.g. if a game is updated, players will lose their savegames. Also,
some space in
the data sections of the cartridge need to be left available to use as storage.
Opens a permanent data storage slot indexed by ID that can be used to store
and retrieve up
to 256 bytes (64 numbers) worth of data using @DSET() and @DGET().
CARTDATA("ZEP_DARK_FOREST")
DSET(0, SCORE)
CARTDATA can be called once per cartridge execution, and so only a single
data slot can be
used.
DGET(INDEX)
DSET(INDEX, VALUE)
-----------------------------------------------------------------------------------
-----------------
GPIO
-----------------------------------------------------------------------------------
-----------------
GPIO stands for "General Purpose Input Output", and allows machines to
communicate with each
other. PICO-8 maps bytes in the range 0x5f80..0x5fff to gpio pins that can be
POKE()ed (to output a value -- e.g. to make an LED light up) or @PEEK()ed (e.g.
to read
CHIP and Raspberry Pi values are all digital: 0 (LOW) and 255 (HIGH)
T = 0
FUNCTION _DRAW()
CLS(5)
FOR I=0,7 DO
VAL = 0
IF (T % 2 < 1) VAL = 255
POKE(0X5F80 + I, VAL)
CIRCFILL(20+I*12,64,4,VAL/11)
END
T += 0.1
END
:: Serial
For more precise timing, the @SERIAL() command can be used. GPIO writes are
buffered and
dispatched at the end of each frame, allowing clock cycling at higher
and/or more regular
speeds than is possible by manually bit-banging using @POKE() calls.
CHANNEL:
0x000..0x0fe corresponds to gpio pin numbers; send 0x00 for LOW
or 0xFF for HIGH
0x0ff delay; length is taken to mean "duration" in
microseconds (excl. overhead)
0x400..0x401 ws281x LED string (experimental)
For example, to send a byte one bit at a time to a typical APA102 LED
string:
Additional channels are available for bytestreams to and from the host
operating system.
These are intended to be most useful for UNIX-like environments while
developing
toolchains, and are not available while running a BBS or exported cart [1].
Maximum
transfer rate in all cases is 64k/sec (blocks cpu).
[1] Channels 0x800 and 0x802 are available from exported binaries, but with
a maximum file
size of 256k, or 128x128 for images.
:: HTML
-----------------------------------------------------------------------------------
-----------------
Mouse and Keyboard Input
-----------------------------------------------------------------------------------
-----------------
Mouse and keyboard input can be achieved by enabling devkit input mode:
0x1 Enable
0x2 Mouse buttons trigger btn(4)..btn(6)
0x4 Pointer lock (use stat 38..39 to read movements)
Note that not every PICO-8 will have a keyboard or mouse attached to it, so
when posting carts
to the Lexaloffle BBS, it is encouraged to make keyboard and/or mouse control
optional and off
by default, if possible. When devkit input mode is enabled, a message is
displayed to BBS users
warning them that the program may be expecting input beyond the standard 6-
button controllers.
-----------------------------------------------------------------------------------
-----------------
Additional Lua Features
-----------------------------------------------------------------------------------
-----------------
PICO-8 also exposes 2 features of Lua for advanced users: Metatables and
Coroutines.
:: Metatables
VEC2D={
__ADD=FUNCTION(A,B)
RETURN {X=(A.X+B.X), Y=(A.Y+B.Y)}
END
}
SETMETATABLE(TBL, M)
RAWGET(TBL, KEY)
RAWEQUAL(TBL1,TBL2
RAWLEN(TBL)
:: Function Arguments
To accept a variable number of arguments, use them to define a table and/or use
Lua's select()
function. select(index, ...) returns all of the arguments after index.
FUNCTION FOO(...)
LOCAL ARGS={...} -- BECOMES A TABLE OF ARGUMENTS
FOREACH(ARGS, PRINT)
?SELECT("#",...) -- ALTERNATIVE WAY TO COUNT THE NUMBER OF ARGUMENTS
FOO2(SELECT(3,...)) -- PASS ARGUMENTS FROM 3 ONWARDS TO FOO2()
END
:: Coroutines
YIELD() any number of times, and then resumed again at the same points.
FUNCTION HEY()
PRINT("DOING SOMETHING")
YIELD()
PRINT("DOING THE NEXT THING")
YIELD()
PRINT("FINISHED")
END
C = COCREATE(HEY)
FOR I=1,3 DO CORESUME(C) END
COCREATE(F)
Run or continue the coroutine c. Parameters p0, p1.. are passed to the
coroutine's
function.
Returns true if the coroutine completes without any errors Returns false,
error_message if
there is an error.
** Runtime errors that occur inside coroutines do not cause the program to
stop running. It
is a good idea to wrap CORESUME() inside an @ASSERT(). If the assert fails,
it will print
the error message generated by coresume.
ASSERT(CORESUME(C))
COSTATUS(C)
YIELD
===================================================================================
=================
Appendix
===================================================================================
=================
-----------------------------------------------------------------------------------
-----------------
Appendix A: P8SCII Control Codes
-----------------------------------------------------------------------------------
-----------------
When printed with @PRINT(), some characters have a special meaning that can be
used to alter
things like the cursor position and text rendering style. Control characters in
PICO-8 are
CHR(0)..CHR(15) and can be written as an escaped sequence ("\n" for newline
etc.)
Some of the control codes below take parameters which are written using a
scheme that is a
superset of hexadecimal format. That is, '0'..'f' also mean 0..15. But
characters after 'f' are
also accepted: 'g' means 16 and so on. Such parameters are written below as P0,
P1.
For example, to print with a blue background ("\#c") and dark gray foreground
("\f5"):
The only side-effects on the draw state are changes in cursor position and
foreground color;
all other attributes are reset each time @PRINT() is called.
:: Control Codes
:: Special Commands
These commands all start with "\^" and take up to 2 parameters (P0, P1) For
example, to
clear screen to dark blue: print("\^c1")
// prefix these with "-" to disable: e.g. ?"\^i on \^-i off "
For example, to write 4 bytes to video memory halfway down the screen:
>?"\^@70000004xxxxhello"
:: One-off characters
Character data can be specified and printed in-line using \^. followed
by 8 bytes of
raw binary data, or \^: followed by 8 2-digit hexadecimal values. The
data format is
the same as custom fonts; each byte specifies a row of 1-bit pixel
values, with the
low bit on the left.
To print a cat:
> ?"\^:447cb67c3e7f0106"
:: Audio
1. (optional) SFX attributes must appear once at the start as they apply to
the whole
sound:
2. Note data:
PRINT "\AS4X5C1EGC2EGC3EGC4"
:: Decoration Characters
The control character \v can be used to decorate the last printed character
with another
character at a given offset, without needing to otherwise manage the cursor
position. After
the decorating character is printed, the previous cursor position is
restored.
The offset has x packed into the lowest 2 bits, and starts (-2,-8) in
reading order. So 3
means (+1, -8), 4 means (-2, -7) and so on.
For example, to write "café!", using a comma to draw the acute accent:
PRINT"\NCAFE\VB,!"
In this case P0 is 'b', which is read as the number 11. So the comma is
drawn at:
x = (11%4)-2 = 1
y = (11\4)-8 = -6
:: Custom Font
The first 128 bytes (characters 0~15 are never drawn) describe attributes
of the font:
0x5600 character width in pixels (can be more than 8, but only 8 pixels
are drawn)
0x5601 character width for character 128 and above
0x5602 character height in pixels
0x5603 draw offset x
0x5604 draw offset y
0x5605 flags: 0x1 apply_size_adjustments 0x2: apply tabs relative to
cursor home
0x5606 tab width in pixels (used only when alt font is drawn)
0x5607 unused
The remaining 120 bytes are used to adjust the width and vertical offset of
characters
16..255. Each nibble (low nibbles first) describes the adjustments for one
characters:
:: Default Attributes
0x5f58 // bitfield
0x1 when set to 0x1, bits 1..7 are observed:
0x2 padding
0x4 wide
0x8 tall
0x10 solid background
0x20 invert
0x40 stripey (when wide or tall)
0x80 use custom font
-----------------------------------------------------------------------------------
---------
PICO-8 VERSION HISTORY
-----------------------------------------------------------------------------------
---------
v0.2.5g
v0.2.5f
v0.2.5e
v0.2.5d
v0.2.5c
Added: set out of range return value for sget, mget, pget: poke
(0x5f36,0x10) and set at 0x5f59..0x5f5b
Changed: rnd(str) no longer returns a random char from strings (breaks
existing carts using e.g. rnd"5")
Changed: html exports default to 75% volume instead of 100%
Fixed: extcmd("audio_rec"), extcmd("audio_end") not working in html
exports
Fixed: drag & drop (via serial 0x802) not responding to dropped file in
html exports
Fixed: (again) can enter an illegal note (e-5) in sfx editor
Fixed: dir() missing during runtime (alias for ls)
Fixed: some mistakes in help text (outdated sub() description, yield
misspelling)
v0.2.5b (Linux)
Fixed: failing to drop down to wget when libcurl can not be loaded
v0.2.5
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_framerate_control 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
v0.2.4c
v0.2.4b
Added: l in sprite sheet navigator to set loop start / end points (then
q,w or a,z to navigate)
Added: ctrl-b in gfx editor to paste 2x2 original size ("paste big")
Added: DEL / backspace to clear selected region in gfx / map editors,
and ctrl-x to cut
Added: aggressive_backups option in config.txt (off by default)
Added: transform_screen in config.txt to globally rotate / flip the
video output
Added: stat(57) (boolean) true when music triggered by music() is
playing or about to start
Changed: memset() faster than using peek4/poke4; now 2 cycles per byte
at 8MHz (was 4)
Changed: "running at < 30fps" warning on boot now only for raspi
builds, and w/ higher tolerance
Changed: Controller inputs are accepted even when PICO-8 is not the
foreground application
Changed: Map can be located at 0x1000 .. 0x2f00 using poke(0x5f56,
0x11) .. poke(0x5f56,0x2f)
Changed: Dotty text mode is now "\^=" ("Stripey") instead of "\^." //
#gunayawuho #klax #impossible
Fixed: (not confirmed) crash causing 0-byte .p8 when audio mixer is
called during save / run
Fixed: preprocessor not counting comments as white space; should allow:
".. end--[[]]if .."
Fixed: pal(nil) behaving the same way as pal(0); should be same as
pal() // broke #rtype-2
Fixed: note entry in sfx tracker is silent after running cartridge
until pressing space to playback
Fixed: sub("abc", 4, 4) returns "c" (regression in 0.2.4)
Fixed: SPLORE cart update prompt does not appear when server replies
too quickly (race condition)
Fixed: SPLORE cart update prompt only checks version once per session
(can't refresh until it shows up)
Fixed: EXPORT command does not flatten includes when exporting
to .p8.png / .p8.rom format
Fixed: EXPORT command discards source code changes since last run or
load
Fixed: printing a one-off glyph using "\^." terminates the whole string
when last byte is a zero
Fixed: Crash when loading a cart with fewer tabs, then creating a new
tab and pasting.
Fixed: . command runs at 30fps even for a 60fps cart (-> _update60 is
called twice, _draw once)
Fixed: Custom menu items become broken after suspending a cart,
entering a lua command, and then resuming
Fixed: memset() with a non-zero value in shared memory region
(0x1000..0x1fff) causes garbage corresponding mget() values
Fixed: web player/exports: ctrl-r causes erroneous "external changes
reloaded" message and code corruption
v0.2.4
v0.2.3
v0.2.2c
Fixed: ?"\ac0" starts from d#0 instead of c0 (again -- 0.2.2b was still
broken)
Fixed: splore local directory navigation fails when using a relative
home path set with -home
Fixed: export .lua.png only shows the first 2730 lines
v0.2.2b
v0.2.2
Added: SFX filters: noiz (white noise for inst 6), buzz, detune
(flange/overtone), reverb, dampen (lpf)
Added: SFX length (leave the second loop value at 0 to use). Can be >=
32.
Added: P8SCII control characters when using print() -- can adjust
colour and cursor position etc.
Added: User-defined font at 0x5600, accessible via control character \
014
Added: poke(addr, val0, val1, val2 .. valn) -- same for poke2, poke4
Added: can peek multiple values: a,b,c = peek(addr, 3) -- same for
peek2, peek4
Added: Locked mouse pointer // poke(0x5f2d, 0x5) and then
stat(38),stat(39) to read
Added: right click in sfx pitch mode to grab the instrument of that
note
Added: IMPORT command can specify target location in pixels: IMPORT
FOO.PNG -X 16 -Y 32
Added: IMPORT -S to shrink the imported image (e.g. -S 3 means shrink
from 384x384 -> 128x128)
Added: ctrl-c at empty command prompt to copy the most recent error
message
Added: extcmd("screen",0,1) / extcmd("video",0,1) saves files in same
path as cart / exported executable or app.
Added: set bit POKE(0x5F36, 0x8) to treat sprite 0 as opaque when drawn
by map(), tline()
Added: shift-tab in gfx/map editor for full-fullscreen mode (with no
red menu bars)
Added: extcmd("rec_frames") to record each gif frame only when flip()
is called regardless of rendering speed
Added: extcmd("folder") to open the folder on the host operating system
(where printf, extcmd saves files to)
Added: custom menu callbacks can optionally leave the pause menu open,
and can read LEFT and RIGHT button presses
Added: ctrl-h hex mode in map / gfx views (displays current sprite in
hex, and shows map tile values)
Added: export map as a single image with export foo.map.png
Added: @weeble's gamepad improvements to the default html shell (dpad
layout detection, better mapping / hotplugging)
Added: stack trace on bad memory access e.g. poke(-1,0)
Added: fillp can now be applied to sprite drawing (spr / sspr / map /
tline), using colours from the secondary palette
Improved: General optimisation pass; heavy carts use 20~30% less host
cpu
Changed: Most api functions are local by default for performance. use
"pico8 -global_api 1" if needed for debugging.
Changed: unpack() now has a non-zero cost but still fairly fast
Changed: .. operator has a small cost based on number of characters
concatenated
Changed: LOADK vm instruction costs 1 cycles (was 2) // otherwise "c=0"
costs more than "c=a+b"!
Changed: removed function cpu refunds; speed-critical calls to bitwise
function should use operator counterparts instead.
Changed: Incremental garbage collection each frame for improved
performance.
Changed: stat(0) performs garbage collection in order to obtain a
meaningful result; use stat(99) for raw value
Changed: options menu always available from pause menu (used to only be
available in web exports)
Changed: tostr() returns "" instead of nil
Changed: exporting gif/png from web version now creates a pop-up div
that can be dismissed
Changed: print() from commandline automatically wraps long strings
Changed: print() returns the x position of the next character to be
printed (can be used to calculate text width)
Changed: glyph constants set only when running cartridge, not when
running a command from prompt
Changed: Using printh from exported carts outputs files in the same
folder as the .exe / .app
Changed: type() returns nothing instead of causing a runtime error
Changed: fill pattern is cleared when program is suspended by default.
Use poke(0x5f2e,0x20) to preserve.
Changed: reset() resets everything from 0x5f00..0x5f7f, same as when
program is initialised (including new random seed)
Changed: font tweaks for hiragana, katagana, ampersand characters
Changed: (raspi) separate binaries that support gpio to remove wiringPi
dependency and gpio poking-related crashes
Fixed: Diagonal lines in editor contain an incorrect step when snapping
to -1:1, 1:-1
Fixed: rnd(tbl) is not random enough when table has 2 elements /bbs/?
pid=81092#p
Fixed: add(tbl) causes runtime error. should have no effect and return
nothing
Fixed: cursor position in code editor incorrect when changing lines
contaning glyphs/tabs
Fixed: CONFIG TAB_WIDTH does not take effect until restarting PICO-8
Fixed: Selecting sprites from bottom right -> top left and then pasting
only pastes a single sprite
Fixed: Moving map selection around with cursor keys beyond original
selection leaves streaks
Fixed: stdout/stdin serial() streams should be binary, not text mode
(causes \r chars under Windows)
Fixed: printh("hello.txt",fn,true,true) fails to save to desktop when
fn has an extention
Fixed: IMPORT FOO.PNG using the current sprite location as target
instead of 0,0
Fixed: tonum behaving differently to parser for string numbers out of
range. e.g. tonum("-0x9000") should be 0x7000
Fixed: Exporting the same zip file multiple times creates duplicate
file entries
Fixed: tline / line clipping // sometimes off by 1px, sometimes
incorrectly discarded altogether
Fixed: poking values with bit 0x80 to 0x5f28,0x5f30,0x5f3c,0x5f3e
clobbers following address
Fixed: deli(tbl,nil) behaves the same as deli(tbl) -- should have no
effect
Fixed: stat(13),stat(15) reporting y coordinates of menu with 0 items
Fixed: memory leak when saving gifs (causes web export to crash after a
few records)
Fixed: print() linefeeds clobber multi-line text printed at bottom of
screen
Fixed: preprocessor can not handle form: "::_::a+=1" (regression in
0.2.1)
Fixed: When split() by group size (e.g. split("ab12",2,false)), last
parameter ignored
Fixed: partial cstore (len < 0x4300) from splore/export clobbering data
outside that range on subsequent reload
Fixed: joystick stops responding after unplug and plug back in twice
(also happens when some devices sleep / wake up)
Fixed: mkdir(nil) crashes
Fixed: possible to edit an SFX without the cursor visible (confusing)
Fixed: menuitem() callbacks broken when there is no _draw() or
_update() defined
Fixed: should only be able to call from commandline: cd mkdir
install_games keyconfig info
Fixed: controller menu (pause->options->controls) does not show custom
key settings
Fixed: -export failing to find files relative from current path
Fixed: -export failing to locate html template path
Fixed: binary export storing multicart cart names with path (should be
named "dat1.p8", not "dat/dat1.p8")
Fixed: pause menu broken when cartridge is launched from splore and
run() is called inside first frame
Fixed: text printing does not respect draw palette (was broken in
0.2) // ref: /bbs/?tid=41428
Fixed: for backwards compatibility, non-numbery colour parameters
should be taken to mean zero
Fixed: preprocessor: self assignment with quoted function calls on RHS
a+=1+cos"0"
Fixed: ctrl-r during pause menu only takes effect after closing menu
Fixed: (bug in RC1) pack(...).n is zero
Fixed: (bug in RC1) using filters noiz:1, dampen:2, lpf is not applied
to melodic instruments (but should be)
v0.2.1b
v0.2.1
v0.2.0i
v0.2.0h
v0.2.0g
v0.2.0f
v0.2.0e
Fixed: pasting a string ending in '1' into the command prompt opens the
editor
Fixed: html export can run out of pre-allocated heap when doing heavy
string operations
Fixed: hex memory addresses displayed in puny font on windows
Fixed: devkit mouse message shown once per cart -- should be once per
chain of carts
Fixed: can't paste sfx notes after moving to another sfx via keyboard
Fixed: copying note select vs. sfx vs. pattern range is ambiguous
Fixed: crash after redefining type()
v0.2.0d
v0.2.0c
v0.2.0b
v0.2.0
v0.1.12c
v0.1.12b
v0.1.12
v0.1.11g
v0.1.11f
v0.1.11e
v0.1.11d
v0.1.11c
v0.1.11b
v0.1.11
v0.1.10c
Fixed: atan flips sign for very negative values of x close to zero
v0.1.10b
Fixed: HTML exporter carts don't run
Fixed: HTML export 60fps support broken
Fixed: HTML export when path has a space in it (common for OSX)
Fixed: atan2 ignores sign of y
Fixed: (Raspberry Pi) Crash when access gpio not as root
v0.1.10
v0.1.9b
v0.1.9
Added: Copy and paste sprites and whole cartridges directly to BBS
posts
Added: JAM category in splore
Added: GPIO support for Raspberry Pi
Added: Read clipboard using stat(4) after user presses CTRL-V
Added: printh() can optionally write to a file or the host clipboard
Added: Editor tool information and tips shown on mouseover
Added: Set desktop path with -desktop (screenshots and gifs are saved
here)
Added: Warning on saving .p8 when compressed code size exceeds .p8.png
limit
Added: Alternative editor colours // config.txt: gui_theme 1
Added: Dotted line every 8 rows in song view
Added: -screenshot_scale (default: 3) and -gif_scale (default: 2)
Added: Can use ctrl-up, ctrl-down to jump to start and end of code
Added: CTRL-M to mute/unmute sound
Added: HTML5-exported carts support 60fps
Added: Timeout switch for splore downloads: -timeout
Changed: Glyph characters typed with alt + a..z
Changed: stat(0) does not include allocations waiting to be garbage
collected
Changed: Unfiltered screen stretching at integer scales by default
Changed: Removed -aspect and -scale settings (use draw_rect instead)
Fixed: -home has no effect under Windows
Fixed: Sometimes frame skipping starts before CPU usage has reached
100%
Fixed: Double-speed BTNP() timing in 60fps mode
Fixed: Exported HTML fails when _update60 is used instead of _update
Fixed: Can't copy and paste button glyphs
Fixed: Lines containing glyphs do not scroll far enough horizontally
Fixed: Loading .p8 renamed as .p8.png from splore freezes
Fixed: Bucketfill in map doesn't sync to shared memory
Fixed: fset fails when de-setting flags
Fixed: Syntax error when beginning with the form: IF (..) [OR|AND]\n
Fixed: cls() costs twice as much cpu as it should
Fixed: wav file exporter missing some data / writing truncated buffers
Fixed: Entering new notes in song view doesn't observe current volume,
instrument
Fixed: alt-tab sometimes generates alt character text entry event
Fixed: Resuming a cancelled download in splore causes crash
Fixed: Controller attributes in log.txt always shown as -1
v0.1.8
v0.1.7
Added: menuitem()
Added: button glyphs in code (shift-L, R, U, D, X, O)
Added: Customisable data directory (e.g. pico8 -home mydata)
Added: Web gpio pins: read and write pico8_gpio[] in javscript
Fixed: SPLORE search doesn't reset
Fixed: Splore skipping 33rd cart listing after loading more items
Fixed: Crash when selecting a local binary file in splore
Fixed: Semicolon can't be used as a list or statement separator
Fixed: Exported html can not cstore self
v0.1.6
v0.1.5
v0.1.4d
v0.1.4c
Fixed: International character entry inserting extra characters
Fixed: Lines with tabs have broken cursor placement and display
boundary
v0.1.4b
v0.1.4
v0.1.3
v0.1.2
v0.1.1
v0.1.0
Added: help()
Added: Ctrl+F / Ctrl+G to search for text, repeat search
Added: del key in code editor
Added: Short-hand single-line IF statements
Added: Unary operators += -= /= *= %=
Added: srand(), time(), added rnd() to docs
Added: Ctrl+D to duplicate line
Added: interactive ls() for multi-page file listings
Added: band() bor() bxor() bnot() shl() shr()
Added: runtime error line number
Added: dir() (aliased to ls())
Changed: print() only autoscrolls when called with no parameters
Changed: alt+up/down to skip between function definitions (was ctrl)
Changed: sspr() dw, dh defaults to sw, sh
Fixed: Load crashes on files that are not .p8 format or directories
Fixed: Misc editor cursor position glitches
Fixed: Crash when syntax error occurs before viewing code
Fixed: Broken newlines after rebooting
Fixed: mkdir() called with no parameters creating "(null)" directory
Fixed: scrolling past top of code with scrollwheel
Fixed: alt-f4 to fastquit
v0.0.4
v0.0.3
v0.0.2
v0.0.1
First Alpha