Axe Memory Mapper v1.1 allows you to look at every address (0000 through FFFF) of your calculator's RAM and their contents. (Like Calcsys' hex editor, but more Axe user friendly)-The useful part is that it shows you the offset of the bytes you are viewing from common saferam areas.
It shows both the decimal and hex value of the highlighted byte, along with the binary translation and the 8x8 sprite of the 8 bytes following the highlighted position. The pointer is displayed in decimal, hex, and L*+offset format. Navigation features include membank switching, a goto function, and a comprehensive search function for programs/appvars as well as strings of up to 8 bytes in ASCII or hex. See readme for full features list and controls
Here are some of the sprites that need to be completed for Ash:Phoenix . I would greatly appreciate any help anyone could offer Normally I like to sprite everything myself, but as you will soon see, the sheer magnitude of the amount of pixel art that needs to be
done makes this rather impractical. This list will be edited as time goes on; as more tiles arise and other tiles are finished.
Everything is in 3lvl grayscale.
Spoiler For Tiles:
All tiles are 12x12, unless otherwise noted (Alternate sizes are usually 24x24, 12x24, or 24x12. If I say any size it means one of the
four) Please try and avoid using grayscale patterns that are dithered or otherwise patterned unless absolutely necessary. If I ask for a building, what I want are: the building roof and body tiles, windows if applicable, doors, and building/roof edges if
applicable (basically the pieces I'd need to piece together any size/shape building) If I ask for an NPC, please give all 4 facing directions if you can
Tree (24x24) Tallish grass Dirt path Desert dunes Cactus Beach shoreline Beach (sand) Beach (sand with starfish or something on it) Palm tree Large rock pillar like object (like a giant tooth) (12x24) Meadow tile Elegant white building Hut/wood/log building Dark black deathly building A building with theme of your choice "Modern" building Dark dungeon floor Dungeon walls Skull pillar (12x24) Cave entrance Rubble pile Torch Ice Water Simple bridge -start of bridge, middle of bridge, and end of bridge (so 3 tiles total, I should be able to chain them to create a bridge
of any length) Swamp boggy floor tile Swampy tree (think about Dagobah, in Star Wars, where Yoda lives) (12x24 or 24x24, i don't care) Swampy wall (basically a wall with seaweed draped on it) Ashes Small, slender tree (12x24) Big willow tree (24x24) Polished hardwood floor Bookshelf (any size) Stove Epic looking stairs Polished tile floor Weathered sign Warpy portal Shack (this is a building) Elegant door (like one you'd see in a palace) Elevator door Elegant staircase (bonus points for carpeting on the stairs) Carpet Glass case of trophies (any size) Bar counter Bar stool Epic looking sword Epic sword stand to match Giant icicles Big block of ice (any size) Typical dining room chairs 24x24 fountain Lobster Road (modern) Fire hydrant
there are definitely more but I can't think of all of them right now
NPCs: Generic male Generic female Guard
Spoiler For Enemies:
All enemies are size 24x24 and generally facing either forwards or towards the left (if you're facing them). Please try to include both
the normal sprite and a "getting hit" sprite if you can
Another development tool I made to help myself figure out bugs with Ash: Phoenix...It's a memory viewer! Allows you to look at every address (0000 through FFFF) of your calculator's RAM and their contents. (Like Calcsys' hex editor, but more Axe user friendly)
Controls: Arrow keys; move cursor around 1-6: Jump to memory locations L1-L6 XT0N: Jump to variables A through theta /:Jump backwards by $1000 (4096) *:Jump forwards by $1000 -:jump backwards by $100 (256) +:jump forwards by $100 GRAPH: Goto 2 byte memory address pointed to by the current selection Clear: quit
The cursor highlights the value of the byte you are looking at in hex and in ASCII (like calcsys) In the upper left hand corner the 8x8 sprite starting at your cursor position is displayed In the lower left hand corner the memory address your cursor is at is displayed in 3 different formats: hex, decimal, and the form Ln+(something) (displays the variable you are looking at if you are looking at variables) In the bottom part of the screen there are two numbers: the value of the byte in decimal, and the value of the two-byte number little-endian (aka normal) starting at your cursor position.
Run with Asm(). Post comments/feedback/possible feature requests
As a side project, I was thinking about creating a utility for Axe programmers that would enable you to run your source code through the program, and it would spit out:
The commands and the number of times they appear in your program. The variables you use (and how many times you use them) The amount of data in the program
and possibly....(using Runer112's documentation on Axe command speed/sizes) the iterations per second of loops in your program. (This could get messy, I think maybe I would have the user instead mark off a section of code with special comments and then Axalyzer would return the iterations per second (and Tstates) of that section perhaps)
I have a basic idea of how the program will work and read tokens and stuff: it's a variation on the NPC system of Ash:Phoenix actually
Yeah so I'm just starting a topic to gather my thoughts...and you can discuss/request features here.
Originally I was just going to post this in the cage match topic, but then I realized that I want to talk about some other stuff as well.
Anyway, here is the fixed level editor that shouldn't freeze upon execution. look further down the thread for the actually-fixed editor
Also, I am looking into making a more official release within the next few weeks or so. So, 1) does anyone have any suggestions for blocks with special effects, and 2) does anyone feel like making some external levelpacks for me?
For this one too, play the Lavender town theme while reading:
"I’m what you could call a collector of bootleg Pokémon games. Pokémon Diamond & Jade, Chaos Black, etc. It’s amazing the frequency with which you can find them at pawnshops, Goodwill, flea markets, and such.
They’re generally fun; even if they are unplayable (which they often are), the mistranslations and poor quality make them unintentionally humorous.
I’ve been able to find most of the ones that I’ve played online, but there’s one that I haven’t seen any mention of. I bought it at a flea market about five years ago.
Unfortunately, when I moved two years ago, I lost the game, so I can’t provide you with screencaps. Sorry.
The game started with the familiar Nidorino and Gengar intro of Red and Blue version. However, the “press start” screen had been altered. Red was there, but the Pokémon did not cycle through. It also said “Black Version” under the Pokémon logo.
Upon selecting “New Game”, the game started the Professor Oak speech, and it quickly became evident that the game was essentially Pokémon Red Version.
After selecting your starter, if you looked at your Pokémon, you had in addition to Bulbasaur, Charmander, or Squirtle another Pokémon — “GHOST”.
The Pokémon was level 1. It had the sprite of the Ghosts that are encountered in Lavender Tower before obtaining the Sliph Scope. It had one attack — “Curse”. I know that there is a real move named curse, but the attack did not exist in Generation 1, so it appears it was hacked in.
Defending Pokémon were unable to attack Ghost — it would only say they were too scared to move. When the move “Curse” was used in battle, the screen would cut to black. The cry of the defending Pokémon would be heard, but it was distorted, played at a much lower pitch than normal. The battle screen would then reappear, and the defending Pokémon would be gone. If used in a battle against a trainer, when the Pokéballs representing their Pokemon would appear in the corner, they would have one fewer Pokéball.
The implication was that the Pokémon died.
What’s even stranger is that after defeating a trainer and seeing “Red received $200 for winning!”, the battle commands would appear again. If you selected “Run”, the battle would end as it normally does. You could also select Curse. If you did, upon returning to the overworld, the trainer’s sprite would be gone. After leaving and reentering the area, the spot [where] the trainer had been would be replaced with a tombstone like the ones at Lavender Tower.
The move “Curse” was not usable in all instances. It would fail against Ghost Pokémon. It would also fail if it was used against trainers that you would have to face again, such as your Rival or Giovanni. It was usable in your final battle against them, however.
I figured this was the gimmick of the game, allowing you to use the previously uncapturable Ghosts. And because Curse made the game so easy, I essentially used it throughout the whole adventure.
The game changed quite a bit after defeating the Elite Four. After viewing the Hall of Fame, which consisted of Ghost and a couple of very under leveled Pokémon, the screen cut to black. A box appeared with the words “Many years later…” It then cut to Lavender Tower. An old man was standing, looking at tombstones. You then realized this man was your character.
The man moved at only half of your normal walking speed. You no longer had any Pokémon with you, not even Ghost, who up to this point had been impossible to remove from your party through depositing in the PC. The overworld was entirely empty — there were no people at all. There were still the tombstones of the trainers that you used Curse on, however.
You could go pretty much anywhere in the overworld at this point, though your movement was limited by the fact that you had no Pokémon to use HMs. And regardless of where you went, the music of Lavender Town continued on an infinite loop. After wandering for a while, I found that if you go through Diglett’s Cave, one of the cuttable bushes that normally blocks the path on the other side is no longer there, allowing you to advance and return to Pallet Town.
Upon entering your house and going to the exact tile where you start the game, the screen would cut to black.
Then a sprite of a Caterpie appeared. It was the replaced by a Weedle, and then a Pidgey. I soon realized, as the Pokémon progressed from Rattata to Blastoise, that these were all of the Pokémon that I had used Curse on.
After the end of my Rival’s team, a Youngster appeared, and then a Bug Catcher. These were the trainers I had Cursed.
Throughout the sequence, the Lavender Town music was playing, but it was slowly decreasing in pitch. By the time your Rival appeared on screen, it was little more than a demonic rumble.
Another cut to black. A few moments later, the battle screen suddenly appeared — your trainer sprite was now that of an old man, the same one as the one who teaches you how to catch Pokémon in Viridian City.
Ghost appeared on the other side, along with the words “GHOST wants to fight!”.
You couldn’t use items, and you had no Pokémon. If you tried to run, you couldn’t escape. The only option was “FIGHT”.
Using fight would immediately cause you to use Struggle, which didn’t affect Ghost but did chip off a bit of your own HP. When it was Ghost’s turn to attack, it would simply say “…” Eventually, when your HP reached a critical point, Ghost would finally use Curse.
The screen cut to black a final time.
Regardless of the buttons you pressed, you were permanently stuck in this black screen. At this point, the only thing you could do was turn the Game Boy off. When you played again, “NEW GAME” was the only option — the game had erased the file.
I played through this hacked game many, many times, and every time the game ended with this sequence. Several times I didn’t use Ghost at all, though he was impossible to remove from the party. In these cases, it did not show any Pokémon or trainers and simply cut to the climactic “battle with Ghost.
I’m not sure what the motives were behind the creator of this hack. It wasn’t widely distributed, so it was presumably not for monetary gain. It was very well done for a bootleg.
It seems he was trying to convey a message; though it seems I am the sole receiver of this message. I’m not entirely sure what it was — the inevitability of death? The pointlessness of it? Perhaps he was simply trying to morbidly inject death and darkness into a children’s game. Regardless, this children’s game has made me think, and it has made me cry."
Attached is the demo of Ash:Phoenix that I've been working on for the past 3 months
Please read the readme for installation instructions (basically, just send everything in the zip to archive) . The game is close to 200k in size, but practically 1/2 of it is empty data. This means that the game will not get any larger, so be prepared for a nice, super long RPG in the final release.
The demo spans the beginning of the game through the first boss in the Wastelands, so after you beat him, there's not much else to do save restarting.
Special Thanks to Quigibo, Runer112, and the rest of Omnimaga staff/admins along with everyone else who's supported me with this project so far
Please discuss/report any bugs and game imbalance issues in this thread. I am especially concerned with the battle engine and your thoughts on how easy/hard the battles are
Known issues: if you die, the game may glitch badly. In other cases, the game will act as if nothing had happened. So...don't die Also, although I believe I've fixed this bug, visiting the shop MAY PROBABLY will cause a crash. Also, I know I misspelled "solely" in the opening sequence
This build is outdated, the current build can be found here
Nooby question: How do you sign apps ? I downloaded wabbitsign but I can't figure out how it works. (Specifically Axe generated appas that are already in .8xk format, not in .bin or .asm format) I don't want to use ducksign (because it doesn't get rid of the 16use limit, right?)
Optimization Compilation: The Quick & Dirty Guide to Optimizing in Axe
This is intended to be a guide for every Axe programmer with general optimization
For demonstration purposes, I will always use the variable A. Of course, you can do these optimizations with any other var or memory address. Most opts are either in code boxes or on bullet points.
Part I: The BASIC Stuff Many of the tricks we've learned from BASIC can also be applied to Axe. However, not all of them are applicable. Let's take a look: Boolean Stuff: As with BASIC, we can optimize
If VAR However, be warned that this may not always work with compound statements (If A and B). Because the logic operators are bitwise operations, the statement "2 or 0" will return 0. This means that unless you are certain that A and B are always boolean values (1 or 0), you need to do If A?0 and (B?0) instead of If A and B
Another random thing is to ALWAYS CLOSE YOUR QUOTES AND PARENTHESES. Unlike BASIC, the store arrow does not close them for you. Unlike BASIC, leaving them out does NOT improve the size of your program; it is unrelated to optimization but I figured I'd just stick it here since it is a good coding habit and makes code more readable.
Pre-evaluating expressions: Especially in games that heavily reference arrays throughout a section of code, it is often good both for speed and memory to pre-evaluate expressions that do not change throughout the loop. Look at this code for drawing a scrolling 16x16 tilemapper with X and Y positions in pixels:
For(A,0,11) For(B,0,7) If {Y/8+B*16+(X/8)+A+L1} Pt-On(A*8-(X^8),B*8-(Y^8),{Y/8+B*32+(X/8)+A}*8+Pic0 End End EndThere is a HUGE speed gain from simply preevaluating some of the expressions before entering the loop:
X/8->E Y/8->F X^8->I Y^8->J For(A,0,7) For(B,0,11) If {F+B*16+E+A+L1}->C Pt-On(A*8-I,B*8-J,C*8+Pic0 End End End Pixel Testing
Pixel testing can be a mean and nasty cycle stealer from many programs. But never fear, it can be optimized...a lot. Remember that we have access to the screen buffer in L6.
If you are pixel testing a constant pixel, like pxl-Test(20,20), you can more than halve the speed of this command with the following optimization:
{20*12+L6+2}^^re4 This optimization relies on the fact that the numbers can basically be pre-computed: use the following formula to derive the numbers you should use:
{Y*12+L6+(X/8)}e(X^8) So for another example, the command pxl-Test(8,1) becomes {12+L6}e1.
The speed gain from this is so great that you can even still save (although not as much) even with a variable Y value. How you treat the constant X value remains the same as before, but simply substitute in your variable Y value in the above code. So for example, pxl-Test(31,Y) becomes {Y*12+L6+3}e7.
!If A-EXP + (B-EXP) where + is the 16bit 'or' operator. Now, if you are checking the same variable for more than one possible expression, then it yields a greater optimization to do this:
If inData(A,Data(EXP1,EXP2,0)) You just have to make sure that you take care of the 0 case first, since this will return a non-zero value if the variable=0 Also, as Quigibo pointed out, this only works with constant, 8bit values.
Simple Math Stuff
If you can, always put the constants last in your expressions:CONST+A to A+CONST
Use two byte vars over one byte vars or nibbles if you can afford the free RAM; it will be slightly smaller and faster. (but only by a little) Note: As far as I can tell the speed difference is negligible. Unless you’re raycasting or something
Avoid parentheses as much as possible when writing expressions. Use Axe's left-to-right order of operations to your advantage. This is in the documentation.
A quick way to determine the sign of a value (better than >>0) is EXP//32768 It will return -1 if the value is negative, and 0 if the value is 0 or positive
The new ++ -- arguments are a smaller and faster way of doing {EXP}+/-1->{EXP}. In the old form, EXP had to be evaluated twice. Now, it only needs to be evaluated once.
One random trick is if you need to initialize a 1-byte variable to 0, the line and 0->{EXP} will be smaller than 0->{EXP}
Optimized Math
Some operations are hard-coded optimized versions that don’t use the usual arithmetic operations. A complete list of them can be found in the following spoiler which I stole from Runer112’s handy list of command speed/size
Spoiler For Optimized Math:
;Optimized Math ;----------------------------------------------- p_Add0: 0 bytes 0 cycles +0 Results in no compiled code
p_Add1: 1 byte 6 cycles +1 Tied for the smallest way to change a value in Axe
p_Add2: 2 bytes 12 cycles +2
p_Add3: 3 bytes 18 cycles +3
p_Add254: 3 bytes 16 cycles +254
p_Add255: 2 bytes 10 cycles +255
p_Add256: 1 byte 4 cycles +256 The absolute smallest and fastest way to change a value in Axe
p_Add257: 2 bytes 10 cycles +257
p_Add258: 3 bytes 16 cycles +258
p_Add510: 4 bytes 20 cycles +510
p_Add511: 3 bytes 14 cycles +511
p_Add512: 2 bytes 8 cycles +512
p_Add513: 3 bytes 14 cycles +513
p_Add514: 4 bytes 20 cycles +514
p_Add767: 4 bytes 18 cycles +767
p_Add768: 3 bytes 12 cycles +768
p_Add769: 4 bytes 18 cycles +769
p_Add1024: 4 bytes 16 cycles +1024
p_Sub0: 0 bytes 0 cycles -0 Results in no compiled code
p_Sub1: 1 byte 6 cycles -1 Tied for the smallest way to change a value in Axe
p_Sub2: 2 bytes 12 cycles -2
p_Sub3: 3 bytes 18 cycles -3
p_Sub254: 3 bytes 16 cycles -254
p_Sub255: 2 bytes 10 cycles -255
p_Sub256: 1 byte 4 cycles -256 Also the absolute smallest and fastest way to change a value in Axe
p_Sub257: 2 bytes 10 cycles -257
p_Sub258: 3 bytes 16 cycles -258
p_Sub510: 4 bytes 20 cycles -510
p_Sub511: 3 bytes 14 cycles -511
p_Sub512: 2 bytes 8 cycles -512
p_Sub513: 3 bytes 14 cycles -513
p_Sub514: 4 bytes 20 cycles -514
p_Sub767: 4 bytes 18 cycles -767
p_Sub768: 3 bytes 12 cycles -768
p_Sub769: 4 bytes 18 cycles -769
p_Sub1024: 4 bytes 16 cycles -1024
p_Mul0: 3 bytes 10 cycles *0 Same as loading the constant 0
p_Mul1: 0 bytes 0 cycles *1 Results in no compiled code
p_MulN1: 6 bytes 24 cycles *?1 Same as p_IntNeg
p_Mul2: 1 byte 11 cycles *2 Tied for the smallest way to change a value in Axe
p_Mul3: 4 bytes 30 cycles *3
p_Mul4: 2 bytes 22 cycles *4
p_Mul5: 5 bytes 41 cycles *5
p_Mul6: 5 bytes 41 cycles *6
p_Mul7: 6 bytes 52 cycles *7
p_Mul8: 3 bytes 33 cycles *8
p_Mul9: 6 bytes 52 cycles *9
p_Mul10: 6 bytes 52 cycles *10
p_Mul12: 6 bytes 52 cycles *12
p_Mul16: 4 bytes 44 cycles *16
p_Mul32: 5 bytes 55 cycles *32
p_Mul64: 5 bytes 144 cycles *64
p_Mul128: 5 bytes 170 cycles *128
p_Mul255: 6 bytes 31 cycles *255
p_Mul256: 3 bytes 11 cycles *256
p_Mul257: 3 bytes 12 cycles *257
p_Mul258: 4 bytes 23 cycles *258
p_Mul260: 5 bytes 34 cycles *260
p_Mul264: 6 bytes 45 cycles *264
p_Mul512: 4 bytes 22 cycles *512
p_Mul513: 6 bytes 37 cycles *513
p_Mul514: 4 bytes 23 cycles *514
p_Mul516: 5 bytes 34 cycles *516
p_Mul520: 6 bytes 45 cycles *520
p_Mul768: 6 bytes 23 cycles *768
p_Mul1024: 5 bytes 33 cycles *1024
p_Mul1028: 5 bytes 34 cycles *1028
p_Mul1032: 6 bytes 45 cycles *1032
p_Mul2048: 6 bytes 44 cycles *2048
p_Mul2056: 6 bytes 45 cycles *2056
p_Mul4096: 5 bytes 290 cycles *4096
p_Mul8192: 5 bytes 314 cycles *8192
p_Mul16384: 5 bytes 338 cycles *16384
p_Mul32768: 6 bytes 24 cycles *32768
p_Mul65535: 6 bytes 24 cycles *65535 Same as p_MulN1
p_Div0: 3 bytes 10 cycles /0 Same as loading the constant 65535
p_Div1: 0 bytes 0 cycles /1 Results in no compiled code
p_Div2: 4 bytes 16 cycles /2
p_Div10: 3 bytes ~1896 cycles /10 n*3+1878 cycles, n=number of set bits in result
p_Div128: 5 bytes 27 cycles /128
p_Div256: 3 bytes 11 cycles /256
p_Div512: 5 bytes 19 cycles /512
p_Div32768: 5 bytes 27 cycles /32768
p_SDiv0: 3 bytes 10 cycles //0 **NOT MATHEMATICALLY CORRECT** Same as loading the constant 65535
p_SDiv1: 0 bytes 0 cycles //1 Results in no compiled code
p_SDiv2: 4 bytes 16 cycles //2
p_SDiv64: 6 bytes 38 cycles //64
p_SDiv128: 4 bytes 23 cycles //128
p_SDiv256: 5 bytes 20 cycles //256
p_SDiv512: 6 bytes 38 cycles //512
p_SDiv16384: 6 bytes 38 cycles //16384
p_SDiv32768: 3 bytes 26 cycles //32768
p_Mod1: 3 bytes 10 cycles ^1 Same as loading the constant 0
p_Mod2: 5 bytes 20 cycles ^2
p_Mod4: 6 bytes 22 cycles ^4
p_Mod8: 6 bytes 22 cycles ^8
p_Mod16: 6 bytes 22 cycles ^16
p_Mod32: 6 bytes 22 cycles ^32
p_Mod64: 6 bytes 22 cycles ^64
p_Mod128: 4 bytes 15 cycles ^128
p_Mod256: 2 bytes 7 cycles ^256
p_Mod512: 4 bytes 15 cycles ^512
p_Mod1024: 4 bytes 15 cycles ^1024
p_Mod2048: 4 bytes 15 cycles ^2048
p_Mod4096: 4 bytes 15 cycles ^4096
p_Mod8192: 4 bytes 15 cycles ^8192
p_Mod16384: 4 bytes 15 cycles ^16384
p_Mod32768: 2 bytes 8 cycles ^32768
p_EQN512: 9 bytes 44 cycles =?512
p_EQN256: 8 bytes 40 cycles =?256
p_EQN2: 8 bytes 40 cycles =?2
p_EQN1: 7 bytes 36 cycles =?1
p_EQ0: 7 bytes 36 cycles =0
p_EQ1: 7 bytes ~29 cycles =1 24 cycles if true, 34 cycles if false
p_EQ2: 8 bytes ~33 cycles =2 28 cycles if true, 38 cycles if false
p_EQ256: 8 bytes 40 cycles =256
p_EQ512: 9 bytes 44 cycles =512
p_NEN512: 9 bytes ~31 cycles ??512 33 cycles if true, 28 cycles if false
p_NEN256: 8 bytes ~27 cycles ??256 29 cycles if true, 24 cycles if false
p_NEN2: 8 bytes 40 cycles ??2
p_NEN1: 7 bytes 36 cycles ??1
p_NE0: 7 bytes ~23 cycles ?0 25 cycles if true, 20 cycles if false
p_NE1: 8 bytes ~27 cycles ?1 29 cycles if true, 24 cycles if false
p_NE2: 9 bytes ~31 cycles ?2 33 cycles if true, 28 cycles if false
p_NE256: 8 bytes ~27 cycles ?1 29 cycles if true, 24 cycles if false
p_NE512: 9 bytes ~31 cycles ?2 33 cycles if true, 28 cycles if false
p_GE0: 3 bytes 10 cycles ?0 Same as loading the constant 1
p_GT65535: 3 bytes 10 cycles >65535 Same as loading the constant 0
p_LE65535: 3 bytes 10 cycles ?65535 Same as loading the constant 1
p_LT0: 3 bytes 10 cycles <0 Same as loading the constant 0
p_GE1: 7 bytes ~23 cycles ?1 25 cycles if true, 20 cycles if false
p_GT0: 7 bytes ~23 cycles >0 25 cycles if true, 20 cycles if false
p_LE0: 7 bytes 36 cycles ?0
p_LT1: 7 bytes 36 cycles <1
p_SGE0: 4 bytes 32 cycles ??0
p_SLT0: 5 bytes 27 cycles <<0
p_GetBit0: 5 bytes 27 cycles ee0
p_GetBit1: 6 bytes 38 cycles ee1
p_GetBit2: 7 bytes 37 cycles ee2
p_GetBit3: 7 bytes 37 cycles ee3
p_GetBit4: 7 bytes 37 cycles ee4
p_GetBit5: 7 bytes 37 cycles ee5
p_GetBit6: 7 bytes 26 cycles ee6
p_GetBit7: 6 bytes 22 cycles ee7
p_GetBit8: 5 bytes 27 cycles ee8 e0
p_GetBit9: 6 bytes 38 cycles ee9 e1
p_GetBit10: 7 bytes 37 cycles ee10 e2
p_GetBit11: 7 bytes 37 cycles ee11 e3
p_GetBit12: 7 bytes 37 cycles ee12 e4
p_GetBit13: 7 bytes 37 cycles ee13 e5
p_GetBit14: 7 bytes 26 cycles ee14 e6
p_GetBit15: 5 bytes 20 cycles ee15 e7
Part III: "HL is the Ans of Axe" -Runer112
Like BASIC, Axe also has an Ans-counterpart, a register called HL. It is written to every time you "load" an expression or constant. This yields a multitude of small optimizations: -Duplicate Arguments:
Text(0,,PTR)See how that works? When Axe goes to parse the second argument of Text(, it would normally load the second argument into HL and then use that as the Y position of the text. However, in the optimized piece it doesn't find anything to load for the second argument, so it just uses what was already in HL, namely, 0.
This, extended, yields further optimization opportunities: -More Omitted Arguments: Not only does loading arguments in commands write to HL, so does loading arguments in normal math and control structure operations. This means that we can omit lots of things, saving more space and speed. For example...
If A>3: ?BBut wait! What if you want to use this optimization on an equality check? Normally, If A=1:?B would work, but if we wanted to optimize the equality check as well, it doesn't work quite right. !If A-1 returns zero if true (or rather, if false), so you might then think that it's probably best to skip this particular optimization and go
:.SMILE :[004466000000817E]->Pic1 :DiagnosticOff :0->X->Y :Repeat getkey(15) :ClrDraw :getKey(3)-getKey(2)+X //check getkeys for X. But we're not going to store the value just yet... :!If +1 //First, check if X is negative one; that is, we'll check if X+1=0 :+1 //if so, add one to make the value in HL 1 (!If statements return 0 if true) :End :-1 //subtract 1 since we added 1 earlier :Pt-On(min(,88)?X,getKey(1)-getKey(4)+Y+(=?1)min(,56)?Y,Pic1) //now we'll use the smallest value of either HL or 88 as the X position. For Y, we'll first handle keypresses, then add 1 (the boolean value of y=-1) if Y=-1. Then we'll use the smallest value of either HL or 56 as the Y position. :DispGraph :End I can't post every possible abuse of HL here: there are so many ways to use it. Study these examples to see how they work and you can apply it in your own programs.
Much of HL abuse has been obsoleted by Axe's peephole optimizer. However, you can still make use of some of these tricks. While the weird syntax is not always necessary, remember to arrange your code and operations such that the peephole optimizer can target it. I left the section somewhat intact so that you can look through it to understand the concept; the optimizer is useless if your code isn’t written intelligently.
Part IV: Subroutines Subroutines probably are capable of saving the most space than any other type of optimization. And they are easy to use, too. There are only a couple rules of thumb to follow: 1. If you are rewriting a section of code more than once (and it is more than just one command), best put it into a subroutine. 2. If you aren't, then don't put it in a subroutine.
Routine calls are around 3-5 bytes, and an additional 3 bytes for every argument you load. Using the recursive subroutine feature that saves your arguments (sub(LBLr....) costs you 15 bytes per argument.
Tail Call Optimization This is lesser known and lesser used, but it is still worth sticking here I guess: If you are calling subroutines from a subroutine as the last line, then you can use a process known as Tail Call Optimization to change this:
Lbl A: Stuff : Goto B The Return is not needed because you end up "stealing" subroutine B's return instead of having to return to A and then return again to the main program.
Part V: Speed over Size These are all optimizations for aggressive speed gain at the expense of size.
Short Circuit Evaluation: In most cases, it yields a (rather impressive) speed gain to change
If EXP1 : If EXP2 Make sure to have EXP1 (the outside If block) be the expression that is less likely to be true to gain the most speed.
Factoring Constants (and powers of 2); When multiplying and dividing by multiples of powers of 2, it yields an optimization to factor the number first.
With powers of two, this is only advantageous with *64 and *128 (Check the auto-optimizations list for the list of numbers that this applies to).
Part VI: Miscellaneous All the stuff I couldn't fit into another category...
If you need to draw horizontal or vertical lines, use the Rect() function with a width or height of 1 instead: Line(0,,20,0) to Rect(0,,20,1) This improves both speed and size
Use ElseIf and DS<( if you can. That's why they're there. They are surprisingly useful.
The Text() and Bitmap() commands are slow because they are TI's routines. Avoid them if possible. (I know, it's hard not to use Text(...but do your best ).
Note about the Fill() command: If you are initializing less than 10 bytes of data, it is better to just use ->{}r 5 times instead.
The new DispGraphClrDraw command is the same as DispGraph:ClrDraw but twice as fast. Use it.
However, remember each new command you use in Axe creates its own subroutine in your program. Thus, (for size reasons) it’s best to avoid using Rect if you've already used Line and Pt-On instead of Pt-Change, (and preinvert the sprite).
Quote from: Quigibo
One major optimization that usually gets ignored is recycling large axe commands. Axe is not like BASIC and so each command needs its own subroutine to add to your program. For instance, lets say you use Pt-On() and Pt-Mask() in your code. Each one has to bring its own 100+ byte subroutine into your program. But you can probably get away with having just a Pt-Mask routine, recycling it to act like Pt-On by simply adding a 2nd layer to your sprite which is only 8 bytes extra instead of 100ish. Or you could do the opposite too and only have Pt-On() and Pt-Change() to manually change both buffers at once. This generally reduces the overall size of the program by a lot when you use the routines only once or very rarely in your code. And I don't mean calling it rarely, it could be the most used routine in your code, I just mean rarely appears in your source.
...And that's all I've got. Happy coding! Let me know if you have any questions, comments, additions, or corrections and I'll be happy to accommodate you This post will also be updated with any new major optimizations found.
Also special thanks to Quigibo and Runer112 who are the main contributors of all these optimizations
Now, I know that everyone's all like: blargh imma wanting some of that multipage app goodness, and Quigibo's like....not practical.
If I understand correctly, it's not that compiling a multipage app is difficult because it's large, but it's that creating the instructions to swap pages and that knowing when and where to swap pages in an arbitrary program makes it difficult.
Now, simplifying, the problem would be would that the parser doesn't have enough information (that only the programmer would know) to accurately create the multipage app.
So my idea was, to have integrate a compiler command to indicate the different pages :| There would probably two commands, one to call the 2nd page and one that is some kind of modification to the #include (prgmXXX) function, where that program would be parsed into the 2nd page. Then, in the app's code, when the program needs to swap pages, there would just be some command in the 'mother app' to switch pages. For example, if you wanted to mix a battle engine (in prgmBATTLE) with a tilemapper, the code might look like this:
blah blah blah tilemapper code <when a battle occurs> instruction to swap to 2nd page of app more code blah ...
instruction to parse prgmBATTLE as the second page of the app
Basically, what this could do would be to enable "packaging" of a second app together with the first app, sort of like what Builderboy's virtual-page program does, but only with real app pages. While this method would in no way be close to producing the flexibility of a real asm-programmed multipage app, I figure that this, well, it's better than what we have right now . It would require the programmer to make sure that his pages can run completely independently of eachother (no subroutines or anything), but that's a small price to pay.
Not exactly an asm question, but I figured I'd have the best audience in this board.
So my question is, how do apps run without using up user RAM? Are they copied to a page reserved for apps? And if so, at what location/address is that?
*ahem* introducing a little side project I've been working on: a raycasting engine in Axe Runs faster on 15Mhz models (maybe a little too fast) and a bit slower on 6mhz models (almost too slow ) It's very basic right now, and supports single color walls (no textures or sprites), but it does have collision testing and all the basic features of a raycasting engine (rotation, moving, etc.) and can be used to make a (sort of) enterntaining maze game It only uses the first 80 columns of the screen (for speed, and because I'm too lazy to change it. Also I'm sure this area could be used when making a real game for a user display or something)
use left/right to turn, up/down to go forward/backwards respectively. You can press Mode to view the map, and 2nd to quit the map. Press clear to exit the game.
Maps are currently 96x64, and the program will take the picture from OS Pic3 to be the map.
Source is in....RAYSRC, executable is in RAY (nostub) A sample map is included
Currently sits at under 1000 bytes
Things I might add sometime are textures (or at least another color of wall), scaled sprites (not too sure how that will work), and fixing any bugs you guys find. The only bug I've found so far is that everything looks kind of distorted if you get too close to it