The Glide

I’m starting to get the hang of skiing. As I watched the snow fall all day Friday at work my excitement grew. I had been hoping for one more good snowfall this season so I could get just a little more skiing in.

Friday night we had a dinner party with neighbors, then after that (around 12:30 am) I headed over to the Arboretum for about 20 minutes of skiing. The sledding hill was packed with (fairly uncooperative) people, most of which appeared to be college students. The snow was a little bit too powdery, but it was a good time nonetheless.

Saturday was supposed to be the big day. I ended up heading out to Allerton Schroth trail at about 2 pm. Unfortunately, much of the snow had melted (though it was still below freezing), and it was very spotty. That is, there was lots of bare ground that I had to try to avoid. So it was good, but not great. I thought the chances for good snow on Sunday were pretty slim.

Fortunately, I was proven wrong. The snow was actually fantastic. I again went over to the Arboretum. The snow was just a little icy, but not too much, and it was less spotty than it had been at Allerton on Saturday. In fact, I was skiing so well that I deviated from my original plan of doing a couple of loops at the Arboretum and just kept heading farther and farther out. There is some wooded area just across Windsor road. When I got to Windsor I took the skis off, ran across the road, put the skis back on, and kept right on going. The snow in this area was packed down and frozen just enough that I was able to glide nearly effortlessly along. After I skied in that wooded area for a while I crossed Race street and skied around at Meadowbrook park. I was now a few miles from home.

It felt so hot Sunday that I took my hat off. Then I took my gloves off (yes, skiing without gloves… it was weird). Then I had to roll up my sleeves. I had the foresight to bring a water bottle with me. Just when I reached to the back of my waist pack to get it the lid popped off and all my water spilled onto the snow. Crap. I guess it was time to head home soon anyway.

After lunch, I went back out for a 40 mile bike ride, which was somewhat enjoyable, though fairly boring. Everything looked like this:

You couldn’t really tell where the ground ended and the sky began.

The Tour de Groundhog

Last night after dinner my dad told me I was crazy for planning to do a bike race today. Of course, at the time it was 34˚ and raining. Luckily the weather was a little bit better for the Tour de Groundhog today. In fact, the weather started out a little too good, as I had to shed several layers once I started warming up.

I noticed on the drive over to Springfield that there was a lot of standing water/ice on the ground from some recent flooding. It wasn’t until I started warming up that I realized what this would mean for the race: mud, and lots of it.

I did the masters 30+ race (which is interesting given that I am 29, but apparently they use your age at the end of the calendar year…). There were only 7 people in the race, so it wasn’t too crowed. After about 10 meters the course immediately turned into the woods and from there it was a battle of wills more than an athletic competition. A good 30-40% of the course was unridable. So there was lots of dismounting and running/walking with the bike. About 2 times each lap my tires would literally stop spinning, so I would have to stop and pull a handful of mud out of both my front and rear brakes in order to be able to ride again.

The course map

It certainly was an adventure. I did get lapped by the winner, but that wasn’t totally unexpected. I ended up 5th of the 7 people in the masters race, which was good enough to win some socks and water bottles (two things of which I own copious quantities, but still never enough). Just to give you an idea how bad the conditions were, I travelled less than 3 miles in 33 minutes of racing. This is a slower pace than I averaged running for 28.4 miles at the Riddle Run ultramarathon 3 weeks ago. Perhaps my dad knew what he was talking about.

The Heart Shaped Window

On our first Valentine’s Day Melissa was mad at me for some reason. So for our second Valentine’s Day I decided to make her a really special gift. I stayed up until about 2-3 a.m. (which was quite rare, as I like to get to bed early) the night before writing a computer program that made a heart shaped window with a customized message in it. With the state of software in 1998 this was actually a pretty challenging task, but I worked hard and made something I thought we could both be proud of.

When I showed it to Melissa the next day (2/14/1998) I didn’t quite get the reaction I was hoping for. Her attitude was basically that if I hadn’t spent so much time working on the program I would have had more time to spend with her. Sheesh.

Anyway, here’s what it looked like:

And here’s the source code (wow I used to write some really ugly code):

//
// HeartWDEF.c
//

#define HASGOAWAY 0


