WebP Rules

This is a PNG.

This is a WebP.

One of these images is a PNG, and one is a WebP. The PNG is 12KB, and the WebP is 5KB. Do you see a difference between the two?

Me, neither.

I’ve been a steadfast PNG supporter since time immemorial. Cringing at any sight of JPEG compression. Way back in the way when forums were still big, and users spent inordinate amounts of time creating cool-looking signature images, I was big into PNGs. I prided myself on every aspect of my little 300×100 pixel PNG box. But mostly the crisp lines and vibrant colors.

When creating images for a site, I’d typically use (old-school) Photoshops ‘optimize for web’ feature for PNGs. This did a pretty decent job at compressing my PNGs to reasonable sizes.

But, nothing ever gave me such a boost as a WebP. Even for large images full of color, I can still drop a picture from 1.2MB to 680KB, while maintaining the same visible quality! That’s insane!

One of these days when I have more time and energy I’m going to read some more specs on how this works, but until then, I’m just going to go on living like it’s pure magic and my days will be a little brighter for it.

On Writing Imperfectly

For a very long time I was a perfectionist, obsessed with doing things the Right Way. Whenever I would undertake some new task, I would frontload all my work with research. How do smarter people do this? What is the generally-agreed-upon best practice for this sort of thing? How do I make sure in some mythical future state of this project, I won’t hate my Present Day Self for laying shoddy groundwork?

In a professional context, this is not really a bad way to approach things. I’ve saved myself a lot of headaches by learning from really smart people and doing lots of long, hard Thinking before I set about big projects.

One thing that took me too long to learn, though, is that this is a wonderful way to kill hobbies.

The Tyranny of the Right Way

I have a graveyard of drafts for stories, novels, and blog posts. Some of them are impeccably outlined and structured; most are pretty much empty, forsaken for the absurdly long notepad where all the “research” went. All lie dead, unposted, unpublished.

Sometimes I would fizzle out and lose motivation halfway through. Other times, I would make good progress, then stop and think, “Does this really need to be said? Am I the right person to say this? Is it worth writing at all?” Usually, the answer to one of those questions would be a resounding No, and I’d move on.

Most of the time, however, I got lost in trying to perfect what I was doing. Outlining, structuring, plotting and planning. There would be times I’d sit at my screen for tens of minutes, trying to think of the right word when Good Enough would have done.

That’s the thing with writing. You read it often enough, but sometimes it takes multiple failures and years of writer’s block before you can actually internalize it.

It doesn’t have to be good, it just has to be done.

Let it be shit. Let it flow, use placeholders, skip sentences or sections or entire chapters. Whatever it takes to keep writing.

Once I managed to keep this mindset front and center, I was able to really get stuff done. I went from two whole years of minimal writing to… checking notes… 66,599 words written so far this year. Sure it’s not a ton compared to others, but I’m running laps around my Last Year’s Self.1

That brings up another issue I struggled with…

The Tyranny of Statistics

A line chart showing a rising wordcount throughout the year, with a plateau in the middle of the year.

A chart showing my writing progress for the year.

For six years now, I’ve kept track of how many words I’ve written every single day. Each entry is assigned a project, which is the novel, story, or other thing (like a blog) I wrote for. Each entry also has a target word count, which I typically set at the beginning of the year or project, which I aim to hit each time I write. For some years, I even kept notes on reasons behind long gaps, but I long ago dropped this practice.

This was inspired by something I read about someone who kept a desktop calendar and crossed out each day he worked on his big thing. It was his major motivation: he didn’t want to lose that streak. I thought, “Wow, that’s a great idea! I’m doing that right now.”

But I didn’t think it entirely through. Like any big lifestyle change — diet, exercise, a new sleep schedule — I didn’t realize that jumping right in would inevitably lead to failure. It’s all about increments. Instantly, I’d internalized that if Line Go Up means Success, then a missed day was an automatic Failure. And that was more demotivating than the motivation I got from Line Go Up.

This semi-unhealthy obsession with metrics eventually invaded a lot of other parts of my life. From tracking the movies I watched to the music I listened to, to all sorts of other things, I wasted so much time and effort in an attempt to quantify my life without thinking about the reasons why. But that’s another blog post.

It took burnout to give up really caring about Line Go Up. In between starting this and burning out, I even wrote not one, not two, but three different applications using three different tech stacks in order to do this “novel project management.” Talk about procrastination.

I ended up wanting to see more and more words written without caring about the quality of the words. Until I missed too many days in a row and just quit. I attributed my lack of motivation to a lot of things, but this obsession with metrics was one of the larger factors.

Eventually I came back to it. I started writing again, and yes, I still tracked my word count. My reasoning is different now, though: I just think it’s neat. If I miss a day, it’s no big deal. If I miss my target, whatever. There’s always tomorrow. And a lot of my writing doesn’t go in the spreadsheet.

What matters more than words written is forward momentum. I can sit an stare at my screen without moving for an hour and make progress.2 After all, you need to know where to go before you can plan how to get there.

This Blog

That all brings it back to the blog. This site has laid fallow for years, untouched and filled with drafts. As a professional procrastinator, I’ve spent time instead swapping between various CMSs, static site generators, themes, etc., while only actually publishing a rare post as an afterthought.

I’m not going back to edit or review the draft graveyard; they’re dead and gone. RIP. But I do aim to start posting more. Posts may be semi-unstructured, a little ranty, maybe short or long. I won’t hold myself to any set schedule, post count, or word count. Posts will come when they, or I, please.

For too long I’ve let my head get all stuffy with thoughts or ideas and never released that pressure in the form of writing. That’s what this place will be for.

Mang JS

A long time ago I built a tool I called mang: Markov Name Generator. It went through a number of iterations, from a .Net class library, to WPF app, to a Vue app, even as part of a gamedev toolkit for a long abandoned roguelike. Most recently, it’s been a desktop app built with Avalonia so I could use it on Linux. Over the years it’s become my “default” project when learning a new language or framework. Well, now I have one of those new-fangled MacBook Pros. Anyone familiar with MacOS development knows how much of a hassle it is to build anything for it, even if you just want to run locally, and I do not want to pull over all my code and an IDE just to run in debug mode to use the app.What I did, instead, was port the whole thing over to a single static webpage.

I put off doing this for a long time because in my spare time I’m an expert procrastinator. Also just as important to note is my tendency to over-complicate, and I kept finding myself wanting to build a full API and web app, which is just completely unnecessary for a tool like this. But I do use mang quite a bit, and there’s nothing so complicated that it can’t be done in vanilla JS. So I bit the bullet and ported the name generation to start.

