<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://roganmurley.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://roganmurley.com/" rel="alternate" type="text/html" /><updated>2025-11-05T12:23:21+00:00</updated><id>https://roganmurley.com/feed.xml</id><title type="html">Rogan Murley</title><subtitle>Adventures in programming and game design</subtitle><entry><title type="html">Career Dev Treasures</title><link href="https://roganmurley.com/2025/11/01/career-dev-treasures.html" rel="alternate" type="text/html" title="Career Dev Treasures" /><published>2025-11-01T14:00:00+00:00</published><updated>2025-11-01T14:00:00+00:00</updated><id>https://roganmurley.com/2025/11/01/career-dev-treasures</id><content type="html" xml:base="https://roganmurley.com/2025/11/01/career-dev-treasures.html"><![CDATA[<p>A big part of my job is helping other engineers grow their careers. I’ve collected resources that have personally helped me, and helped me help others.</p>

<h1 id="practical-career-advice">Practical Career Advice</h1>

<ul>
  <li><a href="https://www.lennysnewsletter.com/p/the-magic-loop">The Magic Loop</a> by Ethan Evans
    <ul>
      <li>Build your career by making a partnership with your manager that accelerates you both. This is the framework I personally use to grow.</li>
      <li><a href="https://www.youtube.com/watch?v=GB0P0_nFPTA&amp;t=637s&amp;pp=ygULZXRoYW4gZXZhbnM%3D">Longer form video version.</a></li>
    </ul>
  </li>
  <li><a href="https://www.kalzumeus.com/2011/10/28/dont-call-yourself-a-programmer/">Don’t call yourself a programmer</a> by Patrick McKenzie
    <ul>
      <li>Frame your career as solving business problems, not building software. Work in profit centers not cost centers. An oldie but a goodie.</li>
    </ul>
  </li>
  <li><a href="https://www.youtube.com/watch?v=xV6j2Dxvoxw">Three Things Blocking Your Promotion to Staff/Principal Engineer</a> by Steve Huynh
    <ul>
      <li>Your org, project and manager may not have the scope for you to grow (but they won’t tell you that).</li>
    </ul>
  </li>
</ul>

<h1 id="staff-engineering">Staff+ Engineering</h1>

<p>What does it mean to be a Staff+ Engineer, and how do you get there?</p>

<ul>
  <li><a href="https://staffeng.com/book/">Staff Engineer</a> by Will Larson
    <ul>
      <li>Mandatory reading for any Staff+ engineer. A comprehensive overview of what it means to be Staff+, how to get there and the different <a href="https://staffeng.com/guides/staff-archetypes/">Staff archetypes</a>. Also available as an audiobook.</li>
    </ul>
  </li>
  <li><a href="https://www.youtube.com/watch?v=Xfq5iyX7BLQ">Why and How to be a big-tech Principal Engineer</a> by John Miller
    <ul>
      <li>Specifically covers Principal and how it differs from Staff.</li>
    </ul>
  </li>
</ul>

<h1 id="leadership-stories">Leadership Stories</h1>

<p>Inspiration from others can be hugely clarifying and motivating.</p>

<ul>
  <li><a href="https://www.youtube.com/@RyanLPeterman">Peterman Pod</a> by Ryan Peterman
    <ul>
      <li>Long form candid conversations with engineering leaders.</li>
      <li>Highlights:
        <ul>
          <li><a href="https://www.youtube.com/watch?v=QUhC5BDZt-E&amp;list=PLmvMm6hsJgCc9VPDgq8SQEFMln_azyKSG">Instagram Principal Engineer on Promotions, Breaking Prod, Tech Leading</a>
            <ul>
              <li>How to tech lead, manage risk and spend your time.</li>
            </ul>
          </li>
          <li><a href="https://www.youtube.com/watch?v=v2JxdjTi_1I&amp;list=PLmvMm6hsJgCc9VPDgq8SQEFMln_azyKSG">OpenAI &amp; Meta Distinguished Engineer (IC9) On Working With Zuck, Carmack &amp; Career Growth</a>
            <ul>
              <li>Breakdown of the impact and scope expected from IC1 to IC9.</li>
            </ul>
          </li>
          <li><a href="https://www.youtube.com/watch?v=40-ENZmqcz0&amp;list=PLmvMm6hsJgCc9VPDgq8SQEFMln_azyKSG">Amazon VP On Stack Ranking &amp; PIPs, Working With Bezos, His Promotions</a>