pascal long main(short, CWindowPtr, short, long);
long  DoDraw(CWindowPtr, long);
long  DoHit(CWindowPtr, long);
long  DoCalcRgns(CWindowPtr, long);
void GetStrucRegion(CWindowPtr window, RgnHandle rgn);
void  GetGoAwayBox(CWindowPtr w, Rect *r);

OSErr PictToRgn(PicHandle pic, RgnHandle rgn){
 int   width = 0, height = 0;
 GrafPtr  mask, savePort;
 Rect   r = (*pic)->picFrame;
 OSErr  err;
 
 width = r.right - r.left;
 height = r.bottom - r.top;
 
 SetRect(&r, 0, 0, width, height);
 //OffsetRect(&r, r.left, r.top);

 mask = (GrafPtr) NewPtr(sizeof(GrafPort));
 OpenPort(mask);
 
 mask->portBits.bounds = r;
 mask->portBits.rowBytes = ((width + 15) / 8);
 mask->portBits.baseAddr = NewPtr(mask->portBits.rowBytes * height);
 
 GetPort(&savePort);
 SetPort(mask);
 
 EraseRect(&r);
 DrawPicture(pic, &r);
 
 SetPort(savePort);
 
 err = BitMapToRegion(rgn, &(mask->portBits));
 
 DisposePtr((Ptr) mask->portBits.baseAddr);
 DisposePtr((Ptr) mask);
 
 return err;
}

pascal long main(short varCode, CWindowPtr w, short message, long param){
 long  val = 0;
 
 switch(message){
  case wDraw:
   val = DoDraw(w, param);
   break;
  
  case wHit:
   val = DoHit(w, param);
   break;
   
  case wCalcRgns:
   val = DoCalcRgns(w, param);
   break;
   
  case wNew:
  case wDispose:
   break;
 }
 return val;
}

long DoDraw(CWindowPtr w, long param){
 long  val = 0;
 WindowPeek wp;
 Rect  r;
 RGBColor save;
 PicHandle pict;
 Point  p;
 WindowPtr window;
 GWorldPtr saveWorld;
 GDHandle device;
 PenState savePen;
 
 GetForeColor(&save);
 GetPenState(&savePen);
 
 PenNormal();
 wp = (WindowPeek) w;
 
 if(param == 0){
  // copy the regions so we can offset them
  RgnHandle diff = NewRgn();
  
  DiffRgn(wp->strucRgn, wp->contRgn, diff);
  
  ForeColor(redColor);
  PaintRgn(diff);
  
  PenNormal();
  /*if(wp->hilited){
   ForeColor(blackColor);
   FrameRgn(diff);
  }*/
  
  DisposeRgn(diff);

#if HASGOAWAY  
  if(wp->hilited){
   GetGoAwayBox(w, &r);
   
   ForeColor(whiteColor);
   PaintRect(&r);
  }
#endif

 }
 
#if HASGOAWAY 
 else if(param == wInGoAway){
  GetGoAwayBox(w, &r);
  InvertRect(&r);
 }
#endif

 SetPenState(&savePen);
 RGBForeColor(&save);
 
 return val;
}

long DoHit(CWindowPtr w, long param){
 long  val = 0;
 WindowPeek wp;
 Point  p;
 Rect  r;
 
 p.h = LoWord(param);
 p.v = HiWord(param);
 
 wp = (WindowPeek) w;
 
 if(PtInRgn(p, wp->contRgn)) val = wInContent;
 
 else if(PtInRgn(p, wp->strucRgn)){
  val = wInDrag;
  
#if HASGOAWAY  
  GetGoAwayBox(w, &r);
  if(PtInRect(p, &r)) val = wInGoAway;
#endif

 }
 else val = wNoHit;
 
 return val;
}

