[WIP][Pine-2K][Game] ARIAT - HW


ARIAT-HW is a spinoff of the main project I started for the Punk Jam and still haven’t released yet -thought secretly working on it.

Ultimately it’ll be a collection of various tiny games taking place in the same universe, and made with the amazing Pine-2K by @FManga.

I took interest into it because it’s great to prototype core gameplays.

This post also serves as a repository for various techniques I’m using.

Minigames & Experiments

Minigame - Highways

The intended game - it’s a prototype of the Highway gameplay from the main game.
It’ll be a sidescrolling road with a moving background and obstacles.

Status: Prototype-in-mind.

Minigame - Rumble

An arena where you’re in a tank and followed by a hoard of other tanks.

Status: In progress.

2020-07-24 - BennyHill
Sorry for that music


Since 2K of code isn’t really much, I rely on the following techniques to help saving a bit of bytes.

Array & Pointer Arithmetics

Variables are great for 1-dimensional values, but I like to get 2 or 3-dimensional myself. Using more variables is just going to increase a lot the code, so how about packing them nicely into array instead?

const objectsCapacity = 5;
const objectSize = 3;
const objectsSize = objectsCapacity * objectSize * 4; // 32bits!
const objects = new Array(objectsCapacity * objectSize);
// objects' array is laid out like this:
// [update0, x0, y0, update1, x1, y1, ...];

var object0 = objects;
var object1 = objects + objectSize * 1;