Candid candid dive into how big tech promos and layoffs actually work.</li>
          <li><a href="https://www.youtube.com/watch?v=rY44ViY45q8">Shopify Distinguished Eng (L10) on Principal+ Engineering, Career Story, Regrets</a>
            <ul>
              <li>Great exploration of Principal+ engineering and the “specialized generalist”.</li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="https://staffeng.com/stories/">Staff Engineer Stories</a> by Will Larson
    <ul>
      <li>Stories from the Staff Engineer book.</li>
    </ul>
  </li>
  <li><a href="https://podcast.staffeng.com/">StaffEng Podcast</a> by David Noël-Romas and Alex Kessinger
    <ul>
      <li>Interviews with Staff+ engineers across the industry.</li>
    </ul>
  </li>
</ul>

<h1 id="management">Management</h1>

<p>As a Staff+ engineer you should understand how your manager thinks, and be good at management-shaped work.</p>

<ul>
  <li><a href="https://www.amazon.co.uk/High-Output-Management-Andrew-Grove/dp/0679762884">High Output Management</a> by Andy Grove
    <ul>
      <li>The canonical engineering management text, a must read.</li>
    </ul>
  </li>
  <li><a href="https://press.stripe.com/an-elegant-puzzle">An Elegant Puzzle: Systems of Engineering Management</a> by Will Larson
    <ul>
      <li>The engineering management counterpart to the Staff Engineer book.</li>
    </ul>
  </li>
</ul>

<h1 id="money">Money</h1>

<p>Money matters. With some minimal effort you can make a lot more of it.</p>

<ul>
  <li><a href="https://www.kalzumeus.com/2012/01/23/salary-negotiation/">Salary Negotiation</a> by Patrick McKenzie
    <ul>
      <li>The ROI on salary negotiation is <em>absurd</em>. Think in terms of fully loaded costs and the business’ goals.</li>
    </ul>
  </li>
  <li><a href="https://blog.pragmaticengineer.com/software-engineering-salaries-in-the-netherlands-and-europe/">The Trimodal Nature of Software Engineering</a> by Gergely Orosz
    <ul>
      <li>Why does the same role pay 2-4x as much as different companies? How can you earn an American tech salary while working in Europe?</li>
    </ul>
  </li>
</ul>

<hr />

<p>I hope you find these resources as useful as I have. If there’s anything I’ve missed please reach out and let me know!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[A big part of my job is helping other engineers grow their careers. I’ve collected resources that have personally helped me, and helped me help others.]]></summary></entry><entry><title type="html">Something a lot like Pokémon Yellow</title><link href="https://roganmurley.com/2024/01/02/something-a-lot-like-pokemon-yellow.html" rel="alternate" type="text/html" title="Something a lot like Pokémon Yellow" /><published>2024-01-02T23:00:00+00:00</published><updated>2024-01-02T23:00:00+00:00</updated><id>https://roganmurley.com/2024/01/02/something-a-lot-like-pokemon-yellow</id><content type="html" xml:base="https://roganmurley.com/2024/01/02/something-a-lot-like-pokemon-yellow.html"><![CDATA[<p>When I was 7 years I old got my hands on the <a href="https://www.docdroid.net/h24r/nintendo-magazine-1999-pokemon-master-guide-pdf#page=28">Pokémon Yellow Master Guide</a>. I devoured it, leaving it dog-eared from multiple cover-to-cover readings.</p>

<p><img src="/assets/pokemon-guide.png" alt="Pokémon guide" width="256" /></p>

<p>But the truth is I didn’t even own a Game Boy, yet alone Pokémon Yellow. I prayed against all odds that I’d find a Game Boy on sale for £1 at Poundland, which only went to show that I didn’t have a great grasp of economics.</p>

<p>I never got to play Pokémon Yellow. I did, however, have a Pokémon Yellow in my head. From the Master Guide I had gained a surprisingly deep knowledge of the game without ever actually playing it. In my head you could find Moltres on the second floor of Victory Road, just as it is in the real game (I am assured). I didn’t know what it felt like to catch Moltres, sure, but I knew its stats, where to find it and what it looked like.</p>