long DoCalcRgns(CWindowPtr w, long param){
 SInt32  val = 0;
 WindowPeek wp;
 PicHandle pic;
 Rect  r;
 RgnHandle rgn;
 
 wp = (WindowPeek) w;
 
 r = w->portRect;
 OffsetRect(&r, - ((WindowPtr)w)->portBits.bounds.left, - ((WindowPtr)w)->portBits.bounds.top);
 
 rgn = NewRgn();
 pic = GetPicture(4096);
 PictToRgn(pic, rgn);
 ReleaseResource((Handle) pic);
 OffsetRgn(rgn, r.left, r.top);
 CopyRgn(rgn, wp->strucRgn);
 DisposeRgn(rgn);
 
 rgn = NewRgn();
 pic = GetPicture(4097);
 PictToRgn(pic, rgn);
 ReleaseResource((Handle) pic);
 OffsetRgn(rgn, r.left, r.top);
 CopyRgn(rgn, wp->contRgn);
 DisposeRgn(rgn);
 
 return val;
}

void GetGoAwayBox(CWindowPtr w, Rect *r){
 WindowPeek  wp = (WindowPeek) w;
 
 *r = (*(wp->contRgn))->rgnBBox;
 
 r->top -= 0;
 r->bottom = r->top + 10;
 r->left += 12;
 r->right = r->left + 10;
}


//
// HeartWindow.c
//
#define kNumFields 7

typedef struct{
	Str255	str[kNumFields];
} Message;
typedef Message* MessagePtr;
typedef MessagePtr* MessageHandle;

void DoUpdate(void);

Boolean 		quitting;
WindowPtr		window;
Message			message;

void LiveDragger(WindowPtr w, Point p, Rect *r){
	Point				mouse, newmouse, original, origin;
	EventRecord			e;
	GrafPtr				save;
	
	EventAvail(mouseDown, &e);
	
	if(!(e.modifiers & optionKey)){
		DragWindow(w, p, r);
	}
	else{
		GetPort(&save);
		SetPort(w);
		
		GetMouse(&mouse);
		LocalToGlobal(&mouse);
		SetPt(&origin, 0, 0);
		LocalToGlobal(&origin);
		original = origin;
		
		SetPort(save);
		
		while(StillDown()){
			if(WaitNextEvent(updateMask, &e, 0, NULL)){
				DoUpdate();
			}
			else{
				GetPort(&save);
				SetPort(w);
				
				GetMouse(&newmouse);
				LocalToGlobal(&newmouse);
				
				if(newmouse.h != mouse.h || newmouse.v != mouse.v){
					if(PtInRect(newmouse, r)){
						MoveWindow(w, origin.h + newmouse.h - mouse.h, origin.v + newmouse.v - mouse.v, false);
						SetPt(&origin,0,0);
						LocalToGlobal(&origin);
						mouse = newmouse;
					}
					else{
						MoveWindow(w, original.h, original.v, false);
					}
				}
				SetPort(save);
			}
		}
		
		GetPort(&save);
		while(w != nil){
			SetPort(w);
			InvalRect(&w->portRect);
			w = (WindowPtr) ((WindowPeek) w)->nextWindow;
		}
		SetPort(save);	
	}		
}

void GetItemHandle(DialogPtr d, short index, Handle *h){
	short	type;
	Rect	r;
	
	GetDialogItem(d, index, &type, h, &r);
}

void EraseWindow(){
	Rect		r;
	GrafPtr		savePort;
	
	GetPort(&savePort);
	SetPort(window);
			
	r = (*(((WindowPeek) window)->contRgn))->rgnBBox;
	OffsetRect(&r, -r.left, -r.top);
	EraseRect(&r);
	InvalRect(&r);
	
	SetPort(savePort);
}

void PStringCopy( ConstStr255Param srcString, Str255 destString ){
	SInt16 index = StrLength( srcString );

	do {
		*destString++ = *srcString++;
	} while ( --index >= 0 );
}

void CopyMessage(MessagePtr src, MessagePtr dst){
	int		i;
	
	for(i = 0; i < kNumFields; i++) PStringCopy(src->str[i], dst->str[i]);
}

void LoadMessage(){
	int				i;
	MessageHandle 	mh = (MessageHandle) GetResource('hwms', 128);
	
	if(mh == nil){	
		for(i = 0; i < kNumFields; i++){
			GetIndString(message.str[i], 128, i + 1);
		}
	}
	else{
		CopyMessage(*mh, &message);
		ReleaseResource((Handle) mh);
	}
}

