Friday, February 28, 2014

FMod - Auto Update

One feature of Piriform apps that I really like is the ability to check for updates automatically. I took that idea and expanded on it for Abiathar, packaging an auto-update client as a resource in the File Emitter and to be used on startup.

When Abiathar is opened, it gets a file from my public Dropbox giving the newest version. If that version is not equal to the one stored in the local executable, it emits the update client and closes. The updater then looks for an instruction file in a folder in my Dropbox named for the new version. It downloads the necessary files, launches the new Abiathar, and closes. Upon reloading from having updated, Abiathar deletes the update client.

It's a very nice system that I think will simplify the dissemination of updates.

Thursday, February 27, 2014

FMod - Paste Preview

Well, this came a lot sooner than I expected. In just four days, I have executed the three most important changes recommended by the most important tester of Abiathar. Fixing the shortcuts to be more like those of The Omegamatic was pretty easy, and I finished up the simultaneous tileset mode yesterday. Today, I attacked and completed the most challenging task: that paste preview overlay. Since I had to implement it as a new render plane anyway, I went for the gold and made it translucent. To keep it in sync with the mouse, I had to get a very complicated chain of notifications through the plane, the paste tool, and the core extension. I think it was worth it:

Getting ready to paste a copy of the Bloog Control Center entrance foreground tiles
(While I was working with mouse movement events, I added a coordinate display.)

Wednesday, February 26, 2014

FMod - Reintegrated Tileset

Today, I continued adjusting Abiathar for its planned release in three weeks. I'm fairly certain I have taken care of two of the deal-breaking issues: tileset shortcuts and the detached tileset. The shortcuts were easy to fix: just assign the 1, 2, and 3 keys to the respective tilesets. The detached tileset was a more challenging problem: I had to figure out how the user will choose which "view" the keys and mouse wheel apply to at any given time. I decided on a "focus" system, the user can press Tab in the newly-renamed Simultaneous Tileset mode to switch keyboard control between the level and the palette. I also did a little work on fixing the Reload Graphics option - namely having it actually reload the graphics.

Tuesday, February 25, 2014

FMod - Increasingly Important Config

At the request of one of Abiathar's private beta testers, I have added more things to the general config file (persist.aconf). Namely, those new sections control the visibility of advanced menu options - variable width, highlight current tile, and locked plane downgrader - and the default tool in each category.

The tester thought that having rarely-used options in the menus would confuse new users and contribute to option bloat. That's probably right, but I didn't want to fully remove the options, so I made the ability to re-show those menu items into a config option.

