Log In  

Some Discord servers have been quite busy lately and we've been discovering (less trivial) things here and there than the officially documented things and I thought it was good to have them available in the BBS instead of losing them in a Discord server's chat log. I'll try to update this post as more discoveries are uncovered.

Don't hesitate to write more discoveries in the thread.


At the moment I'm writing this post, we're running 0.1.0b, stuff might change in the future, nothing is set in stone for now.

POD is data, data is POD

Most of the file one will find on their systems are just POD files with an extension. The extension allows the OS to determine which app to launch but the internal data seems to work the same for all formats: they're PODs.

podtree (your_file) will pop a tool window containing a tree interface with the guts of your file. So you can determine how those file are formatted.

For instance the default drive.loc file, once opened, will show that it has only one property: location. So creating a table looking like that my_table = {location="/path/to/a/program.p64"} and then saving it with store(my_shortcut.loc, my_table) will efectively crete a new shortcut.

In the same idea, GFX files are just arrays of small tables containing an userdata and some editor properties, map files are in the same vein than GFX files.

Remember that there is a spec documentation available to explain in detail what a POD is or how to manipulate them: here.

.loc files

Loc files are conceptually similar to Windows' .lnk files. They have a property (location) pointing to a file or cart, but they can also store arguments in a table (argv) that will be passed to the launched program. Here a snippet to create a sample .loc launching a program with arguments

store("/desktop/my.loc", {location="/dev/a_program.p64", argv={"some", "arguments", "as", "an", "the", "game"}})

Those arguments will be accessible from the launched program through env().argv. Thus, you could make yourself shortcuts to quickly open a tool with a specific file or options or launch a program with a specific mode depending on the flags you launch, you're free to do it the way you want.

I'm working on a tool prototype and here's for instance the current code I'm using to parse the arguments. I'm doing extra logic to handle --gfx or --anim options for absolutely no reason, though. You're free to edit the sample as you wish.

local default_anim_path = "/desktop/untitled.anim"
--local default_gfx_path = "/ram/cart/gfx/0.gfx"
local default_gfx_path = "/dev/platformer.p64/gfx/0.gfx" -- My current project

function get_or(tbl, key, fallback)
    return tbl[key] or fallback