void EditMessage(){
	short	item = 0, i;
	Str255	str;
	Handle	h;
	
	DialogPtr d = GetNewDialog(129, nil, (WindowPtr) -1);
	
	SetDialogDefaultItem(d, 1);
	SetDialogCancelItem(d, 2);
	SetDialogTracksCursor(d, true);
	
	for(i = 0; i < kNumFields; i++){
		GetItemHandle(d, i + 3, &h);
		
		SetDialogItemText(h, message.str[i]);
	}
	
	ShowWindow(d);
	while(item != 1 && item != 2){
		ModalDialog(nil, &item);
	}
	HideWindow(d);
	
	// if the user hit ok
	if(item == 1){
		MessageHandle	old;
		
		// save the info to the app's resource fork
		for(i = 0; i < kNumFields; i++){
			GetItemHandle(d, i + 3, &h);
			
			GetDialogItemText(h, message.str[i]);
		}
		
		old = (MessageHandle) GetResource('hwms', 128);
		if(old == nil){
			old = (MessageHandle) NewHandle(sizeof(Message));
		
			if(old == nil){
				StopAlert(130, nil);
			}
            else{	
				CopyMessage(&message, *old);
		
				AddResource((Handle) old, 'hwms', 128, "\p");
				ChangedResource((Handle) old);
				
				ReleaseResource((Handle) old);
			}
		}
		else{
			CopyMessage(&message, *old);
		
			WriteResource((Handle) old);
			ChangedResource((Handle) old);
			
			ReleaseResource((Handle) old);
		}
		
		
		EraseWindow();
	}
	
	DisposeDialog(d);
	InitCursor();
}

void DoAbout(){
	Alert(128, nil);
}

void DoAppleChoice(short item){
	if(item == 1) DoAbout();
	else{
		Str255	daName;

		GetMenuItemText(GetMenuHandle(128), item, daName);
		OpenDeskAcc(daName);
	}
}

void DoFileChoice(short item){
	switch(item){
		case 1:
			// edit the text
			EditMessage();
			break;
			
		default:
			quitting = true;
			break;
	}
}	

void DoEditChoice(short item){

}

void DoMenuChoice(long choice, EventModifiers modifiers){
	short	id, item;

	id = HiWord(choice);
	item = LoWord(choice);

	switch(id){
		case 128:
			DoAppleChoice(item);
			break;

		case 129:
			DoFileChoice(item);
			break;

		case 130:
			DoEditChoice(item);
			break;
	}

	HiliteMenu(0);
}

void DoDiskEvent(const EventRecord *e){
	Point 	dialogCorner;

	if((e->message >> 16 ) != noErr){
		SetPt(&dialogCorner, 112, 80);
		DIBadMount(dialogCorner, e->message);
	}
}

void DoOSEvent(const EventRecord *e){
	SInt16		osMessage;
	WindowPtr	w;

	osMessage = (e->message & osEvtMessageMask) >> 24;

	switch(osMessage){
		case suspendResumeMessage:
			break;

		case mouseMovedMessage:
			break;
	}
}

void DoNullEvent(const EventRecord *e){
	WindowPtr 	w;	
}

void DrawCenteredString(Str255 str, short y){
	MoveTo((328 - StringWidth(str))/2, y);
	DrawString(str);
}

void DoUpdate(){
	GrafPtr		savePort;
	RGBColor	saveColor;
	Rect		r;
	int			size = 18, i, y = 116;
	Str255		str;
	
	BeginUpdate(window);
	GetForeColor(&saveColor);
	GetPort(&savePort);
	SetPort(window);
	
	ForeColor(redColor);
	TextFont(applFont);
	TextSize(size);
	
	for(i = 0; i < kNumFields; i++, y += (size + 6)){
		DrawCenteredString(message.str[i], y);
	}
	
	SetPort(savePort);
	RGBForeColor(&saveColor);
	EndUpdate(window);
}

void DoMouseDown(EventRecord *e){
	WindowPtr	w;
	SInt16 		part;

	part = FindWindow(e->where, &w);

	switch(part){
		case inMenuBar:
			DoMenuChoice(MenuSelect(e->where), e->modifiers);
			break;
			
		case inSysWindow:
			SystemClick(e, w);
			break;

		case inContent:
			//quitting = true;
			break;

		case inDrag:
			//DragWindow(w, e->where, &qd.screenBits.bounds);
			LiveDragger(w, e->where, &qd.screenBits.bounds);
			break;

		case inGoAway:
			if(TrackGoAway(w, e->where)){
				quitting = true;
			}
			break;
	}
}

