Archive for June, 2018

Meanwhile in Perfectly Stacked

Saturday, June 30th, 2018

The focus of my recent work has been on reliable mining automation. One missing piece is a solution to the abundance of mining outputs of differing qualities that quickly fill all available table slots.

One proposed solution is a stacking algorithm that separates resources first into bands of quality (specified by the user) and then carefully steps through a stacking procedure. A careful procedure is needed because when you stack piles of resources with different qualities the resulting quality of that stacked pile is an averaging based on quality and quantity that rounds down any decimal remainder to an integer. This rounding down amounts to lost value and so to avoid losing value you must only stack in round numbers, as it were.

This careful procedure is going to be messy to write, once I’ve wrapped each step in 1) wait time for server round trip 2) coordination between state machine code and server message handling code 3) error handling / recovery code.

Before I get too far down that road I decided to implement the lossless stacking algo independently in python to make it possible for me to get quicker feedback about the correctness of my assumptions and my understanding of the problem.

Usage reads lines from stdin, treats each line as a specification of a table, losslessly stacks down everything in the table to the minimum number of slots, and prints out a specification of the resulting table.

The specification format of a table is any number of slots separated by a space. The format of a slot is AxBq where A is an integer quantity and B is an integer quality. For example: 200x1000q indicates a slot that contains 200 items of 1000 quality.

Basic Usage
python < stacker.txt

If in the above example stacker.txt has the following contents:

Stacker Test File
44x103q 101x102q 15x104q 12x105q
44x103q 101x102q 15x104q 12x105q 44x107q 11x106q

then the output will be:

Stacker Test Output
110x103q 62x102q
147x104q 80x103q

There is also a verbose mode that lists every stack movement and the table state after each move.

Verbose Usage
python -v < stacker.txt


This code is based on the assumption that separating stacks of different item types, and separating items in different quality bands has already been done. Those separations are trivial to implement and orthogonal to the stacking problem. Therefore all stacks are assumed to be same-item and same-band.


The ordered steps for finding the next one move, at a high level, are as follows:

  • If there are two slots with items if the same quality, move all the items in the higher numbered slot onto the lower.

  • Find the pair of slots that has both even quality (or both odd), has primarily the largest difference in quality and secondarily contains an element with the minimum quantity.

  • If found, move items from the higher quantity stack to the lower quantity slot. The number of items to move is equal to the number of items in the lower quantity slot.

  • If not found, terminate.

The approach is, at worst, near optimal with respect to the number of slot movements required. It may be optimal, but I haven’t taken the time to prove it.

Ashby Gap Seriously Soaked, Dicks Dome, Confederate Ghost

Monday, June 25th, 2018

I spent this past weekend backpacking north from Front Royal. Prior to my arrival, northern Virginia received 38 hours of heavy rain and it didn’t stop just cuz I showed up. Getting there was a bit hairy. The roads had 10 to 20cm of water flowing across them in dozens of places. I mean, it was great, they had signs to warn you "ROAD MAY FLOOD" every mile or so.

It rained through the night, but stopped before dawn. We decided to save breakfast for later and hit the trail early, munching on some snacks. After a couple hours we came to a camp site with a water source and stopped to have our meal.

There we met two super happy hikers wearing clothes that were comical either by design or by irony. I enjoyed my oatmeal and hot coffee to sounds of hysterical laughter and coughing as these jokers got baked. They were actually quite funny, and to their credit, Ren and Stimpy found my jokes hilarious.

I stepped away for a minute to take a piss and this little guy popped up right in front of my face:

Little Orange Beetle
Figure 1. Little Beetle Wearing Orange Turtle Neck

We parted ways with Stimpson et al. and hiked the rest of the morning up to a peak that was covered in trees and gave us no view. There was, however, an odd wooden bench, randomly awaiting us at the top.

Summit Bench
Figure 2. Real Man, Carrot Top and Legs recline on an unexpected bench

This little guy came to get a load of me:

Orange Spider
Figure 3. A Little Spider "Sniffing" Around

The afternoon was all downpour as we climbed down into and back out of the near-flooded Ashby Gap. We then made our way up to Dick’s Dome. The rain quit right before we stopped to make camp and the sun actually peeked through. There were golden rays lighting up the fog over the creek and I thought I might get to capture a nice scene after all. But the moment passed before I could get the camera. This is what the view from camp looked like without the golden rays.

