Showing posts with label tutorial. Show all posts
Showing posts with label tutorial. Show all posts

Sunday, August 24, 2008

Day 225: More Returning

Getting back into the swing of things is difficult. Sundays are great for working on the game because I feel pressure to make something out of my weekend and time's running out; every other day... not so much.

That said, milestone:Tutorial is complete. Sometime soon, hopefully this week, I'm going to look at RubyScript2Exe, see if I can get it integrated into Rake, and have a 'preview' available. I've started to set up a few VirtualBox VMs for testing. Likely the preview release will be linux-only, unless I get very very inspired to build it on windows.

I'm at a critical juncture for this game, because it was right around exactly this point that I stopped developing for TraderMissions. The engine was there, I'd written a tutorial that worked perfectly, I was all ready to create my new universe, and then my enthusiasm for the project vanished entirely.

I'm hoping that won't happen this time. One of the big differences between TM and Kuiper is that Kuiper has a built-in editor. A lesson I learned from TM was that I don't especially enjoy creating content when I could be programming. Hopefully the editor will streamline these efforts for me.

While we're hoping, let's hope I continue working on this and you can all see a blog from me tomorrow!

Thursday, June 19, 2008

Day 159: Profiling

Today was my first in-depth profiling. Yesterday I did some looking over, but even with ruby-prof's excellent HTML output, it was taking me a while to get my head around exactly where the bottlenecks in my code were.

Then I found out that ruby-prof can output in a format that KCacheGrind understands. I also found out that KCacheGrind exists, which was likewise helpful, and suddenly I had a tool which could walk me through parts that needed optimizing.

My first surprise was that Set::merge was called FOUR MILLION TIMES. No, I'm not making that number up. When you ask a KuiObject for its attributes, children, etc, it collects all of the ones it knows about in a set and then merges it with the ones its superclass knows about. Except there was no caching here, it was doing it every single time.

Even that wouldn't have been a problem, because you'd think I wouldn't be asking what every single attribute of an object is except when I'm loading/saving (and then time is not as important). But it turns out that it was heavily used in comparison. Two KuiObjects are == if all their children, attributes, etc, are equal, and this requires me to call Set::merge above.

So I fixed the caching problem, and that sped things up. Then I made it so that two KuiObjects are == if their tags are ==. This changed a dozen+ string comparisons and countless object comparisons into one string comparison. That sped things up a lot.

The beauty of using profiling is that I can tell whether harebrained ideas I have for optimization actually work. For instance, I thought that the string comparisons were likely to also be too slow, and I figured symbol comparisons would be faster. Turns out, no, they're not, converting the strings to symbols and comparing them is a little bit slower than just comparing the strings to begin with.

My other optimization idea, to take a line of the form (angle / 10).to_i and change it to (angle.to_i / 10), did speed things up a bit, due to the fortunate elimination of floating point division.

At this point the game is smooth again in sectors with lots of ships, but not when they start shooting the hell out of someone. There are a few other spots in the code I can optimize, fortunately. I would like to avoid having to drop down to C, as I know that this more likely indicates a failure to optimize on my part than it does a shortcoming on Ruby's part.

Day 158: Reno

I'm currently attending the Smalltalk Solutions conference in Reno, where we sent a man the #kill message, just to see if he understood.

Trust me, if you know Smalltalk, that's a very funny joke.

Good news/bad news on the development front. The good news is that I finished the two tickets I mentioned: #179 was for making lasers that weren't slower than the ships that fired them, and #180 was all about adding "scenery" ships to the tutorial.

That's where I ran into the bad news. The first problem was that adding weapons to the ship in the editor still wasn't working, despite all the testing I'd done. What happened on the back end is that any time you give an object a tag, if it had a tag before it deletes it from the repository and re-registers with the new tag. Whenever I created a new weapon for a ship, it replaced the original! So if I had 2 lasers on board, suddenly the object in the repository would be those same two lasers. It was a mess to track down, but thankfully a relatively easy fix.

The worse news was that it's slow! There are noticeable hiccups in even sectors where there's no combat. The traditional profiling approach of using 'ruby -r profile' had so much overhead I couldn't actually run the game. Thankfully, Ruby-prof exists. It's got a small enough overhead that I can actually play my game.

I have a lot of data to go over for optimizing, and I'm not releasing the tutorial until I'm done, so I have one more ticket to close, #96, which I have aptly titled "OH GOD MY CPU".

