Back to Hardware

Mario

22 Dec 2018
Progress: Complete

This is page 11 of the Games Console project.

An attempt was made to implement the gameplay of the original SUPER MARIO. For this in-browser demo, use the arrow keys and hold A to run, S to jump.


When you are done playing with it, if you want to have the arrow keys back for horizontal scrolling, click this button:

This obviously isn't a full mario game but I wanted to at least get the running and jumping to feel right. Most of the rest of the stuff – bad guys, power-ups, moving platforms, the rest of the levels and so on – all feels individually achievable, with enough time. But the jumping behaviour is so easy to get wrong, it takes more than just grinding to implement it.

There are disassemblies of the game we're trying to copy. It was written in plain 6502 assembly, of course, and the community have studied it and come up with sensible labels for most of it. Although I looked through this a bit, it wasn't particularly useful. I tried a few other community-made references, I don't remember them all, but my main reference for the implementation was actually the following image, which despite the disclaimer at the start is far more useful for getting an overview of how things work than trying to study disassemblies.

This is a very large image, brace yourself for some scrolling.

A complete guide to SMB's physics engine

If you read through most of that, you should hopefully have a pretty good idea of what we're going for.

The author of the above image is Jdaster64, who now has a website called The Super Mario Files. Interesting stuff.

Comparisons

The key task was to implement the javascript prototype as a series of procedural, sequential comparisons. By this point, the code I was writing was so similar in mindset to the assembly target that the structure may even appear comical.

The vast number of comparisons suggests we need a really solid understanding of the AVR status codes and its conditional branch logic. Our main instruction is cpi, compare immediate, and its siblings cp (compare) and cpc (compare with carry), although these are certainly not the only instructions to update the status register. To "compare" means to perform a hypothetical subtraction. We're asking, what would the status register be, if I subtracted this number from that one?

Once we've done that, we then use a conditional branch instruction. Each of these mnemonics is a permutation of "branch if the status flags are in this particular combination."

The status register on an AVR looks like this.

SREG bitfields from AVR datasheet

I is the interrupt enable flag, unrelated to conditions. T is the "bit copy storage" which I think they added because there was a spare bit available. Again, it's unrelated to conditions.

H is the half-carry flag, which is only used for BCD arithmetic, we'll ignore it here.

S, V and N are called Signed, oVerflow, and Negative, and they tell us about the results of a two's complement addition or subtraction. That is, assuming that the arguments to the compare were signed. If they weren't, the S and V flags will contain junk. N will always be equal to the highest bit of the result. Remember, these status flags are updated by combinational logic, they have no idea what type of data is flowing through the ALU.

Z and C are Zero and Carry, and these are, quite frankly, the most important flags. Zero is set if the result is zero. In other words, if you want to know if two numbers are equal, you compare them (hypothetical subtraction) and the Z flag will be set if they are the same value. Carry is the 9th bit of the 8-bit operation, so if adding things causes an overflow, the carry bit is set. Or, in the case of a subtraction, even a hypothetical one, the carry bit is set if the result underflows.

That's a summary. These aren't the only things that update the flags, but the important thing is once we've got those status flags, we need to worry about which branch to use. The following table explains it.

AVR conditional branch summary

What a nightmare! This is taken from the AVR Instruction Set PDF, and one of the things I struggled with while I was trying to teach myself this is that they don't define the mathematical symbols they're using, you're just expected to know them.