Next, he found the detached tileset incredibly inconvenient as a separate window. I agreed with that as well, and to be honest the only benefit of having it as a separate window is being able to put it on a different monitor. I've started work on putting it into the main window, but I'm running into issues with resizability and focus (as in which part the user's keystrokes should go to).

Finally, there is some disagreement about which tool should be default: the tweaker or the pencil. I prefer the more precise tweaker, as does another important tester, but quite a few people like to be able to put tiles everywhere with one mouse stroke. Therefore, I added a config option, which sets the ID of the default tool. It's now very easy to choose your default, even if it's something crazy like the replace tool.

Sunday, February 23, 2014

FMod - Pencil Tool

After speaking at some length with a new private beta tester last night, I got all kinds of new stuff to implement. Most importantly, I discovered that people really do like having the free-drawing pencil tool and seeing a dynamic selection rectangle.

Today, I fixed quite a few bugs, notably the one that causes linkable infoplane values to not stop rendering when they're replaced. There was also an unmanaged memory leak in the links rendering plane, which caused a crash after changing a lot of tiles. I managed to get a detach tileset option, which spawns a window kind of similar to a Keen Next thing. Implementing that required breaking and fixing a whole bunch of tileset management stuff and the extension API. Actually, I might end up changing it to be inside the main window, which would probably be more useful.

The most interesting thing I added today was a pencil tool, which is kind of fun to use. It also took some doing to implement, this time a tool-aware undo action. That was necessary to have the undo stack not get corrupted if the user presses Cntl+Z while drawing with the pencil. In debugging that tool, I found and fixed the first bug mentioned in this post. Actually, the pencil was one of the most challenging tools so far: I had to add an entire hook to the extension API, which was the mouse drag thing.


Saturday, February 22, 2014

FMod - Complicated Replace Tool

I did indeed complete the tile replace tool I mentioned in the last post, and it is indeed very complicated. The Tile Instance Remapper has the following features:

  • Replace only inside region
  • Replace with chance
  • Select instances using AND, OR, or independent
To manage all that, I needed to give this tool its own config window.
When the Remapper is started, it copies the selected tiles to its own array. Then, it allows the user to select the tiles with which they will be replaced. Given the bound region (selectable with R followed by mouse clicks, or clearable with U), it will go through each tile and evaluate it with a selector. "And" requires every active plane of that tile to match the starting set. "Or" will go ahead if at least one plane matches up. "Independent" will just check each plane independently and perform the replace operation on one plane at a time.

Once it has found a point to replace, it rolls the dice, weighted as the user configures it. If the chance is independent, each plane has its own chance to be replaced. If not, it won't do anything if the roll fails, but will apply the operation to all active planes if it succeeds.

I also started documenting Abiathar using HelpNDoc. Writing technical manuals is actually really fun!

Friday, February 21, 2014

FMod - Find & Replace

I could be working on documentation for Abiathar, but it's far more fun to add new tools. One thing that I've been wanting to add for a long time is find and replace. They're critical to word processors, but I haven't seen a level editor that has a decent implementation of them. My competition, The Omegamatic, kind of has a find tool, but it works in a very non-obvious and non-actual-finding way.

I was able to implement the Tile Instance Locator (find) very easily, but replacing is more difficult, especially because I want it to have things like a with-chance replacement and a restrictive bounding box for the changes. Setting up for the second one there required adding a new event bus drive. By the end of tomorrow, I think I'll have a more-than-fully functional tile replace tool.

Thursday, February 20, 2014

FMod - Shock Tile

On Keen Day, I'd like to unleash a flood of amazing Keen-related technology on the general PCKF population. Obviously, Abiathar will be the main attraction, but FleexCore (Abiathar's base) has gone through a lot of updates since its initial release. Since that's only a resource for programmers, it probably only qualifies as a "warm up" to the Keen season.

Today, I started a quick little console program called Shock Tile, to be released the day after Keen Day. It won't take three weeks to write; I actually got essentially all of it done in my study halls just this day. Its purpose is to help squeeze more tiles into a Galaxy mod by compacting the tileset, removing unused tiles, and of course updating levels and tileinfo accordingly.

It works in a few stages:
  1. Read the script file. This is a little text file, ST.TXT, that gives the names of the six (or seven for Keen Dreams) resource files that Shock Tile can affect.
  2. Open the files. Before doing so, ensures that they actually exist and gives a helpful error message if not.
  3. Scan the levels. Found tiles are shoved into a map from the original ID to the new ID, which are sequential in order of discovery. Tileinfo animation offsets are also adjusted during this stage.
  4. Shock the levels. The levels are again looped through, replacing the data with the new IDs.
  5. Shock the graphics. Tile images are re-ordered based on that map and unused images are erased.
  6. Shock the tileinfo. Property entries are re-ordered based on that map and entries past the end of used tiles are set to blank.
I'm currently experiencing issues with tile mapping; it likes to put other tiles into ID 0, which is the "blank" entry and should not be changed.

Wednesday, February 19, 2014

FMod - Customizable UI Colors

During an endless study hall this morning, I made use of my nice XML configuration and event bus features in Abiathar to allow customization of the colors used in rendering extra stuff - like links, the grid, tile properties, and the selected tile crosshairs. It was very simple to use the event bus to put the default values in, which the render planes later use.

The grid, turned red via the XML config file
I should probably add a more human-readable way to change them, maybe an entry under Edit or View.

At the suggestion of a private beta tester, I also made some changes to the Dependency Collector. It now contains two new buttons, Step and Auto Detect. Step presents the user with a series of open file dialogs and inserts the choices into the correct file slots. Auto Detect searches the selected directory for standard files and inserts their names in the correct slots automatically, without asking the user anything.

The new DC

Tuesday, February 18, 2014

FMod - Not My Fault

I had a tiny bit of spare time today, which I used to investigate the error report by the most recent Abiathar tester. She created a set of files (for Keen Dreams) with The Omegamatic that could be opened only by it and Keen. Obviously, this is a problem; I need to be able to handle anything that Keen can.