<p><img src="/assets/victory-road.png" alt="Victory road" width="256" /></p>

<p>The Master Guide was so comprehensive that you could probably reverse-engineer the whole Pokémon Yellow game from it. Stop a minute to think about what that would look like. Really, what would it look like? It wouldn’t be <em>the</em> Pokémon Yellow, but it would be something a lot like Pokémon Yellow. Because of this we will say that the Master Guide is a <em>good model</em> of Pokémon Yellow.</p>

<p><img src="/assets/yellow-dalle.png" alt="DALLE-generated image of a Pokémon Yellow screenshot" width="256" /></p>

<p>We could do the same reverse-engineering exercise using a game review. Could we recreate a high fidelity Pokémon Yellow game just from a review? I don’t think so. The review would underspecify the game, requiring almost all the details be interpolated. We could create another game that shared similar mechanics, but it wouldn’t be much like Pokémon Yellow. Because of this we will say that a game review is a <em>bad model</em> of Pokémon Yellow.</p>

<p><img src="/assets/pokemon-review.jpg" alt="Pokémon review" width="512" /></p>

<p>Game guides are good models of games. In 2023 game guides have come a long way, and are now built collaboratively using wikis like <a href="https://bulbapedia.bulbagarden.net/wiki/Main_Page">Bulbapedia</a>. A game wiki is an <em>excellent</em> model of a game. Given a game wiki we should be able to recreate the corresponding game a reasonable level of fidelity.</p>

<p><img src="/assets/bulbapedia.png" alt="Bulbapedia" width="512" /></p>

<p>In a normal year this is where the thought experiment would end, but 2024 is not a normal year. With the recent advances in GenAI we now have access to capabilities that were once the reserve of science fiction. Using <a href="https://scale.com/blog/text-universal-interface">text as the universal interface</a> we can create text, art, audio and code from raw compute. With this technology we could mechanically create a game from just its model. This ceases to be just a thought experiment: we build a system that takes the Pokémon Yellow wiki in and pushes a Pokémon Yellow game out.</p>

<p>Why is this interesting? Suppose we want a new version of Pokémon Yellow with a new Pokémon creature. We don’t need to change the game’s source code, we just need to edit the wiki and then press the “remake game” button. Modding a game becomes as simple as editing the wiki. Creating a game becomes as simple as writing its wiki.</p>

<p>Despite the GenAI revolution our capabilities aren’t yet advanced enough to do this in the general case. However, I believe that today we are already capable of doing this in a restricted environment. I think we could mechanically create a Game Boy era Pokémon game from a model by using a static game engine and dynamically creating the areas, enemies, items etc. from the wiki. It’s not easy, but I think it’s possible.</p>

<p>This is what I’m hacking on right now: a wiki that makes games. Check out <a href="https://www.langengine.com/waitlist">langengine.com</a> to join the waitlist.</p>

<p><img src="/assets/langengine-peek.png" alt="Langengine screenshot" width="512" /></p>

<p>Some closing thoughts:</p>