It’s more of a transliteration than a “true” port or rewrite. The code is almost exactly the same, line by line and function by function, as it is in C#. But the end result is pretty compact: the CSS itself, which is just a very-slightly-modified fork of simple.css, is almost larger than the entire index.html file. While there is plenty to whine about when it comes to JavaScript (a fault of the ecosystem more than the language), it is nice to have everything in such a plain and accessible format.

The entire tool is unminimized and all of the assets are free to browse.

And all in all, this whole thing went much smoother than I expected for less than an hour of work.

What Changed

As part of this process I removed some of the name types that can be generated. Most of the types available in mang are historical or fictional, and it felt odd to have some name types with contemporary sources. As such, all of the East Asia, Pacific Island, and Middle-East Sources have been removed.

What’s Coming

I have not ported the admittedly barebones character generation stuff yet. I have some better plans for that and will be spending some time fleshing that feature out.

The character generation so far has been “trait” flags, randomly selecting between things like “carefree” and “worried”, or picking a random MBTI type. It’s generally enough for rough sketched of an on-the-fly NPC or something, but could use some more work to be truly useful as a starting point for anyone requiring more detail.


Helion, a Theme

A Brief Rant About Color

I have a lot of opinions on colors and color schemes. For example, if you are implementing a dark mode on your app / site / utility and decide to go with white text on a black background (or close-to-white on close-to-black), you should be charged with a criminal offense.

High contrast is a good thing! Don’t get me wrong! But that type of dark mode is offensive to my eyes and if I find myself using something with that color scheme I will switch to light mode, if possible, or simply leave and never come back. It’s physically painful for me, leaving haloes in my vision and causing pretty harsh discomfort if I try to read for more than a few seconds. And though this may be contentious, I find it a mark of laziness: you’re telling me you couldn’t be bothered to put in even a little effort in finding a better way to do dark mode?

So it may come as no surprise that I am a long-time Solarized user. From my IDEs, to my Scrivener theme, to my Firefox theme, to anything I can theme — if it’s got a Solarized option, I’m using it. For a long time, even this blog used some Solarized colors. (Dracula is a close second in popularity for me.)

Helion: A VS Code Theme

I’ve long experimented with my own color schemes. It’s a perfect channel for procrastination, and a procrastinator’s work is never done. Today, I think I’ve settled on a good custom dark theme which I want to release, on which I’d like to iterate as I continue to use and tweak.

Helion, inspired by Solarized and colors that faded away, is my contribution to the dark theme world. It’s not perfect — few things are — but my hope is that it becomes a worthy entry to this world.

Right now it is only a Visual Studio Code theme. As I continue to use and tweak it, I plan to standardize the template into something I can export and use for other tools.

Here is a screenshot:

A screenshot of the Helion theme in use in Visual Studio Code, viewing two JSON files side by side.

Just comparing some json files

Now, I am not a usability expert. The colors here are in no way based on any scientific study and I do not assert that they are empirically perfect or better than any other dark mode theme. This is simply a theme which I’ve customized for my own tastes, according to colors and contrasts that are appealing to my own eyes

That said, any feedback is greatly appreciated. If anyone ever does choose to use the theme, I would be delighted to hear from you, whether it’s good or bad (or anywhere in between).

Enjoy!

The Querynomicon

I always felt that every well-rounded developer needs to build a strong working knowledge of SQL.

You don’t need to know the internals of the query engine of your chosen DBMS.3 You don’t need to master the arcane depths of proprietary SQL syntax. However, you should know the types of joins, how NULLs are dealth with, what a SARGable4 condition is and how to write it, and most importantly, how to think about your data in sets.

A long time ago I wrote about some of the tools and resources I used to learn SQL. I was also blessed to work at a company which put data first, where one developer had forgotten more about SQL and Windows internals than most people will ever learn. So I had access to a lot of tools and immersion in an environment that would be difficult for some to find. My unstructured approach to learning was not so different from total immersion plus comprehensive input, in the world of second language acquisition; that is, I would have had to try in order to not learn SQL.

Today I came across a new resource for learning SQL that would have been incredible back when I was still learning the ropes: The Querynomicon. 5

This site is not only a great resource, it is built wonderfully. Starting with a quick overview, then onto scope (intended audience, prerequisites, and learning outcomes), then straight onto learning peppered with practice exercises, then wrapping up with a concise yet comprehensive glossary, I’m just impressed by the level of quality here. From the structure to the presentation, it’s impeccably laid out, almost like a condensed textbook turned hypertext.6 You could turn this site into a class! Even the flowcharts, explaining high-level concepts, are masterfully done and read like natural language. I love it.

If you’re slightly familiar with SQL, this really is a great site to check out. If you’re brand-new and want to begin learning, maybe start with SQL Server Central’s Stairway to Data (or DML) series, and then the Querynomicon to reinforce what you’ve learned.

Shuffling

A while back I went through my iTunes library and took the songs/albums/artists not already living in their own playlist and put them into a mega-playlist I called all of it.7 This way I could just hit “shuffle my library” when in my car and emulate what I used to do with my iPod. I don’t want to fuss with any UI, or play some algorithmically-generated playlist full of suggested music I might like. I just want to listen to my library, on shuffle.

But I’ve been feeling lately like shuffling just… isn’t good. Maybe the same artist plays twice in a row, or within just a couple minutes of my past listen. Or each shuffle still front-loads the same small selection of songs, so I don’t really get to explore the depths of my library. It’s not a large library (10,000 songs), but not small either, so why am I hearing the same old stuff? This is the 21st Century, is it really so hard to shuffle music?

The short answer is: no, it’s not. We can pretty much shuffle any list of things in an almost truly random fashion. But there are plenty of reasons shuffling doesn’t seem random. Humans are inherently pattern-seeking animals8, and in a truly random sequence of songs, a given artist is no more or less likely to play next based on the previous artist. So you could have two artists play in a row—or if it’s truly random, the same song!

Another problem is that once software gets Good Enough™ it doesn’t usually get touched again until there are actual problems with it—or there is a strong monetary incentive to do so. So a developer with the task to write a shuffle feature might do what’s Good Enough™ to close the ticket according to the requirements and test plan9, then move onto the next ticket.

So what does it really take to do a good job shuffling a playlist? I wanted to do a little experimenting so I thought I would start from the ground up and walk through a few different methods. First, I need…

The Music

I took my playlist and ran it through TuneMyMusic to get a CSV. Then I wrote a little bit of code to parse that CSV into a list of Song objects, which would be my test subject for all the shuffling to come.