void DoKeyDown(const EventRecord *e){
	SInt16 	key;

	key = (e->message & charCodeMask);

	if(e->modifiers & cmdKey){
		DoMenuChoice(MenuKey(key), e->modifiers);
	}
}

void ProcessEvents(void){
	EventRecord 	e;

	if(WaitNextEvent(everyEvent, &e, 0, nil)){
		
		SetCursor(&qd.arrow);
		
		switch(e.what){
			case nullEvent:
				DoNullEvent(&e);
				break;
	
			case mouseDown:
				DoMouseDown(&e);
				break;
				
			case keyDown:
			case autoKey:
				DoKeyDown(&e);
				break;
			
			case updateEvt:
				DoUpdate();
			
			case diskEvt:
				DoDiskEvent(&e);
				break;
			
			case kHighLevelEvent:
				AEProcessAppleEvent(&e);
				break;
		}
	}
	
	DoNullEvent(&e);
}

pascal OSErr	HandleOpenDocument(const AppleEvent *ae, AppleEvent *reply, SInt32 refCon){
	return noErr;
}

pascal OSErr	HandleOpenApplication(const AppleEvent *ae, AppleEvent *reply, SInt32 refCon){
	
	return noErr;
}

pascal OSErr	HandleQuitApplication(const AppleEvent *ae, AppleEvent *reply, SInt32 refCon){
	
	quitting = true;
	
	return noErr;
}

OSErr InitEvents(void){
	OSErr	err;

	if((err = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, NewAEEventHandlerProc(HandleOpenApplication), 0, false)) != noErr)
		return err;

	if((err = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerProc(HandleOpenDocument), 0, false)) != noErr)
		return err;

	if((err = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, NewAEEventHandlerProc(HandleOpenDocument), 0, false)) != noErr)
		return err;

	if((err = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerProc(HandleQuitApplication), 0, false)) != noErr)
		return err;

	return noErr;
}

void CheckMachine(){
	SysEnvRec	thisMac;

	SysEnvirons(7, &thisMac);
  	
  	if(!thisMac.hasColorQD){
  		StopAlert(131, nil);
  		ExitToShell();	
  	}
	
	if(thisMac.systemVersion < 0x0700){
		StopAlert(131, nil);
		ExitToShell();
	}
}

OSErr Initialize(void){
	OSErr		err;
	short		w1, w2, h1, h2;
	Handle		code;
	MenuHandle	menu;
	
	InitGraf(&qd.thePort);
	InitFonts();
	InitWindows();
	InitMenus();
	TEInit();	
	InitDialogs(nil);
	InitCursor();
	FlushEvents(everyEvent, 0);

	MaxApplZone();
	MoreMasters();
	MoreMasters();
	MoreMasters();

	CheckMachine();

	err = InitEvents();
	if(err != noErr) return err;
	
	LoadMessage(&message);
	
	SetMenuBar(GetNewMBar(128));
	menu = GetMenu(128);
	AppendResMenu(menu, 'DRVR');
	DrawMenuBar();
	
	quitting = false;
	
	window = GetNewCWindow(128, nil, (WindowPtr) -1);
	
	code = GetResource('WDEF',4096);						
	if(code != nil) ((WindowPeek) window)->windowDefProc = code;
	
	ShowWindow(window);
	
	return noErr;
}

void Finalize(void){
	HideWindow(window);
	DisposeWindow(window);
}

void main(void){
	if(Initialize() == noErr){
		
		while(!quitting){
			ProcessEvents();
		}
	}
	
	Finalize();
}

The Bald Tire

Yesterday I did a 50 mile group ride from Urbana to Monticello and back. It was the first ride of this distance & intensity I have done in months, and it gave me a pretty good idea exactly how out of shape I am.

As we were just coming back into town I got a flat tire. Some other riders stopped to help me change it and we quickly realized that my rear tire had outlasted its usefulness by a number of miles. In many places the tread had completely worn through and the purple liner was visible.

