Log In  
00   i make little worlds      |
01   inside a little machine   |
02     because it's just fun   |
[ :: Read More :: ]

Cart #util_crc32-0 | 2024-03-21 | Embed ▽ | License: CC4-BY-NC-SA

This cartridge is an installer and uninstaller for a new globally-available command: 'crc32'.


Install with yotta:

  • yotta util install #util_crc32

Install without yotta:

  • load #util_crc32 in your Picotron terminal
  • Ctrl+R to run installer cartridge
  • Press X to install/uninstall as prompted
    (You can also manually copy the relevant files from the loaded cartridge from /ram/cart/exports to your system if you'd prefer.)


This command will generate CRC32 checksums for the input parameter you provide, which can be either a file path, a folder path, or a bare text string.

If you give it a file, you'll get the file's checksum back.
If you give it a folder, it will checksum every file within the folder then checksum that output one more time to get you a single value.
If you give it neither a file nor a folder, you'll get the input string's checksum back. Note that you are not expected to quote-encapsulate the input string.

Use as a library

This utility file can be included in code as a library to expose the crc32() function:

crc32(input: string|path, is_file: boolean|nil = nil): string

This function will return just a checksum number alone. It will attempt to autodetect if it's a file if you omit is_file, based on file presence or lack thereof.

Checksum file operations

This command accepts three arguments that allow it to place a checksum it generates into a checksum list file (.crcs). You can either append the checksum to the target file, delete the checksum from the target file, or replace all checksums in the file with the newly generated one.

Append: > crc32 -a file.txt.crcs file.txt
Delete: > crc32 -d file.txt.crcs file.txt
Replace: > crc32 -r file.txt.crcs file.txt

Recursive checksumming with -V

This command has a -V or --version parameter.
This will take the provided target, a folder, and walk through every file within it to generate a checksum and place it into a [filename].crcs file alongside it. By default, it will append the hash to the crcs file. If you specify -d or -r, it will either delete or replace the crcs files appropriately.


> crc32 -V /ram/cart/exports
12345 /ram/cart/exports/subdir/file.txt
54321 /ram/cart/exports/subdir/another/manual.txt
> ?fetch("/ram/cart/exports/subdir/file.txt.crcs")

This has plenty of uses I'm sure, but I use this for the new yotta version which supports providing .crcs files next to installable files within a package intended for system install. These .crcs files can be used to determine "safe" versions of the file that can be considered as owned by the package. This helps yotta avoid overwriting customized or external files that also might belong to a package, and helps my package installer/uninstaller shim not delete files that aren't owned by it.

P#144084 2024-03-21 22:04

[ :: Read More :: ]

I'd like to propose a new BBS forum category alongside 'Releases' and 'Work in Progress': 'Supporting'

Picotron is a lot more complicated than PICO-8 and is liable to have a lot more "support" or "non-end-user" releases, like tools intended for developers to use in making their final cartridges or utilities intended to be installed into the system for general use.

These differ from released games or sketches or demos in that they're not usually intended to be terribly useful or entertaining without using them from another place. Given Picotron's nature, I expect a lot of these.

This way, it would be easier to find games and novelties in Releases - but still look through the latest tools and utilities in Supporting. A future SPLORE-style system for Picotron might then be able to focus on just the more entertaining game-style or demo-style releases intended for end use, while hiding away the supporting packages used to make those items.

Does anyone else think this would this be helpful going forward?

P#143603 2024-03-18 00:16 ( Edited 2024-03-18 00:18)

[ :: Read More :: ]

Cart #util_merge-0 | 2024-03-17 | Embed ▽ | License: CC4-BY-NC-SA

This cartridge is an installer and uninstaller for a new globally-available command: 'merge'. You can install this by running this cartridge and pressing X as prompted, manually by copying /ram/cart/exports/appdata/system/util/new.lua to /appdata/system/util/new.lua, or by using my dependency and package manager 'yotta' to install this BBS cartridge ID as a util (yotta util install #util_merge-0).

The merge command will take a number of source directories and a single destination directory, and will recursively merge the source directories atop the destination directory. Normally, the system-provided cp command is not kind to attempts to do this - a cp src dst will result in dst being replaced by a copy of src. This, however, will attempt to intelligently place new source files into the destination directory tree without interfering with any of the remaining destination files.

It also has the ability to attempt an un-merge which will attempt to clean-up after itself if there are lingering empty directories after an unmerge attempt.

usage: merge [options] [src...] [dst]
 Recursively merges source directories into a destination
  -u|--unmerge - Will attempt to undo a merge, removing src files from dst
  -g|--gentle - Will make an in-place backup (file.ext.bak) before overwriting files
  -p|--paranoid - Will simply not overwrite files and will continue merging

Example: merge src_dir dst_dir - simple merge.
Example: merge -g src_dir dst_dir - simple merge but will rename, eg, /dst/file to /dst/file.bak if there is already a /dst/file but /src/file also exists.
Example: merge -p src_dir dst_dir - same as above, but instead of creating a backup it will simply NOT copy the /dst/file to /src/file and will move on.
Example: merge -u src_dir dst_dir will attempt to remove all the files that appear to be merged from src_dir from dst_dir. I say attempt because if you, say, ran a paranoid merge above and then ran this, it would remove the originals from dst_dir!! So, be careful with this, but it's still quite useful in my opinion.

Use as a library

This utility script can also be included by other lua files without executing immediately in order to provide its merge and unmerge functions. Simply include the file in your code (include "/appdata/system/util/merge.lua") and two functions will become available for your use. Just... don't name your own lua file merge.lua. It's kind of dumb, but my merge.lua is looking at the currently running script's basename to determine if it should be a command or a library. Sorry.

merge(string src_path, string dst_path, int gentleness = 0)

Merges source path over the destination path. Gentleness = 0 is equivalent to running the command without -p or -g. Gentleness = 1 is equivalent to running the command with -g. Gentleness = 2 is equivalent to running the command with -p.

The merge function will return a table in the following format that outlines the new files and folders within the destination directory that were created:

  files = {"/created/a/file.txt", "/another/created/file.pod"},
  dirs = {"/created/a"}

unmerge(string src_path, string dst_path)

Attempts to unmerge a source path from a destination path. If it leaves empty directories that it had to create during the merge process, it will attempt to remove these directories on its way out, to hopefully leave your destination just as it was before.

The unmerge function will, similarly to the merge function, return a table in the following format that outlines the destination directory files removed and folders that were pruned due to being empty after file removal:

  files = {"/baleeted/a/file.txt", "/another/removed/file.pod"},
  dirs = {"/baleeted/a"}
P#143595 2024-03-17 23:46

[ :: Read More :: ]

Cart #util_new-1 | 2024-03-18 | Embed ▽ | License: CC4-BY-NC-SA

This cartridge is an installer and uninstaller for a new globally-available command: 'new'. I already released this in bare script form here and it's largely unchanged, but I've adopted a system that I moved it into for installation and uninstallation on my Picotron, and you can too! You can install this by running this cartridge and pressing X as prompted, manually by copying /ram/cart/exports/appdata/system/util/new.lua to /appdata/system/util/new.lua, or by using my dependency and package manager 'yotta' to install this BBS cartridge ID as a system util (yotta util install #util_new-0).

The new command needs a template cartridge (by default, expected to be at /appdata/new/template.p64) which allows you to customize your fresh cartridge. When you issue a 'new' command, this cartridge will be copied into /ram/cart - so if you start with a blank cartridge, customize it to your liking as your new starting-point, and save it as /appdata/new/template.p64 - you're done!

When it creates a new cartridge, it assigns it a custom 'save-as' name. It doesn't automatically save your blank cartridge - it's up to you to press Ctrl+S if you'd like, just like a normal blank cart - but this name will default to /untitled_YYYYMMDD_NN.p64 with a timestamp and an automatically incrementing number to prevent overwriting the first cartridge(s) of the day.

If you'd like this to also be the cartridge that Picotron boots up with, you can edit your /appdata/system/startup.lua to include the following lines:

if fstat("/appdata/system/util/new.lua") == "file" then

My template cart is pretty simple. You can do whatever you want with it, but all I've done to mine is modify the default main.lua script to something like this:

-- New Picotron Cartridge
--  by Your Name Here <[email protected]>
-- v1: inception

-- called once per cart run
function _init()


-- called every frame
function _draw()


-- called every 1/60th of a second
function _update()


So, now, when I boot up Picotron or call new, this is the main.lua that I get in my fresh cart by default.

edited to revision 1: places template file in its namespace at /appdata/new/template.p64 instead of polluting the system namespace.

P#143593 2024-03-17 23:41 ( Edited 2024-03-18 01:28)

[ :: Read More :: ]

Cart #yotta-5 | 2024-03-27 | Embed ▽ | License: CC4-BY-NC-SA

This cartridge is an installer and uninstaller for a new globally-available command: 'yotta'.


Initial Installation:

  • load #yotta in your Picotron terminal
  • Ctrl+R to run installer cartridge
  • Press X to install

Upgrade Installation from v1.0:

  • yotta util install #yotta in your Picotron terminal
  • yotta version in your Picotron terminal
    You should see "yotta version v1.1"
    This 'version' command will also migrate the yotta installation files to clean up a little bit. It will move the global yottafile into /appdata/yotta instead of /appdata/system, and it will clean up some of the new CRC verification files that got installed that the new version will handle better behind-the-scenes.


usage: yotta <commands>
 version - tell you what version yotta is
 init - initialize a yottafile for the current directory
 list - list the current dependencies
   -v - verbose: list all tracked files under each dependency
 add <cart> - add a cart ref as dependency (but don't add/remove anything yet)
 remove <cart> - remove a cart ref as dependency (but don't add/remove anythin yet)
 apply - make the current directory match a yottafile
   (this is what actually adds and removes files!)
 force - this is an apply that will reinstall every dep (for updates?)
 util install [options] <cart> - globally install utility cart ref
  by default, it will make a backup in event of a file conflict (file.txt -> file.txt.bak) then install.
  -g - be gentle. don't install if a file conflict occurs, just move on to install non-conflicting files.
  -f - be forceful. just overwrite if a file conflict occurs.
 util uninstall <cart> - globally uninstall utility cart ref
 util update <cart> - update a globally installed ccart ref
 util list - list all tracked util cart refs and their files

The yotta command seems complicated but it's pretty simple if you've ever used dependency managers or package managers on other systems or for other programming languages. I'll outline its two major functions separately.

Yotta as a dependency manager

  1. > cd /ram/cart
  2. > yotta init
  3. > yotta add #bbs_id /my_libs/library.p64
  4. > yotta apply
  5. In your lua, include "./lib/bbs_id/main.lua" and include "./lib/library/main.lua"
  6. Done!

Let's say you've got your cartridge, and you'd like to use a library that exists on the BBS that is compatible with yotta. Or... let's say you have some yotta-compatible libraries on your system that you've written or downloaded and you'd like to use their features in your current cartridge. Or both!

Sit your terminal in /ram/cart and prepare the cartridge for use with yotta: yotta init. This will create a /ram/cart/lib directory, and a /ram/cart/lib/yottafile.pod file to track dependancies and what dependancies own what files.

Now, to add a dependency, you need to know where it lives. If it's on the BBS, the library cartridge has a BBS ID, and you can supply that like you would with the 'load' command: yotta add #bbs_id. If your dependency lives in a file on your Picotron, you can specify the path: yotta add /my_libs/library.p64. This will track the dependency by adding it to the yottafile, but it won't immediately install it. You can specify multiple packages at once from mixed sources (like yotta add #bbs_id /my_libs/library.p64.)

When you're ready to install the dependencies into your cart's lib/ folder, run yotta apply. It will fetch the BBS carts and copy the filesystem carts and unpack each of their exports/ folders into named directories in libs/. For example, using the above two sources we added, you could expect to see something in your cart like libs/bbs_id/main.lua and libs/library/main.lua. Everything present in the target dependency cart's exports folder will be copied into this lib/[name] directory (except for .map, .sfx, and .gfx files).

If a target cartridge's export folder contains .map, .sfx, or .gfx files, they will be copied into your cartridge's map/, sfx/, and gfx/ directories, named [library_name]_[exported_filename].[ext]. They will still be tracked by yotta, so if you remove the dependency and do a yotta apply, it will remove them cleanly.

If your dependency targets update somehow, you can use yotta force to force it to reacquire all dependencies instead of simply passing over already-installed packages.

If you decide you no longer need a package, you can remove it nearly the same way you add it: yotta remove #bbs_id. Then, the next yotta apply will identify this and remove lib/bbs_id from your cart, and delete any sfx/gfx/map files that were added by it. It won't remove the includes from your lua code - that's up to you!

Yotta as a package manager

  1. > yotta util install /my_utils/cool_util.p64 #bbs_system_package
  2. Done!
  3. Wait I want one of them gone, it sucks!
  4. > yotta util uninstall #bbs_system_package
  5. Done!

Let's say you see a cool yotta-compatible system package like a new command-line utility (like 'new' or 'merge' ;) ) on the BBS, or let's say you've written a yotta-compatible cartridge containing your utility. Or both!

Grab your terminal, its location doesn't matter. If it's on the BBS, the package cartridge has a BBS ID, and you can supply that like you would with the 'load' command: yotta util install #bbs_system_package. If your dependency lives in a file on your Picotron, you can specify the path: yotta util install /my_utils/cool_util.p64. This will track the dependency by adding it to the system yottafile at /appdata/system/global_yottafile.pod, and will immediately install it. You can specify multiple packages at once from mixed sources, also (like yotta util install /my_utils/cool_util.p64 #bbs_system_package.)

If you decide you no longer want or need a package, you can remove it nearly the same way you add it: yotta util uninstall /my_utils/cool_util.p64. It will be immediately uninstalled, but it won't remove any files created by the utility or whatever - only what was installed originally. If it leaves empty directories after it's done removing files, it will clean those up too.

Building libraries for use with yotta

It's simple! Place whatever you'd like to be available in your end-user cart's lib/your_package_name directory into your cart's exports/ folder. Make it if it doesn't exist. If you add .sfx, .map, or .gfx files, they will be copied into your end-users sfx/, map/, and gfx/ folders, named [your_package_name]_[filename_in_your_exports].[ext].

If your cartridge does nothing else other than offer your library to folks, then you're welcome to use my library container utility to help inform users that this is not a 'runnable' cartridge and is instead a kind of 'resource' cartridge. It gives them the ability to view the files if they run it, but not much else.

Once you publish your cart with the exports/ folder to the BBS, other people using yotta will be able to use it in their projects with yotta add #your_bbs_cart_id followed by a yotta apply. I propose a convention of providing a primary 'include entry point' (if applicable) of 'main.lua' - so placing your library file in your /ram/cart/exports/main.lua. But, all files in your exports/ folder will end up in the end-user's lib/your_package_name/ directory, so you can do as you will.

Building packages for use with yotta

It's also simple! Make an exports folder in your cartridge (at /ram/cart/exports). Assume the contents of your cartridge's exports directory will be overlaid atop the end-user's Picotron drive starting at the root level. So, if you'd like to package a system-wide terminal utility, you would want to ensure that /ram/cart/exports/appdata/system/util exists as a folder, and inside of that, you would place your utility (like new.lua!).

When end users use yotta to install your package cartridge, it will copy the exports tree over the root filesystem, and when users instruct yotta to uninstall it, it will remove the files present in your export tree from the user's filesystem (and will clean up empty directories if it leaves them).

If you don't want to require your end users to use yotta to install your package, and your cart does nothing else but hold the package, you could use my package installer (same as my library container). It will let people either manually copy the files out of /ram/cart/exports or will offer to install/uninstall it to their with similar logic to yotta (but without requiring yotta to be installed) by pressing X while running it.

What if a BBS cartridge updates? What if I don't want that? What if I do?

If you include a BBS cartridge, you can specify a revision just like you could with the load command... or you can omit it to get the latest. You can choose at yotta add time. For instance, if you wanted to fetch the first release of my utility shim cart, you'd use yotta add #lib_utilinst-0. When you used yotta apply or yotta force, it would always grab that version. If I fixed a bug in it and updated it, though, its cart ID would change to #lib_utilinst-1, but the first revision would remain accessible and would be fetched by yotta in this case. If you wanted yotta to automatically grab the latest revision possible, you can yotta add #lib_utilinst - and whenever you run a yotta apply or yotta force, it would grab the latest revision (#lib_utilinst-1 or further).


  • r3: added yotta_manual.txt for viewing in the info viewer by @ksting
  • r4: added /appdata/system/man/yotta.1 for viewing in the 'man'-style manual viewer by @jesstelford
  • r5: yotta v1.1: - now actually uploaded! smh
    • smarter package-mode file overwriting & uninstallation:
    • uses CRC32 to determine if a file it's about to overwrite is an old or current version that can be upgraded/replaced, or if it's another file not owned by yotta.
    • by default for installations or upgrades, it will make a backup of the unknown file (file.txt -> file.txt.bak) and will install its packaged files as instructed.
    • using the -g flag will make it gentle, and it will simply skip the file instead of backing-up and installing. it will install the other files that don't have a conflict.
    • using the -f flag will make it forceful, and it will simply overwrite the file instead of backing-up.
    • for uninstallations, it will only remove files that match checksums that it believes it owns, and will thus leave modified or non-matching files in-place.
    • CRC32 support in packages is optional. See my crc32 utility for more information - you can just run crc32 -V exports from the root of the cartridge you intend to distribute, every time you're about to make a release, and it will understand.
    • added yotta util update: will download or copy a new version of a system package and install it over the old one. this works for yotta itself, too, so in the future you can update yotta this way without disruption.
    • fixed yotta apply bugs: it wasn't cleaning up right when packages were removed with yotta remove. now it does!
    • fixed yotta adding multiple instances of the same dependency or package. it won't do that anymore.
    • moved global yottafile into /appdata/yotta instead of /appdata/system to keep everything tidy.
    • allow specifying a package or dependency for uninstallation or update by its 'short name' listed alongside it in yotta list or yotta util list (eg, my_pkg can be used instead of /carts/wip/my_pkg.p64.png)
P#143592 2024-03-17 23:35 ( Edited 2024-03-27 18:49)

[ :: Read More :: ]

Cart #lib_utilinst-0 | 2024-03-17 | Embed ▽ | License: CC4-BY-NC-SA

This cartridge is a library cartridge intended to be used by some of my other cartridges. You can use it too, if you'd like. For utilities, it offers the one-button ability to permanently install or uninstall the files from your Picotron system drive. I use this to install new utilities to/from /appdata/system/util/, but you can install and uninstall files to any permanent storage location with it.

Once loaded, in /ram/cart/exports/, you will find main.lua. This can be copied into your project and included, or installed by my yotta dependency and package manager (released separately). This utility shim offers the following functions which you can either call in your init/draw/update lifecycle functions, or use to replace your lifecycle functions outright. I tend to do the latter.

Here is an example of an installable utility cart using this library, with some special initialization logic but standard draw/update handled by this library.

include "path/to/util_stub/main.lua"
function _init()
  -- ... do other init stuff ...

_draw = util_draw
_update = util_update

If you are packaging a system utility or package, the util_init_installable function is used. This will create an installable/uninstallable package. For this to work, it expects a /ram/cart/exports/ directory which contains a root filesystem overlay. So, if you'd like to install a file to a specific location (like /appdata/system/util/new.lua), you would need to place new.lua at /ram/cart/exports/appdata/system/util/new.lua. Upon running the cartridge, it will detect if all its files are present or not on the system already, and will offer to install or uninstall the data from your system accordingly at a key or button press.

If you are packaging a code library not intended for installation, the util_init_library function is used in place of the util_init_installable function. The only major difference is that it does not offer to install/uninstall the package to the system.

An option to view the exports is always provided by the running cartridge using this library regardless of release type.

If you are using my yotta dependency and package manager (released separately), you can also use the BBS 'hash ID' (like the one used in the 'load' command) of any library-mode cartridge in this format to automatically add the library to your own cartridge's lib/ directory. See the details for this on the yotta utility release page.

P#143587 2024-03-17 23:24

[ :: Read More :: ]

hi there

edit: i've updated this a bit and put this into an installer cartridge (here) which can also be installed via yotta, a package and dependency manager.

i've already wanted to throw out what i'm working on and start a cart from scratch a few times and am accustomed to being able to use PICO-8's reset to do so. this doesn't seem to work well here, and i don't like just closing and reopening picotron. we have save and load but not reset or new. so... i made new.

you can place this code in /appdata/system/util/new.lua:

-- remove current cart and replace with template cart
source_template = "/appdata/new/template.p64"
default_filename = "/untitled.p64"

if(fstat("/appdata/new") != "folder") mkdir("/appdata/new")

-- (most of this copied from /system/util/load.lua, thx zep)

-- remove currently loaded cartridge

-- create new one
local result = cp(source_template, "/ram/cart/")
if (result) then

-- set current project filename
store("/ram/system/pwc.pod", default_filename)

-- tell window manager to clear out all workspaces
send_message(3, {event="clear_project_workspaces"})

dat = fetch_metadata("/ram/cart")
if (dat) dat = dat.workspaces

-- create workspaces
if (type(dat) == "table") then
    -- open in background (don't show in workspace)
    local edit_argv = {"-b"}

    for i=1,#dat do
        local ti = dat[i]
        local location = ti.location
        if (location) then
            add(edit_argv, "/ram/cart/"..location)

    -- open all at once
            argv = edit_argv,
            pwd = "/ram/cart"

print("new cart initialized.")

once /appdata/system/util/new.lua exists, you can immediately invoke it on the terminal by simply typing new. any lua script in this util directory behaves like this, as noted in the readme. i love this.

you'll need a .p64 cart saved that will serve as your "new cart" template. you can customize where to put it, but by default it expects you'll save one at /appdata/new/template.p64. to make this, i suggest freshly booting up picotron, editing the cart and code to whatever you'd like your default cart to look and act like, making the directory (mkdir /appdata/new) and saving it directly (save /appdata/new/template.p64). you can edit anything and it will be carried over to newly-created carts (via new, anyway) - sprites, maps, sfx, multiple luas, even cover art, etc.

if you customize default_filename up at the top, it will change where it saves the cart to if you were to invoke new then make some edits and just hit ctrl+s without manually invoking save <filename>. i've moved this to be /dev/untitled.p64 because i like to hide my clutter away.

warning: it will simply destroy any unsaved changes in the current cart. i would like to find out if there is a way to detect "unsavedness" in the future and prompt/warn about it and maybe require an overwrite flag if the workspace is dirty... but i'm not sure how to do that yet. so, take care.

it looks like /appdata/system/startup.lua can be used to run custom startup code. in here, i would probably invoke this 'new' script, so that when i boot picotron it starts as my template. i haven't done this yet, because i am breaking other things ... but i think i will shortly.

if you're looking for ideas, my template only has a modification of main.lua to be the following:

-- New Picotron Cartridge
-- by Your Name Here

-- called once per cart run
function _init()


-- called every frame
function _draw()


-- called every 1/60th of a second
function _update()

P#143060 2024-03-15 05:52 ( Edited 2024-03-18 01:26)

[ :: Read More :: ]

not a game, not interactive. just an animation. the header for this section claimed to appreciate curiosities :)

it's within a month or so of easter here in the united states, and being within a month of a holiday means retailers are all keen to sell varying confections with absurd sugar contents vaguely themed after the season. "peeps" are a hallmark of this time of year for a lot of people, as a result. they're little marshmallow sweets with what amounts to a pure sugar coating on them. they usually come in packs of 4 or 8, and are shaped like small animals, typically bunnies or chicks (baby chickens...). i don't think they're often widely distributed outside the united states and canada.

there's a recurring urban rumor i hear every year that claims that peeps have their little eyes burnt on with lasers at the factory after they're formed. sadly, this isn't true, but the rumor continues circulating nonetheless. i was talking about this with a friend, and decided to animate a little view into the hypothetical peep factory where the peeps are granted their faces.

Cart #kwapospu-1 | 2023-03-13 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

edit: removed printh calls. oops. learned something new today.

P#127023 2023-03-12 09:04 ( Edited 2023-03-13 20:32)

[ :: Read More :: ]

i spent my time very wisely today while on an extremely unproductive conference call, and now you can benefit from my varied attentional deficiencies.

please enjoy some emoji sprites, completely free for you to use. they work great on the forum here, but i think i'll also put them into my textbox dialogue parser so characters can emoji at the player. maybe that's fun, maybe that's insufferable. i guess i'll find out!


i sure wish we could inline sprites here, though.

P#126761 2023-03-08 02:25 ( Edited 2023-03-08 02:26)