<ul>
  <li>How do we handle the non-determinism of generating parts of the game that are underspecified?</li>
  <li>What happens when we use AI to write the wiki?</li>
  <li>Does this thinking apply outside games?</li>
  <li>How good of a model is Wikipedia for reality?</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[When I was 7 years I old got my hands on the Pokémon Yellow Master Guide. I devoured it, leaving it dog-eared from multiple cover-to-cover readings.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://roganmurley.com/assets/yellow-dalle.png" /><media:content medium="image" url="https://roganmurley.com/assets/yellow-dalle.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Game rules with a Free Monad DSL</title><link href="https://roganmurley.com/2021/12/11/free-monads.html" rel="alternate" type="text/html" title="Game rules with a Free Monad DSL" /><published>2021-12-11T13:00:00+00:00</published><updated>2021-12-11T13:00:00+00:00</updated><id>https://roganmurley.com/2021/12/11/free-monads</id><content type="html" xml:base="https://roganmurley.com/2021/12/11/free-monads.html"><![CDATA[<p>This post is about how and why my multiplayer card game <a href="https://www.galgagame.com">GALGA</a> uses a Free Monad DSL for its game rules. If that’s gobbledygook to you, read on and hopefully it’ll make at least a little more sense by the end!</p>

<p><img src="/assets/galga2.gif" alt="GALGA gif" /></p>

<p><a href="https://www.galgagame.com">GALGA</a> is like Magic: The Gathering in that each card has its own special rules that change the game. These rules can be simple, like “heal 10 life”, or more complex, like “make healing deal damage instead”.</p>

<p>The rules are a <a href="https://en.wikipedia.org/wiki/Domain-specific_language">Domain-Specific Language</a> (DSL) for describing what happens in the game. They are like a mini programming language that runs only on the machinery of the game. Interestingly (or perhaps frighteningly) rules can rewrite other rules at runtime. The rule “make healing deal damage instead” rewrites healing rules to damage rules. The rules DSL can actually <strong>metaprogram itself</strong>!</p>

<p>I implemented <a href="https://www.galgagame.com/">GALGA</a> in Haskell, and one of the many motivations for this is that Haskell has great support for embedded DSLs. It lets you write a DSL while retaining the full and considerable power of the Haskell type system. It shouldn’t be surprising that a language steered by programming language researchers would be good at writing programming languages 😉</p>

<p>There are a few different ways to use embedded DSLs in Haskell (<a href="https://www.haskellforall.com/2012/06/you-could-have-invented-free-monads.html">Free Monads</a>, <a href="https://serokell.io/blog/introduction-tagless-final">Tagless Final</a> and more). Free Monads are <em>usually</em> the wrong choice as they are worse than the other options in both performance and type complexity, but I chose to use them anyway for reasons I’ll be digging into below.</p>

<p><img src="/assets/galga.gif" alt="Another GALGA gif" /></p>

<p><strong><em>A note for the non-Haskeller: the word “monad” might have scared you off there! If you don’t have a vague understanding of monads, I wouldn’t recommend looking for an explanation online. Rather, the only way to truly understand them is to get stuck into Haskell and experience the problems that monads try to solve. There is no other way!</em></strong></p>

<p>So what is a “free” monad?</p>

<p>Free as in freedom?</p>

<p>Free as in beer?</p>

<p>No, <a href="https://en.wikipedia.org/wiki/Free_object"><em>mathematically</em> free</a>.</p>

<p>A free monad is a monad that satisfies the monad laws and nothing more. It has all the stucture of a monad and none of the “effects”. I like to think of it as a program AST (<a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">Abstract Syntax Tree</a>) without an interpreter. The free monad represents some computation, but it doesn’t define what that computation actually means until paired with an interpreter. Different interpreters can interpret in different ways.</p>

<p>Other flavours of DSL can swap implementations in much the same way free monads swap out interpreters, so why choose free monads? Their unique edge is that the “AST” is maintained and can be introspected and metaprogrammed. Earlier I mentioned that this is a key requirement of our DSL, because we have cards that rewrite the rules of other cards at runtime.</p>

<p><em>(side note: <a href="https://hackage.haskell.org/package/freer-simple-1.2.1.1/docs/Control-Monad-Freer.html">“freer monads”</a> improve on the performance and interpreter composability of free monads, but I haven’t played much with them yet. It looks like they are better but there is a lot less beginner-friendly writing about them.)</em></p>

<p><img src="/assets/galga3.gif" alt="GALGA gif" /></p>

<p>GALGA implements multiple embedded DSLs using free monads. There is a high level DSL for game rules as discussed, and lower level DSLs for changing state directly and visualization. The rules DSL interprets to the lower level DSLs as needed, usually embedded together.</p>

<p>The <a href="https://github.com/RoganMurley/GALGAGAME/tree/89064b75f92250c5ce327e261f1ad76eea600c25/server/src/DSL/Alpha">primitive DSL</a> is a low level DSL for changing the game state, consisting of only basic getters and setters. When interpreted it mutates the game state. I like to think of it as the “machine code” to which the rules DSL compiles.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Example primitive DSL program</span>
<span class="kr">do</span>
  <span class="kr">let</span> <span class="n">which</span> <span class="o">=</span> <span class="kt">PlayerA</span>
  <span class="n">life</span> <span class="o">&lt;-</span> <span class="n">getLife</span> <span class="p">(</span><span class="n">other</span> <span class="n">which</span><span class="p">)</span>
  <span class="n">setLife</span> <span class="p">(</span><span class="n">other</span> <span class="n">which</span><span class="p">)</span> <span class="p">(</span><span class="n">life</span> <span class="o">-</span> <span class="mi">4</span><span class="p">)</span>
</code></pre></div></div>

<p>The <a href="https://github.com/RoganMurley/GALGAGAME/tree/89064b75f92250c5ce327e261f1ad76eea600c25/server/src/DSL/Anim">animation DSL</a> is a low level DSL for visualization. When interpreted it calculates state diffs and associated animations to send to players. Because it relies on accessing the game state it is only interpreted when embedded with the primitive DSL.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Example anim DSL program</span>
<span class="kt">Anim</span><span class="o">.</span><span class="n">hurt</span> <span class="kt">PlayerB</span> <span class="mi">4</span> <span class="kt">Slash</span>
</code></pre></div></div>

<p>The <a href="https://github.com/RoganMurley/GALGAGAME/tree/89064b75f92250c5ce327e261f1ad76eea600c25/server/src/DSL/Beta">rules DSL</a> is the high level DSL for describing the game rules, things like drawing cards or healing. When interpreted it compiles to the primitive and animation DSLs embedded together.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Example rules DSL program</span>
<span class="kr">do</span>
  <span class="kr">let</span> <span class="n">which</span> <span class="o">=</span> <span class="kt">PlayerA</span>
  <span class="n">hurt</span> <span class="mi">4</span> <span class="p">(</span><span class="n">other</span> <span class="n">which</span><span class="p">)</span> <span class="kt">Slash</span>
  <span class="n">draw</span> <span class="n">which</span> <span class="n">which</span> <span class="mi">1</span>
</code></pre></div></div>

<p>Because Haskell is a lazy language we only compute the interpretations of the rules DSL that we actually use. In some case we only need the state change, for example when the <a href="https://github.com/RoganMurley/GALGAGAME/blob/0f119962d4e6a240ee549b9d1134bbbcc807030b/server/src/ArtificialIntelligence.hs#L43">AI evaluates moves</a>. If we simply don’t use the animations they are never computed.</p>

<p>Because Free Monads give us ASTs for our DSLs, we can metaprogram them by transforming those ASTs. For example, the rule “healing deals damage instead” can be implemented as a <a href="https://github.com/RoganMurley/GALGAGAME/blob/0f119962d4e6a240ee549b9d1134bbbcc807030b/server/src/StatusEff.hs#L29">rewrite rule that replaces healing AST nodes with damage nodes</a>. This is why we have a separate DSL for rules rather than making it an abstraction over the primitive DSL. It is impossible to identify healing AST nodes when the AST is made of just getters and setters.</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Example healing to hurting rewrite rule</span>
<span class="n">rewriteRule</span> <span class="o">::</span> <span class="kt">Rules</span><span class="o">.</span><span class="kt">DSL</span> <span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">Rules</span><span class="o">.</span><span class="kt">DSL</span> <span class="n">a</span>
<span class="n">rewriteRule</span> <span class="p">(</span><span class="kt">Heal</span> <span class="n">l</span> <span class="n">w</span> <span class="n">n</span><span class="p">)</span> <span class="o">=</span> <span class="kt">Hurt</span> <span class="n">l</span> <span class="n">w</span> <span class="kt">Curse</span> <span class="n">n</span>
<span class="n">rewriteRule</span> <span class="n">dsl</span> <span class="o">=</span> <span class="n">dsl</span>
</code></pre></div></div>

<p>Embedded DSLs have proved a powerful pattern. I initially planned to use the DSLs only for card rules, but they have expanded to be used for every part of the core game logic. They are the killer feature of Haskell and I will definitely be using them again in the future. As for free monads, I wouldn’t recommend them for most use cases as they are relatively complex and slow compared to alternatives. However, metaprogramming was crucial for this project and so they were the right choice.</p>

<p>I hope you gleaned something useful from this blog post! If you did, consider checking out <a href="https://www.galgagame.com">GALGA for free in your browser</a> or <a href="https://github.com/RoganMurley/galgagame">peruse the source on GitHub</a> 🙇‍♂️</p>

<p><em>May your wheel keep turning.</em></p>]]></content><author><name></name></author><summary type="html"><![CDATA[This post is about how and why my multiplayer card game GALGA uses a Free Monad DSL for its game rules. If that’s gobbledygook to you, read on and hopefully it’ll make at least a little more sense by the end!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://roganmurley.com/assets/hero.png" /><media:content medium="image" url="https://roganmurley.com/assets/hero.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Unable to verify the Docker daemon is listening (Digital Ocean / docker-machine)</title><link href="https://roganmurley.com/2020/12/12/docker-machine-digital-ocean-unable-to-verify-docker-daemon-is-listening.html" rel="alternate" type="text/html" title="Unable to verify the Docker daemon is listening (Digital Ocean / docker-machine)" /><published>2020-12-12T10:00:00+00:00</published><updated>2020-12-12T10:00:00+00:00</updated><id>https://roganmurley.com/2020/12/12/docker-machine-digital-ocean-unable-to-verify-docker-daemon-is-listening</id><content type="html" xml:base="https://roganmurley.com/2020/12/12/docker-machine-digital-ocean-unable-to-verify-docker-daemon-is-listening.html"><![CDATA[<p>I practice continuous deployment on my side project <a href="https://github.com/RoganMurley/GALGAGAME">GALGA</a>. Every time the <a href="https://app.circleci.com/pipelines/github/RoganMurley/GALGAGAME?branch=master">continuous integration pipeline</a> finishes green on master the application is deployed… if all goes well. Earlier in the week all deploys broke 😱</p>

<p>Deploying the application involves spinning up a new <a href="https://www.digitalocean.com/">Digital Ocean</a> droplet using <code class="language-plaintext highlighter-rouge">docker-machine</code>. Earlier this week every deployment started mysteriously failing with the following error:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error creating machine: Error running provisioning: Unable to verify the Docker daemon is listening: Maximum number of retries (10) exceeded.
</code></pre></div></div>

<p>It turns out that the <a href="https://github.com/docker/machine/issues/4858">latest Docker version doesn’t work with <code class="language-plaintext highlighter-rouge">docker-machine</code></a>. To make things right again you’ll want to pin the Docker engine version like so:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--engine-install-url "https://releases.rancher.com/install-docker/19.03.9.sh"
</code></pre></div></div>

<p>It took a while of Googling to find the issue so I’m hoping this post can save someone else the headache.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I practice continuous deployment on my side project GALGA. Every time the continuous integration pipeline finishes green on master the application is deployed… if all goes well. Earlier in the week all deploys broke 😱]]></summary></entry><entry><title type="html">Tripwire Tests</title><link href="https://roganmurley.com/2020/02/22/tripwire-tests.html" rel="alternate" type="text/html" title="Tripwire Tests" /><published>2020-02-22T10:00:00+00:00</published><updated>2020-02-22T10:00:00+00:00</updated><id>https://roganmurley.com/2020/02/22/tripwire-tests</id><content type="html" xml:base="https://roganmurley.com/2020/02/22/tripwire-tests.html"><![CDATA[<h3 id="introduction">Introduction</h3>
<p>Tests are usually written to check that code is correct. This is a lofty and noble goal, which makes it easy to forget that they can be used in other ways too. I like to use tests to communicate with future developers who are making certain predictable changes, reminding or warning them through the failure message. I call these tests “tripwire” tests.</p>