I suppose that’s what happens with cheap tires. I got this tire in a pinch at a bike store in Peoria after discovering my rear Brontrager Race Lite had been sliced up somehow in the trunk of my old car. I still had a Race Lite tire in front and it has lasted over 5000 miles.

Needless to say it was time for new tires. Fortunately, Champaign Cycle was not too far away, so I decided to save myself a trip later in the day and just go straight to the bike shop on my way home. I picked up a pair of Bontrager Race Lite Hardcase tires, which have been highly recommended by multiple people.

The Escape

This is the unlikely story of my favorite race of all time.

Most every summer I attend Apple’s annual Worldwide Developers Conference in California. The first few years I attended the conference it took place in San Jose, then it later switched to San Francisco. Both fine choices.

In 2003 I was at the peak of my triathlon prowess. That summer I did a triathlon or ran a road race almost every weekend. Earlier in the spring I was planning which races I wanted to do, and on a whim I decided to search for races in San Francisco on the weekends before and after the conference. I lucked out. The Alcatraz “Escape from the Rock” Triathlon was taking place in San Francisco the day before WWDC started.

There was only one problem. Triathlons have become so popular that big races nearly always fill up months ahead of time. With just a few weeks to go until the race I was sure it would be full. I emailed the race organizers and I was delighted to hear back from them that there were 2 spots available. I promptly snapped one up. The race entry fee was rather large, but I figured that if this actually worked out it would be a once in a lifetime opportunity.

Pre-race

I didn’t really want to deal with taking my own bike on the plane so I rented a bike from Bay City Bike. The organizers required all equipment to be set up the day before the race, so I arranged to fly out to San Francisco a day early. My flight arrived on time. I took a taxi to my hotel, unpacked, then took a bus to Bay City Bike. They had my reservation and I picked up the bike. It was a low end Giant road bike, which is not super great, but it was more than adequate for my needs.

I rode the bike with my backpack full of race gear to the transition area at an old warehouse near Crissy Field. I picked up my registration packet for the race and I set up the bike and all my other gear. Now it was time to return to my hotel. Except I no longer had the bike. And I didn’t see any taxis or buses. So I walked about 3 miles back to the bike shop (the only place I knew for sure where I could find a bus) then took the bus back downtown to my hotel. Given that this was the worse thing that had happened so far, and everything else had gone off without a hitch, I wasn’t too upset about it. I mean, think of all the things that could have gone wrong (delayed flight, missing hotel reservations, missing bike, missing registration, bad weather, etc.).

Race Day

I woke up very early the next morning. I gathered my running shoes, my wetsuit & goggles, and headed to the race start. I had no problems finding a taxi, as the city was dead at this time of the morning. Perhaps I was a little too early. I saw very few people near the race staging area, but I didn’t want to leave anything to chance. I had worked so hard just to get to this point I wasn’t going to let it slip away. I set up my gear in the first transition area. It was time.

The course map

The Swim

All the race participants gathered onto a boat and headed to Alcatraz island. The organizers went over the race instructions on the way out there. The boat pulled up close to the east side of the island, they opened up the doors, and everybody started jumping into the water. I was one of the last people remaining on the boat and I was nervous as hell when it was my turn.

Then I jumped.

The mid-June 58˚ water of the San Francisco bay hit me like a blast of winter wind. I was wearing a wetsuit, but it still took a minute or so for the water in the wetsuit to heat up to a bearable temperature. My face on the other hand didn’t have the luxury of a neoprene covering. It was cold, and it would remain cold for the next 35 minutes.

As I treaded water, trying to catch my breath, I noticed that several people had started swimming already, even though the race hadn’t started yet. I was quite some distance back from the imaginary starting line anyway, so I started swimming as well. The boat horn blew to signal the start of the race just as I reached the line and I kept right on going.

My swim cap

Now, swimming in the San Francisco bay is notoriously difficult–it’s part of the mystique of Alcatraz. I was beginning to understand why. In addition to the frigid water, the waves were pretty rough. No amount of swimming laps in a pool, nor even short open water swims in muddy midwestern lakes, prepared me for the constant ups and downs of the ocean-like bay water. I had never swam in saltwater before and this also caused me some difficulties. In the normal course of swimming I sometimes get water in my mount and I inadvertently swallow some of it. Well, the salt water was causing me to gag and I had to stop for a few seconds a handful of times to prevent myself from puking.