For those interested in my contest entry, I am temporarily hosting it on my own machine, so get it while it's still up! I think the contest turned out really well and I'm happy that my entry ended up as good as it was.

Friday, June 13, 2008

Day 153: Contest

I'm taking today, tomorrow, and Sunday off to participate in the RubyWeekend game creation contest. This is the first extended break I've taken from the game since I moved into my new house, and I need it!

I did get some stuff done yesterday, mostly tweaks. Specifically, fixing the combat tutorial and making tutorial items cost money.

This puts me two tickets away from finishing the milestone and therefore the tutorial. I'll probably do a few things at that point:

  1. Create a github project for the game and upload it there.

  2. Branch the current version off and name it kuiper_rubygame_3.0.0. This will be a reference branch, in case I need to port everything back to Rubygame 3. Why make this branch? Because of the next step:

  3. Backport the game to Rubygame 2.3. I've had people interested in at least downloading and playing with the game, and as I'm using the bleeding edge my-version-may-break-daily version of Rubygame, it discourages people from doing so. I'll have the Rubygame 3 branch ready when I need to switch over. Finally, I'll:

  4. Release a 'preview'. This will consist of the tutorial and be announced here and maybe a few other places (forums only, no formal announcements yet). Get feedback, etc.


And after that, I start work on the main campaign!

Wednesday, June 11, 2008

Day 151: It's Never Easy

Today's subject is a ripoff of a line from the Phantasm series of movies. The movie's been around for as long as I've been alive, so I'm about to spoil the ending.

When the intrepid heroes finally kill the bad guy, they sit back and have a moment to recuperate. They do all their "Sure is better now that the bad guy's dead!" talk, and one of them says "Thank God, it's over"

That's when the bad guy appears behind them, stating "It's Never Over", and the movie ends. All the sequels followed this pattern. This was the first horror movie I saw that that didn't have a "protagonists escape and everything's better" ending (I hadn't yet seen A Nightmare on Elm Street)

So just when I think I've got an easy task ahead of me, I open the editor and the Tall Man appears, looming, intoning "It's Never Easy!" and then throwing killer spheres with knives into my head. Trust me, this is very scary if you've seen the movie. Especially if you saw it at the age of six.

The easy task that actually was easy today was #99, better radar coloration. Since everyone can get ahold of the player's ship, it was trivial to change the color if the current ship was either the target of the player or targeting the player.

I had plenty of time left over after that, though, so I tried my hand at #184, having a 'nearest hostile target' button. Writing it was not hard, and as it turns out the logic was mostly right. The problem came in testing. I'd start up the game, shoot someone until they were angry at me, and hit the button.

The game would freeze.

What this usually means is that it's about to crash and Ruby is about to give me a stacktrace. I don't know if it's Rubygame or my machine or what, but sometimes this takes anywhere from 5 seconds to several minutes to actually happen, and it's eating up 100% of the CPU the entire time. Very inconvenient for debugging, and incredibly inconvenient for detecting infinite loops.

I spent an hour. I thought the error was in the comparison code, as that's one place that it'd be easy to get stuck in a loop. I had print statements everywhere. It was yet another horror of coding, and nothing I did helped.

Until I put a print statement at the very end of the targeting code, that is. This entire time, I'd been assuming it was this new code locking up the game. But it wasn't! Debugging told me that the targeting logic was all working. Furthermore, looking at the output showed me exactly what I was doing wrong. I was doing this:

player_ship.target = chosen if chosen

But 'chosen' in this case is a sprite, not a ship. The player_ship is expecting a model object, not a view! All that work, for a six-byte change:

player_ship.target = chosen.model if chosen

Thus, today's Programming Tip of the Day: Don't try to use your view as your model.

Tuesday, June 10, 2008

Day 150: On Automatic

Today I set out to do #178, the in-game quicksave and autosave. Because I already had a utility function that could save any time from anywhere, I figured this'd be a cinch. Create a new control and if it's pressed, save. Turns out, it really was that easy....

My wife came in at this point and asked me what I was doing, so I mentioned that I was working on autosave. She said:

"Oh good, but make it so that it doesn't overwrite the main save file, I hate it when games do that!"

Guess how my save function works! It can't save using any filename other than the universe's name, which happens to be the main save file.