True Random

First I wanted to see how poorly, or how well, a “true” random shuffle worked.10 This is easy enough.

We’ll just do a simple loop. For the length of the song list, grab a random song from the list and return it:

for (var i = 0; i < songList.Count - 1; i++) 
{
    var index = RandomNumber.Next(0, songList.Count - 1); 
    yield return songList[index]; 
}

And right away I can see that the results are not good enough. Lots of song clustering, even repeats: one song right after the other!

[72]: Goldfinger - Superman 
[73]: Metallica - Of Wolf And Man (Live with the SFSO) 
[74]: Orville Peck - Bronco 
[75]: Def Leppard - Hysteria 
[76]: Kacey Musgraves - Follow Your Arrow 
[77]: Kacey Musgraves - Follow Your Arrow 
[78]: The Thermals - My Heart Went Cold 
[79]: Miriam Vaga - Letting Go

A Better Random

After looking at the results from the first random shuffle, two requirements have become clear:

  1. The input list must have its items rearranged in a random sequence.
  2. Each item from the input list can only appear once in the output list.

Kind of like shuffling a deck of cards. I’d be surprised to see a Deuce of Spades twice after shuffling a deck. I’d also be surprised to see the same Kacey Musgraves song in my queue twice after hitting the shuffle button.

Luckily this is a problem that has been quite handily solved for quite a long time. In fact, it’s probably used as the “default” shuffling algorithm for most of the big streaming players. It’s called the Fisher-Yates shuffle and can be accomplished in just a couple lines of code.

for (var i = shuffledItemList.Length - 1; i >= 0; i--) 
{ 
    var j = RandomNumber.Next(0, i); 
    (shuffledItemList[i], shuffledItemList[j]) = (shuffledItemList[j], shuffledItemList[i]);
}

Starting from the end of the list, you swap an element with a random other element in that list. Here I’m using a tuple to do that “in place” without the use of a temporary variable.

The results are much better, and at first glance almost perfect! But scanning down the list of the first 100 items, I do see one problem:

[87]: CeeLo Green - It's OK 
[88]: Metallica - Hero Of The Day (Live with the SFSO) 
[89]: Metallica - Enter Sandman (Remastered) 
[90]: Guns N' Roses - Yesterdays

It’s not the same song, but it is the same artist, and in a large playlist with lots of variety, I don’t really like this.

Radio Rules

Now I know I don’t want the same song to repeat, or the same artist, either. While we’re at it, let’s say no repeating albums, too. So that’s two new requirements:

  1. No more than x different songs from the same album in a row.11
  2. No more than y different songs from the same artist in a row.

This is pretty similar to the DMCA restrictions set forth for livestreaming / internet radio. It will also help guarantee a better spread of music in the shuffled output.

Here’s some code to do just that:

var shuffledItemList = ItemList.ToArray();
var lastPickedItems = new Queue<T>();
for (var i = shuffledItemList.Length - 1; i >= 0; i--)
{
    var j = RandomNumber.Next(0, i);

    var retryCount = 0;
    while (!IsValidPick(shuffledItemList[j], lastPickedItems) &&
           retryCount < MaxRetryCount)
    {
        retryCount++;
        j = RandomNumber.Next(0, i);
    }

    if (retryCount >= MaxRetryCount)
    {
        // short-circuiting; we maxed out our attempts
        // so increment the counter and move on with life
        invalidCount++;

        if (invalidCount >= MaxInvalidCount)
        {
            checkValidPick = false;
        }
    }
    
    // a winner has been chosen!
    // trim the stack so it doesn't get too long
    while (lastPickedItems.Count >= Math.Max(ConsecutiveAlbumMatchCount, ConsecutiveArtistMatchCount))
    {
        _ = lastPickedItems.TryDequeue(out _);
    }
    
    // then push our choice onto the stack
    lastPickedItems.Enqueue(shuffledItemList[j]);
    
    (shuffledItemList[i], shuffledItemList[j]) = (shuffledItemList[j], shuffledItemList[i]);
}
return shuffledItemList;

This, at its core, is the same shuffling algorithm, with a few extra steps.

First, we introduce a Queue, which is a First-In-First-Out collection, to hold the x most recently chosen songs.

Then when it’s time to choose a song, we look in our queue to determine if any of the recent songs match our criteria. If they do, then we skip this song and choose another random song. We attempt this only so many times. While the chance is low, there’s still a small chance that we could get stuck in a loop. So there’s a short-circuit built in that will tell the loop it’s done enough work and it’s time to move on.

In addition to that, there’s a flag with a wider scope: if we’ve short-circuited too frequently, then the function that checks for duplicates will stop checking.

This is an extra “just in case”, because if I hand over a playlist that’s just a single album or artist, I don’t want to do this check every single time I pick a new song. At one point it will become clear that it isn’t that kind of playlist.

Once a song has been chosen, the lastPickedItems Queue gets its oldest item dequeued and thrown to the wind12, and the newest item is enqueued.

How does this do? Pretty well, I think.

[89]: Metallica - One (Remastered) 
[90]: Stone Temple Pilots - Plush 
[91]: Megadeth - Shadow of Deth 
[92]: System of a Down - Sad Statue 
[93]: Jewel - Don't 
[94]: Def Leppard - Love Bites 
[95]: Elton John - 16th Century Man (From "The Road To El Dorado" Soundtrack) 
[96]: Daft Punk - Aerodynamic 
[97]: Kacey Musgraves - High Horse 
[98]: Above & Beyond - You Got To Go 
[99]: Gnarls Barkley - Just a Thought

But not all playlists are a wide distribution of artists and genres. Sometimes you have a playlist that is, for example, a collection of 80s rock that’s just a bunch of Best Of compilations thrown together. How does this algorithm fare against a collection like that?

Answer: not well.

[0]: CAKE - Meanwhile, Rick James... 
[1]: CAKE - Tougher Than It Is (Album Version) 
[2]: Breaking Benjamin - I Will Not Bow 
[3]: Enigma - Gravity Of Love 
[4]: CAKE - Thrills
[5]: Clutch - Our Lady of Electric Light

It immediately short-circuits, and we see lots of clustering. Maybe not a deal-breaker for a smaller, more focused playlist, but I can’t help but feel there’s a better way to handle this.

Merge Shuffle

Going back to the deck of cards analogy: there are only 4 “albums” in a deck, but shuffling still produces good enough results for countless gamblers and gamers. So why not try the same approach here?

We want to split our list into n elements, then merge them back together, with a bit of randomness. Like cutting and riffling a deck of cards.