Finally, the current is incredibly strong. We couldn’t just swim straight towards our destination or we would end up out in the Pacific ocean. We had to aim significantly to the east of our destination and the current would correct our course. When I was just a few hundred meters out I realized I had overshot the small entrance to municipal harbor and I started swimming vigorously against the current. By the time I reached the entrance I was still 20 meters or so down current of where I needed to be. That’s when the current slammed me (and everyone around me) into the pier. The force of hitting the pier wasn’t so bad, but it was covered with barnacles. As I pushed off of the pier to get back where I needed to be these tiny sharp crustacea sliced my bare fingers and toes in several places, leaving me bleeding as I exited the water and I ran up onto the shore.

All things considered it was actually a pretty decent swim. I did the 1.5-ish miles in 35 minutes, which put me roughly in the middle of the pack. I don’t think I could have expected any better.

The Run

Normally triathlons are arranged in swim-bike-run order, but this race was a little different. In order for the locations to work out correctly there was a 2.5 mile run between the swim and the bike, then another 7.5 mile run after the bike. So it was swim-run-bike-run.

After exiting the water I ran to the transition area put on my shoes and tried to wipe off as much blood as possible from my fingers. I didn’t want it on my triathlon suit, so I wiped it on my race number. Fortunately, the cuts were tiny and the bleeding stopped within a few minutes.

My somewhat bloody race bib

I did the first 2.5 mile run at a pretty easy pace and maintained my position in the middle of the pack. Normally in triathlons the run is where I excel, but there was a lot of racing left to do.

The Bike

I reached the transition area at the warehouse, grabbed my bike, and I was off. Immediately there was a large steep hill (this is San Francisco). The road went uphill for a mile, then downhill for a mile, then there was a turnaround, then uphill for a mile, then downhill for a mile, turnaround, repeat, repeat.

The bike ride was hilly. I had never trained on hills. There are no hills in central Illinois. But a funny thing happened during the race–a rather unexpected thing. I started passing people. And not just a few people, but I started passing a lot of people. Every uphill section I passed dozens of people. Every downhill section I passed a few more. After the hilly 12 mile bike ride there really weren’t that many people left ahead of me. Apparently I can bike well on hills.

Rob on the bike. Thanks to my mom for buying the official race photograph.

The Run

The second run was an out-and-back. It was longer (7.5 miles) and harder (hills, trails, sand, etc). It started up a trail that went right under the Golden Gate bridge. I mean right under. I could jumped up and touched the bottom of the bridge. The trail wound up, down, and around, and ended at Baker beach. It was at the end of the beach that I saw the race leader headed back the opposite direction. Hmm. How far is it to the turn around? How many people are ahead of me?

After a stroll through the sand the course wound through a neighborhood then up a hill to the Legion of Honor. Going up this hill another runner passed me. Again, the run is where I usually excelled, so it was rare that other people passed me while running in a triathlon. I wasn’t going let this slide. I reached the turnaround top of the hill after counting 24 people ahead of me.

On the downhill I caught back up with the runner who had passed me earlier and did likewise to him. Then after running back across Baker beach I reached the notorious sand ladder. The sand ladder is basically a set of stairs made out of wood and sand. It climbs the steep hill from Baker beach up to the trail that goes right under the Golden Gate bridge. There was no chance of running up this thing, so I just did the best I could to keep walking at a brisk pace until I got to the top. From there on it was all down hill.

The windy trail made its way back under the bridge then back down to sea level. I caught one other runner at the bottom of this hill as we both made a mad dash for the finish line.

Epilog

I ended up finishing in 2:38:19, good enough for 23nd place overall (out of 440 finishers) and 2nd place in my age group. Not only was the race a whole lot of fun, it was my best triathlon performance ever, and one of my best performances ever in any kind of race.

I am still amazed to this day that all the necessary pieces came together to allow me to take part in this amazing event. It was truly a once in a lifetime opportunity. Until the next once in a lifetime opportunity comes along, I’m satisfied to have escaped from Alcatraz.