function extract_args(argv)
    local anim, gfx = default_anim_path, default_gfx_path
    if not argv then
        return anim, gfx

    local index = 1
    while index < #argv do
        if index == "--gfx" then
            local gfx_arg = get_or(argv, index + 1)
            if gfx_arg ~= nil then
                gfx = gfx_arg
                index += 2
                index += 1
        elseif index == "--anim" then
            local gfx_arg = get_or(argv, index + 1)
            if gfx_arg ~= nil then
                gfx = gfx_arg
                index += 2
                index += 1
            -- Guessing from extension
            local arg = argv[index]
            local parts = arg:split(".")
            local ext = parts[#parts]
            if ext == "gfx" then
                gfx = arg
            elseif ext == "anim" then
                anim = arg
            index += 1
    return anim, gfx

The Tool Tray

The tool tray (also known as desktop2 in some internal places) is the blue region that shows up when you drag the menu bar down. By default it's pretty bare, only a clock and a pair of eyes will welcome you, but it contains a few tricks under its sleeve (or should I dare to say tray?)


(Widgets isn't an official term, they just remind me of Windows Vista's widgets)

First, adding more programs in the tray can be done by doing something like what does /system/startup.lua

create_process("/path/to/file.lua", {window_attribs = {workspace = "tooltray", x=widget_x, y=widget_y, width=widget_width, height=widget_height}})

More research should be done around those tools, but so far, here's what was found out: Widgets can only draw by default in their region provided in the window_attribs and the drawing coordinates are offset to align (0,0) to the region's top left (basically, clip + camera). As shown by eyes.lua, widgets also can listen to input events. Widgets works like any other process, so they'll be listed by ps and can be killed.

Caution Launching a program as a wallpaper (with the window_attribute wallpaper set to true) doesn't seem to work and it'll blink every other frame, so if you're sensitive to blinking lights, you definitely shouldn't do thqt.

Putting files in the tray

As hinted by some of the OS' code, the tray can act as a second desktop where you can place files in them! Just drag'n'drop from the normal desktop to the tray and you'll can see it can store the file! Actually when you moved the file to the tray, it was moved to /appdata/system/desktop2. So, you could store random shortcuts there, log files, etc. in that tray and show them at will on any other workspace.

Copy data from Pico-8 to Picotron

As Pico-8 and Picotron use the clipboard, you can pass along some data. For instance, copying a sprite from Pico-8's sprite editor to Picotron works. Of course, in Pico-8 you'll be limited to the original 16 colors, so it cannot act as a full replacement for Picotron's drawing tool.

In a pinch, it could help you importing spritesheets to Picotron until other methods are implemented.

POD embedding in code editor

As shown in notebook.p64, Picotron can somewhat support embedded graphics userdata in text file and display them inline with the rest of the file. The way of using that is to pod an userdata into base64 format and concatenating to your file with a comment header like that

--[[pod_type="image"]]unpod("b64:the base64 representation of your image")

And it'll show up as a picture in both in notebook.p64 and the code editor too! I haven't got to make work with print yet so maybe they're more fit for comments?

The desktop envrionment

Theming your wallpaper

A small tip: the wallpapers provided with the OS are grabbing the theme properties to adapt to the system's theme. For instance "desktop0" and "desktop1" are two of the three colors set for the desktop section of the theme. If you want to write a wallpaper adapting itself automatically to the system, you might want to grab those colors on a regular basis with theme("desktop0") and theme("desktop1") as they'll automatically update when needed. You're not limited to those colors either, but I think that sticking to those colors will help with readability as the selected colors will most often be different from the ones used in other GUI elements.

More desktop workspaces?

Caution This is really not tested yet, this seems to work but here be dragons.

Somehow, a certain combination of window attibutes will allow you to spawn additional desktop workspaces. Surprisingly, most of the behvaior of the desktop is not really a program so the wallpaper will do the job. Here's a minimal snippet to clone your default desktop with comments

-- Those two lines are directly copied from startup.lua, they'll fetch your wallpaper program
local sdat = fetch"/appdata/system/settings.pod"
local wallpaper = (sdat and sdat.wallpaper) or "/system/wallpapers/pattern.p64"
create_process(wallpaper, {
    window_attribs = {
        workspace = "new", -- I don't know yet
        desktop_path = "/desktop", -- I suppose that's the folder shown on the desktop
        wallpaper=true}, -- RUn the program in background, for wallpaper programs
    style="desktop"}) -- Somehow this does the trick and will also make the icons show on the workspace but I don't know why it's not in startup.lua

That should do the trick. Of course, you don't have to stick to your configured wallpaper, you could perfectly use another. Note that the theme settings are global so you can't really have different UI themes between workspaces yet.

More colors in your icons

So, you've been probably thinking that three color and an alpha channel aren't enough for your icons? Maybe you want your program to stand out on your desktop? Don't fret any more, I have the solution!

Pictron saves metadata for virtually almost every file it can open, icons are part of them. When you've been editing the icon through the icon editor tool, you have been editing those files' metadta, right? What if I told you this icon is just a simple sprite like any other sprite you can create, draw and store? Catch my drift? Here's a magic trick:

source = fetch "/ram/cart/gfx/0.gfx"
meta = fetch_metadata "/untitled.p64"
meta.icon = source[0].bmp
store_metadata("/untitled.p64", meta)

This will effectively loads the first sprite of your currently loaded cart and set its as the icon of /untitled.p64. There are a few caveats before you get drunk with power:

  • Sadly (or not), this won't work if the icon you want to set is not 16*16 pixels. (Note, right now the OS checks only the width so you can go as tall as you wish but it's most likely a bug)
  • For desktop theming, filenav (and thus the desktop too) remaps the colors 1, 6, 7, 13 to the desktop's theme colors set for the icons and you can't really circumvent that (unless you want to mod your OS). So if you want to get artistic, you might want to skip those colors. Note that those colors are effectively the colors you've been applying to the icon when using the icon editor.