// Iterating the objects.
for (var object = objects; object < objects + objectsSize; object++) {
  var objectUpdate = object[0];

  // Does this object have an update function?
  if (objectUpdate)


function updateBarrel(object) {
  // 1 & 2 are x and y.
  sprite(object[1], object[2], BarrelSprite);

A collection of simple objects.

This allows me to have a dynamic number of objects. If you got only 2 objects, like the player and an enemy it’s probably not worth it.

Function Export / Import

An interesting technique which can help you pack a bit more code.

  • exec() allows you to execute another script.
  • save() to save an array into a file.
  • file() to load a file into an array in RAM.
  • The proc doesn’t mind executing any code at any location in the RAM.
  • Because Pine2K doesn’t check types, pointer arithmetics allows you to read any function’s bytes.

All of these combined mean you can use a script to export simple functions and load them from another script!

However, those functions come with a lot of limitations:

  • You must know in advance how many bytes the function will take.
    • What’s worst is that, while unlikely, code size may at time be greater in future versions of Pine2K.
    • I couldn’t find a reliable way to calculate it.
  • The function cannot call or use most symbols you’re declaring.
    • No function nor var.
    • const are probably ok, if they’re basic int or booleans. More testing to be done, but arrays are definitely out of the game.
  • The function can however call any standard Pine2K function, such as sprite() or exit().
  • Because it’s generated from a script, it will never get higher or equal to 2kB.
  • What it won’t eat in PROGMEM, it’ll eat in RAM.
    • While this is more abundant than PROGMEM, you might still need it for graphics & other data.

You can circumvent the call limitation by passing a function to be called (as a callback) or an array of functions.

I found the following uses for those:

  • Generic pure functions, such as abs or sqrti.
    • But don’t bother with abs/min/max, you’ll consume the same amount of PROGMEM at best.
  • Simplified functions which calls multiple system calls.
  • One-time script-like functions, like setups.
    • In my arena game, I’m going to have various positions for various objects - I could use an array of int and interpret it, or I could use this.

Also, there is a little trick due to Thumb-2 to make it work on the actual Pokitto (emulator is unaffected) (Thanks @FManga for the pointers!):

  • When writing the function to an array, you have to start 1 byte before.
  • Then, when loading it, you have to offset by +1 byte.
  • This is due to how the proc recognize Thumb-2.
// Exports a function into a file.
function exportFunction(func, size, filename)
    var arrayLength = (size + 3) / 4;
    var funcArray = new Array(arrayLength);
    func--; // Thumb-2 Trick.
    for (var iI = 0; iI < arrayLength; iI++) {
        funcArray[iI] = func[iI];
    save(filename, funcArray);
    // This assumes you're not using auto-newlines (io/FORMAT).
    console("wrote "); console(arrayLength * 4); console(" into "); console(filename); console(" \n");

// Renders a sprite at the game position, with origin support and mirror on a sprite-basis, + recolor.
// position256 is assumed to be a pointer containing x, then y, whose value is actually a fixed24/8.
// spriteDef is a pointer containing the sprite, the origin in x, origin in y and a boolean of whether or not the sprite must be mirrored.
// recolor is the same recolor than sprite().
function drawSprite256(position256, spriteDef, recolor)
    sprite(position256[0] / 256 - spriteDef[1], position256[1] / 256 - spriteDef[2], spriteDef[0]);
// Using PROGMEM, I know this function takes 108 bytes.
// This is tricky, because if future versions of Pine2K somehow makes this function bigger, only parts of it will be exported.
// It's probably better to include a few more bytes just in case.
exportFunction(drawSprite256, 108, "drSp256.fun");
// After this call, drSp256.fun will contain the compiled drawSprite256.

Code to export/import a function
// Imports drawSprite256 from the sprite.
const drawSprite256 = file("drSp256.fun", null) + 1;
// PROGMEM will be less affected, however the main RAM's consumption will increase.


// An object updated with updateObject(), at the middle of the screen.
var object = [updateObject, 110 * 256, 88 * 256];
// A debug circle whose origin is at 6, 6, and not mirrored.
const DebugCircleSprite = [file("DebugCircle", 0), 6, 6, false];
// X/Y starts at 4.
drawSprite256(object + 4, DebugCircleSprite, 6)
Code to export/import a function

Great write up of the techniques you are using - will definitely help others!


I love seeing ARIAT story grow farther than just one game :slight_smile: hope to see more.

1 Like

Small update about the rumble!

I’ve been tilemaps and making them scrollable.
The tilemap itself is just a png, it’s using 2 16x16 tiles too.
I also played with window a bit to circumvent a limitation of tileshift (which clamp to 0-7, insufficient for 16x16 tiles).

2020-07-26 - Tilemap16
I’ve been craving chocolate recently, don’t mind the colors. Also, I forgot to rename the text - it’s not tileshift but updateTilemap16.

I’ve also been trying to integrate it into the rumble minigame, but unfortunately I think I ran out of global variables, so I need to pack things tighter before anything else.

Additionally I made a camera offset so it could be used with this tilemap, but it’s hard to demonstrate right now.


More tilemap fun and corruption too

  • I’ve transformed a bit my rumble script to liberate a few “symbols”.
  • I implemented the tilemap rendering on Rumble
    • As my tilemap is actually a 8bpp bitmap, I can render it directly as a minimap, which is done by maintaining B.
  • I experienced various corruption issues (most likely due to my uses of exported functions or/and pointer arithmetics) so I ended up having weird fixes in the code.
    • The whole thing is very likely to break badly when Pine gets an update.

2020-07-28 - Race & Tilemap
The rumble with minimap showing when B is pressed and everything is happening normally.

2020-07-28 - Corruption
The other rumble with the corrupted map.

2020-07-28 - Minimap Corrupted
The minimap also gets corrupted. The large band of noise seems to be the music’s buffer (512 is 12.5% of 64x64 bytes, and it gets updated less often than the rest of the game or corruption).

On the device, some corrupted tiles will restart Pine with a different font - music was also killed in the process, but otherwise the game is running fine.

Gameplay-wise, it feels fit for the racing-like gameplay of the Highways sections that’ll be later included in ARIAT. The tilemap helps a lot getting a notion of movement since the camera is now following the player.

The controls feel nice as well, I’m growing fond of it.


Sprites manipulation, linefillers and an exploration of the “road renderer”

  • I had fun with giants sprites and io("SCALE", 2), and emulated the LoRes 256 buffer.
    • At first messing up with colors.
  • Then I started created a “road renderer”, like Road Rash and similar game using a sprite as a the frame buffer.
    • It might be what will become the highways gameplay in ARIAT in the end.
    • I used a 110x88 sprite and updated it with a bit of maths to make it convincing.
    • I also used soft gray colors for faraway marks.
  • @FManga pointed out that filler was also working with custom functions, opening the door to custom linefillers.
    • It’s using the same linefiller(buffer, y, skip) signature.
    • You can replace a filler in a given slot.
    • I replaced the TILES filler with mine with a filler call.
    • It is much smoother than with a giant sprite, which is expected.
  • After porting the code to an actual linefiller, I drawn some sprites in order to add objects.
    • Basically bill boards and pine trees.
    • I used io("SCALE", 2) again so closer sprites wouldn’t be as costly in ram.
    • For faraway objects I used colored pixels.

2020-07-30 - rainbow 2020-07-30 - rainbow NB 2020-07-30 - rainbow 4b
Using a sprite as a frame-buffer - and a x+y+random() as the color. Miloslave is great for these. First one is 8bpp, low-res. Second one is 1b, hires. Third one is 4b, hires.

2020-07-30 - road
A “low-res” used with a simple road renderer. It’s a bit blocky.

2020-07-30 - road with items
Final result - linefiller-based road renderer, and scaled sprites. Don’t ask about why trees pierced the road. It’s smoother on the actual device too.

It feels satisfying. A challenge in integrating a road renderer in ARIAT is that it’s supposed to be happening in a city with tall buildings everywhere, so I’ll have to think about that somehow.

Also, I’d like to introduce a proper curve for the road, with slight bumbs too. And an actual gameplay too!


Less things today

  • I’ve come up with a structure to more easily draw billboard-type sprites and applied it to the two objects.
    • Basically it’s a list of sprite, with the apparent size.
    • When rendering a billboard, an ideal size will be computed, then the list will be checked once until a good sprite is found.
    • If the end of the list is reached, it’ll be checked a second time, but while taking in consideration that the sprite will be scaled x2.
    • This will get easier on the progmem.
  • I’m having a unit issue.
    • Game-wise, each object has a x (horizontal position) and z (depth position) coordinates.
    • X goes from -110256 to 110256, but each band is of 128 length in z.
    • I tried to fix that today but to no avail.
  • I also noticed minor clipping issues with scaled sprites.
  • Lastly, I applied the trick from @FManga to sort objects - that is, doing it by swapping two objects if they’re not correctly ordered.
    • It’s great until you travel too fast (which shouldn’t happen later).
  • I’m impressed that with ~64 sprites + the filler it’s still pretty smooth on the pokitto.

2020-07-31 - Unsorted2020-07-31 - Sorted
Left is unsorted, right is sorted. Looks much better!

2020-07-31 - 96
Lots of objects!

Upcoming things:

  • Making game units more consistent.
  • Introducing an actual curve for the track.
  • Adding a vehicle maybe

This looks so good!

1 Like