First, I checked the Huffman dictionaries and made sure they were fully intact. When I checked the game maps file, I noticed that the map header was 100 bytes shorter than it should be: it was missing the lengths of the compressed headers. After a little more research, I discovered that The Omegamatic never writes those extra bytes for Keen Dreams. Since patching the short file back in just uses the original version of the non-overwritten part, everything would have appeared to work fine. This time, the bug isn't my fault.

So, to allow Abiathar to modify Omegamatic-produced Keen Dreams levels, I spent 5 minutes creating a tiny utility to append 100 bytes with value "42" to the end of the inputted file. That should cause Abiathar to use the default header length, which The Omegamatic probably assumes. I'll package FxTomDMh.exe with Abiathar in File Emitter.

Monday, February 17, 2014

FMod - Minor Tweaks

The beta tester to whom I sent yesterday's version of Abiathar had sent a reply by the time I checked my inbox this morning. She was amazed that I had managed to get a tile property overlay working and appeared to be impressed at all the tools I had added.

More importantly, she discovered a bug: loading its own Keen Dreams levels caused a crash. I immediately investigated and discovered an issue in writing the map headers that would cause a crash when loading non-contiguous level IDs. There were also a few small UI tweaks that I did to make thing more convenient.

She also mentioned some problems with loading levels made by other editors. I checked that out as well, but discovered nothing shocking. After adding a way to conveniently use a null or default EGA dictionary, I was able to open and save levels from existing mods. It's possible that she was simply pasting a crash report from the old version I had sent her a few weeks ago.

Editing my favorite level from Ceilick's "Dead in the Desert"

Sunday, February 16, 2014

FMod - Rename

The name "Fudge" was a backcronym standing for Fleex Understands [Keen] Dreams [and] Gives [an] Editor. I recently was thinking about it and determined that my competition, The Omegamatic, had a much more impressive name. Because of that and Fudge now being more than just a Keen:Dreams editor, I decided to change the name. Its new title is Abiathar after one of David's thirty mighty men.

I also went on to extensible-ify the tile palette renderers. The state wrapper contains accessors to palette proxies which can calculate maximum width, necessary height, the tile at a position, or the position of a tile. Using that, I re-implemented the standard palette renderer and the selected tile highlighter, both easily supporting Variable Tileset Width Mode. With everything else in place for it, I went ahead and added a plane to the foreground tileset that displays their blocking properties.

This new version has been sent to my private beta testers for examination.

Thursday, February 13, 2014

FMod - Tile Properties!

Fudge is very rapidly becoming distinctly superior to The Omegamatic! Today, I actually finished integrating tile property data! To do that, I had to add a tileinfo file drop-down box to the Gather window and then reorganize it. It also involved modifying the IO code to store the location of that file.



More importantly, I took advantage of the extensible plane API thing to add the tile property overlays. It is very quick, though not as fast as enabling the links plane. The foreground selected tile bay also has the tile property overlay when it is enabled. However, I still need to make the tileset planes extensible to add these overlays to that.


Wednesday, February 12, 2014

FMod - Tile Property Time

I did indeed fix and finalize the path plotter tool for Fudge. Yesterday, it would create an extra arrow path shooting off at a 45 degree angle of the intended one. That was due to the pather using an undefined straight length when it was a purely diagonal movement. Once I fixed that, there was another small problem with pure diagonal lines as the first segment, but I resolved that similarly to the first issue: adding a check for that type of movement.

With that done, I went back and improved the undo feature. It can now more easily affect the level list; this is done with a few small additions to IFudgeState and IViewState. I also added undo/redo support to the creation, deletion, copying, resizing, and importing of levels.

Now that everything is in working order, it's time to distinguish Fudge more from The Omegamatic. One thing that it doesn't have (and people want!) is a tile property overlay mode. They want to be able to see the difference between a secret area tile and a normal wall, or be able to recognize the invisible world map boundary foreground tiles. To implement this, I'm adding a few classes to FMod/FleexCore. I'm fairly certain I have it written correctly, but I haven't been able to test it yet. Fudge probably won't have the ability to modify tile properties, but you never know.

Tuesday, February 11, 2014

FMod - Super Pathing

I discovered today that I was not thinking about the automatic pathing tool correctly. The stuff I did on it yesterday was going to require a whole ton of ElseIf statements, one for each combination of direction and secondary offset direction. Doing that would have resulted in incredibly opaque code involving a million tuples and combinations of "1", "0", and "-1".