Unsorted general tips or info

  • Don't forget to dig in the OS' Lua files, they're currently a good source of info not yet documented, like blit, which allows copying a 2D region of an userdata to another. A few of the info mentionned were dug from snooping in them, so have look!
  • Picotron's /system folder is written as read-only but what it means for now is that any user change will be lost as the folder will be recreated at every launch.
  • In a similar vein, the OS contains some goodies like fonts you can apply on your program by launching a snippet like that one: poke(0x4000, get(fetch("/system/fonts/lil_mono.font"))). Pico-8's font can be found there, have a look!
  • poke(0x5f36, 0x80) as seen in filenav.p64 enables text wrapping using the clip() rect.
  • /ram/system contains a few neat stuff like processes.pod which lists all the executing process.
  • When you launch a cart, it might not close when you exit it and its icon will stick in the menu bar. To effectively close them, you can right-click or middle-click on their icon and hit close. Or use ps and kill. Or use a process manager tool. Or reboot Picotron, etc.
  • The audio editor can be quite unituitive, you might want to look at the doc in case you missed a feature or another. I mean, I spent some time playing with the tool until I noticed that I could sawp wavetables to get white noise by clicking on an osc.'s waveform window.
  • It seems like calling poke(0x547d,0xff) in a windowed program will make any transparent color marked with palt or other mechanisms turn transparent, so you can see what's behind your window. Thanks @taxicomics for the report.
P#143405 2024-03-17 00:22 ( Edited 2024-03-23 10:35)


Thank you for sharing these! I wrote my first widget and stole this little poke from the other existing ones-maybe add it, as it adds "transparency".

P#143455 2024-03-17 07:19

Hey Eiyeron, Ive been doing a similar little digging and experimentation of my own and was messing around with the tooltray. When you restarted the system, did you find the widget thing you added was gone or did you manage to get it to stick around? I keep having an issue here rebooting the system loses both the widget from the tooltray and the file I saved... (I saved it in /system/tooltray as that made sense)

EDIT: I did make sure to add the line in startup to make it load on startup but that change also seemed to be undone

P#143489 2024-03-17 12:17 ( Edited 2024-03-17 12:21)

@SquishyChs you may need to add it to /appdata/system/tooltray as I believe /system gets rewritten on every reboot.

P#143507 2024-03-17 13:49

/system/ appears to be a fake folder created at runtime, so it isn't persistent

P#143509 2024-03-17 13:56

Ah I'm figuring that out now yeah, there wasn't a tooltray folder in appdata/system by default so I made one but on rebooting, that doesn't seem to be pulled into that folder... Was there meant to be that folder by default? Though I know I can just point to it in the startup file but then that resets also. I would have thought boot would be persistent but it's within the system folder so I'm not sure

Edit: Never mind I just saw in startup that it daisychains and runs my own startup script after running the default startup stuff

P#143567 2024-03-17 20:05 ( Edited 2024-03-17 20:18)

spr() in picotron may not have a [w,] [h,] parameters. Currently I know that it takes this parameters in this particular order:

spr(n, x, y, flip_x, flip_y)

I didn't check if [w,] [h,] could be after flips though.

P#143678 2024-03-18 09:59

Given that sprites can now have arbitrary sizes, not just 16x16, I supposed that keeping those arguments would made things more complicated than easier.

P#143684 2024-03-18 11:43

Have you found anything related to CSV?
I recall some early screenshots with an excel like editor but I think it might've been replaced with the new POD format.

P#143730 2024-03-18 17:42

