<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Nat Knight</title>
    <link>http://natknight.xyz/</link>
    <description>Reflections, diversions, and opinions from a progressive ex-physicist programmer dad with a sore back.</description>
    <pubDate>Wed, 08 Apr 2026 13:27:22 -0700</pubDate>
    <item>
      <title>The Unbearable Effectiveness of Being Able to Run the Damn Code</title>
      <link>http://natknight.xyz/the-unbearable-effectiveness-of-being-able-to-run-the-damn-code</link>
      <description>&lt;![CDATA[#practices #rant&#xA;&#xA;I&#39;ve worked on computer systems in many different environments: cloud-native serverless functions, self-managed virtual servers, embedded devices, pushing code for on-prem bare-metal racks that I&#39;m not allowed to look at, much less touch. My experience across all these projects has falsified a lot of opinions, but there&#39;s one thing I believed when I first started coding and still believe now, perhaps more than ever:&#xA;&#xA;You need to be able to run the damn code.&#xA;&#xA;!--more--&#xA;&#xA;Whether you&#39;re starting a new feature, testing your implementation, or trying to root-cause some gnarly bug, you need to be able to run the damn code.&#xA;&#xA;Whether you&#39;re shotgunning printlns, attaching a debugger, or grinding out bugs with fuzzing, you need to be able to run the damn code.&#xA;&#xA;Whether you&#39;re a senior operating at the highest levels of architecture, a junior hanging on by your fingernails, or somewhere in the middle trying to level up, you need to be able to run the damn code.&#xA;&#xA;Whatever your scaling challenges, cost constraints, compliance requirements, ideological loyalties to this or that technology the thing you should never give up, no matter what the content marketing or the case studies or the highly paid consultants say is the ability to run the damn code.&#xA;&#xA;Not &#34;we have a functional core that we run in a serverless shell.&#34; Not &#34;we have a staging environment that developers can access.&#34; Not &#34;you can spin up an emulator for this data service that mostly works except for this long list of caveats.&#34; You need to be able to run the code on your own hardware with full access, zero marginal cost, and total visibility into the process.&#xA;&#xA;Anything less will become a productivity destroying, team blocking, project killing obstacle.&#xA;&#xA;This means:&#xA;&#xA;Writing code that can run in an environment you can emulate: prefer containers to cloud functions unless you have a very good reason&#xA;Use software that can be obtained without approval: either use an open source database or pony up for as many licenses as you have developers&#xA;Write your code to be run: it can be a compose file that emulates production or a local Kubernetes environment, but it can&#39;t be a pile of shell scripts and implicit state (unless you have someone who&#39;s extremely good at shell scripts)&#xA;Write down all the state: Keep your infrastructure definitions in version control and run them. A lot. Do devops as &#34;developers can operate the system&#34;, not &#34;we have someone whose job is YAML and AWS certifications&#34;&#xA;&#xA;And of course having a coding agent makes this so much more true. Having a system you can inspect yourself is necessary. Having a system that an fast-working, dogged, and reasonably capable LLM-driven tool can interact with and iterate on without worrying about costs or permissions or dropping a database that you needed, actually--that&#39;s glorious.]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="http://natknight.xyz/tag:practices" class="hashtag"><span>#</span><span class="p-category">practices</span></a> <a href="http://natknight.xyz/tag:rant" class="hashtag"><span>#</span><span class="p-category">rant</span></a></p>

<p>I&#39;ve worked on computer systems in many different environments: cloud-native serverless functions, self-managed virtual servers, embedded devices, pushing code for on-prem bare-metal racks that I&#39;m not allowed to look at, much less touch. My experience across all these projects has falsified a lot of opinions, but there&#39;s one thing I believed when I first started coding and still believe now, perhaps more than ever:</p>

<p>You need to be able to run the damn code.</p>



<p>Whether you&#39;re starting a new feature, testing your implementation, or trying to root-cause some gnarly bug, you need to be able to run the damn code.</p>

<p>Whether you&#39;re shotgunning <code>println</code>s, attaching a debugger, or grinding out bugs with fuzzing, you need to be able to run the damn code.</p>

<p>Whether you&#39;re a senior operating at the highest levels of architecture, a junior hanging on by your fingernails, or somewhere in the middle trying to level up, you need to be able to run the damn code.</p>

<p>Whatever your scaling challenges, cost constraints, compliance requirements, ideological loyalties to this or that technology the thing you should <em>never</em> give up, no matter what the content marketing or the case studies or the highly paid consultants say is the <strong>ability to run the damn code</strong>.</p>

<p>Not “we have a functional core that we run in a serverless shell.” Not “we have a staging environment that developers can access.” Not “you can spin up an emulator for this data service that mostly works except for this long list of caveats.” You need to be able to run the code on your own hardware with full access, zero marginal cost, and total visibility into the process.</p>

<p>Anything less will become a productivity destroying, team blocking, project killing obstacle.</p>

<p>This means:</p>
<ul><li>Writing code that can run in an environment you can emulate: prefer containers to cloud functions unless you have a <em>very</em> good reason</li>
<li>Use software that can be obtained without approval: either use an open source database or pony up for as many licenses as you have developers</li>
<li>Write your code to be run: it can be a <code>compose</code> file that emulates production or a local Kubernetes environment, but it can&#39;t be a pile of shell scripts and implicit state (unless you have someone who&#39;s <em>extremely</em> good at shell scripts)</li>
<li>Write down all the state: Keep your infrastructure definitions in version control and run them. A lot. Do devops as “developers can operate the system”, not “we have someone whose job is YAML and AWS certifications”</li></ul>

<p>And of <em>course</em> having a coding agent makes this so much more true. Having a system you can inspect yourself is necessary. Having a system that an fast-working, dogged, and reasonably capable LLM-driven tool can interact with and iterate on without worrying about costs or permissions or dropping a database that you needed, actually—<em>that&#39;s</em> glorious.</p>
]]></content:encoded>
      <guid>http://natknight.xyz/the-unbearable-effectiveness-of-being-able-to-run-the-damn-code</guid>
      <pubDate>Wed, 18 Feb 2026 18:46:32 +0000</pubDate>
    </item>
    <item>
      <title>Link: How StrongDM’s AI team build serious software without even looking at the code</title>
      <link>http://natknight.xyz/link-how-strongdms-ai-team-build-serious-software-without-even-looking-at-the</link>
      <description>&lt;![CDATA[From Simon Willison&#39;s blog.&#xA;&#xA;This is an interesting piece of work! A small team built a bunch of software in a short time by leveraging a whole lot of LLM assistance both for building their software and for validating it.&#xA;&#xA;The main things that are new to me in this story are:&#xA;&#xA;Building digital twins of third-party services like Google Docs that you can test against without rate limits.&#xA;Keeping validation scenarios away from the processes doing the building so that they can be &#34;independently verified&#34; by another agent (kind of like a holdout set in machine learning).&#xA;&#xA;I think there are some interesting things in this writeup (though I&#39;d love to see more details about how they build their digital twins), but I have some fundamental concerns as well.&#xA;&#xA;Primarily, even if you keep your validation scenarios hidden, if they&#39;re being done by an LLM they&#39;re inherently probabilistic and could be hiding nasty surprises, especially security problems caused by weird adversarial inputs. Even though Claude can write pretty good code, is very fast, and sometimes catches things that I miss, I&#39;m still not comfortable putting my name on software without a closely reviewed and deterministic validation step.&#xA;&#xA;It also sounds like there are some easily avoidable code quality. I think bringing taste and good practices to a repo is an important part of your work as a senior engineer, and something that can pay dividends as agents follow the patterns that you&#39;ve laid down.]]&gt;</description>
      <content:encoded><![CDATA[<p>From <a href="https://simonwillison.net/2026/Feb/7/software-factory/">Simon Willison&#39;s blog</a>.</p>

<p>This is an interesting piece of work! A small team built a bunch of software in a short time by leveraging a whole lot of LLM assistance both for building their software and for validating it.</p>

<p>The main things that are new to me in this story are:</p>
<ul><li>Building digital twins of third-party services like Google Docs that you can test against without rate limits.</li>
<li>Keeping validation scenarios away from the processes doing the building so that they can be “independently verified” by another agent (kind of like a holdout set in machine learning).</li></ul>

<p>I think there are some interesting things in this writeup (though I&#39;d love to see more details about how they build their digital twins), but I have some fundamental concerns as well.</p>

<p>Primarily, even if you keep your validation scenarios hidden, if they&#39;re being done by an LLM they&#39;re inherently probabilistic and could be hiding nasty surprises, especially security problems caused by weird adversarial inputs. Even though Claude can write pretty good code, is very fast, and sometimes catches things that I miss, I&#39;m still not comfortable putting my name on software without a closely reviewed and deterministic validation step.</p>

<p>It also <a href="https://news.ycombinator.com/item?id=46927737">sounds like</a> there are some easily avoidable code quality. I think bringing taste and good practices to a repo is an important part of your work as a senior engineer, and something that can pay dividends as agents follow the patterns that you&#39;ve laid down.</p>
]]></content:encoded>
      <guid>http://natknight.xyz/link-how-strongdms-ai-team-build-serious-software-without-even-looking-at-the</guid>
      <pubDate>Mon, 09 Feb 2026 20:38:43 +0000</pubDate>
    </item>
    <item>
      <title>Preparing for Advent of Code 2025 with Unison</title>
      <link>http://natknight.xyz/preparing-for-aoc-2025-with-unison</link>
      <description>&lt;![CDATA[#unison #adventofcode #aoc2025&#xA;&#xA;Advent of Code] is back again and I&#39;m joining in the fun! This year, I&#39;ll try solving it with [Unison], a statically typed functional language with some very neat ideas about content-addressable source code and distributed computing. It has a proprietary cloud backend, but you can also [run it on your own machines, and it recently had its 1.0 release.&#xA;&#xA;[Advent of Code]: https://adventofcode.com&#xA;[Unison]: https://www.unison-lang.org&#xA;&#xA;What follows are my very first ignorant baby steps coding in Unison.&#xA;&#xA;!--more--&#xA;&#xA;Pure Functional Programming&#xA;&#xA;I have some experience with other pure functional programming languages. There will, of course, be some differences, but I&#39;m going to assume that I can pick it up as I work through the puzzles. That&#39;s what AoC is all about!&#xA;&#xA;If you&#39;re not familiar with this style of programming, I recommend Elm or Clojure as starting places. They&#39;re quite different (Elm is a little closer to Unison), but both very illuminating, and more approachable if you&#39;re coming from something like Python or JavaScript.&#xA;&#xA;The Unison Codebase Manager&#xA;&#xA;When I&#39;ve worked on Advent of Code in the past I&#39;ve used languages like Chapel, Rust, or Gleam where you write code in files, you edit code in files, and you run code from files.&#xA;&#xA;Unison... doesn&#39;t work like that.&#xA;&#xA;You do write your code in text files, but rather than living there you load it into your content-addressed, append-only codebase with a tool called the Unison Codebase Manager (or ucm).&#xA;&#xA;It feels a bit like using a REPL: you write your code, ucm watches for changes and does type checking, runs tests, etc. When you&#39;re satisfied with the code you&#39;re working on you run an update command at the ucm prompt and your functions are imported.&#xA;&#xA;I followed the tour of the Unison workflow to get a feel for how it works. I wouldn&#39;t say it&#39;s comfortable yet, but I&#39;m ready to get started which is all we need!&#xA;&#xA;Reading Puzzle Input&#xA;&#xA;Rather than using the Advent of Code API I like to get my puzzle inputs manually, read them from a file, and then paste the output to check. I&#39;m a bear of little brain, and sometimes I need to get hands on with debugging my code!&#xA;&#xA;To approach AoC this way I need to know how to:&#xA;&#xA;Read input from a file&#xA;Parse that input into a data structure&#xA;Print output to the terminal&#xA;&#xA;This is slightly complicated by the way Unison handles &#34;effects&#34;, which includes things like input and output. Full treatment is beyond the scope of this writeup, but the relevant functions are documented in base.IO.&#xA;&#xA;Printing a file is easy enough: we can use printLine anywhere in our main function. Reading a file involves constructing the path, opening the file, and reading it as text.&#xA;&#xA;Putting these parts together looks something like:&#xA;&#xA;main : &#39;{IO, Exception} ()&#xA;main =&#xA;    use IO *&#xA;    do&#xA;        inputPath = FilePath &#34;input/foo.txt&#34;&#xA;        inputHandle = open inputPath Read&#xA;        input = getAllText inputHandle&#xA;        printLine input&#xA;&#xA;Parsing is trickier.&#xA;&#xA;There are tools in the base language for doing parsing, but they&#39;re quite simplistic. In particular, they don&#39;t handle parsing recursive structures, which is something that past AoC puzzles have required. On the other hand, sometimes puzzles start with tidy nested structures but then switch to considering the input in a more ad-hoc fashion.&#xA;&#xA;For now, I&#39;ll try using the built-in parsing tools, but I&#39;ll figure out how to bring in a more sophisticated parsing library if I need it.&#xA;&#xA;Sharing Solutions&#xA; Another lovely feature of AoC is reading solutions from other developers. Seriously: BurntSushi&#39;s 2018 solutions in Rust taught me more about working in that language than hours of noodling on code did.&#xA;&#xA;Unison programmers seem to share code on Unison Share instead of GitHub. There&#39;s even an Advent of Code template pinned to the front page!&#xA;&#xA;I followed the instructions in Unison share code hosting to get a feel for this. I&#39;m not sure if my code will be valuable to anyone, but I&#39;ll publish it here anyways!&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="http://natknight.xyz/tag:unison" class="hashtag"><span>#</span><span class="p-category">unison</span></a> <a href="http://natknight.xyz/tag:adventofcode" class="hashtag"><span>#</span><span class="p-category">adventofcode</span></a> <a href="http://natknight.xyz/tag:aoc2025" class="hashtag"><span>#</span><span class="p-category">aoc2025</span></a></p>

<p><a href="https://adventofcode.com">Advent of Code</a> is back again and I&#39;m joining in the fun! This year, I&#39;ll try solving it with <a href="https://www.unison-lang.org">Unison</a>, a statically typed functional language with some very neat ideas about content-addressable source code and distributed computing. It has a proprietary cloud backend, but you can also <a href="https://www.unison-lang.org/blog/cloud-byoc/">run it on your own machines</a>, and it <a href="https://www.unison-lang.org/unison-1-0/">recently had its 1.0 release</a>.</p>

<p>What follows are my very first ignorant baby steps coding in Unison.</p>



<h2 id="pure-functional-programming" id="pure-functional-programming">Pure Functional Programming</h2>

<p>I have some experience with other pure functional programming languages. There will, of course, be some differences, but I&#39;m going to assume that I can pick it up as I work through the puzzles. That&#39;s what AoC is all about!</p>

<p>If you&#39;re <em>not</em> familiar with this style of programming, I recommend <a href="https://elm-lang.org">Elm</a> or <a href="https://clojure.org">Clojure</a> as starting places. They&#39;re quite different (Elm is a little closer to Unison), but both very illuminating, and more approachable if you&#39;re coming from something like Python or JavaScript.</p>

<h2 id="the-unison-codebase-manager" id="the-unison-codebase-manager">The Unison Codebase Manager</h2>

<p>When I&#39;ve worked on Advent of Code in the past I&#39;ve used languages like Chapel, Rust, or Gleam where you write code in files, you edit code in files, and you run code from files.</p>

<p>Unison... doesn&#39;t work like that.</p>

<p>You <em>do</em> write your code in text files, but rather than living there you load it into your content-addressed, append-only codebase with a tool called the Unison Codebase Manager (or <code>ucm</code>).</p>

<p>It feels a bit like using a REPL: you write your code, <code>ucm</code> watches for changes and does type checking, runs tests, etc. When you&#39;re satisfied with the code you&#39;re working on you run an <code>update</code> command at the <code>ucm</code> prompt and your functions are imported.</p>

<p>I followed the <a href="https://www.unison-lang.org/docs/tour/">tour of the Unison workflow</a> to get a feel for how it works. I wouldn&#39;t say it&#39;s comfortable yet, but I&#39;m ready to get started which is all we need!</p>

<h2 id="reading-puzzle-input" id="reading-puzzle-input">Reading Puzzle Input</h2>

<p>Rather than using the Advent of Code API I like to get my puzzle inputs manually, read them from a file, and then paste the output to check. I&#39;m a bear of little brain, and sometimes I need to get hands on with debugging my code!</p>

<p>To approach AoC this way I need to know how to:</p>
<ul><li>Read input from a file</li>
<li>Parse that input into a data structure</li>
<li>Print output to the terminal</li></ul>

<p>This is slightly complicated by the way Unison handles “effects”, which includes things like input and output. Full treatment is beyond the scope of this writeup, but the relevant functions are <a href="https://share.unison-lang.org/@unison/base/code/releases/7.4.2/latest/types/IO">documented in base.IO</a>.</p>

<p>Printing a file is easy enough: we can use <code>printLine</code> anywhere in our <code>main</code> function. Reading a file involves constructing the path, opening the file, and reading it as text.</p>

<p>Putting these parts together looks something like:</p>

<pre><code class="language-unison">main : &#39;{IO, Exception} ()
main =
    use IO *
    do
        inputPath = FilePath &#34;input/foo.txt&#34;
        inputHandle = open inputPath Read
        input = getAllText inputHandle
        printLine input
</code></pre>

<p>Parsing is trickier.</p>

<p>There are <a href="https://share.unison-lang.org/@unison/base/code/main/@g18fbfsnp5t9difgcd4hlv7fft0edplsq6sj6ipmoitf72t4ti6ds43g747sple2679tv0c598jm2k6rvd9f0brpg9dmitca08lnvi0/types/Pattern">tools</a> in the base language for doing parsing, but they&#39;re quite simplistic. In particular, they don&#39;t handle parsing recursive structures, which is something that past AoC puzzles have required. On the other hand, sometimes puzzles <em>start</em> with tidy nested structures but then switch to considering the input in a more ad-hoc fashion.</p>

<p>For now, I&#39;ll try using the built-in parsing tools, but I&#39;ll figure out how to bring in a <a href="https://share.unison-lang.org/@dfreeman/yap">more sophisticated parsing library</a> if I need it.</p>

<h2 id="sharing-solutions" id="sharing-solutions">Sharing Solutions</h2>

<p> Another lovely feature of AoC is reading solutions from other developers. Seriously: <a href="https://github.com/BurntSushi/advent-of-code">BurntSushi&#39;s 2018 solutions in Rust</a> taught me more about working in that language than hours of noodling on code did.</p>

<p>Unison programmers seem to share code on <a href="https://share.unison-lang.org">Unison Share</a> instead of GitHub. There&#39;s even an <a href="https://share.unison-lang.org/@unison/advent-of-code">Advent of Code template</a> pinned to the front page!</p>

<p>I followed the instructions in <a href="https://www.unison-lang.org/docs/tooling/unison-share/">Unison share code hosting</a> to get a feel for this. I&#39;m not sure if my code will be valuable to anyone, but I&#39;ll publish it <a href="https://share.unison-lang.org/@nathanielknight/aoc2025">here</a> anyways!</p>
]]></content:encoded>
      <guid>http://natknight.xyz/preparing-for-aoc-2025-with-unison</guid>
      <pubDate>Tue, 25 Nov 2025 22:40:27 +0000</pubDate>
    </item>
    <item>
      <title>Snaste</title>
      <link>http://natknight.xyz/im-a-sucker-for-an-arcane-word-and-i-recently-learned-a-real-good-one</link>
      <description>&lt;![CDATA[I’m a sucker for an arcane word, and I recently learned a real good one. Quoting from Merriam Webster:&#xA;&#xA;&lt;blockquote cite=&#34;https://www.merriam-webster.com/dictionary/snaste&#xA;&#34;  strongsnaste/strong (noun, obsolete):&#xA;pThe wick of a snuffed candle/p&#xA;/blockquote&#xA;&#xA;What I love about this word is how it conjures up feelings that could only have existed in the past.&#xA;&#xA;!--more--&#xA;&#xA;I might read about changing technology, how in the past light-after-sunset was an expensive luxury and a messy, sooty, stinking affair. I might imagine the advent of clean-burning oil or electric light, or examine graphs of economic indicators and see how they track the adoption of new modes of lighting.&#xA;&#xA;But none of that could make me look at a snuffed out candle and appreciate it as something with parts that I’d care about enough to call by name. It wouldn’t occur to me to differentiate a wick from a snaste, but somebody did, which means they were thinking about candles a whole dang lot. I can imagine, vividly, the feeling of picking up a taper and finding the wick broken, and exclaiming, “a pox on thee Jeremy, thou hast marred my snaste and now my taper is all jacked up.”&#xA;&#xA;The past is always more complicated and more remote than you think. How lucky to glimpse it, even fleetingly. &#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>I’m a sucker for an arcane word, and I recently learned a real good one. Quoting from <a href="https://www.merriam-webster.com/dictionary/snaste">Merriam Webster</a>:</p>

<blockquote cite="https://www.merriam-webster.com/dictionary/snaste">
<strong>snaste</strong> (noun, obsolete):
<p>The wick of a snuffed candle</p></blockquote>

<p>What I love about this word is how it conjures up feelings that could only have existed in the past.</p>



<p>I might read about changing technology, how in the past light-after-sunset was an expensive luxury and a messy, sooty, stinking affair. I might imagine the advent of clean-burning oil or electric light, or examine graphs of economic indicators and see how they track the adoption of new modes of lighting.</p>

<p>But none of that could make me look at a snuffed out candle and appreciate it as something with parts that I’d care about enough to call by name. It wouldn’t occur to me to differentiate a wick from a snaste, but somebody did, which means they were thinking about candles a whole dang lot. I can imagine, vividly, the feeling of picking up a taper and finding the wick broken, and exclaiming, “a pox on thee Jeremy, thou hast marred my snaste and now my taper is all jacked up.”</p>

<p>The past is always more complicated and more remote than you think. How lucky to glimpse it, even fleetingly.</p>
]]></content:encoded>
      <guid>http://natknight.xyz/im-a-sucker-for-an-arcane-word-and-i-recently-learned-a-real-good-one</guid>
      <pubDate>Tue, 07 Oct 2025 05:05:44 +0000</pubDate>
    </item>
    <item>
      <title>A foot gun in ripgrep</title>
      <link>http://natknight.xyz/a-foot-gun-in-ripgrep</link>
      <description>&lt;![CDATA[#ripgrep #tool #footgun&#xA;&#xA;I adore ripgrep. It&#39;s fast, it&#39;s convenient, and I&#39;ve never wanted a feature and found it missing.&#xA;&#xA;But no software is perfect, and today ripgrep was the program causing problems.&#xA;&#xA;div style=&#34;border-radius: 1em; background: aliceblue; padding: 1em; border: 1px solid blue;&#34;&#xA;h2Update/h2&#xA;pAndrew Gallant (the author of RipGrep) a href=&#34;https://bsky.app/profile/burntsushi.net/post/3lvt7jzqejc2v&#34;kindly pointed out on BlueSky/a that you can also address this by running a command in your repo:/p&#xA;&#xA;precodeecho &#39;!/.github/&#39;     .rgignore/code/pre&#xA;&#xA;pThanks Andrew!/p&#xA;/div&#xA;!--more--&#xA;&#xA;By default, ripgrep doesn&#39;t search everything. It checks your .gitignore for example, which is wonderful when you don&#39;t want to be searching all of nodemodules. It also ignores hidden files and that&#39;s what bit me today.&#xA;&#xA;See GitHub Actions are stored under .github/workflows/, so when I removed a particular file and rg&#39;d through my whole codebase making sure it wasn&#39;t used anywhere I missed that it was used in some GitHub Actions.&#xA;&#xA;Easily fixed, but how to make sure I don&#39;t get bit again?&#xA;&#xA;We can search hidden files (but ignore .git/) with&#xA;&#xA;rg --hidden --glob=!.git/&#xA;&#xA;but that&#39;s a bit of a pain to type for a tool I use many times daily.&#xA;&#xA;It turns out ripgrep does have a configuration file that you can activate by setting an environment variable (RIPGREPCONFIGPATH).&#xA;&#xA;After a bit of fussing with fish to get the variable set properly, I ended up with this in my shell config:&#xA;&#xA;set -gx RIPGREPCONFIG_PATH $HOME/.config/ripgreprc&#xA;&#xA;and this in my ripgreprc:&#xA;&#xA;--hidden&#xA;--glob=!.git/&#xA;&#xA;And now I&#39;m on to bigger and better (or at least different) problems.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="http://natknight.xyz/tag:ripgrep" class="hashtag"><span>#</span><span class="p-category">ripgrep</span></a> <a href="http://natknight.xyz/tag:tool" class="hashtag"><span>#</span><span class="p-category">tool</span></a> <a href="http://natknight.xyz/tag:footgun" class="hashtag"><span>#</span><span class="p-category">footgun</span></a></p>

<p>I adore <a href="https://github.com/burntsushi/ripgrep">ripgrep</a>. It&#39;s fast, it&#39;s convenient, and I&#39;ve never wanted a feature and found it missing.</p>

<p>But no software is perfect, and today ripgrep was the program causing problems.</p>

<p><div style="border-radius: 1em; background: aliceblue; padding: 1em; border: 1px solid blue;">
<h2>Update</h2>
<p>Andrew Gallant (the author of RipGrep) <a href="https://bsky.app/profile/burntsushi.net/post/3lvt7jzqejc2v">kindly pointed out on BlueSky</a> that you can also address this by running a command in your repo:</p></p>

<pre><code>echo &#39;!/.github/&#39; &gt;&gt; .rgignore</code></pre>

<p><p>Thanks Andrew!</p>
</div>
</p>

<p>By default, ripgrep doesn&#39;t search everything. It checks your <code>.gitignore</code> for example, which is wonderful when you don&#39;t want to be searching all of <code>node_modules</code>. It also ignores <a href="https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory">hidden files</a> and that&#39;s what bit me today.</p>

<p>See GitHub Actions are stored under <code>.github/workflows/</code>, so when I removed a particular file and <code>rg</code>&#39;d through my whole codebase making sure it wasn&#39;t used anywhere I missed that it was used in some GitHub Actions.</p>

<p>Easily fixed, but how to make sure I don&#39;t get bit again?</p>

<p>We can search hidden files (but ignore <code>.git/</code>) with</p>

<pre><code>rg --hidden --glob=!.git/*
</code></pre>

<p>but that&#39;s a bit of a pain to type for a tool I use many times daily.</p>

<p>It turns out ripgrep does have a <a href="https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#configuration-file">configuration file</a> that you can activate by setting an environment variable (<code>RIPGREP_CONFIG_PATH</code>).</p>

<p>After a bit of fussing with <a href="https://fishshell.com/docs/current/language.html">fish</a> to get the variable set properly, I ended up with this in my shell config:</p>

<pre><code class="language-fish">set -gx RIPGREP_CONFIG_PATH $HOME/.config/ripgreprc
</code></pre>

<p>and this in my <code>ripgreprc</code>:</p>

<pre><code>--hidden
--glob=!.git/*
</code></pre>

<p>And now I&#39;m on to bigger and better (or at least different) problems.</p>
]]></content:encoded>
      <guid>http://natknight.xyz/a-foot-gun-in-ripgrep</guid>
      <pubDate>Wed, 23 Jul 2025 23:49:28 +0000</pubDate>
    </item>
    <item>
      <title>Lazy-loading data modules in Python with one magic function</title>
      <link>http://natknight.xyz/lazy-loading-data-modules-in-python-with-one-magic-function</link>
      <description>&lt;![CDATA[#python #data&#xA;&#xA;Greg Wilson was soliciting strategies for lazy-loading datasets in Python modules. There are, of course, many ways to do this, but I didn&#39;t see this one being discussed.&#xA;&#xA;Since Python 3.7 (released in 2017) you can define a module-level getattr function that gets used for name resolution (see PEP 562). You can use this to call a data-loading function the first time each module-level property is accessed. This implementation uses only functions, dicts, and the one magic &#34;dunder&#34; function, which might be more approachable than programming with classes (depending on your audience).&#xA;&#xA;!--more--&#xA;&#xA;A sketch of an implementation:&#xA;&#xA;Cache loaded datasets&#xA;datasetcache = {}&#xA;&#xA;Dictionary mapping dataset names to loader functions&#xA;datasetloaders = {&#xA;  # maps names to argument-less functions that load datasets&#xA;}&#xA;&#xA;def getattr(name):&#xA;    &#34;&#34;&#34;PEP 562 module-level getattr function for lazy loading&#34;&#34;&#34;&#xA;    # Check if we should load a dataset for this attribute&#xA;    if name in datasetloaders:&#xA;        # If not already cached, load and cache it&#xA;        if name not in datasetcache:&#xA;            datasetcachename] = datasetloaders[name&#xA;        return datasetcache[name]&#xA;&#xA;    # If not a dataset, raise AttributeError&#xA;    raise AttributeError(f&#34;Module has no attribute &#39;{name}&#39;&#34;)&#xA;&#xA;For example, this module has attributes foo and bar which are only calculated when they&#39;re first referenced:&#xA;&#xA;foo.py&#xA;&#xA;Cache loaded datasets&#xA;datasetcache = {}&#xA;&#xA;def loaddata(name):&#xA;    if name == &#34;foo&#34;:&#xA;        print(&#34;loading foo&#34;)&#xA;        return 1&#xA;    elif name == &#34;bar&#34;:&#xA;        print(&#34;loading bar&#34;)&#xA;        return 2&#xA;    else:&#xA;        # This should be unreachable, but just in case...&#xA;        raise AttributeError(f&#34;Module has no attribute &#39;{name}&#39;&#34;)&#xA;&#xA;Dictionary mapping dataset names to loader functions&#xA;datasetloaders = {&#xA;    &#34;foo&#34;: lambda: loaddata(&#34;foo&#34;),&#xA;    &#34;bar&#34;: lambda: loaddata(&#34;bar&#34;),&#xA;}&#xA;&#xA;def getattr(name):&#xA;    &#34;&#34;&#34;PEP 562 module-level getattr function for lazy loading&#34;&#34;&#34;&#xA;    # Check if we should load a dataset for this attribute&#xA;    if name in datasetloaders:&#xA;        # If not already cached, load and cache it&#xA;        if name not in datasetcache:&#xA;            datasetcachename] = datasetloaders[name&#xA;        return dataset_cache[name]&#xA;&#xA;    # If not a dataset, raise AttributeError&#xA;    raise AttributeError(f&#34;Module has no attribute &#39;{name}&#39;&#34;)&#xA;&#xA;In use, this looks like:&#xA;&#xA;      import foo&#xA;      foo.foo&#xA;loading foo&#xA;1&#xA;      foo.foo&#xA;1&#xA;      # NOTE: didn&#39;t load foo a second time&#xA;      foo.bar&#xA;loading bar&#xA;2&#xA;      foo.quuz&#xA;Traceback (most recent call last):&#xA;  File &#34;python-input-4&#34;, line 1, in module&#xA;    foo.quuz&#xA;  File &#34;/Users/nknight/tmp/foo.py&#34;, line 34, in getattr&#xA;    raise AttributeError(f&#34;Module has no attribute &#39;{name}&#39;&#34;)&#xA;AttributeError: Module has no attribute &#39;quuz&#39;&#xA;`]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="http://natknight.xyz/tag:python" class="hashtag"><span>#</span><span class="p-category">python</span></a> <a href="http://natknight.xyz/tag:data" class="hashtag"><span>#</span><span class="p-category">data</span></a></p>

<p>Greg Wilson was <a href="https://third-bit.com/2025/04/21/lazy-loading-data-package/">soliciting</a> strategies for lazy-loading datasets in Python modules. There are, of course, many ways to do this, but I didn&#39;t see this one being discussed.</p>

<p>Since Python 3.7 (released in 2017) you can define a module-level <code>__getattr__</code> function that gets used for name resolution (see <a href="https://peps.python.org/pep-0562/">PEP 562</a>). You can use this to call a data-loading function the first time each module-level property is accessed. This implementation uses only functions, dicts, and the one magic “dunder” function, which might be more approachable than programming with classes (depending on your audience).</p>



<p>A sketch of an implementation:</p>

<pre><code class="language-python"># Cache loaded datasets
_dataset_cache = {}

# Dictionary mapping dataset names to loader functions
_dataset_loaders = {
  # maps names to argument-less functions that load datasets
}


def __getattr__(name):
    &#34;&#34;&#34;PEP 562 module-level __getattr__ function for lazy loading&#34;&#34;&#34;
    # Check if we should load a dataset for this attribute
    if name in _dataset_loaders:
        # If not already cached, load and cache it
        if name not in _dataset_cache:
            _dataset_cache[name] = _dataset_loaders[name]()
        return _dataset_cache[name]

    # If not a dataset, raise AttributeError
    raise AttributeError(f&#34;Module has no attribute &#39;{name}&#39;&#34;)
</code></pre>

<p>For example, this module has attributes <code>foo</code> and <code>bar</code> which are only calculated when they&#39;re first referenced:</p>

<pre><code class="language-python"># foo.py

# Cache loaded datasets
_dataset_cache = {}


def load_data(name):
    if name == &#34;foo&#34;:
        print(&#34;loading foo&#34;)
        return 1
    elif name == &#34;bar&#34;:
        print(&#34;loading bar&#34;)
        return 2
    else:
        # This should be unreachable, but just in case...
        raise AttributeError(f&#34;Module has no attribute &#39;{name}&#39;&#34;)


# Dictionary mapping dataset names to loader functions
_dataset_loaders = {
    &#34;foo&#34;: lambda: load_data(&#34;foo&#34;),
    &#34;bar&#34;: lambda: load_data(&#34;bar&#34;),
}


def __getattr__(name):
    &#34;&#34;&#34;PEP 562 module-level __getattr__ function for lazy loading&#34;&#34;&#34;
    # Check if we should load a dataset for this attribute
    if name in _dataset_loaders:
        # If not already cached, load and cache it
        if name not in _dataset_cache:
            _dataset_cache[name] = _dataset_loaders[name]()
        return _dataset_cache[name]

    # If not a dataset, raise AttributeError
    raise AttributeError(f&#34;Module has no attribute &#39;{name}&#39;&#34;)
</code></pre>

<p>In use, this looks like:</p>

<pre><code>&gt;&gt;&gt; import foo
&gt;&gt;&gt; foo.foo
loading foo
1
&gt;&gt;&gt; foo.foo
1
&gt;&gt;&gt; # NOTE: didn&#39;t load foo a second time
&gt;&gt;&gt; foo.bar
loading bar
2
&gt;&gt;&gt; foo.quuz
Traceback (most recent call last):
  File &#34;&lt;python-input-4&gt;&#34;, line 1, in &lt;module&gt;
    foo.quuz
  File &#34;/Users/nknight/tmp/foo.py&#34;, line 34, in __getattr__
    raise AttributeError(f&#34;Module has no attribute &#39;{name}&#39;&#34;)
AttributeError: Module has no attribute &#39;quuz&#39;
</code></pre>
]]></content:encoded>
      <guid>http://natknight.xyz/lazy-loading-data-modules-in-python-with-one-magic-function</guid>
      <pubDate>Tue, 22 Apr 2025 17:49:33 +0000</pubDate>
    </item>
    <item>
      <title>Web Tool: Text Fragment URL Generator</title>
      <link>http://natknight.xyz/web-tool-text-fragment-url-generator</link>
      <description>&lt;![CDATA[#webtool #hypertext #web&#xA;&#xA;After yet another fight with the syntax for text fragments in URLs (which I&#39;ve written about before) I had Claude whip up a little app for constructing and testing them.&#xA;&#xA;It&#39;s published here.]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="http://natknight.xyz/tag:webtool" class="hashtag"><span>#</span><span class="p-category">webtool</span></a> <a href="http://natknight.xyz/tag:hypertext" class="hashtag"><span>#</span><span class="p-category">hypertext</span></a> <a href="http://natknight.xyz/tag:web" class="hashtag"><span>#</span><span class="p-category">web</span></a></p>

<p>After yet another fight with the syntax for <a href="https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Fragment/Text_fragments">text fragments in URLs</a> (which I&#39;ve <a href="https://natknight.xyz/til-text-fragments">written about before</a>) I had Claude whip up a little app for constructing and testing them.</p>

<p>It&#39;s published <a href="https://claude.site/artifacts/75a71321-3648-446d-a576-57946d47855e">here</a>.</p>
]]></content:encoded>
      <guid>http://natknight.xyz/web-tool-text-fragment-url-generator</guid>
      <pubDate>Tue, 15 Apr 2025 18:30:41 +0000</pubDate>
    </item>
    <item>
      <title>How to Run a Table Top Roleplaying Meetup</title>
      <link>http://natknight.xyz/how-to-run-a-table-top-roleplaying-meetup</link>
      <description>&lt;![CDATA[#aprilcools #ttrpgs #community&#xA;&#xA;It’s April Cools! It’s like April Fools, except instead of cringe comedy you make genuine content that’s different from what you usually do. This is my very first April Cools post. 🥶&#xA;&#xA;I attend (and marginally help organize) a local in-person monthly tabletop role-play game meetup. It&#39;s consistently the extracurricular highlight of my month. Whether you&#39;ve been playing for years, are just getting into the hobby, or (like me) have had to dramatically cut back the time you spend at the table because you&#39;re a parent, it&#39;s a great way to scratch the TTRPG itch without the considerable labour and time commitment of a long-term campaign.&#xA;&#xA;Running games at a public meet up on a monthly basis is very different from running a home game. This article talks about some of the challenges you might have and gives some advice on how you might successfully run a meetup of your very own.&#xA;&#xA;!--more--&#xA;&#xA;h2What you Need/h2&#xA;&#xA;ol&#xA;lipstrongPlayers/strong: Before you can do anything you need folks to play with. Maybe you can get started by dragging some friends along, but you should think about how you&#39;re going to get the word out, especially if you&#39;re trying to include people who aren&#39;t already in the hobby./p&#xA;&#xA;pDo you or some of your friends have network you can tap? Can you try to grow by word of mouth? Do you want to advertise at local game stores if those exist? You don&#39;t need to grow exponentially or anything, but you should think about how to keep your project sustainable and that&#39;s hard to do if nobody&#39;s attending./p/li&#xA;&#xA;lipstrongVenue/strong: Next you need somewhere to play. This could be a local game store or pub or café, but these all come with costs either directly to your players (as they buy pints and fries) or to you (as you pay rental fees). If you&#39;re lucky perhaps one of your players has a space that could host the meetup. Otherwise, venues like churches or community centres might have space you can use for free or for cheap, especially if you emphasize the community building aspect of your project./p/li&#xA;&#xA;lipstrongGames/strong: You might be happy to play D&amp;D on a one-off basis for a few hours a month, but I certainly don&#39;t. When I&#39;m looking for a good meetup game I try to find something that is:/p&#xA;&#xA;ul&#xA;listrongquick and light/strong, so you&#39;re spending your time playing instead of learning rules,/li&#xA;listrongcontained/strong so that you can have a satisfying experience in one session of play, and/li&#xA;listrongdifferent/strong, because I like variety in my gaming life, and because it makes for a more welcoming meetup if there are lots of different options for folks to choose from./li&#xA;/ul&#xA;/li&#xA;/ol&#xA;&#xA;The Agenda (or at least the one we use)&#xA;&#xA;Once you&#39;ve got your players, your venue, and your games, what do you actually do at games night? Here&#39;s the agenda we use.&#xA;&#xA;strongArrival/strong: Meet at the appointed time, welcome folks in, do a bit of idle chit-chat, and eventually gather into a big circle.&#xA;strongIntros/strong: The facilitators introduce themselves, tell folks about the venue (e.g. where the bathrooms are). Then we go around and have folks give their names and pronouns. Then anyone who has brought a game they&#39;d like to run gives quick pitch describing its premise, tone, play time and player count.&#xA;strongMake groups/strong: If we have enough players we divide into smaller groups based on interest. We usually do a quick straw poll to see which games have critical mass and then sort of Vornoi ourselves into groups by putting those games in different corners and having folks position themselves in space according to their relative interest.&#xA;strongPlay!/strong Most games go for 2-3 hours with a short break. Your mileage may, of course, vary.&#xA;strongDebrief/strong: About twenty minutes before we need to close down the venue we re-convene and share something that we appreciated about our game that night.&#xA;&#xA;Then we clean up and close down the venue and wait eagerly to do it again next month.&#xA;&#xA;Games to Play Occasionally and with Strangers&#xA;&#xA;Here are some games that I can personally recommend in a meetup setting:&#xA;&#xA;ul&#xA;lipa href=&#34;https://darringtonpress.com/forthequeen/&#34;For the Queen/a by Alex Roberts/p&#xA;&#xA;blockquote&#xA;pYou are accompanying a powerful and mysterious queen on a perilous journey to end a generations-long war. She has chosen you as her retinue because she knows you love her./p&#xA;&#xA;pFor the Queen is a card-based story-building game that you and up to five other players can begin playing in minutes./p&#xA;/blockquote&#xA;&#xA;pPlay iFor the Queen/i to tell the story of an awesome/awful Queen and kingdom and her complicated, unequal relationships./p&#xA;/li&#xA;&#xA;lipa href=&#34;https://buriedwithoutceremony.com/the-quiet-year&#34;The Quiet Year/a by Avery Alder/p&#xA;blockquote&#xA;pThe Quiet Year is a map game. You define the struggles of a community living after the collapse of civilization, and attempt to build something good within their quiet year. Every decision and every action is set against a backdrop of dwindling time and rising concern./p&#xA;/blockquote&#xA;&#xA;pPlay the iThe Quiet Year/i to tell a zoomed-out story about a community building in the midst of adversity./p&#xA;/li&#xA;&#xA;lipa href=&#34;https://bullypulpitgames.com/products/fiasco&#34;Fiasco/a by Bully Pulpit Games/p&#xA;blockquote&#xA;pFiasco is an award-winning, GM-less game for 3-5 players, designed to be played in a few hours with no preparation. During a game you engineer and play out stupid, disastrous situations, usually at the intersection of greed, fear, and lust. It’s like making your own a href=&#34;https://en.wikipedia.org/wiki/Coenbrothers&#34;Coen brothers/a movie, in about the same amount of time it’d take to watch one./p&#xA;/blockquote&#xA;&#xA;pPlay iFiasco/i to tell a story about people with big ambitions and poor impulse control./p&#xA;/li&#xA;&#xA;lipa href=&#34;https://metagame.itch.io/galactic&#34;Galactic 2e/a and its expansion a href=&#34;https://jumpgategames.itch.io/going-rogue&#34;Going Rogue 2e/a/p&#xA;blockquote&#xA;pA game of relationships, rebellion, and war among the stars./p&#xA;/blockquote&#xA;pPlay iGalactic/i or iGoing Rogue/i to tell a iStar Wars/i story with no dice and no game master./p&#xA;/li&#xA;&#xA;lipa href=&#34;https://blackfiskpub.itch.io/blood-feud&#34;Blood Feud/a by Blackfisk Publishing/p&#xA;blockquote&#xA;pBlood Feud is a game about toxic masculinity: certain common attitudes and behaviours among men, that cause great harm to them and to others around them. This is a game about people being nasty to each other and about figuring out why./p&#xA;&#xA;pIt’s also a game about vikings of pre-christian Scandinavia; about honour and blood feuds, courage and brutality, corruption and consequences. Above all it is a game about what it means to be a man in such a world—and what consequences that has on the communities they live in. /p&#xA;/blockquote&#xA;&#xA;pPlay iBlood Feud/i for a tragic story about men who can&#39;t help but hurt each other./p&#xA;/li&#xA;&#xA;lipa href=&#34;https://deep-dark-games.itch.io/a-perfect-rock&#34;A Perfect Rock/a/p&#xA;&#xA;blockquote&#xA;pYour home planet was destroyed./p&#xA;&#xA;pYou are aboard the last Generation Ship with the sole survivors of your people. You must find a perfect rock: a new planet to call home./p&#xA;/blockquote&#xA;&#xA;pPlay iA Perfect Rock/i for a sci-fi story about exploration, especially if you have a cool rock collection./p&#xA;/li&#xA;&#xA;/ul&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="http://natknight.xyz/tag:aprilcools" class="hashtag"><span>#</span><span class="p-category">aprilcools</span></a> <a href="http://natknight.xyz/tag:ttrpgs" class="hashtag"><span>#</span><span class="p-category">ttrpgs</span></a> <a href="http://natknight.xyz/tag:community" class="hashtag"><span>#</span><span class="p-category">community</span></a></p>

<p><em>It’s <a href="https://www.aprilcools.club/">April Cools!</a> It’s like April Fools, except instead of cringe comedy you make genuine content that’s different from what you usually do. This is my very first April Cools post.</em> 🥶</p>

<p>I attend (and marginally help organize) a local in-person monthly tabletop role-play game meetup. It&#39;s consistently the extracurricular highlight of my month. Whether you&#39;ve been playing for years, are just getting into the hobby, or (like me) have had to dramatically cut back the time you spend at the table because you&#39;re a parent, it&#39;s a great way to scratch the TTRPG itch without the considerable labour and time commitment of a long-term campaign.</p>

<p>Running games at a public meet up on a monthly basis is very different from running a home game. This article talks about some of the challenges you might have and gives some advice on how you might successfully run a meetup of your very own.</p>



<h2>What you Need</h2>

<ol><li><p><strong>Players</strong>: Before you can do anything you need folks to play with. Maybe you can get started by dragging some friends along, but you should think about how you&#39;re going to get the word out, especially if you&#39;re trying to include people who aren&#39;t already in the hobby.</p>

<p>Do you or some of your friends have network you can tap? Can you try to grow by word of mouth? Do you want to advertise at local game stores if those exist? You don&#39;t need to grow exponentially or anything, but you should think about how to keep your project sustainable and that&#39;s hard to do if nobody&#39;s attending.</p></li>

<li><p><strong>Venue</strong>: Next you need somewhere to play. This could be a local game store or pub or café, but these all come with costs either directly to your players (as they buy pints and fries) or to you (as you pay rental fees). If you&#39;re lucky perhaps one of your players has a space that could host the meetup. Otherwise, venues like churches or community centres might have space you can use for free or for cheap, especially if you emphasize the community building aspect of your project.</p></li>

<li><p><strong>Games</strong>: You might be happy to play D&amp;D on a one-off basis for a few hours a month, but I certainly don&#39;t. When I&#39;m looking for a good meetup game I try to find something that is:</p>

<ul><li><strong>quick and light</strong>, so you&#39;re spending your time playing instead of learning rules,</li>
<li><strong>contained</strong> so that you can have a satisfying experience in one session of play, and</li>
<li><strong>different</strong>, because I like variety in my gaming life, and because it makes for a more welcoming meetup if there are lots of different options for folks to choose from.</li></ul>
</li></ol>

<h2 id="the-agenda-or-at-least-the-one-we-use" id="the-agenda-or-at-least-the-one-we-use">The Agenda (or at least the one we use)</h2>

<p>Once you&#39;ve got your players, your venue, and your games, what do you actually <em>do</em> at games night? Here&#39;s the agenda we use.</p>
<ol><li><strong>Arrival</strong>: Meet at the appointed time, welcome folks in, do a bit of idle chit-chat, and eventually gather into a big circle.</li>
<li><strong>Intros</strong>: The facilitators introduce themselves, tell folks about the venue (e.g. where the bathrooms are). Then we go around and have folks give their names and pronouns. Then anyone who has brought a game they&#39;d like to run gives quick pitch describing its premise, tone, play time and player count.</li>
<li><strong>Make groups</strong>: If we have enough players we divide into smaller groups based on interest. We usually do a quick straw poll to see which games have critical mass and then sort of <a href="https://en.wikipedia.org/wiki/Voronoi_diagram">Vornoi</a> ourselves into groups by putting those games in different corners and having folks position themselves in space according to their relative interest.</li>
<li><strong>Play!</strong> Most games go for 2-3 hours with a short break. Your mileage may, of course, vary.</li>
<li><strong>Debrief</strong>: About twenty minutes before we need to close down the venue we re-convene and share something that we appreciated about our game that night.</li></ol>

<p>Then we clean up and close down the venue and wait eagerly to do it again next month.</p>

<h2 id="games-to-play-occasionally-and-with-strangers" id="games-to-play-occasionally-and-with-strangers">Games to Play Occasionally and with Strangers</h2>

<p>Here are some games that I can personally recommend in a meetup setting:</p>

<ul><li><p><a href="https://darringtonpress.com/forthequeen/">For the Queen</a> by Alex Roberts</p>

<blockquote><p>You are accompanying a powerful and mysterious queen on a perilous journey to end a generations-long war. She has chosen you as her retinue because she knows you love her.</p>

<p>For the Queen is a card-based story-building game that you and up to five other players can begin playing in minutes.</p></blockquote>

<p>Play <i>For the Queen</i> to tell the story of an awesome/awful Queen and kingdom and her complicated, unequal relationships.</p>
</li>

<li><p><a href="https://buriedwithoutceremony.com/the-quiet-year">The Quiet Year</a> by Avery Alder</p>
<blockquote><p>The Quiet Year is a map game. You define the struggles of a community living after the collapse of civilization, and attempt to build something good within their quiet year. Every decision and every action is set against a backdrop of dwindling time and rising concern.</p></blockquote>

<p>Play the <i>The Quiet Year</i> to tell a zoomed-out story about a community building in the midst of adversity.</p>
</li>

<li><p><a href="https://bullypulpitgames.com/products/fiasco">Fiasco</a> by Bully Pulpit Games</p>
<blockquote><p>Fiasco is an award-winning, GM-less game for 3-5 players, designed to be played in a few hours with no preparation. During a game you engineer and play out stupid, disastrous situations, usually at the intersection of greed, fear, and lust. It’s like making your own <a href="https://en.wikipedia.org/wiki/Coen_brothers">Coen brothers</a> movie, in about the same amount of time it’d take to watch one.</p></blockquote>

<p>Play <i>Fiasco</i> to tell a story about people with big ambitions and poor impulse control.</p>
</li>

<li><p><a href="https://metagame.itch.io/galactic">Galactic 2e</a> and its expansion <a href="https://jumpgategames.itch.io/going-rogue">Going Rogue 2e</a></p>
<blockquote><p>A game of relationships, rebellion, and war among the stars.</p></blockquote>
<p>Play <i>Galactic</i> or <i>Going Rogue</i> to tell a <i>Star Wars</i> story with no dice and no game master.</p>
</li>

<li><p><a href="https://blackfiskpub.itch.io/blood-feud">Blood Feud</a> by Blackfisk Publishing</p>
<blockquote><p>Blood Feud is a game about toxic masculinity: certain common attitudes and behaviours among men, that cause great harm to them and to others around them. This is a game about people being nasty to each other and about figuring out why.</p>

<p>It’s also a game about vikings of pre-christian Scandinavia; about honour and blood feuds, courage and brutality, corruption and consequences. Above all it is a game about what it means to be a man in such a world—and what consequences that has on the communities they live in. </p></blockquote>

<p>Play <i>Blood Feud</i> for a tragic story about men who can&#39;t help but hurt each other.</p>
</li>

<li><p><a href="https://deep-dark-games.itch.io/a-perfect-rock">A Perfect Rock</a></p>

<blockquote><p>Your home planet was destroyed.</p>

<p>You are aboard the last Generation Ship with the sole survivors of your people. You must find a perfect rock: a new planet to call home.</p></blockquote>

<p>Play <i>A Perfect Rock</i> for a sci-fi story about exploration, especially if you have a cool rock collection.</p>
</li>

</ul>
]]></content:encoded>
      <guid>http://natknight.xyz/how-to-run-a-table-top-roleplaying-meetup</guid>
      <pubDate>Tue, 01 Apr 2025 17:30:11 +0000</pubDate>
    </item>
    <item>
      <title>Link: AI&#39;s Effect on Programming Jobs</title>
      <link>http://natknight.xyz/link-ais-effect-on-programming-jobs</link>
      <description>&lt;![CDATA[#link #ai&#xA;&#xA;Laurie Voss hypothesizes that rather than driving all software developers out of a job, the rise of generative LLMs and &#34;vibe coding&#34; will result in more overall software development jobs, but that they will look different than the ones that currently exist.&#xA;&#xA;The argument goes roughly:&#xA;&#xA;LLMs are a new layer of abstraction for writing programs&#xA;New layers of abstraction create new kinds of programmers who don&#39;t think as much about layers lower on the stack&#xA;There&#39;s lots of unmet demand for software development&#xA;Developers who can &#34;vibe code&#34; to meet this need will be numerous, productive, and decently paid (though perhaps not well compensated as developers in 2025).&#xA;&#xA;!-- more --&#xA;&#xA;He also argues that overall demand for software development will go up as it gets cheaper, so salaries for current developers won&#39;t really be impacted.&#xA;&#xA;While I have a couple of quibbles, I like this a lot! I think &#34;AI is a new level of abstraction&#34; is a much more useful take than &#34;all code will be written by AI&#34; or &#34;AI is useless and will go away&#34;. The idea of a software engineer who doesn&#39;t really know how to code is very strange, but I can sort of see how it would work with the right tools. You would need a bunch of knowledge about protocols, databases, architecture, etc. that previously would have been inaccessible to someone who doesn&#39;t code, but that&#39;s changed now.&#xA;&#xA;I&#39;m not sure I buy the &#34;salaries won&#39;t go down&#34; component. We&#39;ve witnessed fifty years of capital rapaciously sucking up every grain of surplus profit it can, so if there&#39;s a way that LLMs could be used to undermine software developers&#39; bargaining power re: the cost of their labour I bet someone will find it, or at least try hard enough that it will be a bad time, at least in the short term.&#xA;&#xA;With all the AI hype flying around leaders who (for example)&#xA;&#xA;replace their staff with AI agents&#xA;create a class of &#34;vibe coders&#34; whose salaries are anchored lower&#xA;buy AI licenses for their favourite half of the engineering team and lay off the other half&#xA;&#xA;will be lionized as bold and forward thinking, at least until the negative consequences of these actions start to crop up. I think they will crop up, because more code means more tech debt, and with fewer people building theories you&#39;re going to hit that wall sooner or later, but the owning class and their lackeys can remain irrational way longer than it takes to erode your salary.]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="http://natknight.xyz/tag:link" class="hashtag"><span>#</span><span class="p-category">link</span></a> <a href="http://natknight.xyz/tag:ai" class="hashtag"><span>#</span><span class="p-category">ai</span></a></p>

<p><a href="https://seldo.com/posts/ai-effect-on-programming-jobs">Laurie Voss hypothesizes</a> that rather than driving all software developers out of a job, the rise of generative LLMs and “vibe coding” will result in more overall software development jobs, but that they will look different than the ones that currently exist.</p>

<p>The argument goes roughly:</p>
<ul><li>LLMs are a new layer of abstraction for writing programs</li>
<li>New layers of abstraction create new kinds of programmers who don&#39;t think as much about layers lower on the stack</li>
<li>There&#39;s lots of unmet demand for software development</li>
<li>Developers who can “vibe code” to meet this need will be numerous, productive, and decently paid (though perhaps not well compensated as developers in 2025).</li></ul>



<p>He also argues that overall demand for software development will go up as it gets cheaper, so salaries for current developers won&#39;t really be impacted.</p>

<p>While I have a couple of quibbles, I like this a lot! I think “AI is a new level of abstraction” is a <em>much</em> more useful take than “all code will be written by AI” or “AI is useless and will go away”. The idea of a software engineer who doesn&#39;t really know how to code is very strange, but I can sort of see how it would work with the right tools. You would need a bunch of knowledge about protocols, databases, architecture, etc. that previously would have been inaccessible to someone who doesn&#39;t code, but that&#39;s changed now.</p>

<p>I&#39;m not sure I buy the “salaries won&#39;t go down” component. We&#39;ve witnessed fifty years of capital rapaciously sucking up every grain of surplus profit it can, so if there&#39;s a way that LLMs could be used to undermine software developers&#39; bargaining power re: the cost of their labour I bet someone will find it, or at least try hard enough that it will be a bad time, at least in the short term.</p>

<p>With all the AI hype flying around leaders who (for example)</p>
<ul><li>replace their staff with AI agents</li>
<li>create a class of “vibe coders” whose salaries are anchored lower</li>
<li>buy AI licenses for their favourite half of the engineering team and lay off the other half</li></ul>

<p>will be lionized as bold and forward thinking, at least until the negative consequences of these actions start to crop up. I think they <em>will</em> crop up, because more code means more tech debt, and with fewer people <a href="https://pages.cs.wisc.edu/~remzi/Naur.pdf">building theories</a> you&#39;re going to hit that wall sooner or later, but the owning class and their lackeys can remain irrational way longer than it takes to erode your salary.</p>
]]></content:encoded>
      <guid>http://natknight.xyz/link-ais-effect-on-programming-jobs</guid>
      <pubDate>Wed, 26 Mar 2025 16:13:38 +0000</pubDate>
    </item>
    <item>
      <title>TIL: Copying to the clipboard from a webpage is easy now</title>
      <link>http://natknight.xyz/til-copying-to-the-clipboard-from-a-webpage-is-easy-now</link>
      <description>&lt;![CDATA[#til #webdev #javascript #clipboard&#xA;&#xA;I remember a time when there were whole libraries dedicated to clipboard management.&#xA;&#xA;Turns out in browsers released after ~2020 you can do it with one line:&#xA;&#xA;navigator.clipboard.writeText(text).then(console.log).catch(console.error);&#xA;&#xA;!--more--&#xA;&#xA;Some caveats:&#xA;&#xA;this only works in a secure context (which mostly means either &#34;a site served over HTTPS&#34; or localhost).&#xA;the user needs to have interacted with the page before you can actually call the method.&#xA;this was only added to certain browsers in 2020, so it&#39;s new enough that it might cause problems if your constituents have older devices or don&#39;t update often.&#xA;&#xA;Usually when I do this I&#39;m implementing something like a &#34;copy to clipboard&#34; button:&#xA;&#xA;span id=&#39;copy-target&#39;This text is going into a clipboard/span&#xA;button onclick=&#39;copyToClipboard(&#34;copy-target&#34;)&#39;Copy/button&#xA;script&#xA;    const copyToClipboard = (target) =  {&#xA;        const element = document.getElementById(target);&#xA;        const value = element?.innerText&#xA;        if (value) {&#xA;            navigator.clipboard.writeText(value).then(console.log).catch(console.error);&#xA;        }&#xA;    }&#xA;/script&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="http://natknight.xyz/tag:til" class="hashtag"><span>#</span><span class="p-category">til</span></a> <a href="http://natknight.xyz/tag:webdev" class="hashtag"><span>#</span><span class="p-category">webdev</span></a> <a href="http://natknight.xyz/tag:javascript" class="hashtag"><span>#</span><span class="p-category">javascript</span></a> <a href="http://natknight.xyz/tag:clipboard" class="hashtag"><span>#</span><span class="p-category">clipboard</span></a></p>

<p>I remember a time when there were <a href="https://github.github.com/clipboard-copy-element/">whole</a> <a href="https://clipboardjs.com/">libraries</a> dedicated to clipboard management.</p>

<p>Turns out in <a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard#browser_compatibility">browsers released after ~2020</a> you can do it with one line:</p>

<pre><code class="language-javascript">navigator.clipboard.writeText(text).then(console.log).catch(console.error);
</code></pre>



<p>Some caveats:</p>
<ul><li>this only works in a <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts">secure context</a> (which mostly means either “a site served over HTTPS” or <code>localhost</code>).</li>
<li>the user needs to have interacted with the page before you can actually call the method.</li>
<li>this was only added to certain browsers in 2020, so it&#39;s new enough that it might cause problems if your constituents have older devices or don&#39;t update often.</li></ul>

<p>Usually when I do this I&#39;m implementing something like a “copy to clipboard” button:</p>

<pre><code class="language-html">&lt;span id=&#39;copy-target&#39;&gt;This text is going into a clipboard&lt;/span&gt;
&lt;button onclick=&#39;copyToClipboard(&#34;copy-target&#34;)&#39;&gt;Copy&lt;/button&gt;
&lt;script&gt;
    const copyToClipboard = (target) =&gt; {
        const element = document.getElementById(target);
        const value = element?.innerText
        if (value) {
            navigator.clipboard.writeText(value).then(console.log).catch(console.error);
        }
    }
&lt;/script&gt;
</code></pre>
]]></content:encoded>
      <guid>http://natknight.xyz/til-copying-to-the-clipboard-from-a-webpage-is-easy-now</guid>
      <pubDate>Mon, 10 Mar 2025 21:19:49 +0000</pubDate>
    </item>
  </channel>
</rss>