Tiny Mountain Creek at Flood
Figure 4. No Golden Rays in the Air, No Glowing Flames in the Fire Pit

The next day was rain free. We stopped for lunch at Hollow Rod Shelter. Evidently we were hiking the western edge of the forward operating base of the Gray Ghost, confederate col. Mosby.

Sign commemorating J. S. Mosby
Figure 5. One of many signs in Virginia commemorating J. S. Mosby

This section of the hike involved a sequence of steep ascents and descents in quick succession known as 'the roller coaster'. At the top of each peak was an amazing view. At least I told myself there was, using my mind’s eye, because there were so many trees that you couldn’t see a damn thing with your actual eyes.

So…​ here’s a creek:

A Creek Shaped Like an 'S'
Figure 6. A Creek Shaped Like an 'S'

And a rock formation:

A Small Rock Formation
Figure 7. It’s rocky here, good luck finding a spot for your tent.

Rock on.

Foxybot Enhancements Part 9: Build 25 Release

Tuesday, June 19th, 2018

A few errors became immediatly obvious in build 24. This build addresses them.

Place this on top of build 24. Feedback is welcome, please use #eulora.

Changes in Build 25


  • Fixed: bot reports table is full and aborts immediately after moving resources to any previously unused slot.

  • Fixed: bot refuses to leave keys in claim when instructed.

  • Fixed: bot doesn’t always wait long enough for the build recipe from the server.

  • Fixed: bot has extra long pause after table scan before doing the first explore.

  • Fixed: the bot window log lines for resource moves are not consistently colored.

Foxybot Enhancements Part 8: Build 24 Release

Monday, June 18th, 2018

I’ve been focusing my recent eulora client work on getting the bot mining into shape. Today I’m releasing my work so far. The tar ball contains 7 source files that will lay down properly over either EuloraV0.1.2 original source or any previous patch of mine you may have used. 5 of these files contain minor edits and fixes only, while botactivity.cpp and botactivity.h carry the bulk of my work.

My ability to test this is limited by the fact that my character is a noob and doesn’t do things like get multiple quality outputs from a single mine. Feedback is welcome, please use #eulora.

Changes in Build 24


The command for explore has changed slightly

/bot explore total_times [line|grid] size [0|1] [m|M] maxDelay timeout

Using 1 will leave keys in the claim, 0 will keep keys.

Using m will use minimum build materials, M will use max.

The default maxDelay is 8000ms. Timeout default is now 30000ms.

/bot explore 100 line 100 0 M 8000 8000


  • Fixed: bot doesn’t find the craft table in inventory

  • Fixed: bot drops the wrong table when multiple craft tables are in inventory

  • Fixed: bot gets stuck in run mode and keeps going

  • Fixed: bot leaves multiple quality outputs in inventory

  • Fixed: bot ignores large stack of ingredients, instead failing on an insufficient stack earlier in inventory

  • Fixed: in some cases bot fails to change direction in accordance with movement strategy

  • Fixed: in some cases bot leaves craft table on the ground at the end of mining run

  • Fixed: in some cases bot uses movement strategy specified on prior mining run

  • Fixed: bot fails trying to swap out worn tools


  • Added a version string as the first line of output in the bot window

  • Enhanced table pickups and drops to stop using "/pickup" and "/drop"

  • Build 4 added 'backup mode' which caused the bot to move in the reverse direction after getting the message that the table was too far away to pick up, until it was within reach of the table again. This build adds an enhancement where the bot monitors its distance from table and only keeps backing up as long as it’s getting closer to the table. If backing up ever results in becoming more distant from the table it enters a new 'urgent' mode, turning and taking steps directly toward the table until within reach. To test this out, I encourage you to strafe or walk the bot away from the table while in the middle of building a claim to see how it behaves. Keeping the table is of the utmost importance and I’ve tried to make this part solid.

  • The management of resources in the table has been completely rewritten. I consider this to be stage one of managing resource quality. At the start of a mining run, the table is scanned and the name and quality of each item is tracked. During the mining run, each new output is stacked in the table with existing items of identical quality, or stacked in an empty slot if there is not an exact match. Multiple qualities should never be mixed and I welcome assistance in testing this. If it is the case that you get many different quality items, then your table may fill up quickly. In a future build there may be support for more advanced features. The purpose of this stage one feature set is for a solid foundation. The bot will stop if the table fills and you are able to mix qualities as needed and restart the bot.

  • Worn tool swapping works as follows: when a tool is worn, the bot searches inventory for another tool with identical name and higher quality.

  • The default timeout has been changed from 15 minutes to 30 seconds to better accommodate the new building times.

  • Table selection works as follows: at startup the bot selects the heaviest table for use. If there are multiple heaviest tables the one in the lowest numbered slot is used. The benefit of this is that it will always remain the heaviest table and so tracking the slot numbers of used and / or unused tables is unnecessary. The slot number approach seemed to be fragile in practice. Now, when the bot needs to find the table in inventory, it performs a fresh scan for the heaviest table.

