Just tried both of them. They run indeed very smooth!
Butā¦ they look so tiny
Just tried both of them. They run indeed very smooth!
Butā¦ they look so tiny
Iām going to ramble on a bit about the Sprite::drawExternalMask
bug and the process I went through to solve it.
Iām going to put it in a details block because I donāt know how many people are actually interested in the ramblings of a madman, and frankly itās probably a bit boring, but maybe someone will find it useful or interesting.
I guess Arduboy2 porting isnāt as shiny as 3D rendering and emulationā¦
(Warning: these are really long.)
My first port of call was to narrow down the cause of the bug.
I donāt have a hardware debugger yet, so I had to do things the old fashioned way - inserting print statements into the code to print information onto the screen.
This can be very tedious.
First I narrowed it down to gameplay()
,
then I narrowed it down to drawObjects()
then I narrowed it down to drawObject(object)
and from there I narrowed it down to Sprites::drawExternalMask
.
I tried substituting Spritess:drawOverwrite
and SpritesB::drawExternalMask
ā¦ same result.
I then thought āah, maybe I broke it during clean upā, so I substituted the original Arduboy2 Sprites
code (minus the assembly).
Same problem, which means that for some reason code that works perfectly well on the Arduboy doesnāt work perfectly well on the Pokitto.
Next I tried commenting out drawObject
to see if everything else worked.
I discovered drawBackground()
had the same issue, coming from the exact same functions.
So I commented out drawBackground()
and suddenly the game worked - all the logic was there and working perfectly without that rendering code.
I realised that other parts of the code were still using Sprites::drawExternalMask
just fine, so I decided to log the parameters being fed into the drawing function in drawObject
(since that was the first breaking point).
At first I thought it was specific sprites causing the problem so I disabled specific object types.
Iād got to the third disabled object when I suddenly spotted a pattern.
Every time it froze, the y
input was negative.
So I inserted an if(y > -1)
above the drawing call, and sure enough everything started working.
So at this point Iād established that:
y
valuesSo I moved into the drawing functions and started logging everywhere to find the breaking point, as standard.
I manage to pinpoint the break to one exact line:
uint8_t data = Arduboy2Base::sBuffer[ofs + WIDTH];
And I discovered that actually ofs
was surprisingly high (in about the 65000 range).
I know from experience thatās usually a tell-tale sign of negative values being converted to unsigned values.
So I trace the code to ofs
's defnition.
uint16_t ofs = ((sRow * WIDTH) + x + xOffset);
I find that sRow
is defined:
const int8_t yOffset = (y % 8);
const int8_t tempSRow = (y / 8);
int8_t sRow = ((y < 0) && (yOffset > 0)) ? (tempSRow - 1) : tempSRow;
So evidently this is somehow related.
I start analysing the uint16_t ofs = ((sRow * WIDTH) + x + xOffset);
expression and logging its components.
I find that sRow = -1
, WIDTH = 128
, x = 63
and xOffset = 0
, which means (sRow * WIDTH) = -128
and x + xOffset = 63
, so obviously -128 + 63 = -65
and of course -65
converted to a uint16_t
is 65471
.
So then I come back down to uint8_t data = Arduboy2Base::sBuffer[ofs + WIDTH];
.
I think āok, so 65471 + 128 = 65599
, and obviously -65 + 128 = 63
, which cancels out the -128
from earlier (which was from sRow * WIDTH
while sRow = -1
)ā¦ā
āButā¦ā I ask myself, āthis works on Arduboy, so what would cause it to not be working now?ā
And then it hit me.
I remembered a basic law of C++ and suddenly everything made senseā¦
(There was a point where I went down the wrong path and started looking into endianness, but after establishing both devices are little endian I scrapped that idea. A small 15-20 minute red herring.)
So eventually I had isolated the problem and had a moment of inspiration.
I realised two important bits of information and put 2 and 2 together and arrived at a wonderful 4.
The first thing I realised was the solution to the thing that was bugging me - why would it work on Pokitto and not Arduboy?
The answer has to be something thatās different between the Arduboy and Pokitto, and sure enough it is.
The answer is int
.
On the Arduboy an int
is 16 bits wide.
On the Pokitto an int
is 32 bits` wide.
But if ofs
is a uint16_t
, then where is this int
?
And then I remembered one of the fundamental rules of C++.
Something that happens all the time right under everyoneās noses but it often goes unnoticed.
The answer is āinteger promotionā.
From cppreferenceās section on Numeric Promotions:
unsigned char or unsigned short can be converted to int if it can hold its entire value range, and unsigned int otherwise;
arithmetic operators do not accept types smaller than int as arguments
And on the Pokitto,uint16_t
is a type alias forunsigned short
, henceofs + WIDTH
(i.e.uint16_t + <integer literal>
) causesofs
to be promoted toint
.
On the Arduboy, with or without promotion the arithmetic is 16-bit arithmetic, thus overflow occurs, the overflow bits are discarded and the addition correctly cancels out the earlier addition of a negative.
On the Pokitto, the promotion causes 32-bit arithmetic to be used, so instead of the overflow bit being discarded, the overflow bit is kept and the result is larger than the size of the display buffer, thus resulting in a buffer overrun.
It is that buffer overrun that caused the Pokitto to halt.
With this realisation I instantly form a solution to mimic the original behaviour:
uint8_t data = Arduboy2Base::sBuffer[(ofs + WIDTH) & 0xFFFF];
Mask off the higher bits that are dropped in the Arduboyās original calculations.
To think that the addition of one single mask operation can be the difference between perfectly running code and a processor halting bug.
So what can we learn from thisā¦
There are many things to take away from this.
Type safety is important. Always know the possible ranges of types.
int
is at least 16 bits. It could be 16 bits, it could be 32 bits, it could be 64 bits.
If you want to write portable code, keep that in mind.
Be aware of integer promotion.
It can sneak up on you if you arenāt careful.
Sometimes people think their code means one thing,
but integer promotion changes its meaning and result.
Be careful of mixing signed and unsigned values.
Try not to mix them, but if you must mix them, make sure to double check the behaviour.
Always try to make type conversion explicit.
Hiding type conversion makes code harder to undestand.
If conversions arenāt explicit then sometimes you have to really look to understand whatās going on.
And lastly and most importantly:
Avoid integer overflow like the plague.
If your solution to a problem relies on overflow or underflow then you are probably tackling the problem incorrectly.
Donāt wait for overflow to happen - prevent it!
Firstly, compared to the actual Arduboy screen theyāre not that much smaller.
An Arduboy screen is about ~2.9x1.9cm.
The Arduboy emulation on Pokitto results in a ~2.3x1.2cm image (so about 6mm either side).
Secondly, they canāt exactly fit the whole screen, unless someoneās prepare to make a scaling algorithm that gives them a width of 1.5 pixels (by mixing black and white to get grey).
Thirdly, I have a magic trick up my sleve for later.
I donāt want to ruin the suprise because itās really going to be the cherry on top.
At most Iāve told 3-4 people, 2 of whom are actually partly involved in the development.
Jonne (listens to my ramblings and answers the odd question),
Vampirics (told him for a specific reason)
SpaceBruce (I know him in person so I often tell him what Iām working on),
and Filmote (I might not have told Filmote, I canāt remember).
Absolutely nobody else knows.
Iāve now properly commited the fix:
And marked this point in history with another release,
which includes the Minesweeper.bin
.
I couldnāt get the insignificant screen manipulation functions that barely 1% of Arduboy games use working today because of the effort involved in adapting my current code to incorporate the requred changesā¦
So instead of some boring old screen functions, I guess Iāll have to implement EEPROM so you people can save your highscores and stuffā¦
Soā¦ fully functional LoveRush and Minesweeper anyone? :P
LoveRush.bin (56.9 KB)
Minesweeper.bin (53.7 KB)
Just a minute, pardner!
I use a lot of time to come up with a 100% safe EEPROM handler and it seems you intend to write directly to the EEPROM.
Why? Have I failed to explain how it works and/or what are the benefits?
EDIT: this is all toungue-in-cheek ofcourse
EDIT2: I will, ofcourse, help make the safe implementation. But it is essential we donāt go back down to that level where settings get wiped out or something because everyone writes wherever they please in the EEPROM
EDIT3:
It should work something like this.
The Arduboy2 port is a fantastic contribution. Sorry for the hard comment on the EEPROM write/read bits, afterall its only a part of the problem. But I am dead serious. We will never get the EEPROM situation under control if there is no system to prevent accidental overlaps between programs. AFAIK this is a recurring problem on the Arduboy
I havenāt forgotten about the ācookieā system. All in good time.
Before I go about implementing the ācookieā system I need to inspect the ācookieā API and decide the best way to apply it.
Thereās some other bits and pieces I want to get working first.
Despite my little sidetrack with the timers earlier today
(which was primarily for testing the water of how accurate I can get the screen),
I am intending to focus on implementing the minimum amount of features required to get games running first, and then refine them afterwards.
As far as Iām aware, very few people worry about it.
Most people just accept it and remember to back up their saves.
There is actually a way to communicate with the Arduboyās bootloader over serial to get a copy of the EEPROM for backup purposes but Iāve never tried it myself.
@Mr.Blinky wrote a quick and easy Python script for it, included among his Arduboy utilities:
Thereās a video of it in action here.
I havenāt used it myself because I havenāt needed it.
Also, if you have a dig around my Minesweeper game youāll find this little gem:
Fully checksummed to detect data corruption,
and both forward and backwards compatible
(provided that new versions of a game only ever add new save fields and donāt remove old ones).
(I was intending to try to get people to adopt it for Arduboy,
but my to do heap is ever-growing.)
And in case it looks like I have no idea what Iām doing,
I assure everyone that I have a plan.
I have a āto doā list (categorised rather than ordered):
Sprites.cpp
ArduboyCore
wiring.cpp
stdlib.cpp
ArduboyAudio
ArduboyBeep
And outside of that I have various notes on paper.
(I prefer paper for notes because Iām always misplacing my .txt
files and struggling to think of names for them. A piece of paper doesnāt need a name, just some physical space to occupy.)
Just an idea. How about emulating Arduboy EEPROM by saving and loading to SD card?
I hope to get back to working on this if the interest is still there.
Today I implemented itoa
, utoa
, ltoa
and ultoa
.
Theyāre written in a way thatās portable and doesnāt have the bugs that pokItoa
, pokUtoa
and pokLtoa
have (although they might not be as small/fast).
I think there might be interests in thus. But whatās killing it is the fact that the 128*64 res makes it that not many will want to play them that small on the Pokittoā¦
I keep hearing this argument, but if you look at them side by side you realise itās not actually that much of a difference:
(Sorry these are poor quality, my camera doesnāt like artificial light.)
(Also getting the timers 10 ticks apart was completely unintentional.)
The Pokittoās screen is actually significantly larger than the Arduboyās screen,
but the individual pixels arenāt that much smaller.
At most, the Pokittoās pixels are 2/3 of the Arduboyās.
In the future it might be possible to scale it up slightly,
but ultimately itāll come down to the quality of scaling algorithm that can be developed.
Scaling algorithms arenāt easy at the best of times, but most are designed for scales of x2, x3 or x4 and what weād be looking at here is more like x1.5 or x1.6 (1.75 would be too big unless youāre happy to have some pixels chopped off and the LED indicator to be above or below the gameplay area).
If the Pokitto has enough processing power then scaling up 3x and then scaling down x0.5 might be an option, but Iāll cross that bridge when I come to it.
For now I want to focus on other things, like getting an on-screen LED indicator.
I didnāt mean to say it in a bad way. I know itās not that much smaller then on the actual Arduboy. But we have to take into account to that since the Pokitto screen is quite bigger, that makes it ālookā even smaller.
I just think that some might skip the Arduboy ports because for them, it doesnāt take advantage of the Pokitto.
I am all for Arduboy ports, mainly because it could port most of the game I made with Filmote on the pokitto, but knowing that a minority will really try them makes me want to maybe think twice about it before doing anything.
Now, if someone find a voodoo magic way to scale it well on the Pokitto or make the library adjustable in some ways to help make hybrids pokitto/Arduboy gamesā¦
I have a solution for that, but I havenāt got to that part yet.
You donāt really have to do much.
As long as an Arduboy game obeys the rules of standard C++ then all you have to do is rename the .ino
to .cpp
and hook up the library and everything works automatically.
It took me a mere 8 commits (of which 1 was initialising the repo and 1 was uploading the original files) to get CastleBoy into a playable state, and half of that was commenting out stuff I havenāt got round to implementing.
If the library had been in a fully working state then I would have only had to make only one change:
renaming CastleBoy.ino
to CastleBoy.cpp
.
Thatās what this library is going to be capable of when itās finished.
The point of this library isnāt to make porting Arduboy2 games to the Pokitto easier,
itās to make Arduboy2 games work for the Pokitto practically out of the box, no extra effort involved.
Unfortunately there will be cases where this wonāt work because people have depended too much on the Arduino environment
(i.e. -fpermissive
, the behaviour of Arduinoās .ino
file processing and GCCās compiler extensions),
but apart from those cases this library should make direct porting of Arduboy games take practically zero effort.
Like I said, thatās going to be difficult because scaling algorithms arenāt easy.
Scaling algorithms are the kind of thing computer science professors sit around writing really boring mathematical papers about.
Like I said earlier,
I think the best hope for scaling will be to use a x3 scaling algorithm and then downscale to x0.5,
but Iām not even going to think about that until Iāve fixed more of the unimplemented stuff.
If I fixed it tomorrow the games would look better,
but youād still be left with no LED, no āflashlight modeā, no sound,
so youād still have people saying they donāt want to play it.
That is equal than scaling to x1.5 like this( e.g. b/w horizontal pattern):
10101010 ==> 100100100100
I do not think it will look good.
If we do filtered scaling it will decay contrast and eat cpu.
It depends which scaling algorithm is used.
I havenāt specified anything.
It could be nearest neighbour, it could be linear, it could be bilinearā¦
But again, Iām not even thinking about that right now,
scaling is not even close to being the first thing on my todo list.
Iāve got all these functions to implement first:
And thatās not even counting the sound functions.
Donāt forget we have grayscale on Pokitto. Or we can do the interlacing I experimented with in the other thread.
My guess is the 3/2 scale can be done in one step and pretty quickly. I might try to do this once the code is open. I like experimenting with this kind of things.
Things like these could be a compile time option. Iām also thinking about additional options like different colors, or something like a āCRTā look ā maybe the bright pixels could have a small āglowā around them. Iāll try to make some mockups when I come home.
Nice postprocessing can do miracles. Old games were made with the CRT look in mind, take a look at that difference:
From this very nice article.
Iāve done the ādumb 1.5x scalingā versions on Pokitto (every second pixel 2 pixels wide).
I donāt think it looks half bad.
But thats just me
If you can do that fast enough for the full screen, that would be very nice indeed!
Technically 2/3rds is already open source* (I havenāt got round to adding avr-libcās licence because for that one Iāve been working from the documentation, not the actual source code),
but I probably wonāt be accepting any PRs until Iām done because later on Iām going to split the repo into three parts so we end up with an avr-libc implementation, an Arduino implementation and an Arduboy2 implementation.
* I didnāt exactly get a say in the matter, I modified the original source code so I have to obey the already existing licences, LGPL for Arduino and MIT for Arduboy2.
Like I say though, Iām not even thinking about the scaling until Iāve got the other stuff out of the way.
Core functionality before anything flashy.
The core libs are LGPL, no? That means you can license your code what you want if it only uses the library. The modified library is still copyleft.
Sure, I donāt want to push it, I would just try it myself and leave the code somewhere as usual. Unfortunately Iāve discovered a new game and am spending most time playing it now, not doing much coding :[