First, we’ll do the easy part: split up the list into a list of lists – like a bunch of hands of cards.

private List<List<T>> SplitList(IEnumerable<T> itemList, int splitCount)
{
    var items = itemList.ToArray();
    return items.Chunk(items.Length / splitCount).Select(songs => songs.ToList()).ToList();
}

Then, we pass this list to a function that will do the real work of shuffling and merging.

private IEnumerable<T> MergeLists(List<List<T>> lists)
{
    var enumerable = lists.ToList();
    var totalCount = enumerable.First().Count;
    var minCount = enumerable.Last().Count;

    var difference = totalCount - minCount;
    var lastList = enumerable.Last();

    lastList.AddRange(Enumerable.Repeat((T)dummySong, difference));
    
    // set result
    var resultList = new List<T>();
    var slice = new Song[enumerable.Count];

    for (var i = 0; i < totalCount - 1; i++)
    {
        for (var l = 0; l <= enumerable.Count - 1; l++)
        {
            slice[l] = enumerable[l][i];
        }

        for (var j = slice.Length - 1; j >= 0; j--)
        {
            var x = RandomNumber.Next(0, j);

            (slice[x], slice[j]) = (slice[j], slice[x]);
        }

        for (var j = 1; j <= slice.Length - 1; j++)
        {
            if (slice[j - 1] == dummySong)
            {
                continue;
            }
            
            if (slice[j].ArtistName == slice[j - 1].ArtistName)
            {
                (slice[j - 1], slice[slice.Length - 1]) = (slice[slice.Length - 1], slice[j - 1]);
            }
        }

        if (i > 0)
        {
            var retryCount = 0;
            while (!IsValidPick(slice[0], resultList.TakeLast(1)) &&
                   retryCount < MaxRetryCount)
            {
                (slice[0], slice[enumerable.Count - 1]) = (slice[enumerable.Count - 1], slice[0]);
                retryCount++;
            }
        }
        
        resultList.AddRange((IEnumerable<T>)slice.Where(s => s != dummySong).ToList());
    }
    
    return resultList;
}

This is kind of a big boy. Let’s go through it step by step.

First, we cast our input list to a local variable. I am allergic to side-effects, so I want any changes (destructive or otherwise) confined to a local scope inside this function to keep it as pure as possible.

We’ll take the local list, and then find the length of the biggest chunk, and the length of the smallest chunk. There will only be one chunk smaller than the rest. We’ll fill it up with dummy songs so it’s the same length as the other chunks, and then disregard the dummy songs later.13

Once our lists are in order, we slice through them one section at a time. The slice gets shuffled14, then checked for our earlier-defined rules, but a little more relaxed: no artist or album twice in a row. If a song breaks a rule, we just move it to the end of the array and try again, always with a short-circuit so we don’t get caught in an endless loop.

And of course, we will always allow / ignore the dummy songs, so they don’t interfere with any real choice.

But, there’s a problem! Like a deck of cards, shuffling once just isn’t enough. Like you do with a real deck of cards, let’s go through this process at least seven times.

for (var i = 0; i <= ShuffleCount - 1; i++)
{
    var splitLists = SplitList(list.ToList(), SplitCount);
    list = MergeLists(splitLists);
}

And… the output looks really good, in my opinion!

[0]: Daft Punk - Digital Love 
[1]: America - Sister Golden Hair 
[2]: CAKE - Walk On By 
[3]: Guns N' Roses - Yesterdays 
[4]: Hey Ocean! - Be My Baby (Bonus Track) 
[5]: CAKE - Meanwhile, Rick James...
[6]: Fitz and The Tantrums - Breakin' the Chains of Love 
[7]: Digitalism - Battlecry 
[8]: Harvey Danger - Flagpole Sitta 
[9]: Guttermouth - I'm Destroying The World

However, this only really works well in the areas where the plain-old Fisher-Yates shuffle doesn’t. When used on smaller or more homogeneous sets, the results still leave something to be desired. These two shuffle methods complement each other, but cannot replace each other.

Shuffle Factory

So what happens now?

I thought about checking the entire playlist beforehand to see which algorithm should be used. But there’s no one-size-fits-all solution for this. Because, like my iTunes library, there could be a playlist with a huge number of albums, and also a huge number of completely unrelated singles.

So let’s get crazy and use both.

First we need to determine the boundary between the Fisher-Yates shuffle and the “Merge” Shuffle (for lack of a better term). I’m going to just use my instincts here instead of any hard analysis and say: if it’s a really small playlist, or if more than x percent of the playlist is one artist, then we’ll use the Merge Shuffle.

private ISortableList<T> GetSortType(IEnumerable<T> itemList)
{
    var items = itemList.ToList();
    if (HasLargeGroupings(items))
    {
        return new MergedShuffleList<T>(items);
    }
    return new FisherYatesList<T>(items);
}

public bool HasLargeGroupings(IEnumerable<T> itemList)
{
    var items = itemList.ToList();
    if (items.Count <= 10)
    {
        // item is essentially a single group (or album)
        // no point in calculating.
        return true;
    }

    var groups = items.GroupBy(s => s.ArtistName)
        .Select(s => s.ToList())
        .ToList();

    var biggestGroupItemCount = groups.Max(s => s.Count);

    var percentage = (double)biggestGroupItemCount / items.Count * 100;

    return percentage >= 15;
}

Pretty straightforward! There is also the function that performs the check, and returns a new shuffler accordingly.

Now let’s shuffle.

public void ShuffleLongList(IEnumerable<T> itemList,
    int itemChunkSize = 100)
{
    var items = itemList.ToList();
    if (items.Count <= itemChunkSize)
    {
        chunkedShuffledLists.Add(GetSortType(items).Sort().ToList());
        return;
    }

    items = new FisherYatesList<T>(items).Sort().ToList();
    
    // split into chunks
    var chunks = items.Chunk(itemChunkSize).ToArray();

    // shuffle the chunks
    var shuffledChunks = new FisherYatesList<T[]>(chunks).Sort();

    foreach (var chunk in shuffledChunks)
    {
        chunkedShuffledLists.Add(GetSortType(chunk).Sort().ToList());
    }
}

Again, pretty simple.

Split our input into x lists of y chunk size (here, defalt to 100). Again we’ll do a little short-circuiting and say if the input is smaller than the chunk size, we’ll just figure out the shuffle type right away and exit immediately.

Otherwise, we do a simple shuffle of the input list and then split it into chunks of the desired size. I chose to do this preliminary shuffle as an extra degree of randomness. I hate hitting shuffle on a playlist, playing it, then coming back and shuffling again and getting the same songs at the start.15 So this will be an extra measure to guarantee the start sequence is different every time.