Known Issues

  • Logging has yet to be fixed.

  • If you bot-mine on a slope steep enough that you slide down or can’t climb up, you will probably lose your table despite 'urgent' mode.

  • There is an occasional issue with getting a "too far away" message while trying to transfer resources from inventory to the table. I have looked at it and suspect it may be a targeting race condition, but I haven’t found the solution yet. When this occurs the table is not out of range and can be picked up / re-viewed. If the resources that failed to transfer do not put you over weight, the mining run will continue and they will be transferred the next time you mine items with the same name. If it does put you over weight, the bot will be stuck but you should be able to stop it and transfer manually.

  • Claim selection is improved but not yet perfect. Sometimes the wrong claim stick will be targeted and either a transfer to it will fail, or the build will fail. I’ve not yet written the code to reacquire the proper stick. When this happens you will lose the durability on your tool from the dig, but the bundle should be returned to you. This appears to only happen rarely when mining fresh ground and ever more commonly as you retrace the same ground with much spent claim clutter.

  • Table ghosts persist even though I’ve added code that specifically deletes them. It may be that the timing of the deletion is not yet right. They seem to be much more common in certain areas like the sides of hills. This does not seem to hurt the functioning of the current build of the bot.

Foxybot Enhancements Test Sample

Friday, June 8th, 2018

I pulled together some of the fixes so far with some new table dragging fixes into a patch for testing out, primarily the mining.

A link to a patch to the Eulora src based on version 0.1.2 eulora-foxybot-mocky1.patch

A link a tar ball of the 0.1.2 src plus my additions EuloraV0.1.2-foxybot-mocky1.tar.gz

Foxybot Enhancements Part 7: A Quick One Two

Thursday, June 7th, 2018

When I started crafting this week I had level 1 Tinkering. As you can imagine, I gained levels quickly. One thing I noticed about the bot was that when I gained a level during a crafting run it would try to train for me, yet fail.

The apparent failure was that it had miss-parsed the name of the skill to train. But this one was quite the little trickster. The log message immediately after the parse was printing:

Bot Log Message
Received message and will attempt training of skill Tinkering.

And at first, I thought maybe the parse was correct after all. I mean, it looks right. But then I realized that the peroid at the end of the log message was not printed by the message but was part of the skill name. No wonder I couldn’t train the skill Tinkering. . You can hide but you can’t run.

void CraftActivity::HandleMessage(MsgEntry* message )  (1)
	if (!IsOngoing())

	switch (message->GetType() )
			craftFinished = true;
			psSystemMessage incoming(message);
			csString text = incoming.msgline;

			if (text.StartsWith(RANKING_PREFIX))
				needToTrain = true;
-				size_t end = text.FindLast('!');  (2)
+				size_t end = text.FindLast('.');  (3)

				size_t start = RANKING_PREFIX.Length();
				skillToTrain = text.Slice(start, end-start);
				OutputMsg(csString("Received message and will attempt training of skill ")+skillToTrain);

1 This method is the handler for incoming server messages during a craft run. Shown from the top for context: keep scrolling, my eyes are down here.
2 Ahhha, the old 'used to be a bang, but now a dot' trick.
3 And so: out with a bang!

In a nutshell, Foxybot was expecting the training message to end with a ! but in the mean time has changed to a . and so in slicing out the skill name was going too far.

With the parse fixed, the training proceeds with much success but still has a minor issue. The bot helpfully tries to move all of your recipe ingredients to the craft container to avoid a fatness-hampered training-fail. But it does not succeed in this attempt.

//move recipe and ingredients from inventory to container to avoid overweight issues

csHash<int, csString>::GlobalIterator iterIngredients(rcp.GetIngredientsList()->GetIterator());

int nextEmptySlot = 0;
if (!iterIngredients.HasNext())
        OutputMsg(csString("Empty ingredients list!"));