The bulk of that work, then, was fixing that problem. The issue is that if I just tack '-autosave' on to the end of a universe's name and save it, that name persists. So the player, upon loading their autosave and autosaving /that/, gets a name like 'scenario-autosave-autosave.ksg' That would not do.

The save code now looks for two identifiers ('autosave' and 'quicksave'), strips them out, and then adds them back on if you are, in fact, quicksaving or autosaving. I also added in code to put the player's name into the filename as well, so more than one person can play per user account and not have their saves clobber each other. Now if only I could name the player.

I had some extra time, so I got #177 done. That was literally a two-line change, to make some of the editing field bigger. I had time left over.

So, why not, I figured I'd tackle another "easy" task: #160, backporting all the nifty changes to multi-line inputs to the old one-line input.

The plan was, make the one line input a subclass of the multiline, and disallow the creation of new lines. This was easy, but the line was still multi-enabled; it'd wrap if you put too much text in it.

Disabling wrapping was difficult - the MultiLineInput itself delegates the work to the MultiLineLabel. I ended up having to have my subclass override its parent's rendering code entirely (as that's where the wrapping happened), and then set a number of variables that the parent class expected that the rendering would set. It was a pain.

Oh well. At least no global variables have bitten me in the ass.

Monday, June 9, 2008

Day 149: Dep^H^H^HBreadth First Search

It'd be nice if I could implement a Breadth-first search without first accidentally creating a Depth-first search like twelve freaking times.

If the last entry was a blast from the past, this one's an archaeological find. I closed #4, which implements the "Plot Course" button. I did this because the multi-jump tutorial mentions it, and I want everything I mention in said tutorial to be working before I move to the next milestone. This is my new way of putting things off; mentioning them in the tutorial and going "Well, gotta implement this!"

Only this time, I wasn't putting anything off. I've finished #157, and #158, which were the last two editor-intensive parts of the tutorial. So I'm perfectly justified in coding up whatever I feel like.

This blog entry is backwards, because before I did either of those things, I did #176, which lets you display a message to the user once and only once (so they're not constantly pestered with tutorial reminders).

Before I did that, I fixed a bug whereby weapons weren't taking up the right amount of space (their expansion and hardpoints cost was only counted once, regardless of the number of weapons you had). Once again, I discovered that unit testing will only help me if I actually write the tests; I asked myself "Didn't I test for this?" when the bug came up, and the answer turned out to be a big fat "NO!"

And before I did that, I proliferated MiniBuilders to weapons and ammo, which wasn't using them before. At this point I tend to take my editor's stability for granted, so it's very disconcerting when I click a button that's still there because I haven't put a minibuilder in and everything dies.

Finally, the global variable has been quiescent, not biting anything. But I'm watching it....

Friday, June 6, 2008

Day 146: Blast from the Past

Yesterday I finished #168, which prevents the user from seeing (in detail) sectors they haven't been to yet, only to discover that when you add them to your itinerary you still see the names (#175).

Today's subject comes from the fact that, as of earlier today, I had 42 outstanding tickets in milestone:Polish. This is the milestone I throw things in which aren't immediately necessary, but that I want to get done before I ship the game. It's got more tickets in it than any other milestone, because anytime I feel like putting something off, there it goes!

So today I finished #76, the ability to do a quicksave of a universe while you're editing it. This is invaluable because the alternative is to go all the way back to the first editor screen, save, and then immediately load up your scenario again to continue editing.

I had to make two changes in order for this to happen. First, I had to make it possible to respond to a keypress at the engine level; because the editor can be in any one of a dozen states at any time, the engine is the only place they have in common. I was already doing this with the 'kill switch' that shuts the game down (good for testing full-screen!), so I extended it to call arbitrary code in response to any button.