Next we shuffle the chunk ordering. Again, using Fisher-Yates, and again, for improved starting randomness.

After that we just iterate through the chunks and shuffle them according to whichever algorithm performs better for that particular chunk.

The output here is, again, really nice in my testing. I ran through and checked multiple chunks and felt overall very pleased with myself, if I’m being honest.

[0]: Rina Sawayama - Chosen Family 
[1]: Clutch - Our Lady of Electric Light 
[2]: Matchbox Twenty - Cold 
[3]: Rocco DeLuca and The Burden - Bus Ride 
[4]: Rob Thomas - Ever the Same 
[5]: MIKA - Love Today 
[6]: CeeLo Green - Satisfied 
[7]: Metallica - For Whom The Bell Tolls (Remastered) 
[8]: Elton John - I'm Still Standing 
[9]: Rush - Closer To The Heart 
... 
[0]: Wax Fang - Avant Guardian Angel Dust 
[1]: Journey - I'll Be Alright Without You
[2]: Linkin Park - High Voltage 
[3]: TOOL - Schism 
[4]: Daft Punk - Giorgio by Moroder 
[5]: Fitz and The Tantrums - L.O.V. 
[6]: Stone Temple Pilots - Vasoline (2019 Remaster) 
[7]: Jewel - You Were Meant For Me 
[8]: Butthole Surfers - Pepper 
[9]: Collective Soul - No More No Less

Outtro

I don’t think there’s any farther I can take this. I know if I looked closer and the end results, I could find something else to change. There’s a whole world of shuffling algorithms out there, and plenty to learn from. If I felt so inclined I could write something to shuffle my playlists for me, but this exercise was really to learn first why shuffling never seemed good enough, and second if I could do better.

(The answers, as usual, were “it’s complicated” and “maybe”.)

Further Reading

  • The source code for my work is over at my github.
  • Spotify, once upon a time, did some work on their shuffling and wrote about it here
  • Live365 has a brief blog post on shuffling and DMCA requirements here

The Zelda Formula: Part Two

Previously.

We left off talking about how it’s fun, more than any other aspect of a game, that makes you feel like a hero. If you’re handed an über-powerful weapon at the start of the game and just start mowing down mobs, sure, that could be fun for a few minutes, but it very quickly loses its charm. Similarly, the game’s narrative can call you “Hero” all it wants, but if the gameplay doesn’t make you feel it, again, you’re left wanting. The Zelda franchise has mastered the art of achieving this feeling, balancing the narrative with tight, well-balanced gameplay that continuously challenges and delights.

This is done by adhering to a small handful of rules. Each of these leads into and supports each other, less like pillars supporting the structure of the game, and more like a web holding it aloft.

Today I want to focus on the first, and in my opinion, the most important aspect: exploration.

Exploration is the main goal, above all else, and should be rewarded.

If you can create mysterious locales with satisfying discoveries, then the player will continue to dive deeper to sate their curiosity. Image: Digital Dreams via Youtube

If we’re talking about concrete examples of what makes a Zelda game, we must begin ab ovo. Exploration is in the Legend of Zelda’s DNA. Miyamoto’s childhood exploration through caves and waterfalls and woods served as the primary conception point for the entire series.16 You need an exciting world filled with light and darkness, monsters and wonder, new mysteries around each corner. It’s the call to adventure that keeps you pushing forward.

At its peak, this series creates that sense of wonder, that mystery and darkness, almost primal in its allure. The fields are vast and bright. The caves are deep and crawling with danger. Faeries, treasure, and knowledge all wait behind waterfalls, around the bend. Intrepid curiosity is self-rewarding; power and wisdom await those with the strength and courage to plumb the depths and brave the trials that guard these secrets.

The first Zelda game was almost only a series of labyrinthine caves and dungeons to explore. This idea grew to include a large field to connect the dungeons, a place for the player to find respite and choose where they needed to go next.17 But this overworld was not a safe place either. Even outside the dungeons creatures roamed, and before traveling in any direction the player needed to rationalize their choice and ration their resources.

It should be no surprise that even in the first Zelda, everything was already there. The dungeons, the puzzles, the secrets, the steady progression of power until you truly feel like a hero. This was, after all, grown from childhood fantasies. Subsequent games all use these same pillars of progression.

Even so, the team knew that navigating an endless series of labyrinthine dungeons would not be enough.

There needed to be more; more than simply another series of rooms to clear. To continue that drive, to really empower the player, the player needed to be challenged and rewarded at turns. They needed to make choices, sometimes difficult ones, involving real risk. And, most importantly, the world needed to continue to enchant the player, to tantalize with the possibility of something new around every corner.

Some of the strategies they used to achieve this certainly weren’t there in the beginning — but they were very quickly developed, and by the time A Link to the Past was released, were already solidified into the formula.

Combat and exploration are simultaneous

Hyrule is a land teeming with danger. In A Link to the Past, you’re a wanted man: the king’s guards jump at the sight of you and chase you down until you escape or destroy them. In Ocarina of Time, the otherwise peaceful expanse of Hyrule Field becomes claustrophobic with menace once the sun sets, as undead claw their way through the dirt to chase you down as you pass by. And in Breath of the Wild, the world has already ended and the monsters moved in.

Each game, after the introductory level or two, practically opens the entire world to you right away. But this exploration is not without cost: behind every new screen (in the older games) or around every bend in the river, curve of the mountain, there’s a camp of monsters. Always you’re balancing the reward of exploring somewhere new with the cost of encountering some unknown danger. Maybe there’s a new type of monster over there, or a more powerful version of one you know that you’re not yet ready to face. Even when you backtrack through the places you’ve been, it’s possible to get overwhelmed simply by being a bit too cocky.

You know there’s treasure behind that waterfall. Or, if you put a bomb by that crack in the wall, it will reveal a cave with something special inside. You also know that there’s monsters inside — or, instead, some puzzle to solve.

Recently while playing the 3DS’s Link Between Worlds, I found a giant boulder against a wall. I knew that if I blew up this boulder, there would be a cave behind it. But regular bombs did nothing. Several screens over was a giant bomb that would follow you around, but immediately explode when struck. So I have to go back, collect this bomb, and then navigate over the multiple screens around and through monsters trying to kill me. If they struck and missed me, they might hit the bomb. It was an actually challenging bit of fun, entirely optional with no bearing on progression or plot, and when I finally made it (using multiple different tools at my disposal), I felt great. And for my struggles, I received a Piece of Heart.