while (iterIngredients.HasNext())
        csString itemName;

        psInventoryCache::CachedItemDescription* from = worldHandler::FindItemSlot(itemName, false);   (1)
        if (from)
                worldHandler::MoveItems(from->containerID, from->slot, toContainer, nextEmptySlot, from->stackCount);
        nextEmptySlot = nextEmptySlot + 1;
//move recipes too
psInventoryCache::CachedItemDescription * from = worldHandler::FindItemSlot(rcp.GetRecipeName(), false);  (2)
if (from)
        worldHandler::MoveItems(from->containerID, from->slot, toContainer, nextEmptySlot, from->stackCount);

OutputMsg(csString("Done moving items to container."));

1 This false needs to be changed to true.
2 Not so fast! Don’t change this false to true!

If you look at the code next to marker 1 you’ll see our old friend FindItemSlot() and as in times past, if you change that false to a true things will then go according to plan.

Also in that code, immediately after, you can see it tries to move your bps into the container as well. Originally I "fixed" this one two and it does in fact work. But it leads to a failure down the line when it tries to put the bps back. It seems like a race condition where it tries to move the bps back into inventory and then equip them too quickly afterwards. I didn’t see an easy solution to this so I reverted back and now leave the bps in place while training.

Anyway, I’m pretty sure this works. Realize, though, that I only had 100 bps to craft with and to test this fix I had to level up during a crafting run. I only got 6 shots at it and dispite the last one being successful, that’s not much testing. If it doesn’t work for you, I’ll give you your money back.

That was number one. Here’s number two. If you go around exploring and happen to fill you inventory for whatever reason, you’ll get a message saying you’re stuffed. And evidently everyone sees the message that you’re so stuffed. And the bot, bless her heart, keeps on keeping on: spaming the zone with some stuffed like a buritto message.

I found this tiny bit of code commented out. If you uncomment the line at the marker in the following code it helps you out by stopping further exploration when you get that message.

if (!worldHandler::IsInBrain(scrollName))
        if (!worldHandler::EquipRecipe(scrollName))
                        OutputMsg(csString("Missing blueprint for the claim!"));
                        //noBuild = true;  (1)
            return false;

1 Stop feeling so full with this one weird trick.

And that’s it for the quick one two. It was meant to be a quick one too.

Foxybot Enhancements Part 6: A Quick One

Thursday, June 7th, 2018

I had an issue a while ago mining small claims with the bot that got the slap-on-a-bandaid treatment. The problem was that small claims took 5 minutes for me to build, while tiny claims took less than 5 seconds. And I couldn’t figure out the right parameters to pass for the result I wanted.

If I passed a timeout value less than 5 minutes, then the bot got impatient and walked away in the middle of the build. But when passing a value long enough for the build, whenever the bot got hung up on any other situation, it would idle for the whole 5 minutes until the eventual timeout.

I tried a couple code changes that didn’t work. So I just auto-locked the small claims and moved on to more pressing issues. But all of a sudden since yesterday, I can build small claims super fast. Time to revist.

I don’t have the crafting table sorted out completely for exploring yet, so I can’t have the bot table walk with small claim output. So I made the following quick change so that the bot will build the small claim and then lock it after, instead of looting it. It seems like the empty locked claim gets swept quickly but with something in it, swept much later.

void ExploreActivity::DoTakeResult()
	spentClaims.Put(markerID, markerID);

That’s the code to loot the claim after building it. I added a conditional to instead lock the claim if it’s a small, otherwise loot as normal.

void ExploreActivity::DoTakeResult()
	if (claimType.CompareNoCase("Small"))
	spentClaims.Put(markerID, markerID);

So that’s that. It’s not a big deal and it didn’t take much time, But I thought I’d give you this quickie. How was it for you?

Foxybot Enhancements Part 5: Basic Crafting

Monday, June 4th, 2018

I bought a tinker book last week and today I got to crack that sumbitch open. Now that I can actually level up from crafting I can gainfully explore Foxybot’s craft code.

To kick things off I followed some advice and went with indistinct bark shavings as a starter craft. Now, understand that you can’t just throw the ingredients together; you need a fresh blueprint each time. You can either buy the bps or use someone’s premade bundles.

I bought a small lot of ibs bps at what is (was?) apparently the going rate for bps of 10x adjusted base value. Hey, don’t judge. Nobody’s queueing up to get my coppers at 9x so I went with 10. What else am I gonna do, not craft? It’s hard out here for a pimp.