The second change was surprisingly easy. Games-in-progress and scenarios-being-edited, while sharing the same file format, are saved in entirely different ways. I have plans (#169) to widen that gulf even further. So if I'm going to be allowing the person running Kuiper to save the universe they're editing at any time, there needs to be a way to know that the person is, in fact, editing a universe and not playing a game.

Kuiper uses a number of gameplay displays as editors; the map and sector states are examples. Each changes its behavior if it's in "edit mode". A number of other things change if the user is editing. So each thing which cares about edit mode has its edit_mode set to true (usually in its initializer). The problem is, half of the project cares if it's in edit mode! TraderMissions did something similar, but worse; the repository itself was given to everything that needed it, which turned out to be everything. I've done it enough and had it cause enough code bloat to dub it my own personal Anti-pattern: Constantly passing objects that should be global.

So I committed one of the cardinal sins of programming: I made a global variable. It's one of those things you're supposed to do only if you know what you're doing, and usually that rules me out. This time, though, it's making life a lot easier. And that is the story of #174.

The other thing of note I did today was create the KuiSellItemsAction. No, it doesn't force the player to sell items, like I keep thinking when I see it, it puts the items you indicate up for sale on the planets you tell it to. You can sell weapons, cargo, ships, and/or addons. This is useful for, say, simulating technology advances in-game. I use it in the tutorial to only offer weapons to the player after they get the weapons tutorial.

Now taking bets on how long it'll take the global variable to bite me in the ass!

Wednesday, June 4, 2008

Day 144: Your Ship has Come In

So I decided on a whim to cater to that tiny but vocal minority of players who might one day want a ship other than the horrible tiny one they start with. #170 is thus done!

In the fine tradition of making things worse for myself, I then made #171, #172, and #173.

#171 should prevent people from jettisoning mission-related cargo, which is a surprisingly hard fix. The part which displays this is too general to know that it's even displaying cargo, let alone which is mission related. I plan to cheat and have it check to see if its object understands :mission_related, and disallow jettisoning. This also opens things up to one day having missions give the player temporary weaponry and/or addons.

#173 is my pie-in-the-sky ship previewer. When comparing ships, it'd be great to get a side-by-side comparison, complete with how many more (or less) of a given attribute the new ship has. That's easily extended to addons, too.

#172 is, um... making tutorial items actually cost money.

One of my bosses at work has a Staples easy button that says "That was easy!" when pressed.

I'm lobbying to have the voice clip replaced with "That was surprisingly difficult!"

Tuesday, June 3, 2008

Day 143: Addons

First, the huge news: #149 is done!

Okay, that's not huge news. As alluded to before, I've been planning to remove ERB for a while. Today, I wanted to blow another 15 minutes. Thus, an easy ticket (which turned out to actually be easy) was closed.

In more important development related news, #156 was also closed today. That's the Addon tutorial. It took surprisingly long because, as I was testing the tutorial module I asked myself "Self, what if I set out to specifically disregard the tutorial's instructions."

It breaks things, is what!

I had to create a new condition that would check if you'd actually finished a mission, just for this. Now all the tutorial missions are only awarded if you've finished their prerequisite mission. I expect that condition to be handy for multi-mission story arcs, and now you don't have to use flags (which are a pain) to do the same thing.

Finally, I made ticket #170 because it occurred to me that maybe - perhaps - a small minority of players could, at some point in their gameplay experience, want to buy a new ship.

Yeah. I forgot about that until now.

Monday, June 2, 2008

Day 142: Two Hundred Hours

Actually, because I was only a half hour short yesterday, I'm at 201 hours. But still, it's a milestone so I'm naming my blog after it.

Today's work was finishing #166 which means the market dialog is shiny and good and happy. You get details, # of everything it requires that it requires, # of that thing you have free, and the ability to buy/sell more than one of them at a time. I even made it work with weapons, even though that ticket's not up yet.

That's not to say things went extremely well: I had to quash a lot of typos. 15 minutes into some refactoring I decided I didn't like the way it was going, so I ended up reverting all my work up until that point. I kept putting Python syntax in Ruby, thus leading to today's Programming Tip of the Day: To make a new object, use "new".

Still, many things went right. For instance, everything that can be bought and sold has its price stored stored in an instance variable named 'price'. Thank you, me, for making that consistent! Getting weapons to work was literally four lines of code, all of which were copied and pasted from the addons adapter. I got an extra half hour of work in.

Finally, now that the market dialog is handling everything market-related, and the minibuilder and new builder dialog is handling everything build-related, I deleted the old builders. Three entire files (addons.rb, cargo.rb, and weapons.rb) housed nearly identical GUI code that is now handled in one place.

Of course, the downside of this is that, according to my spreadsheet, I wrote -190 lines of code today. I'm still calling it a win.

Sunday, June 1, 2008

Day 141: Slow Ride

No, that's not the title because I took it easy today; it's because my lines of code increased by a whopping 30 over 2.25 hours of work.

Of course, LoC don't tell the whole story - I'm surprised it was positive, as I did some refactoring today and removed a fair bit of code (the old market dialog, for instance). Sometimes the most productive days are the ones where the LoC count goes backwards. That's one of the other ways this differs from Nano.

Today was touchup. #165 required me to put scrollers around the new fancy editors I'd made, and I did it and it was surprisingly easy.

#166, on the other hand, was not so easy. It requires me to have a 'preview' of the cargo/addon/weapon you want to buy. Not a big deal, the preview part was easy. It was the "This requires 1 hardpoint and 2 expansion space, and you have 0 hardpoints and 0 expansions left" part. I essentially made it so any Buyable adapter knows what kind of requirements the things it holds have. The ShipCargoAdapter has "cargo", ShipAddonAdapter will have "expansion" and "hardpoints", etc. These are mapped to actual function names, so I can then dynamically generate the message.

That part's done for the "You have 3 cargo free" part, but not for the requirements. I'm already over my quota for the day, though, so on to tomorrow!

Procrastination for the win!

Saturday, May 31, 2008

Day 140: Map and Jumping

I finished #155 today, and named my blog post after it. The total explanation for how to use the map and how to jump out of the sector was maybe two paragraphs, and it took the entire two hours to do. Thankfully, it's not because I'm a slow typist.

No, the reason it was so problematic was #162. The previous tutorial mission which tells you to land on the planet only ends when you get the map-and-jumping mission. In order to do this, map-and-jumping sets a flag upon receipt, and land-on-planet checks this flag to see if it's ending. If it is, it removes itself.

I have a condition check for this. It had three fields: is_set, number_is and note_is. Those last two are to check if the flag is set to the given number or has the given note attached. Is_set was, I thought, to merely check to see if the flag was set.

I checked the unit tests to find that its purpose was originally the exact opposite. If you didn't specify is_set, it would return true if the flag was not set. Otherwise, the note or number had to match.

So there was some refactoring. Now is_set does what I originally wanted, and there's a new is_not_set which, if selected, returns true if the flag hasn't been set yet.

That was working, the land-on-planet mission was vanishing at the right time, but the map-and-jumping mission wasn't working at all: it was supposed to pop up an informational box about how to, well, pull up the map and jump the ship around. It wasn't. I started having flashbacks to my two-day multi-hour coding binge of doom. I really didn't want there to be something I'd have to re-code the entire notification system for.

And, lucky me, there wasn't. In fact, it was the other mission, the land-on-the-planet mission, that was causing the problem. The loop which checks all the missions looks like this:

@player.missions.each do |m|
m.check
end

The problem being that check can end the mission, and thus remove it from the @player.missions array! This messes up the loop, and it ends up thinking it's done and not checking the other mission.

Thus, @player.missions.dup Life was surprisingly easy after that!

Friday, May 30, 2008

Day 139: Miscalculation

"Hey Roger, when did you start this thing, anyway?"
Yesterday, you might have thought I started it sometime in early February, which would have made it day 119 and been a rather belated New Year's Resolution. I actually started keeping track of everything in a spreadsheet on January 12th. I just today discovered a mistake on my spreadsheet that wasn't counting 20 days of work. So the answer to your question is no, watching the Lost season finale did not catapult you 20 days into the future.

Today I got #120 and #131 done, which means the new market dialog is live! I didn't take any 'before' screenshots, so imagine something that's worse than this 'after' screenshot:



Also, those numbers in the 'status' bit haven't changed, but they're important because I made a huge change to the way cargo was handled. All the tests still pass, though. Ship it!

Wait, no, don't ship it yet. I was kidding! Put down the packing tape!

Thursday, May 29, 2008

Day 119: Lost

My excuse for today's entry being short is that I'm typing it during the LOST season finale. I recall, when I first started this blog up, I feared that all my entries would be short. A friend of mine reassured me: "This isn't NaNoWriMo. You don't have to pad your word count." She makes an excellent point!

Today I finished #154, which is the tutorial telling people what you can do on a planet. It goes into some detail on trading, which is special because that doesn't actually work yet. This brings me nicely to my next topic: I didn't so much get things done today as create a whole slew of things to do:

Moved #120 and #131 to this milestone, because I'll need them in order for the tutorial to be accurate. Created #162, #163, #164, #165 as cleanup tickets to make things look better and more functional.

In short, my total number of tickets for this milestone went from 7 to 11, and my completed number only increased by one :)