<h3 id="example-making-sure-temporary-hacks-are-temporary">Example: Making sure temporary hacks are temporary</h3>
<p>When working on a legacy project I found a bug in the framework we were using. The bug could be worked around with an ugly hack, but it was easy to fix with a PR so I naturally did so. Unfortunately we were stuck on an earlier framework version and not yet ready to upgrade to the latest fixed version. We were stuck with an ugly temporary hack for now.</p>

<p>It seemed excessive to fork the whole framework, but I wanted to make sure the hack would eventually be removed even if I wasn’t around to do so. I wrote a tripwire test which failed when the framework was updated to the fixed version. The failure message told the updating developer to remove the hack as it was no longer needed, acting as a kind of forced documentation.</p>

<p>I left the project, the library was upgraded, the tripwire test was tripped and the hack was removed. Happy ending!</p>

<h3 id="example-highlighting-when-conditions-change">Example: Highlighting when conditions change</h3>
<p>I was iterating fast on an admin tool that used the Wikidata API to make a SPARQL query. Conditions were such that I didn’t need to worry too much about SPARQL injection: the tool was private and the database was read-only. I did some naive validation of parameters and called it a day, but I was worried about what might happen if these conditions changed. For example, what if we moved from the external read-only database to our own internal read/write database? I added some documentation warning about this but it didn’t seem like enough.</p>

