<?xml version='1.0' encoding='UTF-8' ?>
<rss version="2.0">
  <title>druk</title>
  <link>druk.dev</link>
  <description>Blog of Drew Knab</description>
  <language>en-us</language>
  <channel>
    <item>
      <title>The Basics of Fornax</title>
      <link>/posts/getting_started_with_fornax_basics.html</link>
      <guid>/posts/getting_started_with_fornax_basics.html</guid>
      <pubDate>Some(06/03/2020 00:00:00)</pubDate>
      <description><![CDATA[<p><a href="https://github.com/ionide/Fornax">Fornax</a> is a static site generator written in F#. I think it's pretty neat. Fornax generates static sites through a pipeline of F# scripts called <strong>loaders</strong> that inject content into a global context that is then consumed by more F# scripts called <strong>generators</strong> that mold the content its final form. The workflow is decidedly hands-on with a distinct code first approach. This gives Fornax incredible flexibility.</p>
<!--more-->
<p>Fornax is installed as a global .Net Core tool on the command line with:</p>
<p><code>dotnet tool install fornax -g</code></p>
<p>This will install Fornax. We can then create a new folder for our new Fornax site, then:</p>
<p><code>fornax new</code></p>
<p>This will create a new Fornax site using the default blog template.</p>
<p>Now that we have a default Fornax site set up we can run <code>fornax watch</code> which will spin up a little server that watches for changes to our project.</p>
<p>Let's take a look at the folder structure now that we have a working Fornax site.</p>
<ul>
<li>_lib
<ul>
<li>Any external libraries we might like to use go in here and can be referenced from the script files.</li>
<li>By default this contains:
<ul>
<li>Fornax.Core.dll</li>
<li>Markdig.dll</li>
</ul>
</li>
</ul>
</li>
<li>_public
<ul>
<li>This is where the final production built files are created after a build.</li>
</ul>
</li>
<li>loaders
<ul>
<li>Contains all scripts that load data into the global context.</li>
</ul>
</li>
<li>generators
<ul>
<li>Contains all scripts that generate files in _public based on the global context created by <strong>loaders</strong>.</li>
</ul>
</li>
<li>images
<ul>
<li>Images used by our app</li>
</ul>
</li>
<li>js
<ul>
<li>JS used by our app</li>
</ul>
</li>
<li>posts
<ul>
<li>This is a domain specific folder used to hold blog posts written in markdown.</li>
</ul>
</li>
<li>style
<ul>
<li>CSS used by the app</li>
</ul>
</li>
<li>config.fsx
<ul>
<li>This script runs first, we use it to configure the behavior of our generators and add any additional data like secret keys or SQL connection strings.</li>
</ul>
</li>
</ul>
<p>Next time we'll take a look an in depth look at loaders and generators and what they can do.</p>
]]></description>
    </item>
    <item>
      <title>Learning About Loaders and Generators in Fornax</title>
      <link>/posts/getting_started_with_fornax_loaders_generators.html</link>
      <guid>/posts/getting_started_with_fornax_loaders_generators.html</guid>
      <pubDate>Some(07/06/2020 00:00:00)</pubDate>
      <description><![CDATA[<p>Last time we had a brief introduction to <a href="https://github.com/ionide/Fornax">Fornax</a> (a static site generator written in F#). We went over a brief introduction of getting up and running as well as an overview of the files in a Fornax project. This time we're digging into a nuts and bolts by covering <strong>loaders</strong> and <strong>generators</strong>.</p>
<!--more-->
<h4>Loaders</h4>
<p>In order to talk about loaders we have to take a brief detour to talk about <strong>SiteContents</strong>. In Fornax, <strong>SiteContents</strong> is a type defined in the Fornax.Core.dll that can hold any arbitrary number of arbitrary lists of arbitrary objects. Normally, this is a collection of markdown files that are parsed into a collection of <strong>Post</strong> objects, but that's just the beginning.</p>
<p>Now that we have that out of the way, lets have a look at a loader from the project we made last time. Go ahead and take a peek at postloader.fsx. There's quite a lot to take in all at once, so let's break it down.</p>
<pre><code>type Post = {
    file: string
    link: string
    title: string option
    author: string option
    published: System.DateTime option
    tags: string list
    content: string
    summary: string
}
</code></pre>
<p>We declare the post type. So that further down in our program we can add a collection of that type to SiteContents. F# has a fairly robust type system and there's a number of improvements we could make to ensure data integrity, but we'll leave it as it is. We'll jump down to the bottom of the file and take a look at the <strong>loader</strong> function.</p>
<pre><code>let loader (projectRoot: string) (siteContent: SiteContents) =
        let postsPath = Path.Combine(projectRoot, contentDir)
        Directory.GetFiles postsPath
        |&gt; Array.filter (fun n -&gt; n.EndsWith &quot;.md&quot;)
        |&gt; Array.map loadFile
        |&gt; Array.iter siteContent.Add

    siteContent
</code></pre>
<p>We can see here that we harness .NET Framework to use Directory.GetFiles from the postsPath that we declare above it. If you're new to F#, the syntax may be a little confusing, but conceptually it's very similar to chaining array methods in JavaScript.</p>
<p>With the collection of files we return from Directory.GetFiles, we Array.filter the collection and return a new collection of only .md files. Then we iterate over that collection with Array.Map and loadFile function which we'll look at in more detail later. After maping the collection we finally Array.iter and add each object in the collection to siteContent and return siteContent after our additions.</p>
<pre><code>let loadFile n =
    let text = File.ReadAllText n

    let config = getConfig text
    let summary, content = getContent text
    let fileName = buildFileName n
    let file = Path.Combine(contentDir, fileName &quot;.md&quot;).Replace(&quot;\\&quot;, &quot;/&quot;)
    let link = &quot;/&quot; + Path.Combine(contentDir, fileName &quot;.html&quot;).Replace(&quot;\\&quot;, &quot;/&quot;)

    let filter = filterConfig config
    let title = filter &quot;title&quot; |&gt; Option.map trimString
    let author = filter &quot;author&quot; |&gt; Option.map trimString
    let published =
        filter &quot;published&quot; 
        |&gt; Option.map (trimString &gt;&gt; System.DateTime.Parse)

    let tags =
        let tagsOpt =
            filter &quot;tags&quot;
            |&gt; Option.map (trimString &gt;&gt; fun n -&gt; n.Split ',' |&gt; Array.toList)
        defaultArg tagsOpt []

    { file = file
    link = link
    title = title
    author = author
    published = published
    tags = tags
    content = content
    summary = summary }
</code></pre>
<p>The function loadFile does a lot of things I'll leave it to you to figure out the process of reading and parsing a given markdown file as passed in from the loader function. The thing I'd like to note, however, is that each time we iterate through this function we return an object that matches the shape of the Post type that exists at the top of the file.</p>
<p>Now that we've loaded our posts into SiteContents we can take a look at what generators do with that content.</p>
<h4>Generators</h4>
<p>Once we've churned through that loader and have our contents in memory we can generate some front end content. Let's have a look inside the post.fsx inside the generators folder.</p>
<p>This file is a lot shorter than postloader. Let's start at the bottom.</p>
<pre><code>let generate (ctx : SiteContents) (projectRoot: string) (page: string) =
    generate' ctx page
    |&gt; Layout.render ctx
</code></pre>
<p>Immediately we call <strong>generate'</strong> and pass in SiteContents and page then we pipe the value returned from that function to Layout.render. Layout.render is outside the scope of this article, but it lives in layout.fsx.</p>
<p>For now though, let's look at generate'. we'll look at it block by block.</p>
<pre><code>let generate' (ctx : SiteContents) (page: string) =
    let post =
        ctx.TryGetValues&lt;Postloader.Post&gt; ()
        |&gt; Option.defaultValue Seq.empty
        |&gt; Seq.find (fun n -&gt; n.file = page)
</code></pre>
<p>First we try to find the post. We start by using TryGetValues method on SiteContents to pull back all posts that match the shape of the object Post that exists in the loader we looked at previously. Then we iterate through the sequence until we find one that matches the page we're currently iterating through.</p>
<pre><code>    let siteInfo = ctx.TryGetValue&lt;Globalloader.SiteInfo&gt; ()
    let (desc, title) =
        siteInfo
        |&gt; Option.map (fun si -&gt; (si.description, si.title))
        |&gt; Option.defaultValue (&quot;&quot;, &quot;&quot;)
</code></pre>
<p>We do the same thing to grab SiteInfo (held in globalloader.fsx) and map any description and title we have stored.</p>
<pre><code>    Layout.layout ctx (defaultArg post.title &quot;&quot;) [
        section [Class &quot;hero is-info is-medium is-bold&quot;] [
            div [Class &quot;hero-body&quot;] [
                div [Class &quot;container has-text-centered&quot;] [
                    h1 [Class &quot;title&quot;] [!!title]
                    span [] [!!desc]
                ]
            ]
        ]
        div [Class &quot;container&quot;] [
            section [Class &quot;articles&quot;] [
                div [Class &quot;column is-8 is-offset-2&quot;] [
                    Layout.postLayout false post
                ]
            ]
        ]
    ]
</code></pre>
<p>This part is pretty different from what we've looked at previously. This is a feature of Fornax that harnesses an interesting feature of F# called <strong>Domain Specific Language</strong>. Instead of writing HTML in Fornax we use this DSL to simplify the templating process. If you've done work with HTML markup. I'll admit it feels a little awkward to use DSL to write a template, but once I got the hang of it I've found it pretty compelling.</p>
<p>From there generate' returns the DSL HtmlElement we constructed where it can be parsed and turned into a string in Layout.render when we finally output the file into the _public folder.</p>
<p>That's the basic overview of loaders and generators. We only scratched the surface of what we can do with loaders. Because we're working with F# scripts we can get our data from any source we might want.</p>
<p>RESTful API? For sure.</p>
<p>SQL DB? No problem.</p>
<p>CSV? Absolutely.</p>
<p>Similarly, we can output data in a generator in any number of formats. RSS, CSV, JSON, plain text, and so on.</p>
<p>That's enough for today I think. Next time, we'll take a look at creating our own loader using an external API.</p>
]]></description>
    </item>
    <item>
      <title>Writing Custom Loaders and Generators with Fornax</title>
      <link>/posts/getting_started_with_fornax_custom_loader.html</link>
      <guid>/posts/getting_started_with_fornax_custom_loader.html</guid>
      <pubDate>Some(08/22/2020 00:00:00)</pubDate>
      <description><![CDATA[<p>Last time we had an introduction to <a href="https://github.com/ionide/Fornax">Fornax</a> loaders and generators. Today we're going to build a custom loader and generator using an external REST API.</p>
<!--more-->
<h4>Before We Begin</h4>
<p>To facilitate working with data from APIs or sources outside of markdown we're going to bring in a library. Because Fornax is, at heart, a small collection of F# scripts consumed by a library, we won't be dealing with a package manager. I went to <a href="http://fsharp.github.io/FSharp.Data">FSharp.Data</a> and downloaded the precompiled binaries and dropped FSharp.Data.dll and FSharp.Data.DesignTime.dll into the _lib folder.</p>
<p>FSharp.Data is a collection of parsers and type providers that simplify common data routines and allows a more F# centric, functional first, way than if we were to use built in .NET Framework or C# libraries.</p>
<p>The API we're going to use is <a href="https://cat-fact.herokuapp.com/#/">Cat Facts</a> because it's free, open, and is a collection of facts about cats.</p>
<h4>Custom Cat Facts Loader</h4>
<p>We'll make a new file in the loaders folder called catfacts.fsx.</p>
<p>At the top of the file we'll bring in our assembly references:</p>
<pre><code class="language-fsharp">    #r &quot;../_lib/Fornax.Core.dll&quot;
    #r &quot;../_lib/FSharp.Data.dll&quot;
    open FSharp.Data
</code></pre>
<p>That's it, now we can use FSharp.Data to handle our HTTP request and JSON parsing.</p>
<p>We'll declare a JsonProvider with the basic shape of the response from the Cat Facts API and the CatFacts type that we'll be using internally.</p>
<pre><code class="language-fsharp">type CatFactsTemplate = JsonProvider&lt;&quot;&quot;&quot;
{
    &quot;all&quot;:
    [
        {
            &quot;_id&quot;: &quot;string&quot;,
            &quot;text&quot;: &quot;string&quot;
        }
    ]
}&quot;&quot;&quot;&gt;

type CatFacts =
    { Id : string
      Text : string }
</code></pre>
<p>We could make both of these type definitions more elaborate. We get a lot more information out of the Cat Facts API, but this is enough for now.</p>
<p>To explain, CatFactsTemplate uses JsonProvider out of FSharp.Data. When we go to parse the JSON from the HTTP request and translates it into an F# object we can use.</p>
<p>Now that our data types are out of the way, let's dig into the loader function.</p>
<pre><code class="language-fsharp">let loader (projectRoot: string) (siteContent: SiteContents) =
    let httpRequest = Http.RequestString(&quot;https://cat-fact.herokuapp.com/facts&quot;)
    let jsonResponse = CatFactsTemplate.Parse(httpRequest).All

    let facts = seq {
        for record in jsonResponse do
            { Id = record.Id ; Text = record.Text }
    }

    facts
    |&gt; Seq.take 10
    |&gt; Seq.iter (fun f -&gt; siteContent.Add f)

    siteContent
</code></pre>
<p>Taken in steps we can see that we make an HTTP request to the API and pull back a list of facts. FSharp.Data handwaves away a lot of the details that we might have had to do if we used WebClient out of .NET Framework.</p>
<p>After that, we call CatFactsTemplate.Parse to take the JSON body and convert it into an F# object. We tack All on the end so we have only the Array of objects with Id and Text.</p>
<p>Under the hood, the Array we get out of the parse doesn't actually implement <strong>IEnumberable&lt;'T&gt;</strong> so we can't iterate over it by piping it into Seq.xyz. Instead we'll create an <strong>IEnumerable&lt;'T&gt;</strong> ourselves with:</p>
<pre><code class="language-fsharp">let facts = seq {
    for record in jsonResponse do
        { Id = record.Id ; Text = record.Text }
}
</code></pre>
<p>This creates a sequence, which does implement IEnumberable&lt;'T&gt;, by looping through jsonResponse and returning our CatFacts type with the data defined in CatFactsTemplate.</p>
<pre><code class="language-fsharp">facts
|&gt; Seq.take 10
|&gt; Seq.iter (fun f -&gt; siteContent.Add f)

siteContent
</code></pre>
<p>Now that we have our sequence we can pipe that into Seq.take to cut down on the objects we're going to add into siteContent. Then we just Seq.iter to loop through our ten objects and add them to site content so we can generate HTML from them.</p>
<p>That's it, we now have a custom loader that consumes an external API.</p>
<h4>Custom Cat Facts Generator</h4>
<p>Now that we have all of our Cat Facts data loaded into SiteContents, let's bring it out into a generator. We'll make a new file called catfacts.fsx in the generators folder.</p>
<p>Then we'll whip up something that looks like this:</p>
<pre><code class="language-fsharp">#r &quot;../_lib/Fornax.Core.dll&quot;
#load &quot;layout.fsx&quot;

open Html

let catfactCard (fact : Catfactsloader.CatFacts) =
    div [Class &quot;card article&quot;] [
        div [Class &quot;card-content&quot;] [
            div [Class &quot;media-content has-text-centered&quot;] [
                !! fact.Text
            ]
        ]
    ]

let generate' (ctx : SiteContents) =
    let facts =
        ctx.TryGetValues&lt;Catfactsloader.CatFacts&gt;()
        |&gt; Option.defaultValue Seq.empty
        |&gt; Seq.toList
        |&gt; List.map (catfactCard)

    Layout.layout ctx &quot;Home&quot; [
        div [Class &quot;container&quot;] [
            section [Class &quot;articles&quot;] [
                div [Class &quot;column is-8 is-offset-2&quot;] facts
            ]
        ]
    ]

let generate (ctx : SiteContents) (projectRoot: string) (page: string) =
    generate' ctx
    |&gt; Layout.render ctx
</code></pre>
<p>We pull in the files we need at the top and open the module we'll be using, in this case HTML, to handle the Domain Specific Language (DSL) that we talked about in the last article.</p>
<pre><code class="language-fsharp">let generate (ctx : SiteContents) (projectRoot: string) (page: string) =
    generate' ctx
    |&gt; Layout.render ctx
</code></pre>
<p>Like we also talked about last time, generate gets called first by Fornax at build time. It calls another function called generate' and pipes the resulting HtmlElement content into the Layout.render function. Render then parses the template into a string for the resulting HTML document.</p>
<pre><code class="language-fsharp">let generate' (ctx : SiteContents) =
    let facts =
        ctx.TryGetValues&lt;Catfactsloader.CatFacts&gt;()
        |&gt; Option.defaultValue Seq.empty
        |&gt; Seq.toList
        |&gt; List.map (catfactCard)

    Layout.layout ctx &quot;Home&quot; [
        div [Class &quot;container&quot;] [
            section [Class &quot;articles&quot;] [
                div [Class &quot;column is-8 is-offset-2&quot;] facts
            ]
        ]
    ]
</code></pre>
<p>First, we try get to our CatFacts out of SiteContent with ctx.TryGetValues&lt;Catfactsloader.CatFacts&gt;(). Then we set a default value if we wind up not having records in SiteContents. Next, we turn the Sequence into a List. Finally, we map over the List<CatFacts> with the catfactCard function to return a List<HtmlElement>.</p>
<pre><code class="language-fsharp">let catfactCard (fact : Catfactsloader.CatFacts) =
    div [Class &quot;card article&quot;] [
        div [Class &quot;card-content&quot;] [
            div [Class &quot;media-content has-text-centered&quot;] [
                !! fact.Text
            ]
        ]
    ]
</code></pre>
<p>Here we see catfactCard is just a function that takes a CatFact and returns an HtmlElement with CatFact.Text in a div. With that, we should have a working generator ready to build some content.</p>
<h4>Final Steps</h4>
<p>The last thing we have to do is register the generator in config.fsx in the root of our project.</p>
<pre><code class="language-fsharp">let config = {
    Generators = [
        {Script = &quot;less.fsx&quot;; Trigger = OnFileExt &quot;.less&quot;; OutputFile = ChangeExtension &quot;css&quot; }
        {Script = &quot;sass.fsx&quot;; Trigger = OnFileExt &quot;.scss&quot;; OutputFile = ChangeExtension &quot;css&quot; }
        {Script = &quot;post.fsx&quot;; Trigger = OnFilePredicate postPredicate; OutputFile = ChangeExtension &quot;html&quot; }
        {Script = &quot;staticfile.fsx&quot;; Trigger = OnFilePredicate staticPredicate; OutputFile = SameFileName }
        {Script = &quot;index.fsx&quot;; Trigger = Once; OutputFile = NewFileName &quot;index.html&quot; }
        {Script = &quot;about.fsx&quot;; Trigger = Once; OutputFile = NewFileName &quot;about.html&quot; }
        {Script = &quot;contact.fsx&quot;; Trigger = Once; OutputFile = NewFileName &quot;contact.html&quot; }
    ]
}
</code></pre>
<p>This array of Generators determines how Fornax treats the generators we make. In this case we want to generate the file once by adding the following entry to the collection.</p>
<pre><code class="language-fsharp">{Script = &quot;catfacts.fsx&quot;; Trigger = Once; OutputFile = NewFileName &quot;catfacts.html&quot; }
</code></pre>
<p>Script is the generator script we're using and Trigger defines how to handle the generation of the HTML file. We are choosing the built in Fornax value of Once but we could define our own logic for how to handle file generation. Then we have the OutputFile and we're going to call it catfacts.html.</p>
<p>With that, we can <code>fornax watch</code> and visit localhost:8080/catfacts.html to see our result.</p>
<p>Now that we've created a very simple loader and generator to consume an API we could expand that out to any arbitrary data source. We can see that Fornax gives us a very simple method of pulling in and transforming that data. Moving forward we could, for instance, bring in Tweets, dev.to interactions, CSV data, or arbitrary database connections. Any data we can manipulate in F# can be turned into static content.</p>
<p>Next time I think we'll look into some of the features of Fornax and what it's lacking from other static site generators.</p>
]]></description>
    </item>
    <item>
      <title>Musings on Audio</title>
      <link>/posts/musings_on_audio.html</link>
      <guid>/posts/musings_on_audio.html</guid>
      <pubDate>Some(02/02/2024 00:00:00)</pubDate>
      <description><![CDATA[<p>2023 was, by many metrics, my least musical year. Across the board the numbers are glaring. Fewest new artists, genres, scrobbles, and raw minutes. Once again dominated by Boards of Canada and Lana Del Rey. Historically those two groups have made up the bulk of my listening during my professional career. It's easy to put either one on and git a massive amount of work done. Even so, we're looking at historic lows even for BoC.</p>
<!--more-->
<p>What happened? Well, three things: podcasts, YouTube, Twitch, and office changes.</p>
<h2>Podcasts</h2>
<p>I've long been a podcast guy. Since roughly 2011 I've listened to podcasts numbering in the triple digit ranges. Some of them ended prematurely and were mourned and some continue on in a zombie like state. Of particular dominance in 2023 were &quot;Chapo Trap House&quot;, &quot;True Anon&quot;, and the Merlin Mann family of two-guys-podcasting-shows but specifically &quot;Roderick on the Line.&quot; Post election and semi-post-pandemic CTH kept my toe dipped in political discourse enough. TA is a show about human misery and routinely features the worst possible things people do to one another. As for RotL, I like John and I like Merlin. Simple as.</p>
<p>The side effect of listening to that much podcasting is that it tends to just make me miserable. A long form version of doomscrolling piped directly into my ears. Most of my adult life has been a delicate balance between feeling informed and being utterly depressed about being informed, and it just so happens 2023 is the first full year I've been on a form of depression medication.</p>
<h2>YouTube</h2>
<p>Long form video essays took over a large portion of my listening time. Brief seven-hour retrospectives of games I've never played. Also 9 hour brief retrospectives on games I've played so much that I could nod sagely as the narrator described some story beat or mechanic.</p>
<h2>Twitch</h2>
<p>In 2023 I also ended up putting on more Twitch streams as background noise. There's a small collection of streamers that took up a bulk of that time, biggest of which was: <a href="https://www.twitch.tv/barricade">Barricade</a>. There's about a half dozen other developer streams that I picked up over this period of time. I'll have them listed in a separate page under the About section.</p>
<h2>Office</h2>
<p>2023 saw two major changes here. Directlink moved offices and we had some departures. The reduced size of the team led toward more time spent being open to answering questions. The change in office meant that I could no longer get up and walk around downtown on breaks. That means less time sitting at my desk with noise canceling headphones and even less time walking around with my AfterShokz Aeropex.</p>
<h2>What am I going to do about it?</h2>
<p>Well, I don't know. So far this year I've started playing music as part of my morning routine.</p>
<p>Historically, after getting out of bed I would put on a podcast or YouTube video, make breakfast, jump in the shower, get dressed, and drive to work. This year I've tried to listen to recommendations and morning mixes and the like. It's worked to an extent. I've also started blocking out certain periods of the day where I can put headphones on for an hour and be a bit more productive. In general it's helped increase my mood (although I'm sure the anti-depressants also helped).</p>
<p>I'm not sure what other steps to take. I think the most I can do is re-prioritize music in my day-to-day and see what happens.</p>
]]></description>
    </item>
    <item>
      <title>A Lukewarm Defense of Early Magic: The Gathering Creatures</title>
      <link>/posts/lukewarm_defense_of_early_magic_creatures.html</link>
      <guid>/posts/lukewarm_defense_of_early_magic_creatures.html</guid>
      <pubDate>Some(03/05/2024 00:00:00)</pubDate>
      <description><![CDATA[<p>The early days of Magic are &quot;notorious&quot; for the awful creatures present in the game.</p>
<!--more-->
<h2>A world of fast mana</h2>
<p>A cursory glance across the card files for Alpha, Beta, and Unlimited (ABU) reveals a staggering amount of fast mana. Sol Ring, Mana Vault, Basalt Monolith, the Mox cycle, and Black Lotus all deliver an incredible amount of mana very quickly. This reality would almost certainly inform a large portion of design in the first four expansions.</p>
<h2>Why were creatures weak?</h2>
<p>I'd like to submit that they weren't. Not really anyway. Yes, instant and sorcery cards (to use the modern names) were substantially more powerful, but conceptually they could only be cast once. In reality the existence of Regrowth made the nascent four-of rule basically invalid. We see that reflected in DCI quickly restricting it three months into competitive play.</p>
<p>A card like Craw Wurm could be cast on turn two on the play. It would stick around and put the opponent on a four turn clock. Even if it only did six damage that would be over a quarter of the starting life total.</p>
<p>No, Craw Wurm never saw real tournament play. Competitive play was never the only, or even the primary, way people played Magic. To this day the most common and popular format people play is <em>a pile of cards I own, played at my kitchen table</em>. It's a certainty that the crew designing the sets would have been cognizant of this. Further, they would have known after ABU proved there was a consumer appetite for Magic that some players would spend hundreds of dollars to own the best cards.</p>
<p>Ultimately, then, the general power level of creatures was conservative rather than actively bad. The flaw was in over-estimating the primary draw back of instants, sorceries.</p>
<h2>The Case for Lady Orca</h2>
<p>Lady Orca is a much maligned legendary creature from the set Legends.</p>
<p>She is a 7/4 legendary creature for 5RB.</p>
<p>Compare it to a similarly massive creature from ABU like Force of Nature. 2GGGG for an 8/8 trampler that has an upkeep cost of GGGG every turn. Or all stars like Erhnam Djinn a 3G 4/5 with what was intended to be a downside of making your opponents creatures unblockable.</p>
<p>Today we see an overcosted vanilla creature. To the designers, Lady Orca in this format is a three turn clock with 0 the only downside being BR casting cost and Legendary supertype and it dodges two of the premium removal spells in the format (Terror and Lightning Bolt) <em>and</em> receives a chunky 7 life when hit with Swords to Plowshares. Compared to other creatures this is an insane rate.</p>
]]></description>
    </item>
    <item>
      <title>Playing Magic after 30</title>
      <link>/posts/playing_magic_after_30.html</link>
      <guid>/posts/playing_magic_after_30.html</guid>
      <pubDate>Some(03/30/2024 00:00:00)</pubDate>
      <description><![CDATA[<p>I started playing Magic again.</p>
<!--more-->
<p>To be clear, I never really <em>stopped</em> I just moved twice, changed jobs thrice, and lived through a once in a lifetime pandemic. In that time I mostly played Hearthstone to scratch the competitive itch. HS never <em>really</em> cut it, but it was enough. When MtG: Arena came out I tried to play the MacOS client but it had consistent issues updating and other performance bugs.</p>
<p>In December 2023 Kelly started getting into Magic. She started playing MtG:A, watching streamers and commander gameplay. Her getting into it is what started me back into the breach.</p>
<p>After spending, roughly, 5-6 years away my skills are lacking. Especially in commander. I was always trash at commander. I'm a 1v1 competitive player at heart. I don't have a huge amount of practice mentally loading 4-5 player board states into my brain while also maintaining strict adherence to the basic rules of Magic.</p>
<p>In the FFA environment I'm deeply prone to missing triggers, draw steps, and doing what the cards say, so I beat myself up a bit and go on to make more blunders. As the night goes on and my mental game deteriorates after playing 1-2 hour grind-fests so does the play.</p>
<p>The other major challenge is the card pool has increased by leaps and bounds since 2017. Every time I sit down to a pod there are at least a dozen or more cards that I've never heard of provides some insane on-board trick that I need to load into working memory. It hasn't gone smoothly. Of course that permanent on the board with three discrete paragraphs of text is an enchantment. No, that's a 5/4 creature, also it has first strike and when it deals damage it gets +1/+1 counters equal to the damage it does. Idiot.</p>
<p>The other night I had the miserable experience of digging for combo pieces in my Alesha deck. After three games in three hours I just didn't have the mental left to do it in under ten minutes.</p>
<p>I know commander != Magic and that 1v1 events still fire around here. I feel like I could still hang in a 3 round Swiss but the mental drain from commander has me worried. If I can't play this casual ass format acceptably, let alone perfectly, how can I play competitively?</p>
<p>Am I just going to be some Uncle Rico talking about the good old days? &quot;Back in 2015 I used to be able to Top 4 modern RPTQs. Hell, I 2-1 or better'd every Khans block draft I entered.&quot; Just to get combo-killed by some kid on Lotus Field on a win and in into top 8.</p>
]]></description>
    </item>
    <item>
      <title>Adventures in Deck Building: Kaust, Eyes of the Glade</title>
      <link>/posts/adventures_in_deck_building_kaust.html</link>
      <guid>/posts/adventures_in_deck_building_kaust.html</guid>
      <pubDate>Some(05/22/2024 00:00:00)</pubDate>
      <description><![CDATA[<p>Deck building is hard. Building &quot;good&quot; decks with &quot;bad&quot; cards is somewhat harder. Sometimes that &quot;bad&quot; card is the commander.</p>
<!--more-->
<h2>My History with Kaust, Eyes of the Glade:</h2>
<p>I'll admit, I was a little bit excited when Kaust was revealed during preview season. I've loved the face-down/face-up morph/manifest/cloak/disguise concept for years and it looked substantially different from earlier attempts at the theme like Kadena, Slinking Sorcerer or Animar, Soul of Elements. Still, I probably wouldn't have bought it with my own money. Instead, I won the pre-con in an end-of-month giveaway at my LGS (shout out <a href="https://damegames.tcgplayerpro.com/">Dame Games</a>).</p>
<p>Since then I've been tinkering with it. At first attempting to make a value toolbox in the style of Kadena, a colorless beatdown deck, and a D&amp;T-style stax and tax monstrosity. None of these really worked. The themes all fought with the commander and the face-down card pool to choose from.</p>
<p>Instead, there's one thing that's extremely important to understand about Kaust: he isn't a morph/disguise commander. Not in the traditional sense anyway. He doesn't provide you with immediate card and board advantage that can take over a game just by playing face-down creatures. Nor does he reduce the cost of face-down creatures. Instead, Kaust is a manifest/cloak commander. He let's us abuse those mechanics to cheat out some interesting threats for very, very cheap.</p>
<p>Gameplan gives us the two core cards of the deck; Mastery of the Unseen and Ugin's Mastery.</p>
<h2>Manifest Masteries</h2>
<p>How do we make the most of these two cards? Their overall effect is fairly similar, but operate in distinct ways.</p>
<p>We have two distinct lines then, both resulting in different flavors of infinite mana. There are plenty of ways to do this in Naya.</p>
<p>To get the most out of Mastery of the Unseen we simply need the ability to make infinite (white) mana. Some of our options include:</p>
<ul>
<li>Emiel the Blessed + Dockside Extortionist + four artifacts/enchantments on opponent boards</li>
<li>Emiel the Blessed + Selvala, Heart of the Wild + Misc. Haste Enabler</li>
<li>Selvala, Heart of the Wilds + Staff of Domination + Misc. six power creature
And so on, we can adjust to taste and budget.</li>
</ul>
<p>In the case of Ugin's Mastery we need don't necessarily need infinite mana, we just need to loop casting a colorless spell.
For this we can</p>
<h2>Of Manifests and Cloaks</h2>
<p>Once we have those enablers, what are we trying to manifest? We have some criteria to look out for:</p>
<ul>
<li>Expensive beaters for a reduced price
<ul>
<li>Our primary win condition is reducing life totals to 0 and we can save some mana doing it by flipping some big beef.</li>
</ul>
</li>
<li>Negative ETB effects
<ul>
<li>Since we're manifesting, positive ETB triggers are wasted. Negative ETB triggers are effectively dodged though. We can skim some value on some value from big stat lines with awful ETBs.</li>
</ul>
</li>
<li>Triggers on combat damage to players
<ul>
<li>If we attack with 2/2 face-down creatures we miss out on &quot;when this creature attacks&quot; triggers. We do however still get combat damage and &quot;while this creature is attacking&quot; effects.</li>
</ul>
</li>
<li>Immediate status effects
<ul>
<li>Flip over a creature that grants some status, like indestructable</li>
</ul>
</li>
</ul>
<p>With that in mind we can immediately consider some stand-alone win condition all-stars like Blightsteel Colossus. In fact, we immediately end the game if we flip our Colossus while we have a Pyrotechnic Performer on board.</p>
<p>We can bring in a package of 10-12 creatures that include some fun cards, here's a non-exhaustive list at three different pricepoints (as of <em>6/4/2024</em>):</p>
<p>Big time John Hammond players, spare no expense:</p>
<ul>
<li>Blightsteel Colossus</li>
<li>Ancient Copper Dragon</li>
<li>Old Gnawbone</li>
<li>Avacyn, Angel of Hope</li>
<li>Kozilek, Butcher of Truth</li>
<li>Ulamog, the Ceaseless Hunger</li>
<li>Ulamog, the Infinite Gyre</li>
</ul>
<p>Some hitters at mid-tier prices:</p>
<ul>
<li>Elesh Norn, Grand Cenobite</li>
<li>Bloodthirster</li>
<li>Cavern-Hoard Dragon</li>
<li>Balefire Dragon</li>
<li>Blinding Angel</li>
<li>Angel of Destiny</li>
<li>Ancient Bronze Dragon</li>
<li>Ancient Gold Dragon</li>
</ul>
<p>Some choice budget options:</p>
<ul>
<li>Steel Hellkite</li>
<li>Giant Adephage</li>
<li>Port Razer</li>
<li>Siege Behemoth</li>
<li>Akki Underminer</li>
<li>Kutzil, Malamet Exemplar</li>
<li>Dragon Mage</li>
</ul>
<h2>Is it Good?</h2>
<p>Well, no. Still bad.</p>
<p>Sorry.</p>
]]></description>
    </item>
  </channel>
</rss>