Snooping in the system's pokes (0.1.0)

This is going to be "undocumented" and a base for testing/cataloguing the undocumented poke addresses used by the system. I'm going ot list the first occurences I see for those pokes so many of them are called in more than one place.


  • 0x551f : "line drawing state" ?
  • 0x5f56 : primary font address // 256 (the address probably used by the text renderer. Aligned on 256-bytes, hence the division by 256, I suppose)
  • 0x5f57 : secondary font address // 256
  • 0x5606 : tab width
  • 0x5605 : "apply tabs relative to home" ?


  • 0x5f36 : text wrapping (based on clip's right edge)
  • 0x547d : "wm draw mask" (seen also in other files, not yet determined) window background transparency color mask (works with colors marked as transparent in the color tables). For instance on default pal/palt settings, poke(0x547d, 0xFF) will make color 0 (black) transparent in a window.


  • 0x547c : set to 1 to "keep holding frame" (so no automatic cls?), also seen in other apps, commented out, still works?


  • 0x547f : there's a bit comment talking about a hold_frame flag and the window manager deciding whether keeping half drawn frames or not. The flag set there is 1<<1. 1<<0 is the "window is visible" flag and is used by the window manager to decide whether to call a window's update & draw or not.

The rest is similar calls or sfx.p64 poking absolutely everywhere in the mapped SFX data.

EDIT: I added a section in the main post about .loc files.
EDIT 2: Added a section about icon customization outside the icon editor.

P#143940 2024-03-20 12:54 ( Edited 2024-03-23 10:13)

In the sprite editor, any idea what the row of 8 circles above the sprite sheet tabs might be?

You can toggle them on and off. They each have a different colour when "on". And whatever you set them to seems to persist separately for each sprite in the sheet.

Are they flags with some special meaning when sprites are drawn? Or are they just for your own utility for labeling/organising different sprites?

P#144522 2024-03-26 12:55 ( Edited 2024-03-26 12:58)

they are sprite flags, inherited from pico-8!
you can look in the manual or zines or tutorials to find explanations of what you can do with them
the function to look for in docs: fget

P#144534 2024-03-26 14:30 ( Edited 2024-03-26 16:28)

@brightblue PICO-8 lets you set flags for different sprites, such as whether you want a terrain sprite to block player movement, whether it's an enemy, or whatever. Not 100% sure it's the same for Picotron, but I find it very likely that those dots are readable bits on each sprite.

P#144536 2024-03-26 14:59

Right, that makes a lot of sense. Thanks for the explanation, merwok and nephilim!

P#144606 2024-03-27 00:19

If you do the poke referencing a system font does anyone know how you can use the font in your program?

P#144763 2024-03-27 23:51


print("\014And here comes your text"...     ) 

P.S. You don't need to poke for it, it just works.

P#144834 2024-03-28 13:25 ( Edited 2024-03-28 13:26)

I don’t know if that is fully correct

from what I understand, two fonts can be loaded in memory (at two different addresses), the first one is used by default and the second one requires \014

P#144847 2024-03-28 14:45

Thanks. I can't get the pico-8 font to work though.
I am just getting the little font.

P#144868 2024-03-28 17:22

Am I wrong? I was just playing with that yesterday and commented all my font pokes and small p8 font still worked.
Bun now I want to check it again.

P#144898 2024-03-28 20:15

I've been messing around with all the basic coding and opening a .p64s main.lua in the code editor wont allow you to run it you have to load the .p64 from the terminal first in order for ctrl-R to work.
you also can not have multiple main.luas open at the same time or else running them also does not work. Though new workspaces are still made. (idk if that all made complete sense but I'll keep updating on new discoveries I make as I've found it very easy and fun to spend hours just tinkering with everything.)

P#144910 2024-03-28 21:57

As I looked for getting info on userdata for a possible tool, I found userdata:attribs, another undocumented function, that returns an array with 4 elements describing the userdata, for instance

-- for userdata("f64", 3, 32)
INFO: [197] 3
INFO: [197] 32
INFO: [197] ?
INFO: [197] 2

-- for userdata("u8", 4 * 32)
INFO: [199] 128
INFO: [199] 1
INFO: [199] u8
INFO: [199] 1

I suppose the first two elements are width and height, the third 3 is the type* and the fourth might look like the kind of internal element (1 for integer, 2 for floating point).

Also, looks like Picotron has u16, u32 and u64 as alises for i16, i32 and i64 (u8 is kept as is as it's used for byte manipulation).

*it contains "?" if I set it as f64. I wonder if it's a bug or a fallback of some sort.

P#145092 2024-03-30 08:27

I succesfully injected stuff that looks like a sample (while not really being one and sounding actually pretty awful). The format seems to be same than the documented bits from the doc but some of the memory bits are in a different place than what was documented

function inject_wt2()
    local base_array_addr = 0xf40000 -- Memory zone that'll contain the wave table, make sure
    -- you're not destroying anything. I didn't check, so take care.

    local hi = (base_array_addr >> 16) & 0xff
    local lo = (base_array_addr >> 8) & 0xff
    local wave_length = 8 -- (1 << wave_length) samples per wave
    local wave_height = 0xff -- According to one random comment, it's the number of waves in the table

0x00403e0 => wt-0 header
0x00403e4 => wt-1 header
0x00403e8 => wt-2 header 
0x00403ec => wt-3 header
    local wt_2_header = 0x00403e8

    poke(wt_2_header, lo, hi, wave_length, wave_height)

    -- Note, I'm using one bytebeat formula from viznut here, you should replace that for something that loads your samples instead
    for t=0, 131000 do
        local v =  5*t&t>>7|3*t&t>>10
        poke(base_array_addr + t, v)

I haven't tried for longer wave length than 256 samples without getting a decent result that didn't involve reading garbage. I need to do more research on this topic to determine what would be a better procedure to load a custom sample table, but hey! We have a PoC of the possibility of loading a custom wavetable!

EDIT: Seems like the default wavetable has that for settings : 2048 samples in a wave, 255 samples, one cycle per sample. Those should make sawtooth fading in like Picotron's sawtooth

    -- The same code than the previous snippet up to the poke call, but change wave_length with that
    local wave_length = 10 -- 2048 samples
    local off = 0
    for w=0,wave_height do
        local f = w/wave_height
        for s = 0, 2047 do
            local v = s/2047 * 255
            poke(base_array_addr + off, v * f - 128)
            off += 1
P#145720 2024-04-04 21:45 ( Edited 2024-04-06 21:00)

When setting the toolbar icon of your app in window{}, you have the usual icon = userdata"[gfx]:0808 part letting you use 8×8 colour numbers to define the image. If you change that to 0908, you can put a space after every 8 characters making it much more readable.

BUT if you go even further with 0c08 you get 12+1 characters, 12 is the width of the icon and 1 for the beautifying space.

P#146195 2024-04-09 20:01

interestingly, a software running in the tooltray cannot produce sound if the tooltray is closed
for now, i don't know a method to detect if the soft is running in the tooltray, i tried with env() without success

P#146653 2024-04-14 22:38 ( Edited 2024-04-14 22:38)

The blit function allows you to draw a portion of the gfx userdata to the screen. It can also draw to another userdata while reflecting on the screen, or draw from the screen to a userdata (copy?).

blit(src, dst, src_x, src_y, draw_x, draw_y, draw_w, draw_h)

Parameters after src can be omitted.

If a userdata is specified for src and dst, the userdata of src is drawn to the screen and dst.

It seems usable like a sprite sheet, but pal doesn't work, and you can't change the direction or zoom in/out.
(I think it would be great and convenient if those were possible)

However, there may be other parameters.
This is just my experimental result and not official.

P#149543 2024-06-06 17:16

[Please log in to post a comment]