<p>I wrote a tripwire test which failed when the value of the API endpoint URL changed, reminding the developer about the security assumptions. Under continuous integration the developer will be forced to acknowledge the warning in a way that just documentation can’t do.</p>

<h3 id="conclusion">Conclusion</h3>
<p>Tests can be a powerful tool for communication. Don’t just think of them as testing correctness, consider testing other assumptions as well. I’d love to hear other ways you’ve used tests to communicate.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Introduction Tests are usually written to check that code is correct. This is a lofty and noble goal, which makes it easy to forget that they can be used in other ways too. I like to use tests to communicate with future developers who are making certain predictable changes, reminding or warning them through the failure message. I call these tests “tripwire” tests.]]></summary></entry><entry><title type="html">Install an older version of Stack</title><link href="https://roganmurley.com/2019/06/17/using-older-versions-of-stack.html" rel="alternate" type="text/html" title="Install an older version of Stack" /><published>2019-06-17T10:00:00+00:00</published><updated>2019-06-17T10:00:00+00:00</updated><id>https://roganmurley.com/2019/06/17/using-older-versions-of-stack</id><content type="html" xml:base="https://roganmurley.com/2019/06/17/using-older-versions-of-stack.html"><![CDATA[<p>The <a href="https://docs.haskellstack.org/en/stable/README/">Haskell Tool Stack</a> is a great tool for reliably building Haskell projects. Unfortunately its <a href="https://github.com/commercialhaskell/stack/releases/tag/v2.1.1">latest release</a> deprecated the <code class="language-plaintext highlighter-rouge">stack image</code> command, a vital part of my build process. I needed to keep my CI working until I have time to wrangle a replacement for the command, but I couldn’t find an easy way to install an older version of Stack.</p>