Now, I have coupled arrow direction and offset. All the preparation part needs to do is give a set of points with their direction and a distance. It determines those by first moving straight until there is no difference between dX and dY and then moving diagonally for the rest of the way. It works, and it's fast, but it has some problems with creating extra arrows off the real path if the user makes a convex cycle.

Monday, February 10, 2014

FMod - XML Config and Event Bus

I have come to the point in Fudge development where all that remains to do is add advanced features that will set it apart from existing editors. One such feature is the extension API that I've had around forever. Today, I more fully implemented the XML configuration that I talked about in the previous post. I added a ConfigHelper static class that allows directory-like navigation of those document trees, optimized for the standard Fudge way of storing stuff. Those trees are then saved inside the .fudge file or in persist.fconf for general settings.

Another major extension API feature added today is the event bus. Methods that subscribe to such events are flagged with a <FudgeEventBus> attribute. When the GUI performs an extensible action, it uses reflection on all loaded extension objects to find a method with a matching type signature and that attribute. It will then be automatically called, adding its entries to a passed-in list.

I took advantage of the new config mechanism to hold values for an automatic platform pather. The standard tools "extension" uses the XML initializer event bus to set the defaults, which are used by the actual tool later. So far, it can path horizontal and vertical lines, but I need to make it understand non-perfectly-straight changes.

Sunday, February 9, 2014

FMod - API Revamp

The way I was handling undo/redo and tools in Fudge was not good. Not only were they totally divergent, they couldn't affect other important areas of Fudge or access relevant information about the program's general state. To fix these problems, I totally revamped the extension API, which is used by both aforementioned features.

I created a new state manager interface that is implemented only by the main form (instead of going through a whole bunch of lambdas). There are now separate objects for level generation and the view state. It took quite a while to go through and adjust the tools to use the view state object, but the fine-grained control made things a lot faster. The undo/redo API was massively simplified because the system of returning a list of visual updates has been removed. It is now fully up to the action manager to fire the updates - through the nice state manager.

All that made Fudge amazingly easier to think about. I've also started work on an attribute-based event bus system for letting extensions be more involved in creation of resources. The tool and renderer registrars will probably be moved over to that. Finally, to allow persistent configuration, both in program and file scope, XML components are exposed to extensions. Serialized document trees are stored in a "persist.fconf" file for general config and inside the .fudge file for file-specific config.

Saturday, February 8, 2014

Robotics - The End is Here

Today, my robotics team and its sister team from the same school went to the last qualifier for our area. This one was the big one, the one to which everyone goes no matter what. (Unless they already qualified for all the things in a previous one.)

We arrived just a little before check-in time and proceeded to set ourselves up at the proper table, stretching the longest power cable I've ever seen over to the wall outlet to maintain my laptop's power. Inspection was a breeze, having done all the difficult sizing and software downloading work the previous night. In the time between the completion of our pre-game stuff and the actual first match, we went around looking for people to help. I located a team having strange problems with a servo; even though they used LabView, I tried to help.

That went less well than I anticipated. I inspected the physical connections, the LabView paths, the LabView control maps, the emitted file (using a hex editor, which impressed them a lot), and poked a few other things. None of it helped. By the time I was done - two hours later - their laptop's battery had been fully discharged and their servo still didn't work. I think they figured it out later by totally rewriting their program, but I couldn't do anything: LabView is not nearly as reliable as RobotC.

Then came the real start. As I stated last post, I controlled the ops position for the entire day. However, we soon ran into a terrible problem. We misunderstood the rules; all robots' autonomous programs' starting positions must fit in the 18" cube. We thought that the robot just had to be compressible to that size and could be unpacked to start the autonomous. That was not the case, and we had to switch back to the basic autonomous, packing the hanger bar down by wrapping a string around it once. The autonomous executed fine, but when the endgame came, I thought the hanger bar wouldn't reach the field's bar. Fortunately, it did, and we just barely made a pull-up, winning the first game! Win!

Since we had only five minutes between the end of that match and the next, we had to go into screaming rushing do-it-now repair mode. In just a moment, we had added a segment to our block dumper basket to make it longer and fit inside the hanger bar area. I adjusted the code and finished compiling it just in time for us to run out onto the field before they started it without us. Turns out, we didn't even need to adjust the fancy autonomous right then because our alliance member had one as well. We again executed the basic one, scoring 10 points less than the other alliance in autonomous. Our inability to move blocks well caused problems; our opponents were putting all the block ever manufactured into those baskets. Though we hung and our partner team raised the flag, those blocks made a difference by 6 points. Lose!

