vsariola / pakettic

TIC-80 cartridge packer
MIT License
21 stars 4 forks source link

Pakettic

Pakettic is a command-line tool for minifying and compressing TIC-80 fantasy console carts. The tool is written in Python (3.9+) and used especially for sizecoding. It compresses existing carts approximately ~1.2% better than best alternatives, and by using its magic comments, pakettic might find code that compresses even better.

Installation

Installing with pip:

$ pip install pakettic

Installing the latest main branch from GitHub:

$ pip install git+https://github.com/vsariola/pakettic.git@main

Installing a checked out version of the repository:

$ pip install -e path/to/pakettic

Installing a checked out version of the repository using poetry for a nice virtual environment with locked dependencies (run inside the pakettic folder):

$ poetry install

Usage

To compress a cart, run:

$ pakettic path/to/cart.tic

If your PATH is not configured to include pip installed executables, you can use

$ python -m pakettic path/to/cart.tic

If you installed using poetry into a virtual environment, you need to prepend poetry run before every command e.g.

$ poetry run pakettic path/to/cart.tic

Pakettic supports both .tic and .lua carts. Multiple input files may be defined. Input files are globbed, so ?, *, and ** work as wildcards for a single character, multiple characters and a directory, respectively.

For a full list of command line options, see:

$ pakettic --help

See also tips for command line arguments

Running all tests:

$ poetry run python -m unittest discover -s tests

Features

Pakettic first parses the LUA-script to an abstract syntax tree, and then uses a local optimization algorithm (simulated annealing, late acceptance hill climbing or its variant diversified late acceptance search) to randomly mutate the syntax tree & see if it compresses better. Implemented mutations include:

Internally, pakettic uses zopfli for the compression.

load'<code>' is parsed as function(...)<code>end so you can easily recompress already compressed carts. Conversely, function()<code>end or function(...)<code>end is replaced with load'<code>' during compression.

Note that function(...)<code>end and load'<code>' are not 100% semantically identical: the load version cannot access locals in the outer scope. For example: local x="hello" function f()print(x)end works but local x="hello" f=load'print(x)' does not. Since locals are rarely used in size-coding, we default to using the load-trick, but you can disable it with the command-line parameter --no-load.

However, pakettic does not convert functions with parameters. In particular, pakettic does not automatically convert function SCN(x)<code>end into SCN=load'x=...<code>', because they are not semantically identical: in the load version, x is now global and thus could trash a global variable, unintentionally breaking the cart. To make SCN compress nicely, you have to write it as function SCN(...)x=...<code>end, taking responsibility for x not overwriting anything important.

Unnecessary parentheses are removed from expressions so you do not have to worry about those.

Magic comments

Reorderable statements

The algorithm will try to reorder statements between --{ and --}. For example:

 --{
 a="hello"
 b="world"
 --}

will try both a="hello"b="world" and b="world"a="hello" to see which compresses better.

Notice that only complete statements can be reordered. Thus, this will NOT work:

 --{
 for x=0,239 do
  for y=0,135 do
 --}
  end
 end

A good rule of thumb is that you should be able to replace --{ and --} with do and end, respectively, and still have valid code.

Statements between --{! and --} are not ordered, so you can make blocks of statements that are kept in order within a pair of --{ and --} tags.

Alternative expressions

There is a special --| operator that allows alternative expressions to be tested, to see if they compress better. For example: 5--|4--|6 means that the algorithm will try 4 and 6 in place of the 5. This will naturally show up as a comment in LUA so you will have to continue the expression on next line if this is in the middle of an expression. --| has the lowest precedence, even lower than ^, so put parentheses if you want to try more complicated expressions e.g. (x//256)--|(x>>8)

Debug code

Pakettic treats --![ and --!] as multiline comment tags, while LUA treats these as single line comments. Useful for including debug code in the unpacked intro: the code will not be included in the packed cart.

Tips for command line arguments

Known issues

Credits

Code contributors: Veikko Sariola/pestis, wojciech-graj, koorogi, dezgeg

Test corpus contributors: psenough, ilmenit, gigabates, gasman, pellicus, luchak.

License

MIT

The test corpus carts have their own licenses, see the license files in the subdirectories of the corpus directory.