(2023-05-17) Fixing the only serious flaw in the only good "fantasy console" ---------------------------------------------------------------------------- It can become interesting how a great legacy of one platform can lead to the development of totally unrelated ones. Following the success of CHIP-8, some gamedevs decided to capitalize on it 40 years later by releasing the first product that goes by the term coined by themselves, a so-called "fantasy console" PICO-8. While the looks were implemented in the spirit of CHIP-8 (or rather its direct successors, S-CHIP and XO-CHIP), PICO-8 s not a VM in full understanding of this term, and heavily relies upon modern Web engine to execute the games' Lua code with all the API. Needless to say that the success of PICO-8 spawned a gazillion of those "fantasy consoles", both open-source and proprietary, ranging from easier to program for to straight out ridiculous. Unlike CHIP-8 though, almost none of them, including PICO-8 itself, cares about saving the computational resources of the platform it's running on, and essentially offers nothing new to the table. Except this one. Enter Pix64. It's a "fantasy console" with a very original approach to game programming itself. Instead of offering a traditional approach where code is code and data is data, Pix64 makes everything data, i.e. graphics. And the color of the shape of these graphics elements solely determine their behavior. Considering everything must fit onto a single 64x64 PNG image (hence the name), the concept is already mind-blowing. But let's see how it plays. Being an ordinary indie project, no wonder the original Pix64 is hosted at Itch ([1]) and doesn't contain full specification, only a manual which is quite limited, that is, until you look at the live examples. But, before I tell you what it took me to look at them, let me first share the missing specification compiled from the knowledge of the manual and what I saw. So, every Pix64 "cart" is a 64x64 PNG image, and "multicarts" can be created by naming the .png files with an underscore and an ordinal number, like mycart_1.png, mycart_2.png and so on, then they will be automatically played in sequence one after another. Only 6 colors are allowed to be used in any Pix64 image, and every color corresponds to a different type of object: * black (#000000) - empty/background; * white (#FFFFFF) - wall; * cyan (#00FFFF) - player; * green (#00FF00) - goal; * yellow (#FFFF00) - trigger/barrier; * red (#FF0000) - enemy. Alas, that's all that the manual says about how Pix64 works. Everything else following below has been deduced from the live Pix64 behavior and its "tutorial carts". So, let's continue with the semantics of those colors (and let me take the liberty of reordering them for you to better understand what's going on): * Black pixels are empty, which means everything can move to them. * Cyan pixels belong to the player and are movable with a D-pad (all at once), and yes, D-pad is the only form of control in this console. * Red pixels belong to the enemy. If any cyan pixel collides with any red pixel, the game is over. * White pixels are walls, and in this case a wall means an impenetrable object. No other object can cross it. * Yellow pixels are barriers/triggers/"magic" walls (as they are called in one of the tutorials). When interacting with any other pixels, they act like white walls. But, additionally, when a cyan (player's) pixel touches a continuous group of yellow pixels, this group disappears. * Green pixels are the game's goals. Their behavior is exactly like the yellow pixels, but Pix64 reports the successful completion of the game/cart when there are no green pixels left. Now, there is the last concept left: moving objects. Yes, it is possible to create objects that are moving by themselves. The movement speed and overall console framerate, by the way, are not specified anywhere in the manual or "tutorial carts", so I had to guess. And it definitely looks like 15 FPS. Anyway, a moving object of any allowed color can be drawn as an arrow, but, as far as I understood, not just any arrow, but an arrow of specific shape and size. If we assume "x" as a pixel, then the smallest of the allowed "arrows" look as follows. Up-left, up, up-right, down, down-left, down-right: xx x xx x x x x x x x x x xx xx Right, left: x x x x x x You can make these "arrows" longer in any dimension by increasing the number of pixels on the flaps. Not very convenient to implement, but I'm OK with this. But what about the semantics? Well, arrow objects are always processed as a group of pixels and obey all the semantics described above, except the following bits: * Arrow objects drawn in cyan color are not controllable with the D-pad. * When an arrow object collides with another object (arrow or non-arrow) and this collision doesn't lead to game ending or disappearance of this object, it changes its movement direction by being redrawn according to its new direction, only changing the axis where the colliding pixel has been found. I.e. if an arrow up collides with a wall directly up, it becomes an arrow down, and if a down-left arrow hits the corner wall, it becomes an up-right arrow. If, however, it only hits the left wall, it becomes a down-right arrow, and so on. Note that for both vertical/horizontal and diagonal arrow objects their linear dimensions are fully preserved upon transformation. The final important remark is that any moving pixel, regardless of how it's being moved (automatically or manually), wraps around the screen if it doesn't encounter any obstacles. And yes, that's it. That's the entire specification as I understand it. Now, even if all this sounds a bit complicated, it really isn't once you take a look at it. And now, we return to the main and the biggest issue of Pix64: it's written in .NET. On top of this, it's written using the MonoGame framework. Why?.. Just fucking why?.. And yes, the author told that the Linux build is untested, and I totally felt that when I saw it trying to find "soft_oal.dll" instead of "soft_oal.so" it should look for. I ended up launching it under WineMono, which, to be honest, really pissed me off, since the author boasted about the "cross-platformness" of the 1.2 release. But still, now that we have sorted everything out on how this console works, can we just create a normal cross-platform port of it without all the .NET's bloat and everything? Yes, we can. At first, I was really tempted to do this in JS, but then I realized this would put me into the league with all the dozens of other "fantasy consoles" out there that can't work with anything but Web interfaces and depend on a no less bloated Web browser. Well, I still plan on a JS implementation (at least for the KaiOS porting purposes), but for now I decided the following: why not reuse my fresh experience gained with POSIX AWK when writing a CHIP-8 emulator? Especially here, where there is less routine opcode work and more creative thinking on new problems. The first such problem is, of course, having to only use full blocks for pixels and to render full 64 lines. We cannot use any half-blocks or quarter-blocks since any two adjacent pixels can easily have different colors. However, 64 lines at once definitely is a problem for most terminals. What do we do to reduce them to 32? Well, we still use an upper half-block and color the upper pixel as a foreground and lower pixel as a background, whatever they are. Easy! The second problem is having to decode PNG using POSIX AWK (unlike JS where we could directly read pixel data from the canvas we have written our image to). Well, I guess it's fair to introduce the netpbm dependency here and to just convert the PNG file into a nice PPM pure-ASCII text with the png2pnm -n command, where the "-n" switch makes it generate the output in the P3 format instead of binary P6. The P3 format is extremely simple to work with: the first line is the "P3" header, the second line consists of space-separated width and height decimal values, and the third line contains the maximum color value, which is usually 255. Afterwards, a raw RGB decimal number stream follows (normally, 4 pixels per line, but that's not specified anywhere) where every number is also delimited with a whitespace. So, as you can see, this format is ideal for processing with AWK because it consists of whitespace- and newline-separated decimal values only. The third and final problem, and perhaps the most difficult one, is to properly handle the behavior of arrow objects, starting with their detection in the first place. They are like sprites, but with the rules for individual pixels still in place. And if a pixel finds its way into a larger arrow, what shall we do? I understand this is an edge case but we need to handle them somehow. Hence, we need to shape a stricter definition of what can and can't be considered an arrow in our Pix64 engine. My most rational take on this problem is like this: we determine _all_ objects, moving and non-moving, as sprites by their initial positions of adjacent same-colored pixels, and if any two of them are placed close enough to not being able to detect an arrow, it's the game author's fault only. But then, during the gameplay, if such a situation occurs, all collisions are processed as usual. Yeah, complexity has to live somewhere, remember that. And in an engine where all the logic is defined by interaction of moving arrow-like sprites, it's natural that the most complex stuff is going to be exactly there. First, I decided to move away from the naive approach of pure pixel-by-pixel recursive sprite detection and to use the "cross method" which allowed to somewhat reduce the amount of nested calls and make MAWK with its 1024-element call stack a bit more happy. Second, shape detection is also multi-stage, but once the sprite box itself is determined and the positions are sorted in order, it's rather easy to match the X coordinate pattern to determine a particular shape unambiguously, also considering that there must be a particular amount of active pixels in a valid arrow sprite. After all, the main issue with my POSIX AWK implementation was, as always, inability to get timely keyboard input, so some simplest games like race.png are really unbeatable on my current setup. All in all, AWPix is a curious prototype, but that's it, a prototype for now. As usual, I'm sharing whatever I have in my Downloads section (which now lives on a separate Gophermap instead of the main page), and the next part of the Pix64 journey will be a JS implementation for browsers and, of course, KaiOS. So... to be continued! --- Luxferre --- [1]: https://zappedcow.itch.io/pix64