Wednesday, May 28, 2008

Day 118: Medicated, again

If you glance back at the blog there, you'll see the last time I was medicated was at the very beginning of the month. Believe it or not, this is a huge improvement.

Regardless, this will still be a short entry.

#125 is done. You can scroll to your heart's content, and all will be well.

Before:


After:

Tuesday, May 27, 2008

Day 117: Caret

Short blog today, as I'm still not feeling so hot. And I ought to, considering that it's 90 freaking degrees outside.

Today I got 'home' and 'end' to go to the beginning and end of line. I got the editor to correctly recognize more than one return character (this was also causing off-by-one errors when it wasn't working). It turns out that String::split will ignore trailing whitespace unless you tell it not to. Yes, I know the docs I just linked to say that, but I hadn't considered looking there until things weren't going right.

Programming Tip of the Day: Read the Fine Manual.

Other work includes interpreting mouse clicks and positioning the cursor appropriately. After that I did arrow up and arrow down; the clicking was needed because I simulate moving the caret up by pretending there's a click on the line above the current one. This works remarkably well.

Back to sleep!

Monday, May 26, 2008

Day 116: Line10k

I finished my compulsory two hours of work today two lines of code short of 10,000, so I went on to work for another two hours. If you look at the summary to the right, you'll note that in that two hours, I wrote a whopping 10 lines of code. This was because there were tons of bugs and it required a lot of subtle fixes that were also wrong.

All four hours were spent on #125, the ticket to put in saner text editors. As it was before, you could only change the last bit of anything you were typing. So if you'd made a typo a paragraph earlier and just now noticed it, you'd have to delete your way to it, fix it, and then re-type everything.

Today marks the second time I've had to sit down and do some work on paper. It initially seemed easy to figure out where a cursor should be drawn; I was iterating through each line of the input, adding the size of that line to a running total, which I would then compare to the caret's position. This was error prone and hard to tune.

Then, after writing down an example, I figured out that I could determine which row of the input the caret was on by counting the number of newlines before it.

Later, I kept getting these strange off-by-one errors. I'd been thinking that the caret - an index pointing to where in the original string any changes will be made - was off from the displayed string because the display had extra newlines put in there from word wrapping. It wasn't until I wrote down a sample situation that I realized the extra newlines were replacing spaces (because that's where the words would wrap) and my intricate correction code was causing the off-by-one problems.

Additionally, because I want 'home' and 'end' to be valid text editing keys, I had to change the "emergency end the program" key away from 'end'. It's the backslash now. I accidentally ended the program twice today anyway.

For those interested, the 10,000th line of code is:

<child name='flags'/>

Sunday, May 25, 2008

Day 115: Tutorial

Programming work was done yesterday, but by the time I finished it was no longer day 114. 1.75 hours of programming was enough to finish up the Missions milestone! From now on, it's module development.

The problem with tracking my progress is that every file saved by Kuiper was just one line. One gigantic line with no linebreaks - it's XML, so this actually doesn't matter, but it makes reading it with an ordinary text editor a pain. It's this way because any whitespace was interpreted as a text node, and my code wouldn't check for that, instead trying to re-create a ship from whitespace. This is even more impossible than it sounds :)

So the first part of my day was spent changing the way the XML serialization works. It now skips over any text nodes (I don't use them; all text ends up in attributes, which is a bit messy but works) and once more passes the tests. With that out of the way, I can now use Cloc to count work on my module as lines of code.

The first module I'm making is, in my opinion, one of the most important: The Tutorial. One of the reasons I'm doing this now as opposed to later when the editor is more stable is that I'm trying to shake bugs out of the editor. Later one of my goals is to make a scenario-making tutorial, and I'll likely rewrite the original tutorial then, so if I mess up now it's not a huge deal.

Two bugs fixed today in support of the tutorial:

#159 If you ever lose sight of the planet, it's very easy to get lost and not find your way back. Even for me, and I wrote the darn game! Now, if the center of the sector starts leaving sight, a green arrow appears on the radar to show you which way to head back.

#132 Is the first part of the tutorial, giving basic info on how to move around and land on planets. This one helped to reveal not exactly bugs, but things that would be nice. For example, I've had #125: Better Text Editors open forever, but I've moved it to the tutorial milestone because now I'm typing a lot of text and it'd be nice to be able to edit it in a sane manner.

My wrist hurts from typing; I'm taking the rest of the night off!