The ibs recipe calls for shiny rocks and flotsam. I’ve got 5 figures of shiny rock kicking around the root celar but no flotsam. Since I’ve got the basics of exploring down it doesn’t take me long to find more than I can carry.

I only today started crafting, so I’m not exactly hip to all the fancy dance moves of the veteran crafters. I’m still looking up abbreviations and figuring out where to click.

Well, that click figuring didn’t exactly take a long time to get sorted and I quickly handed it off to the bot. But the bot couldn’t pick the ingredients out of the recipe. It thought there was one ingredient called "flotsam,1 shiny rock" and couldn’t find it.

Let’s see, where is the code for this…​ recipe::ParseIngredients()

void recipe::ParseIngredients(csString ingredients, bool useMax)
-	csString separator(", ");  (1)
+	csString separator(",");   (2)
	csString between("between ");
	size_t pos, nextPos, length;
	csString ingredientName;
	int ingredientCount;
	csString currText;

	pos = 0;

	while (pos < ingredients.Length())
		while (ingredients.GetAt(pos) == ' ') pos++;   //skip leading spaces if any

		nextPos = NextPosInString(ingredients, separator, pos);
		length = nextPos - pos;
		currText = ingredients.Slice(pos, length);

		GetCountAndName(currText, useMax);    (3)
		pos = nextPos+1;


1 A comma and a space used to be the separator.
2 Now the separator is just a comma.
3 Passing each ingredient to have its count parsed.

Ok that was a quick fix. After that is applied the bot can separate ingredients but is chopping off the last character of the name, e.g. "Flotsa". Lets have a look at that GetCountAndName() that’s being called at the bottom of the above code.