This, I think, is a great example of the challenges and rewards of exploration. None of this was part of the core game. Every single screen was filled with monsters, and each monster required a different tactic to defeat: Shield Moblins, whose shields protect them from direct attacks; flying Zirros who initially avoid and run from you, only to swoop in and drop a bomb; Snap Dragons who leap at you from afar. And I didn’t need to complete this challenge I set for myself. I could beat the game easily without it. But because I wandered over here and over there, cut my way through and snuck around mob after mob, I could connect the dots and give myself a little challenge to occupy myself between dungeons.

Which is another “pillar” of these games.

Everything is a break from everything else — and it all wears you down

Combat is a break from exploration. Puzzles are a break from combat. The overworld is a break from the dungeons, and the caves and towns are breaks from the overworld. In each area, within each “break,” there’s a new set of goals, something new to accomplish or solve or defeat. Sometimes this is a player-driven goal, sometimes it’s a secret you know is hiding around somewhere, and sometimes it’s a quest or something that pushes forward the story.

What matters is this: when you get tired of one thing, there’s something else. And in the later games they made this even easier. Quick travel, portals to leave the dungeon once you hit around the halfway mark, collectables.

Put another way: there are distractions everywhere. It’s up to the player to choose what they pursue first, and this choice gives agency. Even if it’s mostly an illusion, this agency is what makes it feel like you, the player, are the one choosing to undertake this grand quest to save Hyrule. Because you could, instead, just spend your time gathering trinkets and wandering around.

This does get pushed to an extreme in the more recent Zelda games, and I believe detrimentally so. The Korok seeds in Breath of the Wild or the Maiamais in Link Between Worlds do little but artificially pad the bulk of the game. They pull in the worst aspect of JRPGs — grinding — to give you tiny bits of mechanical progression, to beef up your character without actually giving you something meaningfully new.

Sure, it can be nice to take a break and hunt for an upgrade for your bow. But wandering Hyrule and seeing or hearing that little chirp saying there’s a thing nearby you have to collect right now! feels like the game grabbing onto your belt to slow you down.

But that’s not the way the player should be worn down. Instead, they should feel that simultaneous exhaustion / exhilaration that comes from defeating a particularly difficult foe. Or they’ve used up their arrows or potions, or maybe teased their brain figuring out some puzzle. By the time a particular encounter is over, the player should be ready and happy to move onto a new, different encounter, that will use some other resource while they recharge what was just spent. Think of a dungeon, where one room is a pure combat encounter, and then next is a puzzle, and the next is half-combat, half-puzzle, or sometimes just an empty room with a chest. A quiet spot, for a breath and a break.

Walls are mechanic- and not plot-based

When you can’t progress (spatially) in a Zelda game, it’s typically not because you need to go finish a quest first. It’s because you don’t have an ability that will let you surpass an obstacle. And usually, the ability doesn’t just unlock one new area. For example, when you get the Power Glove in A Link to the Past, you don’t just get the ability to move on to the next dungeon. Entire swaths of the overworld are now yours to explore, and you can go back to any of a dozen areas and suddenly they’re new again: new secrets, new caves to explore, new puzzles to solve.

This really makes you feel like you’re a part of the story, an agent pushing the game forward, instead of just sitting along for the ride. After all, you’re the one who braved the dungeon to find the tool that now unlocks huge chunks of the world. And now you’re the one who can choose to go back and see what new frontiers are available for you to explore.

Sometimes, it isn’t new areas unlocked, but new methods to traverse areas you’ve been to previously. Like the glider in Breath of the Wild (which, yes, is unlocked early, but still feels amazing to get), or the hookshot in A Link to the Past.

This was an interesting choice that Link Between Worlds made: from near the beginning of the game, you can “rent” every special item there is. This means you can traverse all of the overworld from the start. But, you may not be ready: tread too far up the slopes of Death Mountain, for example, and you might run into a deadly Lynel, which will definitely kill you in a single hit in the first portion of the game. And when you die, everything you’ve rented goes back to the shop for you to pony up more rupees to recover.18

Even with so many abilities at your fingertips from the start, there are places you simply can’t get to yet. And then, when you unlock the ability that gave the game its name, you suddenly have another huge section of the world you can explore — not to mention a whole other world!

The world teases you with possibility

This boths follows from, and leads to, the previous “rule.” From the beginning of the game you see areas that you can’t get to — yet. You know, though, that it’s only a matter of time before you’ll be able to go there and see what’s hiding in that cave, under those rocks, beyond that curve.

In Breath of the Wild and Tears of the Kingdom the teasing is less mechanic-based. Past the introductory area, you can literally go to any corner of Hyrule you want, as long as you can evade or fight the enemies there. Instead, you’re tantalized by the possibility of finding new secrets, new collectibles19, something cool, even if it’s nothing more tangible than a breathtaking vista. In these later games especially, the exploration and journey are their own reward, with all of the mechanical and audio design geared toward making it inherently rewarding.

Regardless of the reward, Zelda games have perfected the art of luring the player. The call to adventure becomes irrestistible when you enter Hyrule, the promise of rewards not just material impossible to ignore. When you explore this new world, overcome its trials and tribulations, you’re not just seeing numbers on a screen go up to indicate growing strength. You are truly growing more powerful, both with knowledge, skill, and ability, and the promise of even more on the horizon keeps you coming back for more.

Wrapping Up

That’s about all I have to say about exploration, for now. It’s difficult turn these “rules” into a list, because as I mentioned before, they’re so tightly interwoven. Zelda games are well-oiled machines and these rules are the parts. And the machine doesn’t work unless the parts do, as you can see by playing any number of fan-games, Zelda-wannabes, or even the lesser Zelda titles. It’s a fine, fine tightrope to walk, and it’s a miracle the franchise has been so consistent.

Next up is dungeon design and breaking convention. My notes for that are even rougher, and it’s taking longer than I’d like to organize. So I will be taking a short break from this series to work on that and to work on some other little side projects. There will also be a few gamedev-specific posts about that little game demo I made, the full game following it that is a sort of love letter to A Link to the Past, and some of the more difficult mechanics to implement.

The Zelda Formula: Part One

Before I started writing that little game demo way back in 2021, I knew exactly what kind of game I wanted to make. I learned a lot, threw out just about everything I’d made, and started fresh with the plan crystallized: to make a game evocative of the best Legend of Zelda titles. But I knew I had to step back and think about this. What really makes a Zelda game?

At first I thought I grasped this on an intuitive level. I mean, I’ve played through A Link to the Past at least ten times.20 For a proper throwback I knew I needed more than something that looked like Zelda or played like Zelda. In order for my game to not just be some reskin, I needed to identify and follow the same first principles that guided the Zelda designers themselves.