In the third match, our other alliance member didn't have a flag raiser, but had a hanger. Therefore, we took that job. We also finally got to try our nice autonomous program, but it failed because the hanger bar didn't move up far enough. That was disappointing, but we at least got 10 points from the somewhat failed ramp climb. The flag raiser also failed due to our inability to correctly lock into it. This match was kind of sad. Lose!

Some poor person was on a team all alone with a minimal robot. We had the misfortune of being placed on an alliance with that "team." It's not that the one person wasn't trying hard; he was actually doing his best to do the jobs of at least three people. His robot could only push blocks. While its autonomous flew over the ramp and actually fully of the other side, our full autonomous again failed to place the block in the basket. Though we again could not raise the flag, we hung on the bar. It just wasn't enough - the other alliance had some amazing engineering skills and did all the things. Lose!

Next up was the most dramatic match: the one against our sister team. With our alliance member, we planned an amazing double hang after raising the flag. They would put a block in the infrared bucket while we would do the simple autonomous. However, as it always goes, things were not so smooth. Before the match, one of their hanging motors burned out. They took a few minutes to work on it, but they were able to come out onto the field. There, not only did one of our drive motors fly out of the connector socket, their entire control system failed, rendering us going in circles and them totally stationary. Our alliance scored zero points. Epic fail!

Thinking we had our problems resolved, we went into our final match a little shaken but ready. We executed our simple autonomous perfectly while our alliance member did the infrared block drop well. It was then that one of the nuts on a drive motor came loose, causing an entire tread to fly off, rendering us again uncontrollable. I don't remember what happened to our partner team, but they also encountered issues and could not finish well. Lose!

It was a pretty disappointing day. In fact, we thought we won the second match, but then results came in that we had in reality lost it by a few points. Nevertheless, it was an excellent day of teamwork, engineering, and gracious professionalism. Though my team failed miserably, our sister team placed first! I await the results of the post-qualifying matches.

From left to right: 2 members of another team, our driver observer, the team captain, me, a scoreboard volunteer, 2 members of our sister team, and a referee. From game 5.

It was a good year. Thanks to team 6723, team 7727, FIRST, the STEM committee, and all volunteers involved for a great season!

Friday, February 7, 2014

Robotics - The Last Practice

...though, to be honest, we had to actually make a lot of changes to the robot before practicing.

Coming into this last practice before our competition tomorrow, I didn't expect a lot needed to be done. However, there was actually still quite a bit that needed to be changed. First, the flag raiser needed to be replaced with something shorter to fit in the 18" cube. That was achieved by drilling holes in sheet metal and attaching the little pipes to those.

We also noticed that the hanger bar when folded down to fit horizontally was a tiny bit too tall. So, we took a Dremel and took off a few pieces, dusting the robot in shavings. It was only then that our helpful rules compliance officer (who is also the most frequently mentioned "engineering notebook person" on this blog) discovered that the robot may not exert pressure on the edges; a piece of paper must be able to fit between it and the box. Since our treads were wedged in, we failed to comply. To resolve that, we had to spend quite a bit of time moving the wheels in and removing tread links. (At least we got pizza.)

With everything sized, we moved on to driver selection. I ran the tele-op mode program and evaluated the team members' skills. Given those results and the decision of the team captain, it was determined that I will again run the ops controller and that two people - the captain and a less frequently attending builder - will switch off on the drive controller.

The next logical thing to do was test the autonomous, especially given our slight modifications to the physical layout. We discovered a few issues, such as sometimes hitting the bar (a penalty), which I fixed. I also went ahead and added some code to drive up onto the ramp even if no infrared beacon can be found. Near the end, I also updated the simple autonomous to work with the treads.

Cleverly preventing an all-nighter, the team supervisor volunteered to lend our field to the competition. At 8 PM after five hours of working, we disassembled the field, signed every component, and packaged it all into his car. I took the networking stuff, the captain took the robot, and the supervisor takes the field parts.

Ready.

Thursday, February 6, 2014

FMod - Undo