void recipe::GetCountAndName(csString singleIngredient, bool useMax)
	csString separator(" and ");         (1)
	csString between("between ");
	csString space(" ");
	size_t pos, nextPos, length;
	csString ingredientName;
	int ingredientCount;
	csString currText;

	pos = singleIngredient.Find(between);
	if (pos < singleIngredient.Length()) (2)
		if (useMax)    (3)
			nextPos = singleIngredient.Find(separator);
			nextPos = nextPos + separator.Length();
			length = singleIngredient.Length() - nextPos;

			currText = singleIngredient.Slice(nextPos, length);
			nextPos = currText.Find(space);

			ingredientCount = atoi(currText.GetData());
			length = currText.Length() - nextPos - 2;
			ingredientName = currText.Slice(nextPos+1, length);
		else           (4)
			nextPos = singleIngredient.Find(separator);
			pos = pos + between.Length();
			length = nextPos - pos;
			currText = singleIngredient.Slice(pos, length);
			ingredientCount = atoi(currText.GetData());

			nextPos = nextPos+separator.Length();
			length = singleIngredient.Length() - nextPos;
			currText = singleIngredient.Slice(nextPos, length);

			pos = currText.Find(space);
			length = currText.Length() - pos-2;
			ingredientName = currText.Slice(pos+1, length);
	{//"precise" recipe
		ingredientCount = atoi(singleIngredient.GetData());
		if (ingredientCount == 0)
			//no ingredient count specified, so everything is ingredient name
			ingredientCount = 1;
			ingredientName = singleIngredient;
			pos = singleIngredient.Find(" ");       (5)
			length = singleIngredient.Length()-pos-2;
			ingredientName = singleIngredient.Slice(pos+1, length);

//	Notify3(LOG_USER, "Ingredient=#%s# Count=%d", ingredientName.GetData(), ingredientCount);
	ingredientsList->Put(ingredientName, ingredientCount);

1 Lots of code blocks,
2 that we don’t care about,
3 handling various cases,
4 for more advanced crafters.
5 And one section here at the bottom that we do care about.

I was concerned at first that I might have to muck about in the code that parses the and 's and between 's for the variable count ingredients of the more highly skilled crafters. But as it turns out the one bit at marker 5 in the above code is the source of our problem and doesn’t execute in those other cases.

I modified those three lines at marker 5 to get rid of the off-by-one error.

-        pos = singleIngredient.Find(" ");
+        pos = singleIngredient.Find(" ") + 1;
-        length = singleIngredient.Length()-pos-2;
+        length = singleIngredient.Length()-pos;
-        ingredientName = singleIngredient.Slice(pos+1, length);
+        ingredientName = singleIngredient.Slice(pos, length);

After this change everything looks good. But the ingredients are still not being moved over onto the table, eventhough the names and quantities are being parsed correctly.

For this one we need a tweak to the code that does the moving.

while (iterIngredients.HasNext())
        csString itemName;
        int quantity = iterIngredients.Next(itemName);

        char out[1000];
        sprintf(out, "Ingredient %s: %d", itemName.GetData(), quantity);

-       from = worldHandler::FindItemSlot(itemName, false);  (1)
+       from = worldHandler::FindItemSlot(itemName, true);   (2)
        if (!from || from->stackCount < quantity)
                OutputMsg(csString("Not enough ingredients for crafting! Bot stopping."));
                worldHandler::MoveItems(from->containerID, from->slot, toContainer, nextEmptySlot, quantity);
                logText = logText+csString(out)+ csString(" ");
        nextEmptySlot = nextEmptySlot + 1;

1 FindItemSlot() was not finding the item slot
2 When I pass in true, it finds

And there you have it. The bot will now craft repeatedly, moving ingredients in accordance with the recipe. At least for simple 'precise' recipes it will.

Daniel P. Barron - 2018-06-05 06:21:03

Tested it and it works. Now it needs to figure out to grab stuff from storage as it crafts. Specifically for the heavy stuff.

Mocky - 2018-06-06 17:17:21

That’s a good idea. I’ve added it to the pipeline

Tinker Cliffs and McAfee Knob

Sunday, June 3rd, 2018

I just spent two nights and two days backpacking on the AT near Roanoke. The Friday evening meetup was at a local hiker hostel. By the time we arrived from separate locations all the hostel beds were spoken for so we setup our hammock / bed arrangments in the old hostel barn.

Turns out the crop of hostel-goers in the main building were mostly weenies by mass so we avoided them and their camp fire and held our own festivities in the barn. We provided the music and laughter which was enough to lure in some non- weenies sporting some Appalachian apple moonshine that was surprisingly smooth, while no less potent and enough leaf to pass around.

After some merriment and conversation we got the place back to ourselves and prepared to turn in. A this point, a small black snake fell from the rafters knocking over some sort of kitschy wooden decoration in the corner. This was both freaky and sobering. Nobody wants snakes falling from the rafters.

We got up a little after 5 Saturday morning and after some breakfast, set out on the trail before 6. Sunrise came shortly after and we got a couple miles in before the rain started. The rain lasted only an hour, feeding the creeks and little waterfalls.

Figure 1. Little waterfall complete with crubling ancient saw mill in the background

We spent the morning making the climb to McAfee Knob. McAfee Knob is a massive rock outcrop jutting from the summit of Catawba Mountain with an amazing view of the surrounding mountains and reportedly the most photographed spot on the AT.

The climb features some moderate-to-intense trail ascents but with a short duration with the peak at 975 meters. Along the way there were many cool rock formations including this one.

Rock Formation
Figure 2. A Rock Formation

We reached the top early in the afternoon.

McAfee Knob
Figure 3. Mocky: Master of all he surveys, Yesterday

The camera fails to do justice to the views from the top.

McAfee Knob looking East
Figure 4. East
McAfee Knob looking North
Figure 5. North
McAfee Knob looking West
Figure 6. West

We quit hiking early in the afternoon and spent the night a third of the way down the mountain. At camp we saw half a dozen hikers previously met as well as making a couple new friends. The new friends didn’t have trail names so iirc we named them calamity jane and slush puppy.

The next morning (today) we took our time getting ready. I treated myself to a 1000 calorie breakfast before heading out about 7:30. The hike was quite mild in intensity and instead of rain we were treated to hot sunny weather. We made it to Tinker Cliffs in two hours flat.

Tinker Cliffs
Figure 7. Mocky at Tinker Cliffs in all his shirtless glory, Today

Unlike the single massive outcrop at McAfee Knob, Tinker Cliffs is a kilometer long rocky ridge at all points very much like the above. And like this:

More Tinker Cliffs
Figure 8. More Tinker Cliffs

The rest of the hike was equally mild and near the end there was a great view of Carvins Cove Reservoir, spoiled only by angrily humming power lines.

Carvins Cove Reservoir South East
Figure 9. Carvins Cove Reservoir South East
Carvins Cove Reservoir South
Figure 10. Carvins Cove Reservoir East
Carvins Cove Reservoir North East
Figure 11. Carvins Cove Reservoir North East
MirceaPopescu - 2018-06-04 21:46:32

Pleasure to read!

Mocky - 2018-06-04 21:50:35