The thing about first principles is, intuition isn’t enough. It’s not enough to have played a ton of these games, and it’s not enough to love them. The games, their themes, and their central mechanics all need to be grokked. To start, I needed to codify my beliefs about what a Zelda-like game should be. Once written, these rules can be referenced when in doubt, rewritten when they no longer serve, or set aside for future consideration.

So, I started writing.

What are the Rules?

A picture of a sketch outlining dungeon progression in an early Zelda game.

An early design sketch from Zelda. Dungeons and combat, though a central part of the series, are only that: a part.

To put it lightly, I’m not exactly the first person to tackle this subject. In my quest to define the criteria that makes a Zelda game, I read quite a few excellent articles on specific aspects of game design used. But, it felt like everything I read only examined something specific and easily repeatable: how to replicate the level design, how you might imitate progression through dungeons, and so on. They focused on individual mechanics, which I argue are the expression of the principles that drive a game’s design.

Nothing I read, outside of interviews with Miyamoto, really tried to talk about why we are drawn to Zelda games over and over again. You can imitate the aesthetic and the mechanics, but without knowing exactly how to synthesize them, the end product becomes something like Stranger Things.21 It might look like Zelda and walk like Zelda, but don’t let that fool you.

So what’s the connective tissue here? How do you marry the aesthetic, mechanical, and narrative all together into something that is tangibly Zelda-esque?

I spent a long time reading, thinking, and writing about this, and came up with just four of what I consider to be guiding principles. The first is very broad and may seem obvious, but is the single most important goal for which to aim.22 The other three follow from the first — but this doesn’t make them secondary; in fact, all four of these become pillars upon which the foundation of the game rests.

Fun makes you the hero, not the plot.

(Image: Master Sword by Orioto.)

The Master Sword rules because of what you can do with it, not because it’s a storied blade of virtue.

This might sound a little reductive, or at least obvious. But, it’s important to stress this point and I think a lot of games miss this mark. A big part of the appeal of Zelda is that it makes you feel like a hero. The story gives you a nudge in that direction, but it’s not the story alone that engenders the feeling. It’s fun that does it.

A sprawling overworld, dark dungeons, a princess/world/timeline that needs saving; these alone aren’t enough. No matter how well-written your dialog is, or how moving your cutscenese are, if you aren’t having fun while you traverse and overcome the obstacles set before you by the game, then you might as well be watching a movie. Movies are great, and can be empowering in their own way, but that’s not what these games are after. It’s player agency we want. Zelda offers you power through your choices.

The goal is to empower the player through the actions they choose while playing the game. Let their choices lead to heroic challenges with heroic conclusions. You give them these seemingly insurmountable challenges and an arsenal of tools with which to tackle them, and then leave them to their own devices. When the player comes out the other side of an encounter (dungeon, fight, puzzle, etc.)23 for the first time, they should feel like they synthesized and used all the knowledge they had gained unto that point and that the experience enriched them.

It’s easy, in a Zelda-like game, to make this literal. Dungeon encounters often use tricks that require some special item that you only obtained at the end of the last dungeon, or somewhere in between, making it feel novel. And at the end, you get a new item and more health, literally leaving stronger than when you came. (More on this later.)

That said, your adventure grows kind of stale without some kind of narrative to push you along. The point to remember is that the two don’t live in vacuums. They are inextricably linked; propped up by each other. The story informs the game mechanics and the game mechanics express the story in a literal sense. And in the best Zelda games, “narrative” in a certain sense is superseded by the gameplay.

The “Fun Formula”

Because it’s not enough to say, “just make it fun!” I want to share a formula. This expresses the delicate balance between choice and narrative in a game, and provides a concrete way to think about each. It also makes a nice segue into our next topic.

First, to reiterate: the player’s choice makes them a hero. They must choose this adventure. The corollary to that is that you need to design an adventure that is compelling and challenging. The adventure needs to draw in the player, at the same time empowering the player through their actions. Once combined, the effect propels the player through the game world.

We can look at it this way:

(Exploration + Combat) = Player's Story
--------------------------------------------------
(Puzzles * Narrative * Quests) = Developer's Story

This is the balancing act every Zelda game pulls off, and the same one that makes imitators struggle. If these factors could actually be expressed numerically, we’d want the result to be greater than 1.

Leaning too hard on the Player’s Story can be detrimental, but won’t kill you, if your world is compelling enough (see Breath of the Wild for a Zelda example, or just about any roguelike for examples from other RPGs). Conversely, if you pull hard in the direction of the Developer’s Story, you risk losing the plot altogether. (How many times have you heard a gamer complain about fetch quests?)

So, how do we thread this needle? How do we ensure that everything remains fun and challenging and grounded in player choice without veering into Survival Game territory? How do we really capture that Zelda magic?

If “fun makes the hero” is the keystone, then what follows are the cornerstones, all of which are essential to make the final thing stand on its own.

Wrapping Up

In writing this post I’m trying to condense a notebook’s worth of notes into just a few thousand words. It’s taken much longer than I originally thought, so in the interest of actually publishing something and not holding off forever, I’ll be breaking this into parts. The next part will cover the Player’s Story, and the third part the Developer’s Story.

There’s also a tangentially-related “dungeon design tips” post in the works.

Stay tuned!

Six Right Livelihoods

I originally found this list on some .edu site that has since disappeared from the internet. It took some investigative work, but I was able to find the list and what I think is its source.24 Since parts of it periodically pop back into my mind, I wanted to repost it both for easy reference and as a sort of signal boost.

Consume Mindfully

  • Eat with awareness and gratitude.
  • Pause before buying and see if breathing is enough.
  • Pay attention to the effects of media you consume.

Pause. Breathe. Listen.

  • When you feel compelled to speak in a meeting or conversation, pause.
  • Breathe before entering your home, place of work, or school.
  • Listen to the people you encounter. They are buddhas.

Practice Gratitude

  • Notice what you have.
  • Be equally grateful for opportunities and challenges.
  • Share joy, not negativity.

Cultivate Compassion and Loving Kindness

  • Notice where help is needed and be quick to help.
  • Consider others’ perspectives deeply.
  • Work for peace at many levels.

Discover Wisdom

  • Cultivate “don’t know” mind.
  • Find connections between Buddhist teachings and your life.
  • Be open to what arises in every moment.

Accept Constant Change

2021: Year in Review