I've been pounding out quite a few more tools for Fudge, including a flood fill and circle fill. I've also adjusted a whole bunch of miscellaneous stuff, most of which I can't remember specifically but probably has something to do with the tile palette. I created an undo/redo API, which my tools use to (obviously) push undo actions to the appropriate stack. Undoing single tile changes is simple, but things like adding and deleting rows/columns is harder. There's quite a bit of enterprisey abstraction, like having the IUndoableAction interface's actions return lists of levels with lists of changed tiles attached. Unfortunately, all this extra overhead increased the lag a lot, making Fudge pretty difficult to use on older computers. I'll probably have to implement a "low power" mode to not cause wait times to be measured in years.

Wednesday, February 5, 2014

Robotics - Ready

By the time I got to today's robotics meeting, there was only one other person left. (I had been at play practice.) Before everyone else decided to go, they added a servo to the back of the robot that I'm not sure about. Currently, it doesn't do anything and they didn't leave instructions as to what to do to complete it. Ignoring that, we (me and the other person, who normally writes the engineering notebook) did some practice driving. Quickly becoming disinterested in that, we decided to fix the autonomous once and for all.

The thing about our robot's autonomous performance is that it is critical for the thing to be aligned correctly with the baskets. If it's not, it will either crash into a terribly placed bolt and get all turned around or end up far away from the basket, though in that case it will make it up the ramp. We did all kinds of tweaks to its timings and even added some new behavior. After a little more than a half hour, we had an awesome autonomous program that works every time and goes faster than the old one!

We are ready.

Tuesday, February 4, 2014

Robotics - All the Functionality

In addition to all the amazing progress added to us by the springs, we also did a whole bunch of awesome stuff at today's meeting. First, we actually got the flag raiser's little cylinders put in and secured. Since they were finally put in at the right height and with the right width, it actually worked to raise the flag! With that finally done, we could concentrate our efforts on user-controlled things like driving practice and autonomous tuning. We spent almost an hour giving some of the less common attenders time to practice moving around (and almost breaking off the servo, but that's OK). When they all had their turns, I was able to take over the field and work on the auto program. I tried some major changes like having it stop and turn toward the basket, but it was never right. Instead (to stop the servo thing from hitting a terribly placed bolt on the field), I had the auto program wind up the hanger bar and then retract the servo into it until dumping it in the basket. That worked, but there's still a lot more testing to do before I'm satisfied.

Monday, February 3, 2014

Robotics - Legitimately Almost There

It's critical for any FTC robot to fit inside an 18 inch sizing cube - that's the guiding principle on which it is based. Therefore, we thought it was a good idea to check to make sure that our robot was indeed the correct size.

Except for the flag raiser, which has been causing all kinds of trouble, it fit! The block-dumping servo's corner had to be cut off, but that was very easy to do and caused no problems. It did, however, take quite a while. By the time we had it sized, the important build people had to leave. I was able to find and repair a disconnected wire, which solved some issues with the hanger bar.

With only me and engineering notebook people left, we went to the field room to test and practice driving. Since we had quite a bit of time left, we were very successful in our practice. Everyone there is now capable of both driving the robot and handling the ops controller. The only thing we couldn't check was the raiser.

We're almost there - really this time!

Sunday, February 2, 2014

FMod - Tools Galore

Considering today and the last two days I've been working on Fudge, I have made a huge amount of progress. It is, essentially, release-ready. It has tools: linker, copy/paste, row/column insert/delete (which respect and adjust links!), and of course the tile tweaker. I also made a whole slew of adjustments to the extension API, which now differentiates between level-modifying tools and tileset-modifying tools and is almost ready for undo/redo support. The single level IO features have been perfected; they previously had problems with level names 9 characters long (not sure why, but it's fixed now). I have again changed the infoplane composite font, making the 0 and D more different and the F look less weird.


Saturday, February 1, 2014

Robotics - Springing to Success

Finally, for the first time in a while, we got to have a somewhat lengthy meeting with people who know what they're doing!

When I arrived, I suggested that we go to the nearby hardware store to get some springs in accordance with the plan we made yesterday. We did so, and also collected some rubber bands to use as back-up. With a John Deere engineer there to assist us, we got the springs attached in about a half hour. Keeping the hanger bar straight up required one of the springs to be going the opposite direction, which required moving the bar's attachment points up a little.

With that done, the robot was able to successfully approach the bar on the field and pull up onto it! The flag raiser, however, is still not working. It was too low, so I moved its mount up a little bit, placing it at the right height, but still off-center. We'll need to replace it at the next meeting.