Log In  
Follow
samhocevar

Hi!

It’s been ages since I did anything for Ludum Dare. This time the theme was “Summoning”, so this is my attempt at a puzzle game where you need to complete a pentacle in order to summon whatever it is you summon. I did not spend too much time on this and could not find game mechanics that were both fun and challenging, so don’t expect anything great…

Left/Right arrows: browse runes (in select mode) or rotate rune (in placement mode)
Z/C: select rune (in select mode) or place rune (in placement mode)
X: cancel rune placement and return to select mode

Compo entry page is here.

Cart #pent8cle-0 | 2024-04-15 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
5

5
1 comment



PICO-8 will gladly accept export("sfx_%x_%d.wav") because the string argument contains %d, but the format string is not sanitised and the SFX index will be caught by %x, and %d will then pick who knows what memory location.

Funny things can also happen with %s, %*s etc.; export("%s%s%s%s%s%s%s%s%s%s%s%s%d") is almost a guaranteed crash.

3
0 comments



Here’s a simple example:

a=0
while(a<4) a+=1 if(a!=3) ?a
print('lol')

Expected output:

1
2
4
lol

Actual output:

1
lol
2
lol
lol
4
lol
4
0 comments



The sin() and cos() functions seem to be using some kind of lookup table at ¼ the number resolution, with clamping. It is easy to use linear interpolation instead and improve the precision of these functions by calling them twice:

function trig(f,x)
  local a, b = f(x & 0x.fffc), f(x | 0x.0003)
  return a + (b - a) * (x << 14 & 0x.c)
end
function xsin(x) return trig(sin,x) end
function xcos(x) return trig(cos,x) end

@zep I think PICO-8 could very well do this internally, as the extra cost seems negligible. The same goes with atan2(), especially since in that case it is significantly more difficult to do in PICO-8 user land.

Cart #precise_sincos-1 | 2024-01-21 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
6

[ Continue Reading.. ]

6
2 comments



atan2(1,0x8000.0001) returns 0.25, as expected. However atan2(1,0x8000.0000) returns 0.75.

This is the kind of stuff that could be compiler-specific because 0x80000000 is a bit special, however I get this on both Windows and Linux (64-bit).

3
3 comments



Here is a simple implementation of merge sort in 97 tokens. It uses less PICO-8 CPU than anything else I’ve tried (with a caveat, see below).

A small benchmark comparing CPU usage, sorting an array of 200 random numbers:

Note that non-randomised quicksort suffers from very bad performance on quasi sorted lists, so the above benchmark does not represent what you would get in real life. Always test in your own application.

function sort(t, a, b)
    local a, b = a or 1, b or #t
    if (a >= b) return
    local m = (a + b) \ 2
    local j, k = a, m + 1
    sort(t, a, m)
    sort(t, k, b)
    local v = { unpack(t) }
    for i = a, b do
        if (k > b or j <= m and v[j] <= v[k]) t[i] = v[j] j += 1 else t[i] = v[k] k += 1
    end
end

[ Continue Reading.. ]

8
9 comments



Here are a few token count discrepancies:

print(12)    -- 3 tokens
print(-12)   -- 3 tokens
print(~12)   -- 3 tokens
print(-~12)  -- 4 tokens
print(~-12)  -- 5 tokens
print(~~12)  -- 4 tokens

?12    -- 2 tokens
?-12   -- 3 tokens
?~12   -- 2 tokens
?-~12  -- 3 tokens
?~-12  -- 4 tokens
?~~12  -- 3 tokens

Also this inconsistent behaviour with spaces:

print(-12)   -- 3 tokens
print(- 12)  -- 4 tokens
print(~12)   -- 3 tokens
print(~ 12)  -- 3 tokens
6 comments



I recently wrote a post about storing random data as strings in carts and studying the built-in compression algorithm. It led, among other conclusions, to the following two observations:

  • PICO-8 is not very good at compressing random strings that use more than 64 different characters; those get actually expanded by about logâ‚‚(n)/6. For instance a random string of length 1000 that uses 128 different characters will use 1000*logâ‚‚(128)/6 = 1167 bytes in the cart.
  • The new compression format is redundant and allows for sequences that are not needed. For instance 011 01111 001 and 010 0000001111 001 both encode a back reference of length 4 at offset -16.

Most other compression schemes have some sort of fallback mechanism when they are unable to compress a block. For instance the DEFLATE format (for zip or gzip) has non-compressed blocks. In the above thread I

[ Continue Reading.. ]

5 comments



I would like to propose the following syntax extensions to PICO-8 for symmetry with the shorthand peek operators. Basically it means treating the peek operators result as an lvalue:

@x = y     -- short for poke(x,y)
%x = y     -- short for poke2(x,y)
$x = y     -- short for poke4(x,y)

@x += z       -- short for poke(x, @x + z)
%12 |= %42    -- short for poke2(12, %12 | %42)
$x >><= 4     -- short for poke4(x, $x >>< 4)
4
6 comments



The following operators cost 2 tokens instead of the usual 1 for compound assignment operators:

\=
^=
>><=
<<>=
..=

edit: I confused >>>= and ^= in my initial post.

1
0 comments



I’m writing a minifier and I still can’t really wrap my head around the parser.

Here are two similar snippets that differ only in whitespace and do not do the same thing:

> for i=1,2 do
>  if(i)print(i) end
> print(3)
1
2
3
> for i=1,2 do
>  if(i)print(i) end print(3)
1
3
2
3
5 comments



When attempting to load a .p8 cart that exceeds the 65535 character limit (one that may have been created by an external editor or tool), PICO-8 silently drops lines at the end of the cart and does not report errors. It then accepts to save the (now corrupted) cart when pressing Ctrl-S, overwriting the original file.

Here is a sample cartridge for testing purposes; notice that the last print() line disappears when loading it.

One more observation: sometimes, when the 65535 character threshold lies in the middle of a Lua statement, a loading error does appear. I have been unable to identify when exactly.

0 comments



I created a cart containing the following code:

print("hello")

Then in the shell:

save foobar.p8.png

If I close PICO-8 and reopen foobar.p8.png, I get gibberish in the code section:

1
1 comment



Due to the preprocessor parsing numbers differently than Lua, here are some issues:

Valid code (1e-3 is a valid number in Lua):

a=1e-3

Invalid code (1e-3 is parsed as 1 e - 3 by the preprocessor):

a+=1e-3

Invalid code (2b is an invalid number in Lua):

a=1+2b=3

Valid code (2b is parsed as 2 b by the preprocessor):

a+=1+2b=3
1
11 comments



Not sure if it is really a bug, but poking at the 8 bytes starting at 0x5f4c then immediately peeking will discard the two highest bits:

> p=0x5f4c poke(p,255) print(@p)
63

I know this is a special area and the memory is modified by the virtual hardware between frames, but no other location discards bits like that.

0 comments



The new shift operator behaviour where a>>n returns a<<-n when n is negative causes an infinite loop and freezes PICO-8 when shifting by -32768:

?1<<-32768
?1>>-32768
?1>>>-32768
1
1 comment



shr(x,n) and lshr(x,n) have always handled shift values outside the 0…31 range in a peculiar way: negative values of n are treated like n&31, while values ≥ 32 always return 0 (or 0xffff.ffff for a signed shift).

But now the new >>> operator diverges from lshr by treating n ≥ 32 like n&31, so we get:

   1 >> 32 = 0
 shr(1,32) = 0
  1 >>> 32 = 1
lshr(1,32) = 0

edit: same with << which no longer behaves like shl()

1
7 comments



Just a few things I thought I’d share. Sorry if it’s gibberish and/or uninteresting to most people.

Many carts store extra data in the code section, most of the time inside strings. Some people will store structured data such as JSON or JSON-like data and let the PICO-8 compression mechanism deal with it, but you must have seen people who rolled their own compression mechanism and use custom formats such as modified base64 that takes advantage of the 59 “good” characters (i.e. the ones that needed only one byte of storage in the .p8.png format instead of two).

-- custom base64 data
local data = "e*!jfg57yfl+[7dmrin_bt#0/g6!1y68(.xh.ata_kn3j7!un_s+jn5..a)s8xi/ou0/{ff)ec}["

Such base64 data encodes 6 bits of information per character, and requires an average of 8.625 bits per character on the old format cartridge (pre-0.2.0). It means that this “alphabet” gave us about 5.565 bits of information per byte on the cartridge (8 * 6 / 8.625).

[ Continue Reading.. ]

4
16 comments



Not really a bug, but I accidentally discovered that '!' now gets replaced with 'self' at runtime, except when followed by '=':

> !ish = 42
> ?selfish
42

This is a bit confusing because in other languages, ! is an operator, whereas here it behaves almost like any other letter.

1
5 comments



I made a tiny plugin that lets Tiled load .p8 cartridges, edit the map section, and save them back without breaking the code or other sections. Works great for me, so I hope you may find it interesting!

Available on GitHub: https://github.com/samhocevar/tiled-pico8

Here is what it looks like with a dark theme and the grid color set to white:

Update 2021/02/20: The plugin was ported to JavaScript for compatibility with all OSes. Requires a preview version of Tiled (1.5.0) for now.

38
10 comments





Top    Load More Posts ->