Last year I wrote a semi-brief retrospective on the previous year where I went over my annual statistics and the hobbies I pursued. I think this is a worthwhile pursuit. The unexamined life, etc.

2021 was a tough year. There was a coup attempt in the U.S.25 Coronavirus exploded (again and again). More personally, I started going to therapy. I stopped writing (so this blog languished, although I have many drafts I’m working on!) and started reading. I spent a lot of time outside.

Hobby Shuffling

I’m sort of a collector of hobbies. In 2020, I spent a lot of time with my cameras, trying to improve as a photographer and as a photo editor. In 2021 I don’t think I picked up my cameras once. Instead, my outdoor activity was disc golf. Indoors, I fell away from writing and instead I worked on some game development projects. I also read quite a bit.

There are certain categories of hobbies that I try to fill: at least one restful activity (reading, watching movies, etc.), one mindful activity (writing, programming, etc.), and one outdoor activity (disc golf, photography, anything moderately active). This keeps things varied and interesting and prevents me from falling too deep into any one rabbit hole, since I tend to fixate on a thing sometimes and need something else to pull me away.

Notice how, for the “indoor” activities, I differentiate between mindful and restful — not mindful and mindless. Mindless consumption in any form is, at best, a waste of time. It’s important to critically examine the media we consume. The most basic reason for this is at least to determine what it is we actually like about something, so that when we search out new media it’s easier to find something to either challenge or comfort ourselves. The other reason is to gain a better understanding and appreciation for the art in our everyday lives, and to learn what the things we enjoy tell us about ourselves. If an unexamined life is not worth living, then an unexamined movie, book, TV show are not worth consuming.

Writing

I did write some this year: a whopping 18,000 words, all in two months.

My primary project was a near-future science fiction project. It took place around the year 2100, under the effects of climate change, radically increased in pace. After being uprooted by collateral damage wrought by bands of eco-terrorists and the fascist state police trying to stop them by any means possible, a found-family started to splinter, competing allegiances chipping away at their unity.

I really liked this premise, and I might retool it to work in a different setting. Working on this project, looking into projections for what things might be like ~80 years from now, looking at various projections and climate models, I started to get kind of sad. It’s one thing to write a dystopian sci-fi, but the closer it hews to Real Life Possibilities, the harder it gets to think about. Eventually I grew too discouraged and started working on short stories until I started programming, which took up all my writing energy.

Game Development

At some point late in 2020, I got the old game development bug. I made a mini-game in 30 days then blogged about it. Then I threw it away and started over. This time I wrote pages and pages of notes, drew up a decently terse GDD — just long enough to answer some important, broad questions; just short enough to easily change — and dived right in. I bought Aseprite and started learning how to make pixel art and then decided I was not good enough and paid for assets that I could use. More importantly, I made a lot of mistakes, which means I learned a lot!

In my demo project I worked on a “vertical slice” of gameplay: like a demo level for a game. Here, I spent a lot of time working on mechanics and behaviors individually, and I put off things like level design and puzzles and narrative for later. I put the player in an empty room with various enemies spawning around so I could playtest all of the player abilities and enemy abilities in a sort of vacuum. This was a ton of fun and I think could make its own arena-style mini-game.

Effort kind of waned around July or August. This is when my sabbatical from work ended, and I started programming “for a living” again. It was also around the time I started going to therapy and reading more, which also took a lot of energy.

I learned so much from working on this game, and I’m so eager to pick it back up and start working on it again. I genuinely think it could be a good game. A worthy entry in the genre. I have a lot of stuff written up on it: a heft devlog, many screenshots, etc., that I will start to sort through and probably blog about.

In the meantime, here’s a couple of incredibly short videos showing very tiny bits of the game.

(The art in these is a mixture of open source art, my own art, purchased assets, and assets from ROMs. Basically, all placeholders.)

Disc Golf

According to UDisc, I tracked 87 rounds of disc golf with a total of 1,453 holes. My best score was -3 (3 under par) and my average was +3.89. I threw one ace26 and 123 birdies27.

I picked up UDisc about a month or two after picking the sport back up, after going through a number of subpar (heh) apps to track my scores. Although I no longer care to track my scores in as much depth, UDisc’s course finder and score card tool are killer features that keep me coming back.

Disc golf is a wonderful sport. I walked more this past year than in the past two years combined, and I don’t think that’s too much of an exaggeration. I started waking up earlier so I could get out to farther courses earlier, and then to beat the heat. I met people and made new friends. The people who play this sport tend to be very friendly and easygoing, and if you’re not the type to learn things from YouTube or by reading then there’s usually someone out there who will show you how to do things on the course.

Even in California, though, we had to take a break for the winter. I recently started getting out again and I believe this is definitely something I’m going to continue to do, to strive to improve at, and most importantly, to enjoy.

Movies

I watched 177 movies in 2021. This year, instead of doing Hooptober, I watched a scary movie every day in September. I saw a lot of truly great films this past year. Not much else to reflect on here – I keep my movie notes elsewhere.

Beyond the Numbers

Keeping track of all these metrics can be fun. I like to see trends take shape. Like watching my average score gradually fall in disc golf. That’s a definite measure of improvement.

There’s not much real insight, though, to be gained just by looking at numbers. I wrote X,XXX words. I saw YYY movies. This doesn’t describe my year any more than counting up all my steps would. I’m not learning anything here, I don’t gain anything by looking at these.

2021 was a busy year for me, and there might be a number of ways to characterize the year. If I were to choose a single theme or defining trait, however, it would be self-discovery. In between the movies, the disc golf, and the programming, I went to therapy (individual and marriage). I read twenty or so books, many of them in that much-derided genre of self-help. I expended a great deal of energy and spent an incredible amount of time trying to learn about myself, how I work, and how I don’t.

When the year started I didn’t know where I was going, or even how to learn to figure out what I wanted out of life. I was so out of touch with myself that discovering what I wanted, even in the most basic sense, was incredibly difficult. What’s more, the prospect of admitting this — or sharing any sort of vulnerability at all — was completely foreign to me. It took quite a bit of introspection and an equal amount of guidance to learn all of these things. Then it took even more to deprogram those behaviors. Then the hardest part of all: real, actual change, in assuming new and healthier behaviors and habits. This is an ongoing process and may never end, but like the Bojack saying goes, it gets easier. The hard part is you have to keep doing it.

Now I still don’t know where I’m going. If you ask me what my Ultimate Goal is out of life, I will thousand-yard stare past you. But I’m a hell of a lot closer to being able to figure out the answers to those questions. Or, at least, to making a decision about them. None of us know where we’re going, but we all choose a path. I’m just working on finding the right path for myself.