<p>I ended up using the raw install script from a previous GitHub release just like I would use the script hosted at <a href="https://get.haskellstack.org">https://get.haskellstack.org</a>. The following let me install <a href="https://github.com/commercialhaskell/stack/releases/tag/v1.9.3">Stack 1.9.3</a>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo curl -sSL https://raw.githubusercontent.com/commercialhaskell/stack40cf7b37526b86d1676da82167ea8758a854953b/etc/scripts/get-stack.sh | sh &amp;&amp;
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[The Haskell Tool Stack is a great tool for reliably building Haskell projects. Unfortunately its latest release deprecated the stack image command, a vital part of my build process. I needed to keep my CI working until I have time to wrangle a replacement for the command, but I couldn’t find an easy way to install an older version of Stack.]]></summary></entry><entry><title type="html">The bug I wish I hadn’t caught</title><link href="https://roganmurley.com/2019/06/08/the-bug.html" rel="alternate" type="text/html" title="The bug I wish I hadn’t caught" /><published>2019-06-08T10:00:00+00:00</published><updated>2019-06-08T10:00:00+00:00</updated><id>https://roganmurley.com/2019/06/08/the-bug</id><content type="html" xml:base="https://roganmurley.com/2019/06/08/the-bug.html"><![CDATA[<p>When I catch a bug before release I’m usually glad, because I’ve narrowly averted ruining someone’s experience or eating their data. Today I found a bug that I wish I hadn’t caught, a bug that I wish I’d released into the wild to witness in its full glory. I found this bug in my digital card game <a href="https://github.com/RoganMurley/Ring-of-Worlds">Ring of Worlds</a>, and I’ll tell you its story.</p>

<p>The diligent player <code class="language-plaintext highlighter-rouge">Catherine32</code> has been grinding the <code class="language-plaintext highlighter-rouge">CPU</code> player for experience points. She’s making good progress but not enough; she wants more, much more. She wonders how much experience <code class="language-plaintext highlighter-rouge">CPU</code> must have, playing against her and everyone else with no rest. It must be quite a lot.</p>

<p><img src="/assets/catherine32_playing.png" alt="Catherine32 playing" /></p>

<p><code class="language-plaintext highlighter-rouge">Catherine32</code> tries to look up the <code class="language-plaintext highlighter-rouge">CPU</code> stats but finds something mysterious… there is no <code class="language-plaintext highlighter-rouge">CPU</code> account! It doesn’t have an account because it’s not a real player, so all of its experience goes into the void. It plays so many games but doesn’t get any experience. Tough break.</p>