⊕ (the plus within a circle, in case your font doesn't support it) represents exclusive or. This is a bit confusing because there are also + symbols in the table, which you'd think would represent addition. And when we talk about bitwise addition, that's the same as an exclusive or. In binary, 1 + 1 is 10, which if we only care about the lowest bit, is the same as doing XOR. I am pretty sure that + represents bitwise OR. By the process of elimination, the · (dot) must represent AND.

The mnemonics are bit ridiculous, there's GE (greater or equal), SH (same or higher), LT (less than), LO (lower), MI (minus (not to be confused with NE, which is Not Equal, not "Negative")), PL (plus)... it's like they were trying to make it extra difficult. The difference between GE and SH is that GE operates on signed data, SH operates on unsigned. Why they couldn't have chosen more helpful mnemonics is beyond me.

The only conditional branches that are easy to understand are the last four in the above table. BRCS is branch-if-carry-set, BRCC is branch-if-carry-clear, and so on. The only way to stay sane while implementing the fractal-like condition tree of mario's jumping behaviour is pick a method and stick to it. For the conditions I just consider all the numbers to be unsigned, and only worry about whether it's zero or if there was a carry. Pretty much all of the logic is done with BRNE, BREQ, BRCC, and BRCS.

Oscilloscope screenshot of Mario

Block ejections

Colliding with blocks has its own peculiarities. Head-banging is probably the simplest part. Mario's Y position is (arbitrarily) at his feet. If the tile a certain number of pixels above us is solid, that means our head is overlapping the block, so zero our vertical speed and round down our Y position. All the blocks are 16 pixels square, and line up with tile boundaries, so this makes things simpler.

After performing the head-bang, we send that object through an animation of Y positions, and if we'd gotten further, this would be where the block smashes or releases a mushroom or whatever.

Left and right wall collisions are not as easy. Like with Retro Racer, we want to give an impulse outwards from the block, to move the character away from it but stop when they no longer overlap, which is complex if speeds are at more than one pixel per frame. Mario physics extends this weirdly, by only impulsing up to once per frame. This is important, because if we jump into the corner of a block, we want to be smoothly pushed outwards, not suddenly kicked.

Wall ejection in Mario

The last bit of logic here is to push upwards if we're standing on a block. Like with head-banging, we want to round up our position to the tile boundary. Not only does this correctly catch us when we're falling, but it's crucial for landing horizontal jumps. As you probably know if you're a player of mario, when there are a series of closely spaced blocks, you can run along the top without needing to jump, and Mario doesn't fall because each block corner catches him and gives an upwards impulse.

Level data, scrolling and graphics

As with the other games, there are a handful of snippets of javascript in the directory which were used to extract and process the level data and the graphics. The stages have a fixed height (the screen height) but variable lengths, so the sensible solution for tile maps is to scan vertically.

Each row of the level data corresponds to one column of tile data. The part of the level shown on screen is held in SRAM, and when our X position crosses a threshold, an offset is increased, which visually shifts the screen along. When that offset reaches 16 pixels, the leftmost column of level data is deleted, every column is shifted along by one, and a new column of level data is loaded onto the right.

The tiles themselves are drawn the same way as the sprites in other games. The delay routines clearly aren't optimal, since when the screen is full the framerate visably drops, and the music slowing down makes it even more noticeable. Part of the reason I didn't persue mario much further is that it became obvious the hardware was at its limit. With faster op-amps, which aren't even that expensive, we could have sped up the drawing routines considerably, and recovered plenty of time for the game logic. Remember that the NES processor only ran at about 1.8MHz, compared to our cushy 8MHz.

OK – that's about as much as I'll explain here. What follows is the highlighted source code to the javascript prototype above, and then the assembly source of the end result. If you've read through everything so far (congrats) then I'm sure you won't want to miss the conclusion at the bottom of this page.

JS


<canvas width=256 height=224 id=cnv ></canvas>

<script>
c=document.getElementById('cnv')
ctx=c.getContext('2d');

c.width*=2;c.height*=2;ctx.scale(2,2)
ctx.mozImageSmoothingEnabled=false;
ctx.webkitImageSmoothingEnabled=false;
ctx.imageSmoothingEnabled=false;

levelData=[0,0,0,0,0,0,0,0,0,0,0,0,26,1,0,0,0,0,0,0,0,0,0,0,0,26,27,1,0,0,0,0,0,0,0,0,0,0,24,27,32,1,0,0,0,0,0,0,0,0,0,0,0,28,33,1,0,0,0,0,0,0,0,0,0,0,0,0,28,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,34,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,36,1,0,0,0,0,0,0,0,0,0,2,0,0,26,1,0,0,0,0,0,0,0,0,0,0,0,24,27,1,0,0,0,0,0,0,0,0,0,0,0,0,28,1,0,0,9,13,0,0,0,0,0,0,0,0,0,1,0,0,10,14,0,0,0,0,0,3,0,0,0,1,0,0,11,15,0,0,0,0,0,2,0,0,0,1,0,0,0,0,0,2,0,0,0,3,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,34,1,0,0,0,0,0,0,0,0,0,3,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,36,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,9,13,0,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,0,0,5,7,1,0,0,0,10,14,0,0,0,0,0,0,6,8,1,0,0,0,10,14,0,0,0,0,0,0,0,0,1,0,0,0,11,15,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,9,13,0,0,0,0,0,0,0,0,0,1,0,0,10,14,0,0,0,0,0,0,0,0,0,1,0,0,10,14,0,0,0,0,0,0,5,7,7,1,0,0,11,15,0,0,0,0,0,0,6,8,8,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,34,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,36,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,5,7,7,7,1,0,0,0,0,0,0,0,0,0,6,8,8,8,1,0,0,0,0,0,0,0,0,0,0,0,0,26,1,0,0,0,0,0,0,0,0,0,0,0,26,27,1,0,0,0,0,0,0,0,0,0,0,24,27,32,1,0,0,0,0,0,0,0,0,0,0,0,28,33,1,0,0,0,0,0,0,0,0,0,0,0,0,28,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,9,13,0,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,5,7,7,7,1,0,0,0,11,15,0,0,0,0,6,8,8,8,1,0,0,0,0,0,0,0,0,0,0,0,0,34,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,36,1,0,0,0,0,0,0,0,0,0,0,0,0,26,1,0,0,0,0,0,0,0,0,0,0,0,24,27,1,0,0,0,0,0,0,0,0,0,0,0,0,28,1,0,0,9,13,0,0,0,0,0,0,0,0,0,1,0,0,10,14,0,0,0,0,0,0,0,0,0,1,0,0,11,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,36,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,9,13,0,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,3,0,0,0,1,0,0,0,10,14,0,0,0,0,2,0,0,0,1,0,0,0,11,15,0,0,0,0,3,0,0,0,1,0,0,0,0,0,3,0,0,0,0,0,0,0,1,0,0,0,0,0,3,0,0,0,0,0,0,0,1,0,0,0,0,0,3,0,0,0,0,0,0,0,1,0,0,0,0,0,3,0,0,0,0,0,0,0,1,0,0,9,13,0,3,0,0,0,0,0,0,0,1,0,0,10,14,0,3,0,0,0,0,0,0,0,1,0,0,10,14,0,3,0,0,0,0,0,0,0,0,0,0,11,15,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,3,0,0,0,0,0,0,35,1,0,0,0,0,0,3,0,0,0,0,0,0,36,1,0,0,0,0,0,3,0,0,0,0,0,0,0,1,0,0,0,0,0,2,0,0,0,3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,26,1,0,0,0,0,0,0,0,0,0,0,0,26,27,1,0,0,0,0,0,0,0,0,0,0,24,27,32,1,0,0,0,0,0,0,0,0,0,0,0,28,33,1,0,0,0,0,0,0,0,0,0,3,0,0,28,1,0,0,0,0,0,0,0,0,0,3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,9,13,0,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,0,0,0,0,1,0,0,0,11,15,0,0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,34,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,2,0,0,0,2,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,37,1,0,0,0,0,0,0,0,0,0,2,0,29,38,1,0,0,0,0,0,0,0,0,0,0,0,30,39,1,0,0,0,0,0,0,0,0,0,0,0,0,40,1,0,0,9,13,0,0,0,0,0,0,0,0,0,1,0,0,10,14,0,0,0,0,0,0,0,0,0,1,0,0,11,15,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,34,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,3,0,0,0,0,0,0,36,1,0,0,0,0,0,3,0,0,0,0,0,0,0,1,0,0,0,9,13,3,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,0,0,0,0,1,0,0,0,11,15,0,0,0,0,0,0,0,0,1,0,0,0,0,0,3,0,0,0,0,0,0,0,1,0,0,0,0,0,2,0,0,0,3,0,0,0,1,0,0,0,0,0,2,0,0,0,3,0,0,0,1,0,0,0,0,0,3,0,0,0,0,0,0,0,1,0,0,9,13,0,0,0,0,0,0,0,0,0,1,0,0,10,14,0,0,0,0,0,0,0,0,0,1,0,0,10,14,0,0,0,0,0,0,0,0,4,1,0,0,11,15,0,0,0,0,0,0,0,4,4,1,0,0,0,0,0,0,0,0,0,0,4,4,4,1,0,0,0,0,0,0,0,0,0,4,4,4,4,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,0,0,0,35,1,0,0,0,0,0,0,0,0,0,4,4,4,4,1,0,0,0,0,0,0,0,0,0,0,4,4,4,1,0,0,0,0,0,0,0,0,0,0,0,4,4,1,0,0,0,0,0,0,0,0,0,0,0,0,4,1,0,0,0,0,0,0,0,0,0,0,0,0,26,1,0,0,0,0,0,0,0,0,0,0,0,26,27,1,0,0,0,0,0,0,0,0,0,0,24,27,32,1,0,0,0,0,0,0,0,0,0,0,0,28,33,1,0,0,0,0,0,0,0,0,0,0,0,0,4,1,0,0,0,0,0,0,0,0,0,0,0,4,4,1,0,0,0,0,0,0,0,0,0,0,4,4,4,1,0,0,0,0,0,0,0,0,0,4,4,4,4,1,0,0,0,9,13,0,0,0,0,4,4,4,4,1,0,0,0,10,14,0,0,0,0,0,0,0,0,0,0,0,0,11,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,1,0,0,0,0,0,0,0,0,0,0,4,4,4,1,0,0,0,0,0,0,0,0,0,0,0,4,4,1,0,0,0,0,0,0,0,0,0,0,0,0,4,1,0,0,0,0,0,0,0,0,0,0,0,0,36,1,0,0,0,0,0,0,0,0,0,0,0,0,26,1,0,0,0,0,0,0,0,0,0,0,0,24,27,1,0,0,0,0,0,0,0,0,0,0,0,0,28,1,0,0,9,13,0,0,0,0,0,0,0,5,7,1,0,0,10,14,0,0,0,0,0,0,0,6,8,1,0,0,11,15,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,34,1,0,0,0,0,0,0,0,0,0,3,0,0,35,1,0,0,0,0,0,0,0,0,0,3,0,0,36,1,0,0,0,0,0,0,0,0,0,2,0,0,0,1,0,0,0,9,13,0,0,0,0,3,0,0,0,1,0,0,0,10,14,0,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,0,0,0,0,1,0,0,0,11,15,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,5,7,1,0,0,9,13,0,0,0,0,0,0,0,6,8,1,0,0,10,14,0,0,0,0,0,0,0,0,4,1,0,0,10,14,0,0,0,0,0,0,0,4,4,1,0,0,11,15,0,0,0,0,0,0,4,4,4,1,0,0,0,0,0,0,0,0,0,4,4,4,4,1,0,0,0,0,0,0,0,0,4,4,4,4,4,1,0,0,0,0,0,0,0,4,4,4,4,4,4,1,0,0,0,0,0,0,4,4,4,4,4,4,4,1,0,0,0,0,0,4,4,4,4,4,4,4,4,1,0,0,0,0,0,4,4,4,4,4,4,4,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,26,1,0,0,0,0,0,0,0,0,0,0,0,26,27,1,0,0,0,0,0,0,0,0,0,0,24,27,32,1,0,0,0,0,0,0,0,0,0,0,0,28,33,1,0,0,0,0,0,0,0,0,0,0,0,0,28,1,0,0,0,16,0,0,0,0,0,0,0,0,0,1,0,0,12,17,18,19,19,19,19,19,19,19,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,9,13,0,0,0,0,0,0,0,0,1,0,0,0,10,14,0,0,0,0,0,0,0,0,1,0,0,0,11,15,0,0,0,0,0,20,22,22,1,0,0,0,0,0,0,0,0,20,21,25,22,22,1,0,0,0,0,0,0,0,0,20,22,25,31,41,1,0,0,0,0,0,0,0,0,20,23,25,22,22,1,0,0,0,0,0,0,0,0,0,0,20,22,22,1,0,0,0,0,0,0,0,0,0,0,0,0,36,1,0,0,0,0,0,0,0,0,0,0,0,0,26,1,0,0,0,0,0,0,0,0,0,0,0,24,27,1,0,0,0,0,0,0,0,0,0,0,0,0,28,1]

levelCols=211;
levelRows=14;


objects=[]

blockAnim=[-1,0,1,2,3,4,5,6,6,5,4,3,2,1] //constant

for (var j=levelRows;j--;) 
 for (var i=levelCols;i--;)
  if (object(levelData[j + levelRows*i])) objects.push([i,j,levelData[j + levelRows*i],0])


frame=0 //animation
backgroundFrame=0;
scroll=0

m_x=50
m_y=128
m_xspeed=0
m_yspeed=0
skidding=false
fastjump=false    //Jump started at > maxWalkSpeed
fasterjump=false  //Jump started at > airspeedCutoff
fastVjump=false   //Jump started at > jumpCutoff1
fasterVjump=false //Jump started at > jumpCutoff2
facingLeft=false

runcount=0

//Constants
minWalkSpeed = 1/16 + 3/256
walkAccel    = 9/256 + 8/(16*16*16)
maxWalkSpeed = 1 + 9/16
releaseDecel = 13/256
skidDecel    = 1/16 + 10/256
turnSpeed    = 9/16
maxRunSpeed  = 2 + 9/16
runAccel     = 14/256 + 4/(16*16*16)

airspeedCutoff  = 1 + 13/16
airSlowGain  = 9/256 + 8/(16*16*16)
airFastGain  = 14/256 + 4/(16*16*16)
airFastDrag  = 13/256
airSlowDrag  = 9/256 + 8/(16*16*16)

jumpSpeed    = 4
bigJumpSpeed = 5
smallUpDrag  = 2/16 
mediumUpDrag = 1/16 + 14/256
bigUpDrag    = 2/16 + 8/256
smallGravity = 7/16
medGravity   = 6/16
bigGravity   = 9/16
jumpCutoff1  = 1
jumpCutoff2  = 2 + 5/16
maxVspeed    = 4

function draw(){
  var standingOn= 
        solid(m_x,m_y+8)
    || (solid(m_x+4, m_y+8) && ((m_y+8)%16) <1+m_yspeed)
    || (solid(m_x-4, m_y+8) && ((m_y+8)%16) <1+m_yspeed) ;
  
  
  if (standingOn) {  //On the ground
  //  if (m_yspeed>0) {
    m_y=16*Math.floor((m_y+8)/16)-8
    m_yspeed=0;
  //  }
  
    var accel;
    if (keys[65]) {
      runcount=10;
      accel = runAccel
    } else {
      if (runcount>0) runcount-=1;
      accel = walkAccel;
    }

    if (keys[39]){ //Right
      if (m_xspeed<0) { //Skid
        skidding=true
        if (m_xspeed>-turnSpeed) m_xspeed=0
        else m_xspeed+=skidDecel
      } else {
        skidding=false
        facingLeft=false
        
        if (m_xspeed==0) {m_xspeed=minWalkSpeed}
        else {m_xspeed+= accel}
        if (m_xspeed> maxRunSpeed) m_xspeed= maxRunSpeed
        if (m_xspeed> maxWalkSpeed &&runcount==0) m_xspeed= maxWalkSpeed;
      }
      
    } else if (keys[37]){ //left
      if (m_xspeed>0) { //Skid
        skidding=true
        if (m_xspeed<turnSpeed) m_xspeed=0
        else m_xspeed-=skidDecel
      } else {
        skidding=false
        facingLeft=true
        
        if (m_xspeed==0) {m_xspeed=-minWalkSpeed}
        else {m_xspeed-= accel}
        if (m_xspeed<-maxRunSpeed) m_xspeed= -maxRunSpeed
        if (m_xspeed<-maxWalkSpeed &&runcount==0) m_xspeed= -maxWalkSpeed;
      }
    
    } else { //No direction pressed - decelerate
      var decel = skidding ? skidDecel : releaseDecel;
      
      if (m_xspeed> decel) m_xspeed-=decel
      else if (m_xspeed< -decel) m_xspeed+=decel
      else m_xspeed=0
      
    }

    var absxspeed = m_xspeed
    if (absxspeed<0) absxspeed=-absxspeed;
    
    if (absxspeed > jumpCutoff2) {
      fasterVjump=true
    } else if (absxspeed > jumpCutoff1) {
      fastVjump=true
    } else {
      fasterVjump=false
      fastVjump=false
    }
    fastjump = false
    fasterjump=false
    if (absxspeed > maxWalkSpeed) fastjump=true
    if (absxspeed > airspeedCutoff) fasterjump=true
    
    if (keys[83] && !prevkeys[83]) { 
      if (fasterVjump) m_yspeed = -bigJumpSpeed
      else m_yspeed = -jumpSpeed
    }
    
    
  } else {  //In midair
    if (keys[39]){ //Right
      if (m_xspeed >= maxWalkSpeed || m_xspeed <= -maxWalkSpeed) {
        m_xspeed += airFastGain
      } else {
        if (m_xspeed>0) { //If going forward - use facing var?
          m_xspeed+=airSlowGain
        } else {
          if (fasterjump) {
            m_xspeed+=airFastDrag
          } else {
            m_xspeed+=airSlowDrag
          }
        }
      }
    } else if (keys[37]) { //Left
      if (m_xspeed >= maxWalkSpeed || m_xspeed <= -maxWalkSpeed) {
        m_xspeed -= airFastGain
      } else {
        if (m_xspeed<0) { //If going forward - use facing var?
          m_xspeed-=airSlowGain
        } else {
          if (fasterjump) {
            m_xspeed-=airFastDrag
          } else {
            m_xspeed-=airSlowDrag
          }
        }
      }    
    }
    if (fastjump) {
      if (m_xspeed<-maxRunSpeed) m_xspeed= -maxRunSpeed
      if (m_xspeed> maxRunSpeed) m_xspeed=  maxRunSpeed
    } else {
      if (m_xspeed<-maxWalkSpeed) m_xspeed= -maxWalkSpeed
      if (m_xspeed> maxWalkSpeed) m_xspeed=  maxWalkSpeed
    }
    
    if (m_yspeed<0 && keys[83]) {
      if (fasterVjump) {
        m_yspeed += bigUpDrag;
      } else if (fastVjump) {
        m_yspeed += mediumUpDrag;
      } else {
        m_yspeed += smallUpDrag;
      }
    } else {
      if (fasterVjump) {
        m_yspeed += bigGravity;
      } else if (fastVjump) {
        m_yspeed += medGravity;
      } else {
        m_yspeed += smallGravity;
      }
    }
    if (m_yspeed>maxVspeed) m_yspeed=maxVspeed
    
  }
  
  m_x+=m_xspeed
  m_y+=m_yspeed
  
  
  // Wall collisions - need improvement?
  var solidLeft=solid(m_x-7,m_y),
     solidRight=solid(m_x+7,m_y);
  
  if (solidLeft &&!solidRight) {
    if (facingLeft) {
      m_xspeed=0
    }
    
    m_x+=1;
  }
  if (solidRight &&!solidLeft) {
    if (!facingLeft) {
      m_xspeed=0
    }
    
    m_x-=1
  }
  
  // Headbang
  var i=Math.floor((scroll+m_x)/16), j=Math.floor((m_y-4)/16);
  if( solid(m_x,m_y-4)) {
    m_yspeed=0
    m_y=16*Math.floor((m_y-4)/16+1)+4
    
    for (var k in objects) if (objects[k][0]==i&&objects[k][1]==j) {
      if (objects[k][2]==2||objects[k][2]==3) objects[k][3]=blockAnim.length;
      break;
    }
  }
  
  //Scroll view
  if (m_x<8) {m_x=8;m_xspeed=0}
  if (m_x>90) {var a= (m_x-90)/2; scroll+=a;m_x-=a;}  //Needs work
  
  
  frame=m_xspeed||keys[37]||keys[39]?(frame+1+Math.abs(m_xspeed*2))%48:0
  backgroundFrame=(backgroundFrame+1) %80
  
  for (var j=14;j--;)
  for (var sx=Math.floor(scroll/16),i=sx+18;i>=sx;i--) {
    if (!object(levelData[j + levelRows*i]))
    ctx.drawImage(tiles, levelData[j + levelRows*i]*16,0,16,16, i*16-Math.round(scroll),j*16, 16,16);
    else
    ctx.drawImage(tiles, 0,0,16,16, i*16-Math.round(scroll),j*16, 16,16);
  }
  for (var i in objects) {
    var x=objects[i][0]*16-scroll,y=objects[i][1]*16;
    if (x>-16 || x<256+16){
     ctx.drawImage(sprites, Math.floor(backgroundFrame/16)*16,objects[i][2]*16, 16,16, x,y -(objects[i][3]?blockAnim[--objects[i][3]]:0), 16,16);
    }
  }
  
  ctx.drawImage(sprites, (!standingOn)?80: m_xspeed||keys[37]||keys[39]?(skidding?64:16+Math.floor(frame/16)*16):0, facingLeft?16:0 ,16,16, m_x-8,m_y-7, 16,16)
  
  prevkeys=keys.slice()
  requestAnimationFrame(draw);
}

function solid(x,y){n=levelData[Math.floor((scroll+x)/16)*levelRows +Math.floor(y/16)];return (n&&n<9)}
function object(n){return (n==2||n==3)}

(tiles=new Image()).src="Tileset1.png";
tiles.onload=function(){
 (sprites=new Image()).src="MarioSprites.png";
 sprites.onload=function(){draw();}
}
keys=[]; keys[37]=keys[39]=keys[65]=keys[83]=0;
document.onkeydown=function(e){ keys[e.keyCode]=true; }
document.onkeyup = function(e){ keys[e.keyCode]=false; }

</script>

ASM

There are a lot of single-use local labels. The other day when I was looking through blargg's test roms for the gameboy, the assembler there has the functionality to use + and - as re-usable local labels, which I think is a neat feature. I don't think avrasm2.exe has that, but if it does, I didn't know about it.


### Mario.asm ###



; Constants... lots of them. Some of these are only approximate.
.equ minWalkSpeed = $0013
.equ walkAccel    = $0009 ; + .5
.equ maxWalkSpeed = $0190
.equ releaseDecel = $000D
.equ skidDecel    = $001A
.equ turnSpeed    = $0090
.equ maxRunSpeed  = $0290
.equ runAccel     = $000E ; + .25

.equ airspeedCutoff  = $01D0
.equ airSlowGain  = $0009 ; + .5
.equ airFastGain  = $000E ; + .25
.equ airFastDrag  = $000D
.equ airSlowDrag  = $0009 ; + .5

.equ jumpSpeed    = $0400
.equ bigJumpSpeed = $0500
.equ smallUpDrag  = $0020
.equ mediumUpDrag = $001E
.equ bigUpDrag    = $0028 
.equ smallGravity = $0070
.equ medGravity   = $0060
.equ bigGravity   = $0090
.equ jumpCutoff1  = $0100
.equ jumpCutoff2  = $0250
.equ maxVspeed    = $0400

; Variables
.def ctrlButtons = r2
.def prevButtons = r3

.def runcount = r4
; r5
.def scroll = r6
.def blockAnim = r7
.def m_x_L = r8
.def m_x_H = r9
.def m_y_L = r10
.def m_y_H = r11
.def m_xspeed_L = r12
.def m_xspeed_H = r13
.def m_yspeed_L = r14
.def m_yspeed_H = r15

.def m_flags = r25

; m_flags bit numbers
.equ skidding    = 0
.equ fastjump    = 1  ; Jump started at > maxWalkSpeed
.equ fasterjump  = 2  ; Jump started at > airspeedCutoff
.equ fastVjump   = 3  ; Jump started at > jumpCutoff1
.equ fasterVjump = 4  ; Jump started at > jumpCutoff2
.equ facingLeft  = 5
.equ standingOn  = 6

; more constants (for now)
.equ levelCols = 211
.equ levelRows = 14
.equ levelColsInBuffer = 15
.equ levelBufferSize = (levelRows*levelColsInBuffer)

; Memory locations
.equ frame = D_START
.equ backgroundFrame = D_START + 1
.equ levelPointerH = D_START + 2
.equ levelPointerL = D_START + 3
.equ objectsLength = D_START + 4
.equ blockAnimX = D_START + 5
.equ blockAnimY = D_START + 6
.equ levelBuffer = D_START + 7
.equ objects = levelBuffer + levelBufferSize
; object table columns
.equ obj_x = 0
.equ obj_y = 1
.equ obj_timer = 2


;/////////////////


    ;init frame counter
    ldi r16,0
    out TCNT0, r16
    ldi r16, (1<<CS02)|(1<<CS01)|(1<<CS00)
    out TCCR0, r16
    ldi r16, 128
    out OCR0, r16

  
    ; Init music
    ldi r16, HIGH(Music*2)
    sts MUSIC_START_H, r16
    ldi r16,  LOW(Music*2)
    sts MUSIC_START_L, r16
    call resetMusic
  

  ; Load first bit of level into ram
    ldi r16, HIGH(levelData1*2)
    sts levelPointerH, r16
    ldi r16,  LOW(levelData1*2)
    sts levelPointerL, r16


    ldi r18, levelColsInBuffer
loadLevelDataLoop:
    rcall loadLevelCol
    dec r18
    brne loadLevelDataLoop






  
; load all(?) objects into ram

  
    ; clear registers
    clr scroll

    clr prevButtons
    clr runcount
    clr m_x_L
    clr m_y_L
    clr m_xspeed_L
    clr m_xspeed_H
    clr m_yspeed_L
    clr m_yspeed_H
    clr m_flags
    clr blockAnim
    ldi r16,200
    mov m_x_H,r16
    ldi r16,128
    mov m_y_H,r16

    ldi r16,0
    sts backgroundFrame,r16
    sts frame,r16

  
Main:
    rcall readController

    ;//////////
    ;rjmp standingOnSolid
    ;////////

; Are we standing on something?
    cbr m_flags, (1<<standingOn) ; assume not

    mov r16,m_x_H
    mov r17,m_y_H
    subi r17, -8
    rcall checkSolid  ; check solid 8 pixels underneath
    breq standingOnSolid

    ; check to either side as well.
    mov r16,m_x_H
    mov r17,m_y_H
    subi r17, -8
    subi r16, -4
    rcall checkSolid
    breq standingOnSolidSide

    mov r16,m_x_H
    mov r17,m_y_H
    subi r17, -8
    subi r16, 4
    rcall checkSolid
    breq standingOnSolidSide

    rjmp inMidair

standingOnSolidSide:
    ; Additionally check our alignment with the tile and our vertical speed
    ; - check this.
    
    mov r16, m_y_H
    subi r16,-8
    andi r16, $F0
    mov r17, m_yspeed_H
    inc r17
    cp r16,r17
    brcc standingOnSolid
    rjmp inMidair
    

standingOnSolid:
    sbr m_flags, (1<<standingOn)  ; flag that we are on solid ground

    clr m_yspeed_H  ; empty vertical speed
    clr m_yspeed_L
    clr m_y_L

    mov r16, m_y_H
    subi r16,-8 ; is there a faster way (better mask) to do this?
    andi r16, $F0 ; align with tiles
    subi r16,8
    mov m_y_H,r16

    ; temp var
    .def accel = r24

    ; check "run" button
    sbrs ctrlButtons, ctrlB
    rjmp notHoldingRun
    ldi r16,10
    mov runcount, r16
    ldi accel, LOW(runAccel)
    rjmp checkWalkRight

notHoldingRun:
    clr r16
    cpse runcount,r16
    dec runcount
    ldi accel, LOW(walkAccel)


; Handle moving left and right
checkWalkRight:
    sbrs ctrlButtons, ctrlLeft
    rjmp checkWalkLeft

    sbrs m_xspeed_H,7 ; check sign bit of xspeed
    rjmp xSpeedPositive

    ; moving left when holding right means we're skidding
    sbr m_flags, (1<<skidding)

    ldi r16, LOW(-turnspeed)
    ldi r17,HIGH(-turnspeed)
    cp m_xspeed_L, r16
    cpc m_xspeed_H, r17
    brcs aboveTurnspeedRight
    clr m_xspeed_L
    clr m_xspeed_H
aboveTurnspeedRight:
    ldi r16, LOW(skidDecel)
    ldi r17,HIGH(skidDecel)
    add m_xspeed_L,r16
    adc m_xspeed_H,r17

    rjmp checkJump
xSpeedPositive:

    cbr m_flags, (1<<skidding)|(1<<facingLeft) ; not skidding, facing right

    clr r16
    cp m_xspeed_L, r16
    cpc m_xspeed_H, r16
    brne speedNonZeroRight
    ; if xspeed=0, start at minimum speed
    ldi r16, LOW(minWalkSpeed)
    mov m_xspeed_L,r16
    ldi r16,HIGH(minWalkSpeed)
    mov m_xspeed_H,r16
    rjmp checkMaxRunSpeedRight

speedNonZeroRight:
    clr r16
    add m_xspeed_L,accel
    adc m_xspeed_H,r16


checkMaxRunSpeedRight:
    ;if (m_xspeed> maxRunSpeed) m_xspeed= maxRunSpeed
    ldi r16, LOW(maxRunSpeed)
    ldi r17,HIGH(maxRunSpeed)
    cp  r16,m_xspeed_L
    cpc r17,m_xspeed_H
    brcc belowMaxRunSpeedRight
    mov m_xspeed_L, r16 ; above max run speed - set to max run speed.
    mov m_xspeed_H, r17

belowMaxRunSpeedRight:
    ;if (m_xspeed> maxWalkSpeed &&runcount==0) m_xspeed= maxWalkSpeed;
    tst runcount
    brne belowMaxWalkSpeedRight
    ldi r16, LOW(maxWalkSpeed)
    ldi r17,HIGH(maxWalkSpeed)
    cp  r16,m_xspeed_L
    cpc r17,m_xspeed_H
    brcc belowMaxWalkSpeedRight
    mov m_xspeed_L, r16 
    mov m_xspeed_H, r17

belowMaxWalkSpeedRight:
    rjmp checkJump  ; end of checkWalkRight


checkWalkLeft:
    sbrs ctrlButtons, ctrlRight
    rjmp groundNoDirection

    sbrc m_xspeed_H,7 ; check sign bit of xspeed
    rjmp xSpeedNegative

    ; moving right when holding left means we're skidding
    sbr m_flags, (1<<skidding)

    ldi r16, LOW(turnspeed)
    ldi r17,HIGH(turnspeed)
    cp r16,m_xspeed_L
    cpc r17,m_xspeed_H
    brcs aboveTurnspeedLeft
    clr m_xspeed_L
    clr m_xspeed_H
aboveTurnspeedLeft:
    ldi r16, LOW(skidDecel)
    ldi r17,HIGH(skidDecel)
    sub m_xspeed_L,r16
    sbc m_xspeed_H,r17

    rjmp checkJump
xSpeedNegative:

    cbr m_flags, (1<<skidding) ; not skidding
    sbr m_flags, (1<<facingLeft) ; we are indeed facing left

    clr r16
    cp m_xspeed_L, r16
    cpc m_xspeed_H, r16
    brne speedNonZeroLeft
    ; if xspeed=0, start at minimum speed
    ldi r16, LOW(-minWalkSpeed)
    mov m_xspeed_L,r16
    ldi r16,HIGH(-minWalkSpeed)
    mov m_xspeed_H,r16
    rjmp checkMaxRunSpeedLeft

speedNonZeroLeft:
    clr r16
    sub m_xspeed_L,accel
    sbc m_xspeed_H,r16


checkMaxRunSpeedLeft:
    ; if (m_xspeed<-maxRunSpeed) m_xspeed= -maxRunSpeed
    ldi r16, LOW(-maxRunSpeed)
    ldi r17,HIGH(-maxRunSpeed)
    cp  m_xspeed_L,r16
    cpc m_xspeed_H,r17
    brcc belowMaxRunSpeedLeft
    mov m_xspeed_L, r16 ; above max run speed - set to max run speed.
    mov m_xspeed_H, r17

belowMaxRunSpeedLeft:
    ;if (m_xspeed<-maxWalkSpeed &&runcount==0) m_xspeed= -maxWalkSpeed;
    tst runcount
    brne belowMaxWalkSpeedLeft
    ldi r16, LOW(-maxWalkSpeed)
    ldi r17,HIGH(-maxWalkSpeed)
    cp  m_xspeed_L,r16
    cpc m_xspeed_H,r17
    brcc belowMaxWalkSpeedLeft
    mov m_xspeed_L, r16 
    mov m_xspeed_H, r17

belowMaxWalkSpeedLeft:
    rjmp checkJump  ; end of checkWalkLeft


groundNoDirection:
    ; if we have started to skid, continue at skidding deceleration
    ldi accel, LOW(skidDecel)
    sbrs m_flags, (1<<skidding)
    ldi accel, LOW(releaseDecel)

    ; if xspeed > decel amount, subtract it, else set xspeed=0.
    sbrc m_xspeed_H,7
    rjmp gndNoDirNegative

    clr r16
    cp  accel,m_xspeed_L
    cpc r16,m_xspeed_H
    brcc gndNoDirZero
    sub m_xspeed_L,accel
    sbc m_xspeed_H,r16
    rjmp checkJump

gndNoDirNegative:
    clr r16
    clr r17
    sub r16,accel
    sbci r17,0
    cp  m_xspeed_L,r16
    cpc m_xspeed_H,r17
    brcc gndNoDirZero
    sub m_xspeed_L,r16
    sbc m_xspeed_H,r17
    rjmp checkJump

gndNoDirZero:
    clr m_xspeed_L
    clr m_xspeed_H


checkJump:

    .def abs_xspeed_H = r26
    .def abs_xspeed_L = r27
    mov abs_xspeed_H, m_xspeed_H
    mov abs_xspeed_L, m_xspeed_L
    sbrs abs_xspeed_H,7
    rjmp checkJumpCutoffs
    
    com abs_xspeed_H    ; 16 bit negation
    neg abs_xspeed_L
    sbci abs_xspeed_H,255
    
checkJumpCutoffs:
    ldi r16, LOW(jumpCutoff2)
    ldi r17,HIGH(jumpCutoff2)
    cp r16, abs_xspeed_L
    cpc r17, abs_xspeed_H
    brcc belowJumpCutoff2
    sbr m_flags, (1<<fasterVjump)
    rjmp checkFastJumpCutoffs

belowJumpCutoff2:
    ldi r16, LOW(jumpCutoff1)
    ldi r17,HIGH(jumpCutoff1)
    cp r16, abs_xspeed_L
    cpc r17, abs_xspeed_H
    brcc belowJumpCutoff1
    sbr m_flags, (1<<fastVjump)
    rjmp checkFastJumpCutoffs
    
belowJumpCutoff1:
    cbr m_flags, (1<<fastVjump)|(1<<fasterVjump)

checkFastJumpCutoffs:
    cbr m_flags, (1<<fastjump)|(1<<fasterjump)
    
    ldi r16, LOW(maxWalkSpeed)
    ldi r17,HIGH(maxWalkSpeed)
    cp r16, abs_xspeed_L
    cpc r17, abs_xspeed_H
    brcc belowJumpCutoff3
    sbr m_flags,(1<<fastjump)
    
belowJumpCutoff3:
    ldi r16, LOW(airspeedCutoff)
    ldi r17,HIGH(airspeedCutoff)
    cp r16, abs_xspeed_L
    cpc r17, abs_xspeed_H
    brcc belowJumpCutoff4
    sbr m_flags,(1<<fasterjump)
    
belowJumpCutoff4:

    sbrs ctrlButtons,ctrlA
    rjmp actuallyMove
    sbrc prevButtons,ctrlA
    rjmp actuallyMove
    
    sbrc m_flags, (1<<fasterjump)
    rjmp startFasterJump
    
    ldi r16, LOW(-jumpSpeed)
    ldi r17, HIGH(-jumpSpeed)
    mov m_yspeed_L, r16
    mov m_yspeed_H, r17
    
    rjmp actuallyMove
    
startFasterJump:
    ldi r16, LOW(-bigJumpSpeed)
    ldi r17,HIGH(-bigJumpSpeed)
    mov m_yspeed_L, r16
    mov m_yspeed_H, r17

    rjmp actuallyMove




inMidair:

; check float right
    sbrs ctrlButtons, ctrlLeft
    rjmp checkFloatLeft
    
    ; if m_xspeed >= maxWalkSpeed or m_xspeed<=-maxWalkSpeed
    ldi r16, LOW(maxWalkSpeed)
    ldi r17,HIGH(maxWalkSpeed)
    cp  r16,m_xspeed_L
    cpc r17,m_xspeed_H
    brcc belowMaxWalkSpeedFloat
    
    ldi r16, LOW(-maxWalkSpeed)
    ldi r17,HIGH(-maxWalkSpeed)
    cp  m_xspeed_L,r16
    cpc m_xspeed_H,r17
    brcc belowMaxWalkSpeedFloat
    
    rjmp aboveMaxWalkSpeedFloat
    
belowMaxWalkSpeedFloat:
    ldi r16, LOW(airFastGain)
    ldi r17,HIGH(airFastGain)
    add m_xspeed_L,r16
    adc m_xspeed_H,r17
    rjmp checkFastJump

aboveMaxWalkSpeedFloat:
    sbrc m_xspeed_H,7   ; if negative
    rjmp  floatingBackwards
    ldi r16, LOW(airSlowGain)
    ldi r17,HIGH(airSlowGain)
    add m_xspeed_L,r16
    adc m_xspeed_H,r17
    rjmp checkFastJump
    
floatingBackwards:
    sbrs m_flags, fasterjump
    rjmp notFasterJump
    ldi r16, LOW(airFastDrag)
    ldi r17,HIGH(airFastDrag)
    add m_xspeed_L,r16
    adc m_xspeed_H,r17
    rjmp checkFastJump
    
notFasterJump:
    ldi r16, LOW(airSlowDrag)
    ldi r17,HIGH(airSlowDrag)
    add m_xspeed_L,r16
    adc m_xspeed_H,r17
    rjmp checkFastJump
    
    
    
; Now do all that again but with signs reversed...
checkFloatLeft:
    sbrs ctrlButtons, ctrlRight
    rjmp checkFastJump
    
    ldi r16, LOW(maxWalkSpeed)
    ldi r17,HIGH(maxWalkSpeed)
    cp  r16,m_xspeed_L
    cpc r17,m_xspeed_H
    brcc belowMaxWalkSpeedFloatLeft
    
    ldi r16, LOW(-maxWalkSpeed)
    ldi r17,HIGH(-maxWalkSpeed)
    cp  m_xspeed_L,r16
    cpc m_xspeed_H,r17
    brcc belowMaxWalkSpeedFloatLeft
    
    rjmp aboveMaxWalkSpeedFloatLeft
    
belowMaxWalkSpeedFloatLeft:
    ldi r16, LOW(airFastGain)
    ldi r17,HIGH(airFastGain)
    sub m_xspeed_L,r16
    sbc m_xspeed_H,r17
    rjmp checkFastJump

aboveMaxWalkSpeedFloatLeft:
    sbrs m_xspeed_H,7   ; if negative
    rjmp  floatingBackwardsLeft
    ldi r16, LOW(airSlowGain)
    ldi r17,HIGH(airSlowGain)
    sub m_xspeed_L,r16
    sbc m_xspeed_H,r17
    rjmp checkFastJump
    
floatingBackwardsLeft:
    sbrs m_flags, fasterjump
    rjmp notFasterJumpLeft
    ldi r16, LOW(airFastDrag)
    ldi r17,HIGH(airFastDrag)
    sub m_xspeed_L,r16
    sbc m_xspeed_H,r17
    rjmp checkFastJump
    
notFasterJumpLeft:
    ldi r16, LOW(airSlowDrag)
    ldi r17,HIGH(airSlowDrag)
    sub m_xspeed_L,r16
    sbc m_xspeed_H,r17
    rjmp checkFastJump


; Limit maximum floating speeds 
; (constants are same as whatever they were when we were on the ground)
checkFastJump:

    sbrs m_flags, fastjump
    rjmp notFastJump
    
    sbrc m_xspeed_H,7
    rjmp cFJnegative
    
    ldi r16, LOW(maxRunSpeed)
    ldi r17,HIGH(maxRunSpeed)
    cp  m_xspeed_L,r16
    cpc m_xspeed_H,r17
    brcc cFJaboveMaxRunSpeed
    rjmp endCheckFastJump
    
cFJaboveMaxRunSpeed:
    mov m_xspeed_L,r16
    mov m_xspeed_H,r17
    rjmp endCheckFastJump
    
cFJnegative:
    ldi r16, LOW(-maxRunSpeed)
    ldi r17,HIGH(-maxRunSpeed)
    cp  r16,m_xspeed_L
    cpc r17,m_xspeed_H
    brcc cFJaboveMaxRunSpeed
    rjmp endCheckFastJump
    
notFastJump:
    sbrc m_xspeed_H,7
    rjmp cFJnegativeSlow
    
    ldi r16, LOW(maxWalkSpeed)
    ldi r17,HIGH(maxWalkSpeed)
    cp  m_xspeed_L,r16
    cpc m_xspeed_H,r17
    brcc cFJaboveMaxWalkSpeed
    rjmp endCheckFastJump
    
cFJaboveMaxWalkSpeed:
    mov m_xspeed_L,r16
    mov m_xspeed_H,r17
    rjmp endCheckFastJump
    
cFJnegativeSlow:
    ldi r16, LOW(-maxWalkSpeed)
    ldi r17,HIGH(-maxWalkSpeed)
    cp  r16,m_xspeed_L
    cpc r17,m_xspeed_H
    brcc cFJaboveMaxWalkSpeed
    rjmp endCheckFastJump

    
    
; Now handle vertical movement
endCheckFastJump:
    ; reduce gravity if holding A and moving up
    sbrs ctrlButtons,ctrlA
    rjmp movingDown
    sbrs m_yspeed_H,7   ; if yspeed<0, moving up
    rjmp movingDown
    
    sbrs m_flags, fasterVjump
    rjmp notFasterVjumpUp
    
    ldi r16, LOW(bigUpDrag)
    ldi r17,HIGH(bigUpDrag)
    add m_yspeed_L,r16
    adc m_yspeed_H,r17
    rjmp belowMaxVspeed
    
notFasterVjumpUp:
    sbrs m_flags, fastVjump
    rjmp notFastVjumpUp
    
    ldi r16, LOW(mediumUpDrag)
    ldi r17,HIGH(mediumUpDrag)
    add m_yspeed_L,r16
    adc m_yspeed_H,r17
    rjmp belowMaxVspeed
    
notFastVjumpUp:
    ldi r16, LOW(smallUpDrag)
    ldi r17,HIGH(smallUpDrag)
    add m_yspeed_L,r16
    adc m_yspeed_H,r17
    rjmp belowMaxVspeed

movingDown:
    sbrs m_flags, fasterVjump
    rjmp notFasterVjumpDown
    
    ldi r16, LOW(bigGravity)
    ldi r17,HIGH(bigGravity)
    add m_yspeed_L,r16
    adc m_yspeed_H,r17
    rjmp limitMaxVspeed
    
notFasterVjumpDown:
    sbrs m_flags, fastVjump
    rjmp notFastVjumpDown

    ldi r16, LOW(medGravity)
    ldi r17,HIGH(medGravity)
    add m_yspeed_L,r16
    adc m_yspeed_H,r17
    rjmp limitMaxVspeed
    
notFastVjumpDown:
    ldi r16, LOW(smallGravity)
    ldi r17,HIGH(smallGravity)
    add m_yspeed_L,r16
    adc m_yspeed_H,r17
    
limitMaxVspeed:
    sbrc m_yspeed_H,7
    rjmp belowMaxVspeed
    
    ldi r16, LOW(maxVspeed)
    ldi r17,HIGH(maxVspeed)
    cp  r16,m_yspeed_L
    cpc r17,m_yspeed_H
    brcc belowMaxVspeed

    mov m_yspeed_L,r16
    mov m_yspeed_H,r17
    
belowMaxVspeed: ;end of inMidair












actuallyMove:
    add m_x_L, m_xspeed_L
    adc m_x_H, m_xspeed_H
    add m_y_L, m_yspeed_L
    adc m_y_H, m_yspeed_H

    
    
    ; Wall collisions
    
    mov r16,m_x_H
    mov r17,m_y_H
    subi r16, 7
    rcall checkSolid
    brne checkSolidRight
    sbrs m_flags, facingLeft
    rjmp solidLeftFacingRight
    clr m_xspeed_H
    clr m_xspeed_L
    
solidLeftFacingRight:
    inc m_x_H
    rjmp endWallCollisions
    
checkSolidRight:
    mov r16,m_x_H
    mov r17,m_y_H
    subi r16, -7
    rcall checkSolid
    brne endWallCollisions
    sbrc m_flags, facingLeft
    rjmp solidRightFacingRight
    clr m_xspeed_H
    clr m_xspeed_L
    
solidRightFacingRight:
    dec m_x_H

    
endWallCollisions:
    
    ; Headbang
    mov r16,m_x_H
    mov r17,m_y_H
    subi r17, 4
    rcall checkSolid
    brne notBangingHead
    
    clr m_yspeed_H
    clr m_yspeed_L
    clr m_y_L
    ldi r16,4
    sub m_y_H,r16
    ldi r17,$F0
    and m_y_H,r17
    
    ldi r16,20
    add m_y_H,r16
    
    ldi r16,16
    mov blockAnim,r16
    
    mov r16,m_x_H
    sub r16,scroll
    subi r16,-16
    andi r16,$F0
    add r16, scroll
    sts blockAnimX,r16
    
    mov r16,m_y_H
    subi r16,4
    andi r16,$F0
    sts blockAnimY,r16
    
    
    
    
notBangingHead:

    
    ; limit moving backwards offscreen
    ldi r16, 232
    cp m_x_H, r16
    brcs dontLimitMovBack
    clr m_xspeed_H
    clr m_xspeed_L
    mov m_x_H, r16

dontLimitMovBack:

    ; Scroll view
    
    ; if m_x  < 150, {scroll++, m_x--}
scrollView:

    ldi r16,150
    cp m_x_H,r16
    brcc doneScrollView

    inc scroll
    inc m_x_H
    lds r17,blockAnimX
    inc r17
    sts blockAnimX,r17
    
    rjmp scrollView

doneScrollView:


    ; if scroll>=16, loadLevelCol & scroll-=16
    ldi r16,16
    cp scroll, r16
    brcs doneScrollLevel
    
    sub scroll, r16
    rcall loadLevelCol
    

doneScrollLevel:


    
    ; mario animation frame
    ;if xspeed or holding left or right...
    sbrc ctrlButtons, ctrlLeft
    rjmp increaseMarioFrame
    sbrc ctrlButtons, ctrlRight
    rjmp increaseMarioFrame
    tst m_xspeed_H
    brne increaseMarioFrame
    
    ;set frame to 0
    clr r16
    sts frame,r16
    rjmp endIncreaseFrame
    
increaseMarioFrame:
    ;increment frame (by xspeed?)
    mov r17,m_xspeed_H
    sbrc m_xspeed_H,7
    neg r17 ; get absolute xspeed
    lsr r17
    
    lds r16,frame
    inc r16
    add r16,r17 ; animate faster if moving fast
    andi r16, 0b00111111    ; mod 64
    
    cpi r16,48
    brcs dontResetMarioFrame
    ;subi r16,48
    clr r16
    
dontResetMarioFrame:
    sts frame,r16
    

endIncreaseFrame:
    
    
    lds r16,backgroundFrame
    inc r16
    cpi r16,(4*16)
    brne endIncBackgroundFrame
    clr r16
endIncBackgroundFrame:
    sts backgroundFrame,r16
    
    
    tst blockAnim
    breq noBlockAnimFrame
    dec blockAnim
noBlockAnimFrame:

    
;draw mario



    sbrc m_flags, standingOn    ;check if in midair
    rjmp drawMarioGround
    ldi ZH, HIGH(MarioSprites*2+32*5)
    ldi ZL,  LOW(MarioSprites*2+32*5)
    rjmp drawMarioCheckFacing
    
drawMarioGround:
    ldi ZH, HIGH(MarioSprites*2)
    ldi ZL,  LOW(MarioSprites*2)
    
    ldi r16,0
    cp m_xspeed_L,r16
    cpc m_xspeed_H,r16
    breq drawMarioCheckFacing   ;if not moving, draw standing
    
    lds r16,frame
    andi r16,0b00011000
    lsl r16             ; sprites are 32 bytes apart
    lsl r16
    add ZL,r16
    clr r16
    adc ZH, r16
    
    sbrs m_flags, skidding
    rjmp drawMarioCheckFacing
    ldi ZH, HIGH(MarioSprites*2+32*4)
    ldi ZL,  LOW(MarioSprites*2+32*4)

drawMarioCheckFacing:
    ; if facingleft, add 6*32=192
    sbrc m_flags, facingLeft
    rjmp drawMarioNotLeft
    ldi r16,192
    add ZL, r16
    clr r16
    adc ZH,r16
drawMarioNotLeft:

    
    mov r16,m_x_H
    mov r17,m_y_H
    subi r16,-8
    subi r17,-7
    rcall drawSprite16
    



; draw background

    ldi YH, HIGH(levelBuffer)
    ldi YL,  LOW(levelBuffer)
    
    ldi r16,16
    add r16, scroll
    ldi r22, levelColsInBuffer
drawLevelRowLoop:
    
    ldi r17,16
    ldi r21, levelRows
drawLevelColLoop:
    
    ld r18, Y+
    cpi r18, 0
    breq dontDraw
    push r16
    push r17
    
    
    tst blockAnim
    breq noHeadBang
    lds r19,blockAnimX
    ;add r19,scroll
    cp r19,r16
    brne noHeadBang
    lds r19,blockAnimY
    cp r19,r17
    brne noHeadBang
    
    ldi ZH, HIGH(BlockAnimData*2)
    ldi ZL,  LOW(BlockAnimData*2)
    add ZL, blockAnim
    ldi r19,0
    adc ZH,r19
    lpm r19,Z
    
    sub r17,r19 ; adjust Y position by loaded amount
    ;dec blockAnim
    
noHeadBang:

    cpi r18,2   ; question block - animate.
    brne drawStatic
    
    
    ldi ZH, HIGH(QuestionSprite*2)
    ldi ZL,  LOW(QuestionSprite*2)
    
    lds r19, backgroundFrame
    andi r19, $F0   ; mod 16
    lsl r19
    add ZL,r19
    ldi r19,0
    adc ZH,r19
    
    rjmp endDrawTile
    
drawStatic:

    ldi ZH, HIGH(Tileset*2)
    ldi ZL,  LOW(Tileset*2) 
    ldi r19,32
    mul r18,r19
    add ZL, r0
    adc ZH, r1
    
endDrawTile:
    rcall drawTile
    pop r17
    pop r16

dontDraw:
    
    subi r17,-16
    
    dec r21
    brne drawLevelColLoop
    subi r16,-16
    
    dec r22 
    brne drawLevelRowLoop
    

    








    
    
    mov prevButtons, ctrlButtons ; remember controller state
    


    
    call processAudio
    
    ldi r16,0
    out PORTA,r16
    out PORTC,r16
waitforframe:
    in r16, TIFR
    sbrs r16, OCF0
    rjmp waitforframe
    ldi r16, (1<<OCF0)
    out TIFR, r16
    ldi r16, 0
    out TCNT0, r16
    rjmp Main



; Function checkSolid (x=r16 pixels, y=r17 pixels)
checkSolid:

    sub r16,scroll

    ldi YH, HIGH(levelBuffer)
    ldi YL,  LOW(levelBuffer)
    
    andi r16, $F0 ; Faster than four shifts
    swap r16  ; round?
    ldi r18, levelRows
    mul r16,r18
    add YL, r0
    adc YH, r1
    
    andi r17,$F0
    swap r17
    clr r18
    add YL, r17
    adc YH, r18

    ld r16, Y
    cpi r16,0
    breq cNotSolid
    cpi r16,9
    brcc cNotSolid
    sez
    ret
cNotSolid:
    clz
    ret

;function isObject - what tiles are interactive?
isObject:
    cpi r16,2       ; question block
    breq yesObject
    cpi r16,3       ; bricks
    breq yesObject
    sez
yesObject:
    clz
    ret


; Function loadLevelCol
; Pushes a row of level data into the sram
loadLevelCol:
    lds ZH, levelPointerH
    lds ZL, levelPointerL

    ldi YH, HIGH(levelBuffer + levelBufferSize-levelRows)
    ldi YL,  LOW(levelBuffer + levelBufferSize-levelRows)

    ; shift all rows down by one
    ldi r16,((levelColsInBuffer-1)*levelRows)
loadLevelShiftLoop:
    ld r17, -Y
    std Y+levelRows, r17

    dec r16
    brne loadLevelShiftLoop

    ldi YH, HIGH(levelBuffer)
    ldi YL,  LOW(levelBuffer)

    ldi r16, levelRows      ; now add the new row at the beginning
loadLevelColLoop:
    lpm r17, Z+
    st Y+, r17
    dec r16
    brne loadLevelColLoop

    sts levelPointerH, ZH
    sts levelPointerL, ZL

    ret






; Function readController
readController:
    sbis SPSR,SPIF
    rjmp readController     ; Wait for reception complete
    in ctrlButtons,SPDR             ; Read received data
    com ctrlButtons
  
    push r16
    ldi r16,0b00000000      ; init controller read again
    out SPDR,r16
    pop r16
    ret

    
; Function drawSprite16
drawSprite16:
    ldi r20,16

drawSpriteWordRow:
    push r16
    
    lpm r19,Z+
    lpm r18,Z+
drawSpriteWordLoop:
    sbrs r18,0
    rjmp drawSpriteBit
    out PORTA, r16
    out PORTC, r17
    rcall delaySprite16
    
drawSpriteBit:
    dec r16
    lsr r19
    ror r18
    brne drawSpriteWordLoop
    cpi r19,0
    brne drawSpriteWordLoop
    inc r17
    pop r16
    
    dec r20
    brne drawSpriteWordRow
    
    ret


delaySprite16:
    push r16
    ldi r16,7
delSprite16Loop:
    dec r16
    brne delSprite16Loop
    pop r16
    ret
    
    
    



; Function drawTile
; Identical to drawSprite but with reduced delay
drawTile:
    ldi r20,16

drawTileWordRow:
    push r16
    
    lpm r19,Z+
    lpm r18,Z+
drawTileWordLoop:
    sbrs r18,0
    rjmp drawTileBit
    out PORTA, r16
    out PORTC, r17
    rcall delayTile
    
drawTileBit:
    dec r16
    lsr r19
    ror r18
    brne drawTileWordLoop
    cpi r19,0
    brne drawTileWordLoop
    inc r17
    pop r16
    
    dec r20
    brne drawTileWordRow
    
    ret


delayTile:
    push r16
    ldi r16,1
delTileLoop:
    dec r16
    brne delTileLoop
    pop r16
    ret






MarioSprites:
; left
.db 3,224, 31,240, 5,240, 29,216, 59,152, 17,248, 15,224, 3,240, 31,248, 63,252, 63,252, 63,252, 63,252, 14,112, 28,56, 60,60, 0,0, 7,192, 63,224, 11,224, 59,176, 119,48, 35,240, 31,192, 23,224, 63,240, 31,248, 15,248, 15,248, 7,124, 3,132, 7,128, 3,224, 31,240, 5,240, 29,216, 59,152, 17,248, 15,224, 3,240, 7,248, 14,248, 15,248, 15,248, 7,240, 7,224, 15,224, 1,224, 3,224, 31,240, 5,240, 29,248, 59,248, 17,248, 15,224, 3,252, 127,255, 127,119, 39,243, 63,248, 63,252, 62,62, 0,14, 0,28, 3,224, 15,248, 31,208, 62,252, 62,102, 31,244, 15,248, 31,252, 31,252, 31,252, 15,248, 15,240, 7,240, 47,224, 63,0, 30,0, 224,0, 231,192, 255,224, 235,224, 251,176, 247,48, 67,240, 63,192, 31,252, 159,254, 159,255, 255,247, 255,250, 255,252, 7,254, 0,242
;right
.db 7,192, 15,248, 15,160, 27,184, 25,220, 31,136, 7,240, 15,192, 31,248, 63,252, 63,252, 63,252, 63,252, 14,112, 28,56, 60,60, 0,0, 3,224, 7,252, 7,208, 13,220, 12,238, 15,196, 3,248, 7,232, 15,252, 31,248, 31,240, 31,240, 62,224, 33,192, 1,224, 7,192, 15,248, 15,160, 27,184, 25,220, 31,136, 7,240, 15,192, 31,224, 31,112, 31,240, 31,240, 15,224, 7,224, 7,240, 7,128, 7,192, 15,248, 15,160, 27,184, 25,220, 31,136, 7,240, 63,192, 255,254, 238,254, 207,228, 31,252, 63,252, 124,124, 112,0, 56,0, 7,192, 31,240, 11,248, 63,124, 103,124, 47,248, 31,240, 63,248, 63,248, 63,248, 31,240, 15,240, 15,224, 7,244, 0,252, 0,120, 0,7, 3,231, 7,255, 7,215, 13,223, 12,239, 15,194, 3,252, 63,248, 127,249, 255,249, 239,255, 95,255, 63,255, 127,224, 79,0


BrickSprite:
.db 255,255, 128,128, 128,128, 255,255, 8,8, 8,8, 8,8, 255,255, 128,128, 128,128, 128,128, 255,255, 8,8, 8,8, 8,8, 255,255

QuestionSprite:
.db 127,254, 128,1, 160,5, 131,225, 134,49, 134,49, 134,49, 135,1, 129,129, 129,129, 128,1, 129,129, 129,129, 160,5, 128,1, 127,254, 127,254, 255,255, 223,251, 255,255, 254,63, 247,191, 247,191, 247,159, 241,255, 253,255, 252,255, 255,255, 253,255, 220,251, 255,255, 127,254, 127,254, 255,255, 223,251, 252,31, 248,15, 241,143, 241,143, 240,159, 240,127, 252,127, 252,255, 254,127, 252,127, 220,251, 255,255, 127,254, 127,254, 255,255, 223,251, 255,255, 254,63, 247,191, 247,191, 247,159, 241,255, 253,255, 252,255, 255,255, 253,255, 220,251, 255,255, 127,254


BlockAnimData:
.db -1,0,1,2,3,4,5,6,6,5,4,3,2,1,0


Tileset:
.db 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 123,254, 134,1, 134,1, 134,1, 142,1, 122,1, 254,1, 134,1, 134,1, 134,1, 131,3, 131,15, 129,253, 129,241, 193,129, 127,126, 127,254, 128,1, 160,5, 131,225, 134,49, 134,49, 134,49, 135,1, 129,129, 129,129, 128,1, 129,129
.db 129,129, 160,5, 128,1, 127,254, 255,255, 128,128, 128,128, 255,255, 8,8, 8,8, 8,8, 255,255, 128,128, 128,128, 128,128, 255,255, 8,8, 8,8, 8,8, 255,255, 127,255, 63,255, 31,255, 15,255, 8,15, 8,15, 8,15, 8,15, 8,15, 8,15, 8,15, 15,255, 16,7, 32,3, 64,1, 128,0, 255,255, 255,255, 15,193, 111,207, 111,207
.db 111,207, 111,207, 111,207, 111,207, 111,207, 111,207, 111,207, 111,207, 111,207, 255,255, 255,252, 255,255, 255,255, 128,0, 234,0, 244,0, 234,0, 244,0, 234,0, 244,0, 234,0, 244,0, 234,0, 244,0, 234,0, 255,255, 63,255, 223,60, 223,60, 223,60, 223,60, 223,60, 223,60, 223,60, 223,60, 223,60, 223,60, 223,60
.db 223,60, 223,60, 223,60, 223,60, 223,60, 61,0, 58,0, 61,0, 58,0, 61,0, 58,0, 61,0, 58,0, 61,0, 58,0, 61,0, 58,0, 61,0, 58,0, 61,0, 58,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 224,0, 16,0, 8,0, 0,0, 6,0, 1,0, 1,0, 2,0, 3,192, 4,32, 8,24, 40,4, 80,4, 130,4, 132,98, 128,17, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0
.db 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,9, 0,21, 0,18, 0,80, 0,160, 0,128, 0,128, 0,64, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 3,192, 4,96, 8,48, 8,48, 8,16, 8,16, 4,32, 3,192, 36,0, 72,0, 144,0, 16,0, 224,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 2,0, 1,2, 67,199, 62,252, 28,48
.db 0,129, 195,70, 60,56, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,32, 0,64, 0,128, 0,0, 0,96, 0,25, 0,6, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 255,0, 254,0, 252,0, 248,0, 240,0, 224,0, 192,0, 128,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 1,128, 1,255, 1,193, 1,148, 1,182, 1,162, 1,136, 1,128, 1,227
.db 1,255, 1,254, 1,252, 1,248, 1,240, 1,224, 1,192, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 1,128, 248,15, 8,8, 8,8, 8,8, 8,8, 8,8
.db 8,8, 255,255, 128,128, 128,128, 128,128, 255,255, 8,8, 8,8, 8,8, 255,255, 0,128, 0,128, 0,128, 0,255, 0,8, 0,8, 0,8, 0,255, 0,128, 0,128, 0,128, 0,255, 0,8, 0,8, 0,8, 0,255, 128,128, 128,128, 128,128, 255,255, 8,8, 8,8, 8,8, 255,255, 128,128, 128,128, 128,128, 255,255, 8,8, 8,8, 8,8, 255,255, 128,0, 128,0
.db 128,0, 255,0, 8,0, 8,0, 8,0, 255,0, 128,0, 128,0, 128,0, 255,0, 8,0, 8,0, 8,0, 255,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 7,224, 56,28, 192,3, 248,143, 8,136, 8,136, 15,248, 8,8, 8,8, 8,8, 255,255, 128,128, 128,128, 128,128, 255,255, 8,8, 8,8, 8,8, 255,255, 128,0, 64,0, 32,0, 16,0
.db 8,0, 4,0, 2,0, 1,0, 0,128, 0,64, 0,32, 0,16, 0,8, 0,4, 0,2, 0,1, 32,0, 112,0, 112,0, 112,0, 118,0, 38,0, 6,0, 6,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,1, 0,2, 0,4, 0,8, 0,16, 0,32, 0,64, 0,128, 1,0, 2,0, 4,0, 8,0, 16,0, 32,0, 64,0, 128,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0
.db 0,0, 128,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 7,240, 60,14, 224,1, 120,31, 96,7, 64,3, 0,0, 128,1, 128,1, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,32, 0,112, 0,112, 0,112, 0,118, 0,38, 0,6
.db 0,6, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 224,0, 16,0, 8,0, 0,0, 6,0, 1,0, 1,0, 2,0, 3,192, 4,32, 8,24, 40,4, 80,4, 130,4, 132,98, 128,17, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,9, 0,21, 0,18, 0,80, 0,160, 0,128, 0,128
.db 0,64, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,9, 0,21, 0,18, 0,80, 0,160, 0,128, 0,128, 128,64, 64,0, 32,0, 16,0, 8,0, 4,0, 2,0, 1,0, 0,128, 0,64, 0,32, 0,16, 0,8, 0,4, 0,2, 0,1, 0,0, 144,0, 56,0, 56,0, 56,0, 59,0, 19,0, 3,0, 3,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,1, 0,2, 0,4, 0,8, 0,16, 0,32
.db 0,64, 0,128, 1,0, 2,0, 4,0, 8,0, 16,0, 32,0, 64,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0



levelData1:
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,26,27,1
.db 0,0,0,0,0,0,0,0,0,0,24,27,32,1
.db 0,0,0,0,0,0,0,0,0,0,0,28,33,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,28,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,1,1 ; end of temp
.db 0,0,0,0,0,0,0,0,0,0,0,0,34,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,36,1
.db 0,0,0,0,0,0,0,0,0,2,0,0,26,1
.db 0,0,0,0,0,0,0,0,0,0,0,24,27,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,28,1
.db 0,0,9,13,0,0,0,0,0,0,0,0,0,1
.db 0,0,10,14,0,0,0,0,0,3,0,0,0,1
.db 0,0,11,15,0,0,0,0,0,2,0,0,0,1
.db 0,0,0,0,0,2,0,0,0,3,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,2,0,0,34,1
.db 0,0,0,0,0,0,0,0,0,3,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,36,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,9,13,0,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,0,0,5,7,1
.db 0,0,0,10,14,0,0,0,0,0,0,6,8,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,1
.db 0,0,0,11,15,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,9,13,0,0,0,0,0,0,0,0,0,1
.db 0,0,10,14,0,0,0,0,0,0,0,0,0,1
.db 0,0,10,14,0,0,0,0,0,0,5,7,7,1
.db 0,0,11,15,0,0,0,0,0,0,6,8,8,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,34,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,36,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,5,7,7,7,1
.db 0,0,0,0,0,0,0,0,0,6,8,8,8,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,26,1
.db 0,0,0,0,0,0,0,0,0,0,0,26,27,1
.db 0,0,0,0,0,0,0,0,0,0,24,27,32,1
.db 0,0,0,0,0,0,0,0,0,0,0,28,33,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,28,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,9,13,0,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,5,7,7,7,1
.db 0,0,0,11,15,0,0,0,0,6,8,8,8,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,34,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,36,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,26,1
.db 0,0,0,0,0,0,0,0,0,0,0,24,27,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,28,1
.db 0,0,9,13,0,0,0,0,0,0,0,0,0,1
.db 0,0,10,14,0,0,0,0,0,0,0,0,0,1
.db 0,0,11,15,0,0,0,0,0,0,0,0,0,0
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,0
.db 0,0,0,0,0,0,0,0,0,0,0,0,34,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,36,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,9,13,0,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,3,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,2,0,0,0,1
.db 0,0,0,11,15,0,0,0,0,3,0,0,0,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,0,1
.db 0,0,9,13,0,3,0,0,0,0,0,0,0,1
.db 0,0,10,14,0,3,0,0,0,0,0,0,0,1
.db 0,0,10,14,0,3,0,0,0,0,0,0,0,0
.db 0,0,11,15,0,3,0,0,0,0,0,0,0,0
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,0
.db 0,0,0,0,0,0,0,0,0,0,0,0,34,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,36,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,2,0,0,0,3,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,26,1
.db 0,0,0,0,0,0,0,0,0,0,0,26,27,1
.db 0,0,0,0,0,0,0,0,0,0,24,27,32,1
.db 0,0,0,0,0,0,0,0,0,0,0,28,33,1
.db 0,0,0,0,0,0,0,0,0,3,0,0,28,1
.db 0,0,0,0,0,0,0,0,0,3,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,9,13,0,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,1
.db 0,0,0,11,15,0,0,0,0,2,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,34,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,2,0,0,0,2,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,37,1
.db 0,0,0,0,0,0,0,0,0,2,0,29,38,1
.db 0,0,0,0,0,0,0,0,0,0,0,30,39,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,40,1
.db 0,0,9,13,0,0,0,0,0,0,0,0,0,1
.db 0,0,10,14,0,0,0,0,0,0,0,0,0,1
.db 0,0,11,15,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,3,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,34,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,36,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,0,1
.db 0,0,0,9,13,3,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,1
.db 0,0,0,11,15,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,2,0,0,0,3,0,0,0,1
.db 0,0,0,0,0,2,0,0,0,3,0,0,0,1
.db 0,0,0,0,0,3,0,0,0,0,0,0,0,1
.db 0,0,9,13,0,0,0,0,0,0,0,0,0,1
.db 0,0,10,14,0,0,0,0,0,0,0,0,0,1
.db 0,0,10,14,0,0,0,0,0,0,0,0,4,1
.db 0,0,11,15,0,0,0,0,0,0,0,4,4,1
.db 0,0,0,0,0,0,0,0,0,0,4,4,4,1
.db 0,0,0,0,0,0,0,0,0,4,4,4,4,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,4,4,4,4,1
.db 0,0,0,0,0,0,0,0,0,0,4,4,4,1
.db 0,0,0,0,0,0,0,0,0,0,0,4,4,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,4,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,26,1
.db 0,0,0,0,0,0,0,0,0,0,0,26,27,1
.db 0,0,0,0,0,0,0,0,0,0,24,27,32,1
.db 0,0,0,0,0,0,0,0,0,0,0,28,33,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,4,1
.db 0,0,0,0,0,0,0,0,0,0,0,4,4,1
.db 0,0,0,0,0,0,0,0,0,0,4,4,4,1
.db 0,0,0,0,0,0,0,0,0,4,4,4,4,1
.db 0,0,0,9,13,0,0,0,0,4,4,4,4,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,0
.db 0,0,0,11,15,0,0,0,0,0,0,0,0,0
.db 0,0,0,0,0,0,0,0,0,4,4,4,4,1
.db 0,0,0,0,0,0,0,0,0,0,4,4,4,1
.db 0,0,0,0,0,0,0,0,0,0,0,4,4,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,4,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,36,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,26,1
.db 0,0,0,0,0,0,0,0,0,0,0,24,27,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,28,1
.db 0,0,9,13,0,0,0,0,0,0,0,5,7,1
.db 0,0,10,14,0,0,0,0,0,0,0,6,8,1
.db 0,0,11,15,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,34,1
.db 0,0,0,0,0,0,0,0,0,3,0,0,35,1
.db 0,0,0,0,0,0,0,0,0,3,0,0,36,1
.db 0,0,0,0,0,0,0,0,0,2,0,0,0,1
.db 0,0,0,9,13,0,0,0,0,3,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,1
.db 0,0,0,11,15,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,5,7,1
.db 0,0,9,13,0,0,0,0,0,0,0,6,8,1
.db 0,0,10,14,0,0,0,0,0,0,0,0,4,1
.db 0,0,10,14,0,0,0,0,0,0,0,4,4,1
.db 0,0,11,15,0,0,0,0,0,0,4,4,4,1
.db 0,0,0,0,0,0,0,0,0,4,4,4,4,1
.db 0,0,0,0,0,0,0,0,4,4,4,4,4,1
.db 0,0,0,0,0,0,0,4,4,4,4,4,4,1
.db 0,0,0,0,0,0,4,4,4,4,4,4,4,1
.db 0,0,0,0,0,4,4,4,4,4,4,4,4,1
.db 0,0,0,0,0,4,4,4,4,4,4,4,4,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,26,1
.db 0,0,0,0,0,0,0,0,0,0,0,26,27,1
.db 0,0,0,0,0,0,0,0,0,0,24,27,32,1
.db 0,0,0,0,0,0,0,0,0,0,0,28,33,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,28,1
.db 0,0,0,16,0,0,0,0,0,0,0,0,0,1
.db 0,0,12,17,18,19,19,19,19,19,19,19,4,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,0,1
.db 0,0,0,9,13,0,0,0,0,0,0,0,0,1
.db 0,0,0,10,14,0,0,0,0,0,0,0,0,1
.db 0,0,0,11,15,0,0,0,0,0,20,22,22,1
.db 0,0,0,0,0,0,0,0,20,21,25,22,22,1
.db 0,0,0,0,0,0,0,0,20,22,25,31,41,1
.db 0,0,0,0,0,0,0,0,20,23,25,22,22,1
.db 0,0,0,0,0,0,0,0,0,0,20,22,22,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,36,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,26,1
.db 0,0,0,0,0,0,0,0,0,0,0,24,27,1
.db 0,0,0,0,0,0,0,0,0,0,0,0,28,1





.include "audio.asm"
Music:
.include "Music/MarioMusic.asm"


.include "Bootloader.asm"



Conclusion

This is the last page of the writeup, so the conclusion is going here.

In the four years it's taken me to document this two-month project, I have incidentally authored a masterclass in procrastination.

I have improved slightly since creating this, and there is a temptation to go back and re-do it better. I suspect it would take a further four years to document though, so you won't be hearing about it any time soon.

I shall end by saying that this is not the only old project I have on my shelf which I never documented. Stay tuned for more absurdities, only on mitxela.com!