<p>Late that night <code class="language-plaintext highlighter-rouge">Catherine32</code> gets a mischievous idea. She opens up her computer and creates an account. That account’s name? <code class="language-plaintext highlighter-rouge">CPU</code>. She does literally nothing for the next 7 days. The <code class="language-plaintext highlighter-rouge">CPU</code> account does the opposite of nothing, eating up experience through all hours via its robot slave. It becomes a behemoth, leveled above all others. Yet it is still under the control of <code class="language-plaintext highlighter-rouge">Catherine32</code>, and she is on top of the world.</p>

<p>Oops, it turns out players are looked up by username even if they’re an AI with no account. You can harvest the AI’s experience just by creating an account with the same username. This is a silly artefact left over from when there were no real player accounts and everything was just drop-in-drop-out.</p>

<p><img src="/assets/cpu_experience.png" alt="SQL statement showing that the CPU player has lots of experience" /></p>

<p>All the players hide in fear, for nobody can hope to match the experience of the dreaded <code class="language-plaintext highlighter-rouge">CPU</code>. Until one day a new challenger arises, an account named <code class="language-plaintext highlighter-rouge">Guest</code>. It turns people play a lot of games when not signed in too. <code class="language-plaintext highlighter-rouge">CPU</code> and <code class="language-plaintext highlighter-rouge">Guest</code> become locked in an eternal war for experience supremacy, at least until I ban them both.</p>

<p>Unfortunately this didn’t actually occur because I caught the bug before release, but I wish it did. In fact, I was very tempted to just close my eyes and pretend I hadn’t seen anything. If you fancy hoarding some sweet experience yourself then <a href="https://www.ringofworlds.com/signup">sign up and play</a>!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[When I catch a bug before release I’m usually glad, because I’ve narrowly averted ruining someone’s experience or eating their data. Today I found a bug that I wish I hadn’t caught, a bug that I wish I’d released into the wild to witness in its full glory. I found this bug in my digital card game Ring of Worlds, and I’ll tell you its story.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://roganmurley.com/assets/catherine32_playing.png" /><media:content medium="image" url="https://roganmurley.com/assets/catherine32_playing.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Your project has constraints</title><link href="https://roganmurley.com/2019/06/02/your-project-has-constraints.html" rel="alternate" type="text/html" title="Your project has constraints" /><published>2019-06-02T15:10:00+00:00</published><updated>2019-06-02T15:10:00+00:00</updated><id>https://roganmurley.com/2019/06/02/your-project-has-constraints</id><content type="html" xml:base="https://roganmurley.com/2019/06/02/your-project-has-constraints.html"><![CDATA[<p>I’m working on a <a href="https://github.com/RoganMurley/Ring-of-Worlds">digital card game</a> with a focus on simple but deep gameplay, and I have implemented lots of interesting cards. However, my artistic skills are… somewhat lacking. Why, then, did I copy other card games by trying to illustrate each card with beautiful art? This ties what I’m good at to what I’m not good at, so I can’t scale what I’m good at. That’s not a recipe for success.</p>

<p>Instead, I could illustrate cards with simpler symbols and runes. This way I can focus on what I’m good at, which is the only thing that sets the project apart. This doesn’t mean I can’t have beautiful art in the game, it just means I won’t let its cost limit what I create. I’ll be able to make the gameplay the best it can be without worrying about budget.</p>

<p>The same holds true when considering scale. When you’re small you have few (or no) users, so you can’t play like the big guys. In fact you don’t <em>want</em> to play like them, they are the ones wishing they could play like you! If they could do
what you have the opportunity to do on a small scale they most certainly would. Where they build anonymous matchmaking you can build community and genuine connection. Where they try to appeal to the broadest audience you can totally envelope a niche.</p>

<p>Your project has constraints. Don’t fight them, acknowledge them.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I’m working on a digital card game with a focus on simple but deep gameplay, and I have implemented lots of interesting cards. However, my artistic skills are… somewhat lacking. Why, then, did I copy other card games by trying to illustrate each card with beautiful art? This ties what I’m good at to what I’m not good at, so I can’t scale what I’m good at. That’s not a recipe for success.]]></summary></entry></feed>